Extract installed Software Details using WMI sys management

Ravi Dhoble

Member
Joined
Dec 11, 2019
Messages
13
Programming Experience
1-3
Hello,

I am a newbie to c#. I am currently working on a task to extract information of all the software installed on my Windows PC (Win 10 OS). I found the following example Link. But, the code throws an error related to the management class object and also for the "var date and time" variable assignment. I tried a couple of solutions..partially managed to resolve the errors. but still not able to get the desired output as shown in the link above.
Can anyone try this example in Visual Studio.? and help to resolve the issue. (I am using VS 2019 version).

Well, my end goal is to extract "InstallLocation" path data for a particular software I have to work with further. Also, wants to know which will be the best WMI or Win32 Registry?
 

Attachments

  • WMI_Get_SW_Details_issue1.PNG
    WMI_Get_SW_Details_issue1.PNG
    145.6 KB · Views: 21
That means that the "Name" property of that particular management object came back as null.

Personally, I don't like WMI, and inspecting the registry is just a major pain. I suggest using the actual installer APIs.
 
I'm a fan of WMI for a few reasons, but only when I want a particulate from it offered up easily by no other API. It's also worth using if you are willing to write a good class for determining the different program architecture for 32/64bit programs, and additional checks for each of the property names you wish you receive. It's up to you as the developer to write checks ensuring null values are not being processed and treated as non null though. It's also worth noting that WMI is heavy on resources, so if using it, acquire as much info as possible and make it worth using, or additionally use alternative methods. ^^
 
That means that the "Name" property of that particular management object came back as null.

Personally, I don't like WMI, and inspecting the registry is just a major pain. I suggest using the actual installer APIs.
Thanks for providing the info. I would like to know, Are you able to run and get the expected result from the example link i posted? I also checked the link you provided but can't see any example given in c# to extract product info, install location info. can you provide an example? That would help.
 
Last edited:
I'm a fan of WMI for a few reasons, but only when I want a particulate from it offered up easily by no other API. It's also worth using if you are willing to write a good class for determining the different program architecture for 32/64bit programs, and additional checks for each of the property names you wish you receive. It's up to you as the developer to write checks ensuring null values are not being processed and treated as nonnull though. It's also worth noting that WMI is heavy on resources, so if using it, acquire as much info as possible and make it worth using or additionally use alternative methods. ^^
Thanks for the reply. Earlier I also tried using win 32 registry technique. I am able to retrieve the name, version, install location data. But these methods didn't give me the "install location" of all the software. In my c# application, I wanted to extract install location path for all the "NI (National instruments related softwares)" as you can see in the screenshot attached. can you provide an example for WMI, to get this?
MicrosoftTeams-image.png
 
Are you able to run and get the expected result from the example link i posted?
I didn't run it because I can't copy and paste a screenshot into something that will compile. In the future, post code in code tags instead of screenshots of code.

I also checked the link you provided but can't see any example given in c# to extract product info, install location info. can you provide an example?
Those are Win32 APIs. To use them from C#, you would need to use P/Invoke.

Anyway, if you truly did read the documentation instead of just scanning looking for C# code that you could copy and paste,, you would use the product enumerator API to get a list of product IDs. For each produce ID that you get back you would call MsiGetProductInfoEx() which would let you pass in a property that you are interested in like: INSTALLPROPERTY_PUBLISHER, INSTALLPROPERTY_INSTALLLOCATION, etc.
 
If you are not really into P/Invoke, you could also use the Installer Automation Interface which gives you a COM object to talk to. Since C# knows how to talk to COM, you can get relatively far without having to learn about P/Invoke. Just get to Installer.ProductsEx to get the list of installed Products, and then inspect each one. It'll be like using WMI, but without the WMI overhead.
 
If you are not really into P/Invoke, you could also use the Installer Automation Interface which gives you a COM object to talk to. Since C# knows how to talk to COM, you can get relatively far without having to learn about P/Invoke. Just get to Installer.ProductsEx to get the list of installed Products, and then inspect each one. It'll be like using WMI, but without the WMI overhead.
Hello,
Thanks for providing detail information on WMI. I guess it will take time for me to get through the concepts of WMI. For now, I am using win32.Reg APIs to get the required information for a particular installed software on my system.
 
??? I gave you information on how to avoid using WMI and use the Windows Installer APIs directly. Perhaps you meant to reply to my virtual twin @Sheepings ?
 
No problem, you are not the first to confuse the two of us. And we also discovered that we are eerily similar in other ways in real life that we joke that I must be his evil twin.
 
Some installs might not be MSI based installs which means they will not have a set value for the InstallLocation unless it has also been set by the installer or manually by the developer themselves. So the value may not exist.

I am writing out something you can try, but I won't be able to post it until a bit later today.
 
To use the registry, you will also experience problems retrieving any such values as I've out lined on post 13. I've wrote a multi-threaded example for you. It may not be perfect, but It does retrieve what you want if those values exist. You will have to append your own filter to the returning functions so as they only return the values you want. Currently it will return all programs installed. The code that returns the keys values is this :
C#:
        private static bool All_KeyValues_Exist(RegistryKey subkey)
        {
            string app_Name = (string)subkey.GetValue("DisplayName");
            string publisher_Name = (string)subkey.GetValue("Publisher");
            string installLocation = (string)subkey.GetValue("InstallLocation");
            if (string.IsNullOrEmpty(app_Name) || string.IsNullOrEmpty(publisher_Name) || string.IsNullOrEmpty(installLocation))
                return false;
            else
                return true;
        }
If you want to get additional keys, then you can go here : SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall to look at the available values for those keys. For each value you want to include, you will need to add them, just as I have done below :
C#:
            string app_Name = (string)subkey.GetValue("DisplayName");
            string publisher_Name = (string)subkey.GetValue("Publisher");
            string installLocation = (string)subkey.GetValue("InstallLocation");
Which I believe is what you're after. This piece of code enumerates all the keys in the hive and then iterates over the subkeys to get the values. Note : if (All_KeyValues_Exist(subkey)) => If any keys names are null or don't exist, they will not be allowed be entered into the program_Values list, and subsequently won't be shown in the UI. This next bit of code is responsible for returning a list of the currently found applications :
C#:
        public static List<string> GetInstalled_Programs()
        {
            List<string> program_Values = new List<string>();
            program_Values.AddRange(ReadProgram_Instalations(RegistryView.Registry32));
            program_Values.AddRange(ReadProgram_Instalations(RegistryView.Registry64));
            return program_Values;
        }
All of these keys are being pulled from : @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; And it works by the following code kicking off a background worker. I've used a background worker, because this enumeration process can take a few seconds to complete, and if you were to execute this code on the UI thread, it would likely freeze until the process completed. I have added two events as you can see, the DoWork event, and the RunWorkerCompleted event. Once the worker has been executed, the work will begin. See :
C#:
        private void MainMethod()
        {
            bgWorker.DoWork += BgWorker_DoWork;
            bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
            bgWorker.RunWorkerAsync();
        }
The worker is responsible for getting the necessary registry values from the hive, and then returning them as a list<string>, that list is defined as : public List<string> result = new List<string>();. When the worker begins execution :
C#:
        private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            result = Get_Installed_Software.GetInstalled_Programs();
            if (result != null)
            {
                result.ForEach((string item) => listBox1.Invoke(new DelegateCallback(UpdateUI),
                          new string[] { item })); /* Use string.split to split at "," */
            }
        }
It will return a result, then ensure the result is not null and begin iterating through the list, and then invokes the control on the UI thread by passing the iterated values through the UpdateUI method which is a thread-safe operation. See :
C#:
        private void UpdateUI(string item)
        {
            listBox1.Items.Add(item);
        }
The reason we need to do this and update the UI by invoking the control is because the background worker is currently running on a NON-UI thread, which our control (Listbox) was not created on. The delegate action basically puts us back on "talking terms" with our UI, and allows us to safely add the new value before returning to repeat the process over. The all important line for allowing this to happen is our DelegateCallback : public delegate void DelegateCallback(string s);
Once our work has completed, the background worker : public BackgroundWorker bgWorker = new BackgroundWorker(); will ensure the completed events is fired where we then remove our events :
C#:
        private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bgWorker.DoWork -= BgWorker_DoWork;
            bgWorker.RunWorkerCompleted -= BgWorker_RunWorkerCompleted;
        }
While I admit, this is obviously not the best place to remove these, but It is safe and it does work. What you may want to do is add some kind of spinning ball animation to let your users know that the UI is currently executing a task and to be patient. You could do that from the background workers do work event also. Using the template I' have provided for multithreading, you would simply setup a new thread to execute the animation, so that too does not hog your UI and freeze your application. The finished code assembled should look like this :
C#:
using Microsoft.Win32;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

namespace TestCSharpApp
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            MainMethod();
        }
        public List<string> result = new List<string>();
        public BackgroundWorker bgWorker = new BackgroundWorker();
        public delegate void DelegateCallback(string s);
        private void MainMethod()
        {
            bgWorker.DoWork += BgWorker_DoWork;
            bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
            bgWorker.RunWorkerAsync();
        }
        private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bgWorker.DoWork -= BgWorker_DoWork;
            bgWorker.RunWorkerCompleted -= BgWorker_RunWorkerCompleted;
        }
        private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            result = Get_Installed_Software.GetInstalled_Programs();
            if (result != null)
            {
                result.ForEach((string item) => listBox1.Invoke(new DelegateCallback(UpdateUI),
                          new string[] { item })); /* Use string.split to split at "," */
            }
        }
        private void UpdateUI(string item)
        {
            listBox1.Items.Add(item);
        }
    }
    public static class Get_Installed_Software
    {
        private const string reg_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
        public static List<string> GetInstalled_Programs()
        {
            List<string> program_Values = new List<string>();
            program_Values.AddRange(ReadProgram_Instalations(RegistryView.Registry32));
            program_Values.AddRange(ReadProgram_Instalations(RegistryView.Registry64));
            return program_Values;
        }
        private static IEnumerable<string> ReadProgram_Instalations(RegistryView registryView)
        {
            List<string> program_Values = new List<string>();
            using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(reg_key))
            {
                for (int i = 0; i < key.GetSubKeyNames().Length; i++)
                {
                    using (RegistryKey subkey = key.OpenSubKey(key.GetSubKeyNames()[i]))
                    {
                        if (All_KeyValues_Exist(subkey))
                        {
                            program_Values.Add(string.Concat((string)subkey.GetValue("DisplayName"), ", ", (string)subkey.GetValue("Publisher"), ", ", (string)subkey.GetValue("InstallLocation")));
                        }
                    }
                }
            }
            return program_Values;
        }
        private static bool All_KeyValues_Exist(RegistryKey subkey)
        {
            string app_Name = (string)subkey.GetValue("DisplayName");
            string publisher_Name = (string)subkey.GetValue("Publisher");
            string installLocation = (string)subkey.GetValue("InstallLocation");
            if (string.IsNullOrEmpty(app_Name) || string.IsNullOrEmpty(publisher_Name) || string.IsNullOrEmpty(installLocation))
                return false;
            else
                return true;
        }
    }
}
 
Last edited:
If you want to keep going with your WMI method, here's a short example for you, although I must admit, it runs very slowly. And It's definitely advisable to run this code on a new thread. For WMI : 164,625ms is a very long running execution. Whereas the code above for using the registry method only took : 5,693ms
C#:
        public Form1()
        {
            InitializeComponent();
            MainMethod();
        }
        public delegate void DelegateCallback(string s);
        private void MainMethod()
        {
            Task t = new Task(GetDependentObjects, TaskCreationOptions.LongRunning);
            Thread tt = new Thread(GetDependentObjects, 0);
            tt.Start();
        }
        private void GetDependentObjects()
        {
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Product");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Name"] != null && queryObj["InstallLocation"] != null)
                {
                    listBox1.Invoke(new DelegateCallback(UpdateUI), new string[] { string.Concat((string)queryObj["Name"], ", ", (string)queryObj["InstallDate"], ", ", (string)queryObj["InstallLocation"]) });
                }
            }
        }
        private void UpdateUI(string item)
        {
            listBox1.Items.Add(item);
        }
 
Back
Top Bottom