95

How to get the applications installed in the system using c# code?

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Sauron
  • 16,668
  • 41
  • 122
  • 174

15 Answers15

124

Iterating through the registry key "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" seems to give a comprehensive list of installed applications.

Aside from the example below, you can find a similar version to what I've done here.

This is a rough example, you'll probaby want to do something to strip out blank rows like in the 2nd link provided.

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))
        {
            Console.WriteLine(subkey.GetValue("DisplayName"));
        }
    }
}

Alternatively, you can use WMI as has been mentioned:

ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
foreach(ManagementObject mo in mos.Get())
{
    Console.WriteLine(mo["Name"]);
}

But this is rather slower to execute, and I've heard it may only list programs installed under "ALLUSERS", though that may be incorrect. It also ignores the Windows components & updates, which may be handy for you.

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Xiaofu
  • 15,523
  • 2
  • 32
  • 45
  • 27
    It's worth noting that using the WMI Win32_Product class is a bad idea if you plan to run this query repeatedly. See this Microsoft KB article: http://support.microsoft.com/kb/974524/EN-US The core problem is the (a) Win32_Product is really slow and (b) it generates a "Windows Installer reconfigured the product." event log message for *every* installed product on your system... every time you run the query. Doh! This article recommends using the Win32reg_AddRemovePrograms class... which isn't present unless you have installed SMS. Doh! So probably better to stick with the registry query. – Simon Gillbee Mar 05 '10 at 00:00
  • Simon Gillbee's comment should be the accepted answer, or Kirtans! WMI WIN32_Product is not the way to go here, trust me! – bdd Mar 16 '10 at 23:58
  • 15
    Er, that's why the registry example is first in my answer. WMI was presented simply as an alternative solution, and even there I state "this is rather slower to execute" and other drawbacks. Read the answer from the beginning. ;) – Xiaofu Mar 17 '10 at 07:42
  • I don't want whole list , I just need some selected install programs so what can i do for that . Thank you – Dhru 'soni Jan 21 '15 at 10:16
  • @Dhru'soni, if you know which programs you need, this might not be the way to go; it'd be quicker if you know which Registry Keys you're expecting for the specific programs you want (e.g. "SOFTWARE\JavaSoft\Java Development Kit" or whatever). If you look at the entries in the registry that live under the "Uninstall" node, as per this answer, you'll see that their keys are GUIDs (GUID-like), so you have no option, other than to iterate through the whole list to find what you want. – LeeCambl Mar 24 '15 at 10:04
  • 1
    Kinda weird but if you uninstall a program and install it back then try to find it using using registry keys you can't unless you restart your machine – Yar Nov 24 '15 at 19:05
  • I think some software have not registry in it. – lindexi Jun 20 '17 at 02:41
  • Is there any reason why this (as in the first example would miss a 'subkey' under 'CurrentVersion\Uninstall'? I can see the software installed. I can see it in the registry. Yet when iterating through the list, it does not show the one in question that I wanted. all around it shows... I will reveal the 'subkey name is': 'com.a2d2xray.XWaveDR_is1'. Sometimes they just have GUID codes. And the display-name is like 'X-Ray'... I've determined it doesn't even get that far, because the loop misses the key. Is this a permissions thing? Although I was running in a Win-service. – Robert Koernke Jul 27 '17 at 12:53
  • 4
    To answer my own question: https://stackoverflow.com/questions/27838798/getsubkeynames-does-not-return-all-keys Although annoying that you might have to query both 64bit and 32bit. – Robert Koernke Jul 27 '17 at 18:11
  • Thanks for a very great answer although I jus noticed that some apps like mongoDB don't show up in the list despite the fact that they are I the registry. – Rishav Aug 13 '18 at 14:00
  • This is great answer but what accounts for the blank rows? I am missing some software from output ( I am checking both 64bit & 32bit registry keys). The keys exist when I check regedit.exe but are being reported by the script's for loop... – Vynce82 Jun 16 '20 at 02:17
  • Thank you for your code provided. I wanted to get information of remote computer. Can you tell me how to do that. Plus the above link isn't working. – Awais ali Sep 09 '20 at 05:06
  • Thanks so much for sharing this solution ! – ryuzaki Jun 21 '21 at 18:43
  • @Xiaofu some applications doesn't have Display name. – Meric Ozcan May 05 '22 at 11:57
16

I wanted to be able to extract a list of apps just as they appear in the start menu. Using the registry, I was getting entries that do not show up in the start menu.

I also wanted to find the exe path and to extract an icon to eventually make a nice looking launcher. Unfortunately, with the registry method this is kind of a hit and miss since my observations are that this information isn't reliably available.

My alternative is based around the shell:AppsFolder which you can access by running explorer.exe shell:appsFolder and which lists all apps, including store apps, currently installed and available through the start menu. The issue is that this is a virtual folder that can't be accessed with System.IO.Directory. Instead, you would have to use native shell32 commands. Fortunately, Microsoft published the Microsoft.WindowsAPICodePack-Shell on Nuget which is a wrapper for the aforementioned commands. Enough said, here's the code:

// GUID taken from https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
var FOLDERID_AppsFolder = new Guid("{1e87508d-89c2-42f0-8a7e-645a0f50ca58}");
ShellObject appsFolder = (ShellObject)KnownFolderHelper.FromKnownFolderId(FOLDERID_AppsFolder);

foreach (var app in (IKnownFolder)appsFolder)
{
    // The friendly app name
    string name = app.Name;
    // The ParsingName property is the AppUserModelID
    string appUserModelID = app.ParsingName; // or app.Properties.System.AppUserModel.ID
    // You can even get the Jumbo icon in one shot
    ImageSource icon =  app.Thumbnail.ExtraLargeBitmapSource;
}

And that's all there is to it. You can also start the apps using

System.Diagnostics.Process.Start("explorer.exe", @" shell:appsFolder\" + appModelUserID);

This works for regular Win32 apps and UWP store apps. How about them apples.

Since you are interested in listing all installed apps, it is reasonable to expect that you might want to monitor for new apps or uninstalled apps as well, which you can do using the ShellObjectWatcher:

ShellObjectWatcher sow = new ShellObjectWatcher(appsFolder, false);
sow.AllEvents += (s, e) => DoWhatever();
sow.Start();

Edit: One might also be interested in knowing that the AppUserMoedlID mentioned above is the unique ID Windows uses to group windows in the taskbar.

2022: Tested in Windows 11 and still works great. Windows 11 also seems to cache apps that aren't installed per se, portable apps that don't need installing, for example. They appear in the start menu search results and can also be retrieved from shell:appsFolder as well.

user1969903
  • 810
  • 13
  • 26
  • Thank you so much, a really good way to achieve this. Did you know if there's a way to get name, parsing name or so directly from ShellObjectWatcher ? – forlayo Feb 28 '20 at 11:54
  • There are other event types apart from ```AllEvents```such as ```ItemCreated``` or ```ItemRenamed``` which I tried using to keep track of apps as they were installed or removed. The event args of these events contain a ```Path``` property but this property is always null, at least in my tests.I have not bothered trying to figure out how to get a parsing name from it since it's always null. Instead, I simply keep a list of apps which I sync whenever an item is raised by iterating through the apps in the folder. Not ideal but gets the job done. – user1969903 Feb 28 '20 at 16:23
  • 1
    Thanks! I am actually doing the same; also this have helped me on other quesion about "how to discover the main executable of an application that have been just installed" -> https://stackoverflow.com/questions/60440044/how-to-discover-the-main-executable-file-of-an-application Then thanks for that! :) – forlayo Feb 29 '20 at 19:08
  • Is there a way to start said process *with* arguments? – Starwave Apr 05 '21 at 12:58
  • Unfortunately, this method is an extreme memory hog - my app was using 57 MB of memory, and when I used this method to get the list of apps, it shot up to 882 MB. Note - this only happens, if I also get the icon data. – Starwave Apr 05 '21 at 14:19
  • If you are also gona show icon, use `SmallBitmapSource` to speed up creation of collection and reducing memory usage. – Starwave Apr 05 '21 at 14:27
  • The ```Process.Start``` method does have overrides for providing arguments but I haven't tried them. How much memory did using ```SmallBitmapSource``` free up by comparison? – user1969903 Apr 06 '21 at 12:19
  • @Starwave If you want the large icons, you could cache the icons and only load them when they are scrolled into view or, if you paginate the apps list, you could load only the icons for the apps on the current page. – user1969903 Apr 08 '21 at 17:08
  • `SmallBitmapSource` icons increased memory usage by 5 MB (so total usage of my app was 62 MB), which is nothing (my apps folder had 217 items in it). – Starwave Apr 09 '21 at 09:20
  • The provided nuget is not maintained anymore. Here is the new one with support for .NET 6: https://www.nuget.org/packages/Microsoft-WindowsAPICodePack-Shell/ – Damian Ubowski Jul 29 '23 at 11:30
10

I agree that enumerating through the registry key is the best way.

Note, however, that the key given, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", will list all applications in a 32-bit Windows installation, and 64-bit applications in a Windows 64-bit installation.

In order to also see 32-bit applications installed on a Windows 64-bit installation, you would also need to enumeration the key @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall".

Stephen Walter
  • 269
  • 2
  • 8
8

You can take a look at this article. It makes use of registry to read the list of installed applications.

public void GetInstalledApps()
{
    string uninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
    using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(uninstallKey))
    {
        foreach (string skName in rk.GetSubKeyNames())
        {
            using (RegistryKey sk = rk.OpenSubKey(skName))
            {
                try
                {
                    lstInstalled.Items.Add(sk.GetValue("DisplayName"));
                }
                catch (Exception ex)
                { }
            }
        }
    }
}
Kirtan
  • 21,295
  • 6
  • 46
  • 61
7

While the accepted solution works, it is not complete. By far.

If you want to get all the keys, you need to take into consideration 2 more things:

x86 & x64 applications do not have access to the same registry. Basically x86 cannot normally access x64 registry. And some applications only register to the x64 registry.

and

some applications actually install into the CurrentUser registry instead of the LocalMachine

With that in mind, I managed to get ALL installed applications using the following code, WITHOUT using WMI

Here is the code:

List<string> installs = new List<string>();
List<string> keys = new List<string>() {
  @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
  @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
};

// The RegistryView.Registry64 forces the application to open the registry as x64 even if the application is compiled as x86 
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64), keys, installs);
FindInstalls(RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64), keys, installs);

installs = installs.Where(s => !string.IsNullOrWhiteSpace(s)).Distinct().ToList();
installs.Sort(); // The list of ALL installed applications



private void FindInstalls(RegistryKey regKey, List<string> keys, List<string> installed)
{
  foreach (string key in keys)
  {
    using (RegistryKey rk = regKey.OpenSubKey(key))
    {
      if (rk == null)
      {
        continue;
      }
      foreach (string skName in rk.GetSubKeyNames())
      {
        using (RegistryKey sk = rk.OpenSubKey(skName))
        {
          try
          {
            installed.Add(Convert.ToString(sk.GetValue("DisplayName")));
          }
          catch (Exception ex)
          { }
        }
      }
    }
  }
}
Pic Mickael
  • 1,244
  • 19
  • 36
3

it's worth noting that the Win32_Product WMI class represents products as they are installed by Windows Installer. not every application use windows installer

however "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" represents applications for 32 bit. For 64 bit you also need to traverse "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" and since not every software has a 64 bit version the total applications installed are a union of keys on both locations that have "UninstallString" Value with them.

but the best options remains the same .traverse registry keys is a better approach since every application have an entry in registry[including the ones in Windows Installer].however the registry method is insecure as if anyone removes the corresponding key then you will not know the Application entry.On the contrary Altering the HKEY_Classes_ROOT\Installers is more tricky as it is linked with licensing issues such as Microsoft office or other products. for more robust solution you can always combine registry alternative with the WMI.

antiduh
  • 11,853
  • 4
  • 43
  • 66
Akshita
  • 849
  • 8
  • 15
2
string[] registryKeys = new string[] {
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
@"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" };

public class InstalledApp
{
    public string DisplayName { get; set; }
    public string DisplayIcon { get; set; }
    public string Version { get; set; }
    public string InstallLocation { get; set; }
}

private void AddInstalledAppToResultView(RegistryHive hive, RegistryView view, string registryKey,Dictionary<string,InstalledApp> resultView)
{
    using (var key = RegistryKey.OpenBaseKey(hive, view).OpenSubKey(registryKey))
    {
        foreach (string subKeyName in key.GetSubKeyNames())
        {
            using (RegistryKey subkey = key.OpenSubKey(subKeyName))
            {
                var displayName = subkey.GetValue("DisplayName");
                var displayIcon = subkey.GetValue("DisplayIcon");
                if (displayName == null || displayIcon == null)
                    continue;

                var app = new InstalledApp
                {
                    DisplayName = (string)displayName,
                    DisplayIcon = (string)displayIcon,
                    InstallLocation = (string)subkey.GetValue("InstallLocation"),
                    Version = (string)subkey.GetValue("DisplayVersion")
                };
                
                if(!resultView.ContainsKey(app.DisplayName))
                {
                    resultView.Add(app.DisplayName,app);
                }
            }
        }
    }
}


void Main()
{
    var result = new Dictionary<string,InstalledApp>();
    var view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32;
    AddInstalledAppToResultView(RegistryHive.LocalMachine, view, registryKeys[0],result);
    AddInstalledAppToResultView(RegistryHive.CurrentUser, view, registryKeys[0],result);
    AddInstalledAppToResultView(RegistryHive.LocalMachine, RegistryView.Registry64, registryKeys[1],result);
    
    Console.WriteLine("==============" + result.Count + "=================");
    result.Values.ToList().ForEach(item => Console.WriteLine(item));
}
sunth
  • 77
  • 3
1

Use Windows Installer API!

It allows to make reliable enumeration of all programs. Registry is not reliable, but WMI is heavyweight.

Brian Cannard
  • 852
  • 9
  • 20
  • sure is heavy weight - if run repeatedly, one will see performance drops like a heavy weight. if a feature of my app depends on another app, and know if installed properly, I only need the uninstall registry key for 32 or 64 only if the app is avail in 64 bit also) on the other hand if I must use wmi, I will limit to use only once during a application via a smart property trick. – gg89 Mar 28 '15 at 20:41
  • https://learn.microsoft.com/en-us/windows/desktop/msi/installer-function-reference – Brian Cannard May 08 '19 at 18:01
1

The object for the list:

public class InstalledProgram
{
    public string DisplayName { get; set; }
    public string Version { get; set; }
    public string InstalledDate { get; set; }
    public string Publisher { get; set; }
    public string UnninstallCommand { get; set; }
    public string ModifyPath { get; set; }
}

The call for creating the list:

    List<InstalledProgram> installedprograms = new List<InstalledProgram>();
    string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
    using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key))
    {
        foreach (string subkey_name in key.GetSubKeyNames())
        {
            using (RegistryKey subkey = key.OpenSubKey(subkey_name))
            {
                if (subkey.GetValue("DisplayName") != null)
                {
                    installedprograms.Add(new InstalledProgram
                    {
                        DisplayName = (string)subkey.GetValue("DisplayName"),
                        Version = (string)subkey.GetValue("DisplayVersion"),
                        InstalledDate = (string)subkey.GetValue("InstallDate"),
                        Publisher = (string)subkey.GetValue("Publisher"),
                        UnninstallCommand = (string)subkey.GetValue("UninstallString"),
                        ModifyPath = (string)subkey.GetValue("ModifyPath")
                    });
                }
            }
        }
    }
1

As others have pointed out, the accepted answer does not return both x86 and x64 installs. Below is my solution for that. It creates a StringBuilder, appends the registry values to it (with formatting), and writes its output to a text file:

const string FORMAT = "{0,-100} {1,-20} {2,-30} {3,-8}\n";

private void LogInstalledSoftware()
{
    var line = string.Format(FORMAT, "DisplayName", "Version", "Publisher", "InstallDate");
    line += string.Format(FORMAT, "-----------", "-------", "---------", "-----------");
    var sb = new StringBuilder(line, 100000);
    ReadRegistryUninstall(ref sb, RegistryView.Registry32);
    sb.Append($"\n[64 bit section]\n\n{line}");
    ReadRegistryUninstall(ref sb, RegistryView.Registry64);
    File.WriteAllText(@"c:\temp\log.txt", sb.ToString());
}

   private static void ReadRegistryUninstall(ref StringBuilder sb, RegistryView view)
    {
        const string REGISTRY_KEY = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
        using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
        using var subKey = baseKey.OpenSubKey(REGISTRY_KEY);
        foreach (string subkey_name in subKey.GetSubKeyNames())
        {
            using RegistryKey key = subKey.OpenSubKey(subkey_name);
            if (!string.IsNullOrEmpty(key.GetValue("DisplayName") as string))
            {
                var line = string.Format(FORMAT,
                    key.GetValue("DisplayName"),
                    key.GetValue("DisplayVersion"),
                    key.GetValue("Publisher"),
                    key.GetValue("InstallDate"));
                sb.Append(line);
            }
            key.Close();
        }
        subKey.Close();
        baseKey.Close();
    }
Mike Lowery
  • 2,630
  • 4
  • 34
  • 44
1

Iterate through "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" keys and check their "DisplayName" values.

Moayad Mardini
  • 7,271
  • 5
  • 41
  • 58
0

I used Nicks approach - I needed to check whether the Remote Tools for Visual Studio are installed or not, it seems a bit slow, but in a seperate thread this is fine for me. - here my extended code:

    private bool isRdInstalled() {
        ManagementObjectSearcher p = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
        foreach (ManagementObject program in p.Get()) {
            if (program != null && program.GetPropertyValue("Name") != null && program.GetPropertyValue("Name").ToString().Contains("Microsoft Visual Studio 2012 Remote Debugger")) {
                return true;
            }
            if (program != null && program.GetPropertyValue("Name") != null) {
                Trace.WriteLine(program.GetPropertyValue("Name"));
            }
        }
        return false;
    }
Marc Loeb
  • 570
  • 9
  • 27
0

Your best bet is to use WMI. Specifically the Win32_Product class.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
0

Might I suggest you take a look at WMI (Windows Management Instrumentation). If you add the System.Management reference to your C# project, you'll gain access to the class `ManagementObjectSearcher', which you will probably find useful.

There are various WMI Classes for Installed Applications, but if it was installed with Windows Installer, then the Win32_Product class is probably best suited to you.

ManagementObjectSearcher s = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
Nick
  • 1,799
  • 13
  • 13
-1

My requirement is to check if specific software is installed in my system. This solution works as expected. It might help you. I used a windows application in c# with visual studio 2015.

 private void Form1_Load(object sender, EventArgs e)
        {

            object line;
            string softwareinstallpath = string.Empty;
            string registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
            using (var baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
            {
                using (var key = baseKey.OpenSubKey(registry_key))
                {
                    foreach (string subkey_name in key.GetSubKeyNames())
                    {
                        using (var subKey = key.OpenSubKey(subkey_name))
                        {
                            line = subKey.GetValue("DisplayName");
                            if (line != null && (line.ToString().ToUpper().Contains("SPARK")))
                            {

                                softwareinstallpath = subKey.GetValue("InstallLocation").ToString();
                                listBox1.Items.Add(subKey.GetValue("InstallLocation"));
                                break;
                            }
                        }
                    }
                }
            }

            if(softwareinstallpath.Equals(string.Empty))
            {
                MessageBox.Show("The Mirth connect software not installed in this system.")
            }



            string targetPath = softwareinstallpath + @"\custom-lib\";
            string[] files = System.IO.Directory.GetFiles(@"D:\BaseFiles");

            // Copy the files and overwrite destination files if they already exist. 
            foreach (var item in files)
            {
                string srcfilepath = item;
                string fileName = System.IO.Path.GetFileName(item);
                System.IO.File.Copy(srcfilepath, targetPath + fileName, true);
            }
            return;

        }
Michael Kunst
  • 2,978
  • 25
  • 40