C#: How to get installing programs exactly like in control panel programs and features?
Asked Answered
L

5

20

I read a lot of information of getting programs. None of algorithms did do what I want. I need to get installed programs exactly like in control panel.

So I used:

  1. WMI Win32_Product class. It shows only msi installed programs.
  2. Registry keys. HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall. Again, some programs are not displayed in control panel, some programs displayed in control panel not in this registry node.

So, is there anyone in this world, who knew which algorithm use control panel to display installed programs?

UPD1:yes, i use 64 bit, i know there is another node for 64bit installed programs "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" but the following code enumerates twise HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall section, strange...

var programs = new List(); string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; using (Microsoft.Win32.RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key)) { foreach (string subkey_name in key.GetSubKeyNames()) { using (RegistryKey subkey = key.OpenSubKey(subkey_name)) { var name = (string)subkey.GetValue("DisplayName"); if(!string.IsNullOrEmpty(name)) { programs.Add(name); } } } } registry_key = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; using (Microsoft.Win32.RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key)) { foreach (string subkey_name in key.GetSubKeyNames()) { using (RegistryKey subkey = key.OpenSubKey(subkey_name)) { var name = (string)subkey.GetValue("DisplayName"); if (!string.IsNullOrEmpty(name)) { programs.Add(name); } } } } foreach (var program in programs.OrderBy(x => x)) { Console.WriteLine(program); }
Lingenfelter answered 20/3, 2013 at 12:55 Comment(6)
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall should do it. Can you give an example of a program that appear in Control Panel but not in this reg key?Eustacia
are you using a 64 bit machine?Tired
i updated topic, the following code didnt display me winrar program which is in SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall nodeLingenfelter
Are you running a 32-bit process? If so, you'll need to use RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) instead of Registry.LocalMachine to get the entries from the 64-bit key.Galvano
@RichardDeeming, awesome! but there is still problem with displayig windows updates and hotfixes. I think there is some key defining that program is update pr hotfix. Any ideas?Lingenfelter
blogs.msdn.com/b/oldnewthing/archive/2013/12/30/10485905.aspxQuarles
L
38

Ok gyus, i wrote class that can get installed programs from registry without hotfixes and updates. It is still not exactly like in control panel but almost. I hope this helps anyone else.

public static class InstalledPrograms
{
    const string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

    public static List<string> GetInstalledPrograms()
    {
        var result = new List<string>();
        result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry32));
        result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry64));
        return result;
    } 

    private static IEnumerable<string> GetInstalledProgramsFromRegistry(RegistryView registryView)
    {
        var result = new List<string>();

        using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registry_key))
        {
            foreach (string subkey_name in key.GetSubKeyNames())
            {
                using (RegistryKey subkey = key.OpenSubKey(subkey_name))
                {
                    if(IsProgramVisible(subkey))
                    {
                        result.Add((string)subkey.GetValue("DisplayName"));
                    }
                }
            }
        }

        return result;
    }

    private static bool IsProgramVisible(RegistryKey subkey)
    {
        var name = (string)subkey.GetValue("DisplayName");
        var releaseType = (string)subkey.GetValue("ReleaseType");
        //var unistallString = (string)subkey.GetValue("UninstallString");
        var systemComponent = subkey.GetValue("SystemComponent");
        var parentName = (string)subkey.GetValue("ParentDisplayName");

        return
            !string.IsNullOrEmpty(name)
            && string.IsNullOrEmpty(releaseType)
            && string.IsNullOrEmpty(parentName)
            && (systemComponent == null);
    }
}

Lingenfelter answered 21/3, 2013 at 6:12 Comment(3)
very elegant class. For me it gave one extra autodesk entry and missed 6 of the 8 nvidia entries that showed in Programs and features. If I find out why I'll post back here.Bulrush
I found that if I change the last line of method IsProgramVisible(RegistryKey subkey) from: && (systemComponent == null) to: && (systemComponent == null || (int)systemComponent == 0); then it matches perfectly with what I see in programs in features, that is , I can now see the NVIDIA driver programs and features entries.Bulrush
Thank you for posting the useful code. Having identified the list of installed programs, what is the programmatic link between the registry keys and discovering the exact name of the installed exe file? I went through all the fields that I saw in several cases but could only find the names of the install directories and of the uninstall exe files (sometimes). I understand I could just look for exe files in the app directories, but that's not the same as tracing the official info to the exe that gets started. Thank youSenior
E
5

I took the code that MelnikovI wrote (which was super helpful) and added a couple things. First, it search four places in the registry:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

HKCU\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

It also checks to see if there are any subkeys - if not it skips that one.

Lastly, it does a regex to only allow a certain set of characters [^a-zA-Z0-9 .()+-].

I'm only starting at C#, so I didn't know a way to loop through all four reg locations, so I have two loops (one for HKLM and one for HKCU).

public static class InstalledPrograms
    {
      public static List<string> GetInstalledPrograms()
        {
            var result = new List<string>();
            result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry32));
            result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry64));
            result.Sort();
            return result;
        }
        private static string cleanText(string dirtyText)
        {
            Regex rgx = new Regex("[^a-zA-Z0-9 .()+-]");
            string result = rgx.Replace(dirtyText, "");
            return result;
        }
        private static IEnumerable<string> GetInstalledProgramsFromRegistry(RegistryView registryView)
        {
            var result = new List<string>();
            List<string> uninstall = new List<string>();
            uninstall.Add(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
            uninstall.Add(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");
            foreach (string registry_key in uninstall)
            {
               using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registry_key))
               {
                    foreach (string subkey_name in key.GetSubKeyNames())
                    {
                        using (RegistryKey subkey = key.OpenSubKey(subkey_name))
                        {
                            if (IsProgramVisible(subkey))
                            {
                                result.Add(cleanText(subkey.GetValue("DisplayName").ToString()).ToString());
                            }
                        }
                    }
                }
                using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, registryView).OpenSubKey(registry_key))
                {
                    if (key != null)
                    {
                        foreach (string subkey_name in key.GetSubKeyNames())
                        {
                            using (RegistryKey subkey = key.OpenSubKey(subkey_name))
                            {
                                if (IsProgramVisible(subkey))
                                {
                                    result.Add(cleanText(subkey.GetValue("DisplayName").ToString()).ToString());
                                }
                            }
                        }
                    }
                }
            }

            return result;
        }

If anyone is interested, I compared the results to the PowerShell I've been using and they are the same.

##Get list of Add/Remove programs
if (!([Diagnostics.Process]::GetCurrentProcess().Path -match '\\syswow64\\'))
{
$uninstallPath = "\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"
$uninstallWow6432Path = "\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"
@(
if (Test-Path "HKLM:$uninstallWow6432Path" ) { Get-ChildItem "HKLM:$uninstallWow6432Path"}
if (Test-Path "HKLM:$uninstallPath" ) { Get-ChildItem "HKLM:$uninstallPath" }
if (Test-Path "HKCU:$uninstallWow6432Path") { Get-ChildItem "HKCU:$uninstallWow6432Path"}
if (Test-Path "HKCU:$uninstallPath" ) { Get-ChildItem "HKCU:$uninstallPath" }
) |
ForEach-Object { Get-ItemProperty $_.PSPath } |
Where-Object {
$_.DisplayName -and !$_.SystemComponent -and !$_.ReleaseType -and !$_.ParentKeyName -and ($_.UninstallString -or $_.NoRemove)
} |
Sort-Object DisplayName |
Select-Object DisplayName
}
else
{
"You are running 32-bit Powershell on 64-bit system. Please run 64-bit Powershell instead." | Write-Host -ForegroundColor Red
}
Endanger answered 14/9, 2015 at 21:48 Comment(1)
You are on the rigt track but you should lok into RegistryView as using wow6432Node paths on a 64-bit system does not return what you think it might. I found that if you use a path like SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall with Registry32 that will return SOFTWARE\Wowo6432Node\Microsoft\Windows\CurrentVersion\Uninstall and when you use the exact same path with Registry64 it returnsSOFTWARE\Microsoft\Windows\CurrentVersion\UninstallBulrush
F
5

MelnikovI's answer is sufficient for most purposes -- I had 144 items in my list vs 143 in Programs and Features. For review, his solution is to hit these registry locations:

  • HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall
  • HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall
  • HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

To qualify, each subkey MUST have:

  • The DisplayName REG_SZ value

And MUST NOT have:

  • The SystemComponent REG_DWORD (non-zero)
  • The ParentKeyName or ParentDisplayName REG_SZ values
  • The ReleaseType REG_SZ value

The one addtional enhancement I have found is for Windows Installer entries, defined as:

  • The key name is a standard GUID string
  • The WindowsInstaller REG_DWORD is present (and non-zero)

For such entries, you can take the additional step of using the Win32 function MsiGetProductInfoW from msi.dll, and asking for the "VersionString" property for the GUID represented by the key.

If this function returns 1605: ERROR_UNKNOWN_PRODUCT, it means that the entry is not installed according to Windows Installer, and should be excluded from display.

After implementing this minor tweak, my list is now identical to Programs and Features.

Faddist answered 22/10, 2015 at 5:40 Comment(0)
D
0

The SystemComponent registry key discussed in several other answers here is usually a REG_DWORD with possible values of 0 or 1. However, I have seen a couple of instances (such as Microsoft Visio 2010 and Microsoft Project 2010) where SystemComponent is a REG_SZ with no data. Any solution that casts SystemComponent to an int is therefore liable to throw an exception in these situations.

Dubose answered 18/4, 2019 at 16:36 Comment(0)
E
0

I modified the above code a little bit. Thanks a lot to @MelnikovI and @John for giving the basics. It works great under Windows 10 with C# .net 6, but some programs are not listed. For example NanaZip, HP Smart or the nVidia Components.

Does anyone have an idea, why?

Also System-Tools like Paint or Calc, which are shown in Apps and Features with the newest updates are not listed, but that's OK I think.

class SoftwareClass
{

    public static List<string> GetDistinctInstalledPrograms()
    {
        List<string> installedPrograms = GetInstalledPrograms().Distinct().ToList();
        installedPrograms.Sort();
        return installedPrograms;
    }

    public static List<string> GetInstalledPrograms()
    {
        var result = new List<string>();
        result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry32));
        result.AddRange(GetInstalledProgramsFromRegistry(RegistryView.Registry64));
        return result;
    }

    private static string CleanText(string dirtyText)
    {
        Regex rgx = new Regex("[^a-zA-Z0-9 .()+-]");
        string result = rgx.Replace(dirtyText, "");
        return result;
    }

    private static IEnumerable<string> GetInstalledProgramsFromRegistry(RegistryView registryView)
    {
        var result = new List<string>();
        List<string> uninstall = new List<string>();
        uninstall.Add(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall");
        uninstall.Add(@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");

        foreach (string registry_key in uninstall)
        {
            using (RegistryKey? key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registry_key))
            {
                if (key != null)
                {
                    foreach (string subkey_name in key.GetSubKeyNames())
                    {
                        using (RegistryKey? subkey = key.OpenSubKey(subkey_name))
                        {
                            if (subkey != null && IsProgramVisible(subkey))
                            {
                                var displayName = subkey.GetValue("DisplayName") as string;
                                if (!string.IsNullOrEmpty(displayName))
                                {
                                    result.Add(CleanText(displayName));
                                }
                            }
                        }
                    }
                }
            }

            using (RegistryKey? key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, registryView).OpenSubKey(registry_key))
            {
                if (key != null)
                {
                    foreach (string subkey_name in key.GetSubKeyNames())
                    {
                        using (RegistryKey? subkey = key.OpenSubKey(subkey_name))
                        {
                            if (subkey != null && IsProgramVisible(subkey))
                            {
                                var displayName = subkey.GetValue("DisplayName") as string;
                                if (!string.IsNullOrEmpty(displayName))
                                {
                                    result.Add(CleanText(displayName));
                                }
                            }
                        }
                    }
                }
            }
        }

        return result;
    }
}
Etan answered 1/4 at 20:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.