Home > Net >  How to get every single control panel applet and their localized names in C#?
How to get every single control panel applet and their localized names in C#?

Time:01-03

I want to get all of the control panel applets available on a given user's system, so that I can display a list of each one and give the user the ability to run them.

I've been thinking of simply fetching every single .cpl file located in %SYSTEMROOT%\system32 (usually C:\Windows\system32), but I couldn't extract the title of those applets. Additionally, after looking at screenshot showing the names of certain applets

CodePudding user response:

With the help of user2864740, I've come up with a registry-based solution:

const string APPLETS_LOCATION = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace";

/// <summary>
/// Gets GUIDs for all the installed Control Panel applets.
/// </summary>
/// <returns>A collection of control panel GUIDs.</returns>
static IEnumerable<string> GetAppletGuids(){
    using (RegistryKey reg32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(APPLETS_LOCATION)) {
        using (RegistryKey reg64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64).OpenSubKey(APPLETS_LOCATION)) {
            return reg32.GetSubKeyNames()
                .Concat(reg64.GetSubKeyNames())
                .Distinct() // Both the 32-bit registry and the 64-bit registry can contain duplicates - lets skip them
                .Where(x => x.StartsWith("{") && x.EndsWith("}")); // Contain only GUIDs
        }
    }
}

/// <summary>
/// Gets the name of an applet by its GUID.
/// </summary>
/// <param name="guid">The GUID of an applet.</param>
/// <returns>The name of the applet, or <see langword="null"/> if the applet couldn't be found.</returns>
static string GetNameForApplet(string guid)
{
    using (RegistryKey appletKey32 = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry32).OpenSubKey(@"CLSID\"   guid)) {
        // Try looking in the 32-bit registry first
        if (appletKey32 != null)
            return (string)appletKey32.GetValue("");
        else {
            // No such applet in the 32-bit registry, try the 64-bit one
            using(RegistryKey appletKey64 = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64).OpenSubKey(@"CLSID\"   guid)) {
                if (appletKey64 != null)
                    return (string)appletKey64.GetValue("");
                else
                    // No applet in both registries
                    return null;
            }
        }
    }
}

These methods can then be used as such:

var applets = GetAppletGuids();

foreach (string guid in applets) {
    Console.WriteLine("Applet: "   GetNameForApplet(guid)   ", GUID: "   guid);
}

...and this provides the following output (snippet):

Applet: Taskbar, GUID: {0DF44EAA-FF21-4412-828E-260A8728E7F1}
Applet: Credential Manager, GUID: {1206F5F1-0569-412C-8FEC-3204630DFB70}
Applet: Set User Defaults, GUID: {17cd9488-1228-4b2f-88ce-4298e93e0966}
Applet: Workspaces Center, GUID: {241D7C96-F8BF-4F85-B01F-E2B043341A4B}
Applet: , GUID: {38A98528-6CBF-4CA9-8DC0-B1E1D10F7B1B}
Applet: Phone and Modem Control Panel, GUID: {40419485-C444-4567-851A-2DD7BFA1684D}
Applet: User Accounts, GUID: {60632754-c523-4b62-b45c-4172da012619}
Applet: Region and Language, GUID: {62D8ED13-C9D0-4CE8-A914-47DD628FB1B0}

Sometimes the name is set to an empty string, and from my testing, trying to invoke these applets doesn't result in anything - no errors, no windows, no processes, so I think it's best to ignore those applets.

In order to invoke an applet using its GUID, you can use Explorer:

// Open Power Options
string guid = "{025A5937-A6BE-4686-A844-36FE4BEC8B6D}";
Process.Start("explorer", "shell:::"   guid);

CodePudding user response:

Control panel applets come from two places, the registry and .cpl files in the system32 directory. Each .cpl file provides 1 or several entries in control panel.

While there is an API to interact with .cpl files, it is usually easier to just use IShellFolder since it gives you the exact same list as Explorer.

using System;
using System.Runtime.InteropServices;
    
namespace Test { class TestApp {

public class WAPI
{
public const int CSIDL_CONTROLS = 0x03;
[DllImport("shell32.dll")] public static extern int ILFree(IntPtr p);
[DllImport("shell32.dll")] public static extern int SHGetFolderLocation(IntPtr hwnd, int csidl, IntPtr hToken, uint reserved, out IntPtr pidl);
[StructLayout(LayoutKind.Explicit, Size = 520)] public struct STRRETinternal { [FieldOffset(0)] public IntPtr pOleStr; [FieldOffset(0)] public IntPtr pStr; [FieldOffset(0)] public uint uOffset; }
[StructLayout(LayoutKind.Sequential)] public struct STRRET { public uint uType; public STRRETinternal data; }
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214F2-0000-0000-C000-000000000046")]
public interface IEnumIDList {
    [PreserveSig] int Next(uint celt, IntPtr rgelt, out uint pceltFetched);
}
public static Guid IID_IShellFolder = new Guid("{000214E6-0000-0000-C000-000000000046}");
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214E6-0000-0000-C000-000000000046")]
public interface IShellFolder {
    [PreserveSig] void ParseDisplayName();
    [PreserveSig] int EnumObjects(IntPtr hwnd, int grfFlags, out IntPtr ppenumIDList);
    [PreserveSig] int BindToObject(IntPtr pidl, IntPtr pbc, [In]ref Guid riid, out IntPtr ppv);
    [PreserveSig] void BindToStorage();
    [PreserveSig] int CompareIDs();
    [PreserveSig] void CreateViewObject();
    [PreserveSig] void GetAttributesOf();
    [PreserveSig] void GetUIObjectOf();
    int GetDisplayNameOf(IntPtr pidl, int uFlags, IntPtr pName);
}
[DllImport("shell32.dll")] public static extern int SHGetDesktopFolder(out IShellFolder shf);
[DllImport("shlwapi.dll")] public static extern int StrRetToBSTR(IntPtr pstr, IntPtr pidl, out IntPtr pbstr);
}


[STAThread]
static void Main(/*string[] args*/) 
{
    IntPtr pidl = IntPtr.Zero, ptr;
    int hr = WAPI.SHGetFolderLocation(IntPtr.Zero, WAPI.CSIDL_CONTROLS, IntPtr.Zero, 0, out pidl);
    if (hr != 0) Environment.Exit(hr);
    WAPI.IShellFolder root;
    hr = WAPI.SHGetDesktopFolder(out root);
    if (hr != 0) Environment.Exit(hr);
    hr = root.BindToObject(pidl, IntPtr.Zero, ref WAPI.IID_IShellFolder, out ptr);
    WAPI.ILFree(pidl);
    Marshal.ReleaseComObject(root);
    if (hr != 0) Environment.Exit(hr);
    root = (WAPI.IShellFolder) Marshal.GetObjectForIUnknown(ptr);
    IntPtr enumpidlsptr;
    hr = root.EnumObjects(IntPtr.Zero, 0x00060, out enumpidlsptr);
    if (hr >= 0 && enumpidlsptr != IntPtr.Zero)
    {
        WAPI.IEnumIDList enumpidls = (WAPI.IEnumIDList) Marshal.GetObjectForIUnknown(enumpidlsptr);
        uint fetch;
        IntPtr bytes = Marshal.AllocCoTaskMem(999);
        while ((hr = enumpidls.Next((uint) 1, bytes, out fetch)) >= 0 && fetch > 0)
        {
            IntPtr child = Marshal.ReadIntPtr(bytes);
            hr = root.GetDisplayNameOf(child, 0, bytes);
            if (hr >= 0)
            {
                IntPtr bstr;
                hr = WAPI.StrRetToBSTR(bytes, child, out bstr);
                if (hr >= 0)
                {
                    string name = Marshal.PtrToStringBSTR(bstr);
                    Marshal.FreeBSTR(bstr);
                    Console.WriteLine(name);
                }
            }
            Marshal.FreeCoTaskMem(child);
        }
        Marshal.FreeCoTaskMem(bytes);
        Marshal.ReleaseComObject(enumpidls);
    }
    Marshal.ReleaseComObject(root);
}
}}

To take full advantage of this you need to read more about the shell namespace.

  • Related