# VBForums CodeBank > CodeBank - Visual Basic .NET >  Example of how to get a list of installed programs (like Add and Remove Programs)

## chris128

First of all I just want to say that this is different to every other example of programmatically retrieving a list of installed software that I have seen on the internet. All of the other examples seem to get way too many programs compared to what you would see in Add/Remove Programs in Windows and sometimes miss programs as well - both of these issues are caused by the fact that they rely on just looping through the Uninstall key in the registry or using WMI (which probably does the same thing). Add/Remove Programs looks in several other registry locations as well and I spent a considerable amount of time trying to figure out exactly how it decides which programs to include and which to ignore... but think I have finally got my code to produce an identical list to the list that Add/Remove Programs produces.
So as you have probably guessed, my code does not use WMI, it is all done by just reading registry keys  :Smilie:  

*Description*
Retrieves the names (and version) of all installed programs on a computer just like Add/Remove Programs. This was made to work as similarly to Add/Remove programs as possible, so whilst you could actually retrieve more programs (such as system components and drivers), my code should only get the ones that you would normally expect to see. 
One advantage my code has over the built in Add/Remove Programs window is that you can run my code against a remote computer just as easily as you can run it on your local computer  :Smilie:  See below for details. 

*Usage*
The DLL contains just one class, InstalledProgram, which has a single Shared method named GetInstalledPrograms which returns a generic List(Of InstalledProgram) and you can specify whether or not you want the returned list to include updates or not. The class also has several properties such as DisplayName, Version and IsUpdate.

I have included an example project with the solution attached to this post that demonstrates how you could use the InstalledProgram class to populate a ListView with all installed programs and their version numbers. If you want to get the list of installed programs from another computer on the network then there is an overloaded version of the GetInstalledPrograms method that accepts a computer name or IP address as an additional parameter. The demo project shows how to use this as well. 
Here is a screenshot of the example application included with the attached solution that uses the InstalledProgram class to populate a listview:



As you can see, some items do not have the Version information but this is the same in Add/Remove Programs.

*Per User Programs*
There is one distinct difference in the way that my code works compared to Add/Remove Programs and that is the way in which user specific programs are handled. When you install a program that just installs for the currently logged on user, such as a ClickOnce app, then Add/Remove Programs will only show you that program if you are logged in as the user that the program was installed for. With my code however, you will see any user specific programs for every user no matter who you are logged on as (as long as the account you are logged in with has the relevant permissions). I made it this way intentionally but let me know if you dont think it should work like that.

*Known Problems*

Windows/Program Updates
I have not really concentrated on this side of things, I just wanted it to retrieve all installed programs and didnt care about windows updates etc but it does get the majority of them, just not all of them. If I get some spare time I'll try and sort that out but at the moment its not a priority

64 bit
The code seems to work perfectly fine on a 64 bit system if the code is built to target x64 (or AnyCpu) but if you set the code to target x86 then Windows hides/redirects one of the registry keys from the process and that key contains some of the installed programs - the end result being that not all programs may be displayed in this scenario. No such issues on 32 bit operating systems of course.

Version history is now attached to this post

So yeah, try it out and let me know if you find it useful or have any problems/feedback  :Smilie:  Of course if anyone has any suggestions for improvements to the code as well then feel free to point them out.

Chris

*Download latest version below*

----------


## NickThissen

I just tried it even though I don't have any real need for it, but it works fine. I was surprised how fast it was, especially compared to the 'real thing' in windows. What does the real windows programs list do that makes it so slow? The only large difference I can see is the icons, do they cost that much time to retrieve? And while on that subject, perhaps a nice update would be to also retrieve the icons, or the publisher, installed date, size, basically everything the programs list also shows? I would imagine the information to be found in a similar place, although that is just a guess  :Stick Out Tongue: 



EDIT
On second look, the real programs list shows 5 programs that are not in your list:
- ATI Catalyst Install Manager
- Microsoft .NET Compact Framework 2.0 SP2
- Microsoft .NET Compact Framework 3.5
and as you mentioned Office 2010 and Visio 2010

Not sure why the the first three are not listed..?

But otherwise very nice job!

----------


## chris128

Thanks for the feedback Nick  :Smilie:  yeah I imagine it is the icons that make it take longer in the Windows program manager... oh and the fact that it queries each relevant registry key about 9 times and tries to read from several keys which dont seem to exist on any OS I have found, where as my program only does it once and only looks in the keys that do actually exist and do have programs in. Not sure why those 3 apps are not showing, I'm disappointed as I thought I had finally got it to get ALL programs apart from Office 2010! I'll install the compact framework on my PC and see if I can figure that one out. Cheers

----------


## chris128

Damn you Nick! I've got to re-write half of this now that I've discovered something quite important after investigating the entries for the programs you mentioned. I dont understand how I managed to test this on over 15 machines and have it work perfectly though...

----------


## NickThissen

Hehe, sorry  :Stick Out Tongue: 
Perhaps office will work now too though  :Wink:

----------


## chris128

Actually it wasnt as much hassle as I thought  :Smilie:  It now detects the compact framework for me and I've also fixed the bug that stopped office 2010 from being detected at all so can you give the new version a go and let me know if it now detects everything? New solution (version 1-5) attached to original post  :Smilie:  Thanks

----------


## NickThissen

Yep, it gets them all now, including Office 2010 (and visio). Nice!

----------


## chris128

Cool  :Smilie:  thanks a lot for trying it and alerting me to that problem!

----------


## chris128

Version 1.6 now attached to original post, see "Known Problems and Fixes" section of the post for details on the changes in this latest version.

----------


## chris128

Fixed a stupid bug in the way that I was comparing instances of the InstalledProgram class. For some reason I made it so that when comparing two InstalledProgram instances, it first checks to see if the display name is the same (which makes sense), then checks to see if the versions are the same (still making sense), then if the versions are not the same it checks to see if one of them has a version that is not string.empty and if they do then it classes them as being the same (huh???)... dont ask, I must have been tired when I wrote that bit. It now just checks to see if DisplayName and Version are the same and if they are then it classes them as the same, if not then they are not the same. 

This means that if you have multiple versions of the same program installed then they might not have been detected before, where as now in version 1.7 they will  :Smilie:  Version 1.7 uploaded to original post.

----------


## weirddemon

I think adding icons would be great. It seems to work just fine for me. But I think most of us are visual people. I know when I'm getting ready to uninstall an application, I look for the familiar icon first.

Not a huge deal and will probably make the load time increase significantly. I know it does in my process manager.

 :Big Grin:

----------


## chris128

Yeah, to be honest the reason I didnt add that originally was because I didnt realise how easy it was to get the icon from a file, I thought it required API calls and all sorts but thanks to your process manager code I realised it can be done in a couple of lines  :Smilie: 

I may well include that in the next version but to be honest this wasnt really meant to be a complete replica of Add/Remove Programs (I wanted the program discovery process to work exactly the same though) as it was never intended to be able to uninstall programs, it was just for anyone wanting to list the installed programs on a PC from within their app (like I am doing in my auditing project that I am currently working on). I think if I do include it then I will add an overloaded method of the GetInstalledPrograms method that lets you specify that you do want it to retrieve the icons (so not getting the icons is the default) because as you have said, it does add considerable performance overhead and it would also be impossible to make it work reliably on remote computers as well. 

Thanks for the feedback though  :Smilie:

----------


## Drewster727

Having some issues with this and connecting to a remote win7 box, keep getting "the network path was not found" error.

WinXP machines return programs just fine, only Win7 machines I'm seeing the error with.

Im calling:

lstPrograms.Items.AddRange(InstalledProgram.GetInstalledPrograms(server, True).ToArray())

where lstprograms is my listbox
server is the remote computer

Like I said remote WinXP box works fine, remote Win7 doesn't seem to work, your try/catch ex/end try statement catches the "the network path was not found" error. (calling from a winxp to a win7 btw, havent tried win7 to win7 if that matters)

Great job BTW, if I could get this thing working for Win7 it would be amazing!!!

----------


## chris128

Hmm its probably something to do with permissions being more restrictive on Windows 7. If you open registry editor on your PC and go to File -> Connect Network Registry, can you connect to the Win7 machine's registry?

----------


## Drewster727

wow good call:

"Unable to connect to xxx. Make sure that this computer is on the network, has remote administration enabled, and that both computers are running the remote registry server."

any idea how to enable those permissions on a win7 box off hand? I have a domain admin account that should have full access to the win7 machine.

thanks!

----------


## Drewster727

haha! yep, looks like the remote registry service wasn't started..

thanks a bunch chris!!

----------


## chris128

no problem, glad the code was useful for you  :Smilie:

----------


## chris128

New version uploaded to original post. Just fixed a stupid bug where a program would not be detected if the UninstallString registry value was not in the correct case - only found one program so far (some random screen saver app) that does not create the registry value as "UninstallString" but instead creates it as "Uninstallstring". I've changed the code now so that it is no longer case sensitive.

----------


## Drewster727

Chris,

Thanks again for your work you've done on this!  :big yellow: 

I wanted to see if it were possible to implement a way for a non-admin user to browse these remote registry keys if they were to provide an administrator username/password.

i.e. some of my coworkers have both admin accounts and non-admin accounts, they are generally logged in with the non-admin accounts. I've had to reconfigure my program to prompt for admin user credentials on program start, I wanted to see if there was a way I could pass those credentials to your "InstalledProgram" class and it uses those credentials when making the registry connection.

Hope that made sense, I believe this is doable but it's a bit out of my expertise. This would be an incredibly useful function.

----------


## chris128

The problem is the VB.NET methods for connecting to the registry dont let you supply a username and password so I think the only way to do that is a bit of a messy method (and I've never tested it against a Windows 7 machine). Basically you would just map to the C$ share on the remote PC (which you _can_ provide credentials for) and then when you attempt to connect to the registry on that remote machine it uses the connection that was already established by the C$ mapping so lets you on. I'm afraid I wouldn't really want to include that as a 'feature' of my InstalledProgram class as like I said its a bit messy and relies on quite a few external items but you should be able to implement it yourself and still use my InstalledProgram class - you would just do the C$ mapping from your own program before calling my class. If you do not know what I mean by C$ mapping, you could just execute the Net Use command from your program via Process.Start. The Net Use command goes like this:


```
Net Use \\RemoteMachineName\C$ /User:MyAdminUsername MyAdminPassword
```

then when you are done with it:


```
Net Use \\RemoteMachineName\C$ /delete
```

Hope that helps!  :Smilie:

----------


## Drewster727

thanks for the reply chris.

In regards to the messiness and reliability of other items are you talking about making a managementscope connection to the remote machine? If that is so that is pretty simple, I'm doing that for a bunch of WMI queries already. (but I may be underestimating how to implement that in your code)

the "Net Use" commands didn't seem to work like I had hoped, it could be because the remote machines I've been connecting to are windows 7.

----------


## chris128

Nah I wasnt talking about WMI, I just meant using the Net Use command I mentioned. What problems did you have with them and how exactly did you try to do it?

----------


## Drewster727

sorry for the late reply. 

basically I ran the 'net use' commands to connect to the remote machine that I wanted to enumerate the apps from. This was done outside of my application. Next, with my app I attempted to load the programs (using your code) and it fails, on the visual studio immediate window it says there was a security exception 'registry access is not allowed'

This occurs when attempting to connect to a win7 box, the strange thing is I can connect to our winXP boxes and enumerate the apps just fine not even using the 'net use' commands. My win7 image and winXP image both have the same registry access permissions and both have the 'Remote Registry' service enabled.

So basically it seems like the 'net use' commands did nothing, really not sure what can be done here.

----------


## chris128

Ah ok I didnt realise this was just happening for your Windows 7 machines. Unfortunately I havent got one that is on a domain to test with but I'm guessing it is some new security feature in Windows 7 that locks down remote registry access. If you launch regedit from another PC and try and connect it to the Windows 7 machine remotely (File -> Connect network registry) does that work? I'm guessing it wont do, but can you just confirm? If it doesnt, might be worth checking the permissions on the registry key mentioned in the second half of this article to see if that is what is restricting it: http://www.windowsreference.com/secu...ss-in-windows/

----------


## pcmechanix

Can you set this up to output to a text file, or at least cut and paste the output?  Great program!

----------


## chris128

> Can you set this up to output to a text file, or at least cut and paste the output?  Great program!


As all of the main program logic is contained in the InstalledProgram class, which is in its own DLL project, you can just create your own application that references that DLL and calls the GetInstalledPrograms method, then write the returned data to a text file yourself.
The application shown in the screenshot is just a demo of what you could do with the InstalledProgram class - the whole point of separating the core functionality into its own class is that you can use it to get the data and then do whatever you want with it (display it on a form like in my example, write it to a database, write it to a text file, etc etc)  :Smilie:

----------


## SuperJohn

can u use this to uninstall programs? like in xp when you double click the program?

icons would be cool too

----------


## chris128

> can u use this to uninstall programs? like in xp when you double click the program?
> 
> icons would be cool too


See post #13 in this thread further up the page  :Wink:

----------


## heyu

Very nice job! 

But I have to say I would be interested in a way to say, select a bunch to be batch uninstalled. I did read post #13, I guess we should be able to expand on this ourselves, so thanks for sharing this awesome code!!


 :Thumb:

----------


## chris128

Thanks  :Smilie:  Uninstalling will be tricky if you want it to work on remote PCs as well. If you want to do it only on a local PC then it should be fairly easy - just grab the UninstallString value from the registry (if you look at my code you will see where the code checks to make sure this value exists, so thats where you need to get it from) and then pass whatever you got from that in to Process.Start. The problem with any kind of bulk uninstall (assuming you mean you want them all to run at the same time) is that you can't run more than one installation/uninstall at once if the installers use the Windows installer packages (MSI files).

Oh and if anyone wants to see a slightly more 'real' example of how this code can be used, see my network PC auditing program here: http://cjwdev.co.uk/Software/FastSof...udit/Info.html

----------


## heyu

I was kind of thinking of garbing that UninstallString and putting it in a toRemove arraylist then loop that list using process wait on a local pc only. Of course the user would have to select what programs would go in that arraylist.......I will have to look at this more closely though cause this is just off the top of my head.   :Alien Frog: 

Thanks

----------


## Drewster727

Chris,

How did you get your program to separate out the version number from the program name? I'm trying to use your class to dump program names into a column in a datagridview, and the version number of the program into a different column of the datagridview.

Thanks in advance.

----------


## chris128

Well my class has a separate property for the name and the version, so just use each property wherever you need to use that value...

When you call GetInstalledPrograms it returns a List(Of InstalledProgram) so you can loop through that and for each item in the list you add the relevant property to the relevant column. Here's a brief example of how to just show the program name and version on separate lines in a MessageBox - hopefully you can figure out how to apply this to your situation  :Wink: 


vb Code:
For Each Program As InstalledProgram In InstalledProgram.GetInstalledPrograms
    MessageBox.Show(Program.DisplayName & vbNewLine & Program.Version)
Next

----------


## JamesVB

nice job m8  :Big Grin:

----------


## neopreno999

Thank you very much for this application. I'm doing a job in class a it's very useful for me. I'm serching for an app like this because no one works in 64. 

I don't know if you still see this forum. But i have a trouble and i want to make you a question. I need where the installed programs are installed, i.e i need the dir of each program installed. 

Could you help me? Sorry about my english. :Smilie: 

Thank you very much again!!

----------


## chris128

Have a look in the registry keys that my code works with, I think there might be a registry value in some of them for each program that lists where the app was installed to.

----------


## Drewster727

Chris,

I'm trying to use the InstalledProgram class in a vb.net console application.

Everything seems to work when I try to list out the programs but I'm running into two problems and I can't figure out why:

1. duplicates, I see each program twice in the list I'm outputting to the console.

here is a snapshot of part of the console output:



2. some programs seem to be missing... one of them would be autodesk autocad 2010 off the top of my head. I'll have to find the registry location. It's odd though... I'm using the installed programs class in a winforms app and it seems to list all of the software, including the example I gave (autodesk autocad 2010).


here is my code:



```
For Each Program As InstalledProgram In InstalledProgram.GetInstalledPrograms(True)
                            Console.WriteLine(Program.DisplayName & " -- " & Program.Version)
                        Next
```

Any input on this would be great, I'm kinda stumped and would love to have this in my console program! thanks!

----------


## chris128

I would guess that your console app is running on a 64 bit OS and is not set to target the x64 CPU type - see this part of my original post:




> 64 bit
> The code seems to work perfectly fine on a 64 bit system if the code is built to target x64 (or AnyCpu) but if you set the code to target x86 then Windows hides/redirects one of the registry keys from the process and that key contains some of the installed programs - the end result being that not all programs may be displayed in this scenario. No such issues on 32 bit operating systems of course.

----------


## Drewster727

you are the man.... that fixed it. thanks!!!

----------


## ClarkVent

Quick question. First of all, I'm just starting with VB.Net so I'm kinda new to this (not new to programming though).

Your test application works fine.

I created a new project and just pasted the code from InstalledProgram.vb into a new class (didn't want to use a DLL). Now when I run my little test program, when it enters "GetUserInstallerKeyPrograms()" it croaks on the line



```
For Each UserDataKeyName As String In HklmRootKey.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Installer\UserData").GetSubKeyNames
```

Apparantly, "HklmRootKey.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Installer\UserData").GetSubKeyNam  es" contains "Nothing". Oddly enough, in your test app it's not empty (even with the same CuInstallerKey).

So what am I doing wrong?

Also, have you ever implemented getting the Icons as well?

----------


## chris128

Probably this issue that I mentioned in the original post:



> 64 bit
> The code seems to work perfectly fine on a 64 bit system if the code is built to target x64 (or AnyCpu) but if you set the code to target x86 then Windows hides/redirects one of the registry keys from the process and that key contains some of the installed programs - the end result being that not all programs may be displayed in this scenario. No such issues on 32 bit operating systems of course.

----------


## ClarkVent

I had considered that, but wouldn't your test app fail too then?

----------


## chris128

No because that targets AnyCpu

----------


## ClarkVent

Oddly enough, I can't change the target platform. In your test app, I can't change it from "Active (Any CPU)" and in my app I can't change it from "Active (x86)"...

----------


## chris128

You're changing it in the wrong place then. Double click on the "My Project" node in the Solution Explorer tree on the right hand side, then go to the Compile tab on the left in the new window that opens, then depending on which version of VS you are using you may have to click the Advanced Compile Options button from there.

----------


## ClarkVent

Doesn't matter really, since the project I want to use this for can't use x64 as a target anyway...  :Frown: 

Anyway, you were thinking about including getting the application's icons as well. Is that still on the table?

----------


## ClarkVent

> You're changing it in the wrong place then. Double click on the "My Project" node in the Solution Explorer tree on the right hand side, then go to the Compile tab on the left in the new window that opens, then depending on which version of VS you are using you may have to click the Advanced Compile Options button from there.


Yes I did that and it didn't work. What did work eventually, was going to Tools -> Options -> Projects and Solutions -> General, then tick "Always show solution". Then in the Solution Explorer, right click the solutions (which will now show) and click "Configuration Manager". There I first have to add a solution platform, then I can choose it as the active platform.

----------


## chris128

> Anyway, you were thinking about including getting the application's icons as well. Is that still on the table?


I'm afraid not, but there's nothing to stop someone else adding that functionality. It would be relatively easy if you only ever want to run it on the local machine rather than running it remotely against other computers on the network.

----------


## etorreshn

Well, I tested this. but it doesnt work with windows vista and windows 7.
Am I Right?

----------


## chris128

> Well, I tested this. but it doesnt work with windows vista and windows 7.
> Am I Right?


No you are not right. Care to explain what exactly "doesnt work" ?

----------


## Drewster727

Chris,

I'm getting a registry access denied error when I run this under certain users. I'm certain it's a permissions issue with those particular users, but do you have a good suggestion for where in your installedprograms class I can output what area of the registry it's being denied access to? i.e. where can I place a debug.writeline method in your class to output the current key.

Thanks man, I can elaborate more on this if needed, just pressed for time.

-Drew

----------


## chris128

The code attempts to read from the following registry locations (and subkeys under each one):
HKEY_LOCAL_MACHINE\Software\Classes\Installer\Products
HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Installer\UserData
but then it also looks at each of the user subkeys under HKEY_USERS so that may be where you are getting the permissions error if its trying to read data from other user's registry keys that the current user does not have access to (though I've never had any such issues and no one else has reported that).

----------


## Drewster727

Thanks for the reply & info,

I believe I may have figured out what the problem is... This is just a hunch but it makes some sense.

My exe is running on a login script when a user logs into a computer, it seems that when a user is already logged in to a computer (and the account is locked, i.e. the session is being switched without logging that user off) another user logs in and it attempts to query some reg key that is locked down from the other logged on user's session. 

I've been able to reproduce this, otherwise it works great... :Cool:

----------


## chris128

Yeah that would explain why I have never come across this issue. Just change the code in my class so that it doesn't try to get the programs installed for other users as well then. If you look at the public function GetInstalledPrograms, you should see near the end of it there is a part where it loops through each of the keys in the HKEY_USERS key, just change that so that it only uses the HKEY_CURRENT_USER key instead (so get rid of the loop completely).

----------


## Drewster727

Is there a particular spot in the code I could just place a try catch statement to catch that issue rather than removing the ability to search for other user programs?

Thanks.

-Drew

----------


## chris128

> Is there a particular spot in the code I could just place a try catch statement to catch that issue rather than removing the ability to search for other user programs?
> 
> Thanks.
> 
> -Drew


Yeah you could just wrap each iteration of the loop through individual user's programs because if it fails for one item in that user's registry hive then it will fail for all of them so it doesn't make any sense to add the Try/Catch any further into the code.

Just try replacing this:


vb.net Code:
Private Shared Function InternalGetInstalledPrograms(ByVal IncludeUpdates As Boolean, ByVal HklmPath As RegistryKey, ByVal HkuPath As RegistryKey) As List(Of InstalledProgram)
        Dim ProgramList As New List(Of InstalledProgram)
         Dim ClassesKey As RegistryKey = HklmPath.OpenSubKey("Software\Classes\Installer\Products")
         '---Wow64 Uninstall key
        Dim Wow64UninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(Wow64UninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         '---Standard Uninstall key
        Dim StdUninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(StdUninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         For Each UserSid As String In HkuPath.GetSubKeyNames
            '---HKU Uninstall key
            Dim CuUnInstallKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Windows\CurrentVersion\Uninstall")
            ProgramList = GetUninstallKeyPrograms(CuUnInstallKey, ClassesKey, ProgramList, IncludeUpdates)
            '---HKU Installer key
            Dim CuInstallerKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Installer\Products")
            ProgramList = GetUserInstallerKeyPrograms(CuInstallerKey, HklmPath, ProgramList)
        Next
         'Close the registry keys
        Try
            HklmPath.Close()
            HkuPath.Close()
        Catch ex As Exception
            Debug.WriteLine("Error closing registry key - " & ex.Message)
        End Try
         'Sort the list alphabetically and return it to the caller
        ProgramList.Sort()
        Return ProgramList
    End Function

with this:


vb.net Code:
Private Shared Function InternalGetInstalledPrograms(ByVal IncludeUpdates As Boolean, ByVal HklmPath As RegistryKey, ByVal HkuPath As RegistryKey) As List(Of InstalledProgram)
        Dim ProgramList As New List(Of InstalledProgram)
         Dim ClassesKey As RegistryKey = HklmPath.OpenSubKey("Software\Classes\Installer\Products")
         '---Wow64 Uninstall key
        Dim Wow64UninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(Wow64UninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         '---Standard Uninstall key
        Dim StdUninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(StdUninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         For Each UserSid As String In HkuPath.GetSubKeyNames
            Try
                '---HKU Uninstall key
                Dim CuUnInstallKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Windows\CurrentVersion\Uninstall")
                ProgramList = GetUninstallKeyPrograms(CuUnInstallKey, ClassesKey, ProgramList, IncludeUpdates)
                '---HKU Installer key
                Dim CuInstallerKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Installer\Products")
                ProgramList = GetUserInstallerKeyPrograms(CuInstallerKey, HklmPath, ProgramList)
            Catch ex As Exception
                Debug.WriteLine("Error encountered in HKEY_USERS\" & UserSid & " : " & ex.Message)
            End Try
        Next
          'Close the registry keys
        Try
            HklmPath.Close()
            HkuPath.Close()
        Catch ex As Exception
            Debug.WriteLine("Error closing registry key - " & ex.Message)
        End Try
         'Sort the list alphabetically and return it to the caller
        ProgramList.Sort()
        Return ProgramList
    End Function

let me know how it goes

----------


## Blagojce

In registry under HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall I have keys which starts with "{some number" but application skip this keys and start with application name keys.
I search through the code,but didn't notice how you did this.

----------


## chris128

> In registry under HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall I have keys which starts with "{some number" but application skip this keys and start with application name keys.
> I search through the code,but didn't notice how you did this.


Those programs are programs that have been installed by a Windows Installer (MSI) setup program. For each of the registry keys under that location you mentioned, my code checks to see if they have the WindowsInstaller value within them set to 1, if they do then we just get the name of the key (so{DJFHDFH34} for example) and pass it to our GetInstallerKeyNameFromGuid function which takes the name and works out the MSI installer ID for it (which is done by just moving some of the characters around, just look at the function to see exactly what it does). Then we look for that MSI installer ID in another part of the registry (HKEY_CLASSES_ROOT\MSI_installer_ID_here) to actually get the program name and other information.

This is the part of the code that does it (within the GetUninstallKeyPrograms method):


vb.net Code:
If Not CInt(CurrentSubKey.GetValue("WindowsInstaller", 0)) = 1 Then
'...
'... irrelevent code here
'...
Else   'If WindowsInstaller
     Dim ProgVersion As String = String.Empty
     Dim Name As String = String.Empty
     Dim FoundName As Boolean = False
     Try
           Dim MsiKeyName As String = GetInstallerKeyNameFromGuid(SubKeyName)
           Dim CrGuidKey As RegistryKey = ClassesKey.OpenSubKey(MsiKeyName)
           If Not CrGuidKey Is Nothing Then
                  Name = CStr(CrGuidKey.GetValue("ProductName", String.Empty))
           End If
     Catch ex As Exception
           Debug.WriteLine(SubKeyName & " - " & ex.Message)
     End Try

Hope that explains it - I know its not very straight forward, that's why it took me ages to figure it out and get this code working  :Smilie:

----------


## Drewster727

> Yeah you could just wrap each iteration of the loop through individual user's programs because if it fails for one item in that user's registry hive then it will fail for all of them so it doesn't make any sense to add the Try/Catch any further into the code.
> 
> 
> vb.net Code:
> Private Shared Function InternalGetInstalledPrograms(ByVal IncludeUpdates As Boolean, ByVal HklmPath As RegistryKey, ByVal HkuPath As RegistryKey) As List(Of InstalledProgram)
        Dim ProgramList As New List(Of InstalledProgram)
         Dim ClassesKey As RegistryKey = HklmPath.OpenSubKey("Software\Classes\Installer\Products")
         '---Wow64 Uninstall key
        Dim Wow64UninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(Wow64UninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         '---Standard Uninstall key
        Dim StdUninstallKey As RegistryKey = HklmPath.OpenSubKey("Software\Microsoft\Windows\CurrentVersion\Uninstall")
        ProgramList = GetUninstallKeyPrograms(StdUninstallKey, ClassesKey, ProgramList, IncludeUpdates)
         For Each UserSid As String In HkuPath.GetSubKeyNames
            Try
                '---HKU Uninstall key
                Dim CuUnInstallKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Windows\CurrentVersion\Uninstall")
                ProgramList = GetUninstallKeyPrograms(CuUnInstallKey, ClassesKey, ProgramList, IncludeUpdates)
                '---HKU Installer key
                Dim CuInstallerKey As RegistryKey = HkuPath.OpenSubKey(UserSid & "\Software\Microsoft\Installer\Products")
                ProgramList = GetUserInstallerKeyPrograms(CuInstallerKey, HklmPath, ProgramList)
            Catch ex As Exception
                Debug.WriteLine("Error encountered in HKEY_USERS\" & UserSid & " : " & ex.Message)
            End Try
        Next
          'Close the registry keys
        Try
            HklmPath.Close()
            HkuPath.Close()
        Catch ex As Exception
            Debug.WriteLine("Error closing registry key - " & ex.Message)
        End Try
         'Sort the list alphabetically and return it to the caller
        ProgramList.Sort()
        Return ProgramList
    End Function
> 
> let me know how it goes


worked like a charm chris, thanks a bunch.

-Drew

----------


## cevem

That's great article.

I have a question. How to find executable location of the programs? Thanks.

----------


## chris128

> That's great article.
> 
> I have a question. How to find executable location of the programs? Thanks.


There isn't any way that I know of. I know we all like to think that an installed program has a "main" EXE file but at the end of the day an installed program can be any number of EXEs (maybe even none at all) so Windows does not have any relation between an entry in Add/Remove Programs and actual EXE's (other than the location of the uninstaller for that program). If anyone finds a way to do it I would be interested to hear how, but as far as I know it is not really possible.

----------


## Blagojce

> Those programs are programs that have been installed by a Windows Installer (MSI) setup program. For each of the registry keys under that location you mentioned, my code checks to see if they have the WindowsInstaller value within them set to 1, if they do then we just get the name of the key (so{DJFHDFH34} for example) and pass it to our GetInstallerKeyNameFromGuid function which takes the name and works out the MSI installer ID for it (which is done by just moving some of the characters around, just look at the function to see exactly what it does). Then we look for that MSI installer ID in another part of the registry (HKEY_CLASSES_ROOT\MSI_installer_ID_here) to actually get the program name and other information.
> 
> This is the part of the code that does it (within the GetUninstallKeyPrograms method):
> 
> 
> vb.net Code:
> If Not CInt(CurrentSubKey.GetValue("WindowsInstaller", 0)) = 1 Then
'...
'... irrelevent code here
'...
Else   'If WindowsInstaller
     Dim ProgVersion As String = String.Empty
     Dim Name As String = String.Empty
     Dim FoundName As Boolean = False
     Try
           Dim MsiKeyName As String = GetInstallerKeyNameFromGuid(SubKeyName)
           Dim CrGuidKey As RegistryKey = ClassesKey.OpenSubKey(MsiKeyName)
           If Not CrGuidKey Is Nothing Then
                  Name = CStr(CrGuidKey.GetValue("ProductName", String.Empty))
           End If
     Catch ex As Exception
           Debug.WriteLine(SubKeyName & " - " & ex.Message)
     End Try
> 
> Hope that explains it - I know its not very straight forward, that's why it took me ages to figure it out and get this code working


Now it is clarified, Thanks!

----------


## Drewster727

Chris,

 I know that in the sub-directories in the following paths most programs have an 'installdate' key associated with them:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

Would you be able to give me some instructions on how to query the 'installdate' reg key and return that as well (similar to calling program.displayname & program.version? i.e. so I can return program.installdate

What parts of the code do I need to add to in order to be able to grab that?

I would find this very useful.

Thanks in advance, your help is always appreciated!

-Drew

----------


## Drewster727

well, I think I figured it out, was easier than I thought.  :Smilie:

----------


## max_carpenter

Hi Chris128, Thanks for this example works brilliantly, My code was only getting half of the apps.

I have chopped out alot of code as I didn't require it for this project as its an automated program to get details from the local machine (no updates).

It all works brilliantly I have the Target cpu set to Active (any CPU) however one small problem which isn't a big deal is the duplication of the odd few programs.

This is not a problem for us and I noticed someone else mentioned it but also mentioned it worked fine after they set it to Any CPU.

Not a problem just thought I would bring it up. But well done on the great coding :-)

----------


## chris128

Thanks  :Smilie:  surprised to hear you are getting duplicats though when you are compiling to AnyCPU as I've been using this code for months on loads of machines and not found any machines where it does not produce an identical list to Add/Remove Programs. Are you sure you didn't change something when you were chopping out the bits that you don't need? Or maybe these machines have multiple versions of these programs installed?

----------


## elielCT

This is really awsome.. 
I was using my own script, with additional scripts found elsewhere, but this handles it all!!

Very nice..

----------


## chris128

Thanks  :Smilie:

----------


## elielCT

ok everyone seems to be asking about the icon thing... 
its really cake to do, and I wanted that feature as well so I put it in...
now as perfect as this solution seemed (unghhhhh!), it wasnt really what i wanted after all.. but chris u definately put me in the right path.. like sum1 mentioned B4, what i really wanted was the path to the executables, and didnt want bundles (such as office, or adobe cs3), but rather an itemized account of the exe's in those bundles (word, excel, ect.).. so using your structure (nicely done with the Ienum, i guess i dont know as much as i thought i did) i will search the start menu, and program files folder, and possibly system32 for any & all exe.  when i finish, following tradition, i will post in new thread. Now for the icon matter this is how i did it:


in the properties region add:

vb Code:
Private _IconPath As String = String.Empty
    ''' <summary>
    ''' If this program specifies an icon file, it's path will be returned 
    ''' </summary>
    Public Property IconPath() As String
        Get
            Return _IconPath
        End Get
        Set(ByVal value As String)
            _IconPath = value
        End Set
    End Property


in the constructors region change second sub to:


vb Code:
Public Sub New(ByVal ProgramDisplayName As String, ByVal ProgramParentDisplayName As String, ByVal IsProgramUpdate As Boolean, ByVal ProgramVersion As String, ByVal ProgramIconPath As String)
        Me.DisplayName = ProgramDisplayName
        Me.ParentDisplayName = ProgramParentDisplayName
        Me.IsUpdate = IsProgramUpdate
        Me.Version = ProgramVersion
        Me.IconPath = ProgramIconPath
    End Sub


in the GetUserInstallerKeyPrograms function edit to the following:


vb Code:
If Not CInt(UserDataProgramKey.GetValue("SystemComponent", 0)) = 1 Then
                                        Dim Name As String = CStr(CuInstallerKey.OpenSubKey(CuProductGuid).GetValue("ProductName", String.Empty))
                                        Dim ProgVersion As String = String.Empty
                                        Dim ProgIcon As String = String.Empty
                                        Try
                                            ProgVersion = CStr(UserDataProgramKey.GetValue("DisplayVersion", String.Empty))
                                            ProgIcon = CStr(UserDataProgramKey.GetValue("DisplayIcon", String.Empty))
                                        Catch ex As Exception
                                            Debug.WriteLine(ex.Message)
                                        End Try
                                        If Not Name = String.Empty AndAlso Not IsProgramInList(Name, ExistingProgramList) Then
                                            ExistingProgramList.Add(New InstalledProgram(Name))
                                            ProductFound = True
                                        End If
                                    End If


in the GetUninstallKeyPrograms function edit to the following:


vb Code:
If IncludeUpdates Then
                                    'Add the program to our list if we are including updates in this search
                                    Dim Name As String = CStr(CurrentSubKey.GetValue("DisplayName", String.Empty))
                                    Dim IconP As String = CStr(CurrentSubKey.GetValue("DisplayIcon", String.Empty))
                                    If Not Name = String.Empty AndAlso Not IsProgramInList(Name, ExistingProgramList) Then
                                        ExistingProgramList.Add(New InstalledProgram(Name, CStr(CurrentSubKey.GetValue("ParentDisplayName", String.Empty)), True, ProgVersion, IconP))
                                    End If
                                End If


and still in the GetUninstallKeyPrograms function edit to the following:


vb Code:
If UninstallStringExists Then
                                    Dim Name As String = CStr(CurrentSubKey.GetValue("DisplayName", String.Empty))
                                    Dim IconP As String = CStr(CurrentSubKey.GetValue("DisplayIcon", String.Empty))
                                    If Not Name = String.Empty AndAlso Not IsProgramInList(Name, ExistingProgramList) Then
                                        If Not IconP = String.Empty Then
                                        End If
                                        ExistingProgramList.Add(New InstalledProgram(Name, CStr(CurrentSubKey.GetValue("ParentDisplayName", String.Empty)), False, ProgVersion, IconP))
                                     End If
                                End If


and still in the GetUninstallKeyPrograms function edit to the following:


vb Code:
Else 'If WindowsInstaller
                            Dim ProgVersion As String = String.Empty
                            Dim Name As String = String.Empty
                            Dim FoundName As Boolean = False
                            Dim IconP As String = String.Empty
                            Try
                                Dim MsiKeyName As String = GetInstallerKeyNameFromGuid(SubKeyName)
                                Dim CrGuidKey As RegistryKey = ClassesKey.OpenSubKey(MsiKeyName)
                                If Not CrGuidKey Is Nothing Then
                                    Name = CStr(CrGuidKey.GetValue("ProductName", String.Empty))
                                    IconP = CStr(CrGuidKey.GetValue("DisplayIcon", String.Empty))
                                End If
                            Catch ex As Exception
                                Debug.WriteLine(SubKeyName & " - " & ex.Message)
                            End Try
                            Try
                                ProgVersion = CStr(CurrentSubKey.GetValue("DisplayVersion", String.Empty))
                                IconP = CStr(CurrentSubKey.GetValue("DisplayIcon", String.Empty))
                            Catch ex As Exception
                                Debug.WriteLine(ex.Message)
                            End Try
                            If Not Name = String.Empty AndAlso Not IsProgramInList(Name, ExistingProgramList) Then
                                ExistingProgramList.Add(New InstalledProgram(Name, CStr(CurrentSubKey.GetValue("ParentDisplayName", String.Empty)), False, ProgVersion, IconP))
                            End If
                        End If


Now in your form get the icons as follows:


vb Code:
Private Sub GetProgramsFinished(ByVal InstalledPrograms As List(Of InstalledProgram))
        If Not InstalledPrograms Is Nothing Then
            For i As Integer = 0 To InstalledPrograms.Count - 1
                If InstalledPrograms(i).IsUpdate = False Then
                    Dim theIcon As Icon
                    Dim theIconPath As String
                    Try
                        If Not InstalledPrograms(i).IconPath = Nothing Then
                            If InstalledPrograms(i).IconPath.IndexOf(",") > 0 Then
                                theIconPath = InstalledPrograms(i).IconPath.Substring(0, InstalledPrograms(i).IconPath.IndexOf(","))
                            Else
                                theIconPath = InstalledPrograms(i).IconPath
                            End If
                            theIcon = System.Drawing.Icon.ExtractAssociatedIcon(theIconPath)
                        Else
                            theIcon = My.Resources.error_icon1
                        End If
                    Catch ex As Exception
                        theIcon = My.Resources.error_icon1
                    End Try
                     ProgDataGridView.Rows.Add("", theIcon, InstalledPrograms(i).DisplayName)
                    Me.ProgDataGridView.Refresh()
                End If
            Next
        End If
        Me.Enabled = True
        If ProgDataGridView.Rows.Count = 1 Then
            MessageBox.Show("No programs were found - this could be because there were no programs installed on the computer (that is not very likely really though is it)." & vbNewLine & _
                            "Try again... ", "Ooops", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        Else
        End If
    End Sub

this will get u almost all icons, and puts one in its place where it didnt find an icon. Im not going to perfect this, as im going a slightly different route based on chris's solution... but should help point yall in the right direction...

----------


## chris128

Cool thanks for posting that, I'm sure it will help others out  :Smilie:  The only reason I didn't add the icons originally is because this is intended to be usable against remote PCs and in that situation your code (which is the same as how I would have done it) wont work. If you only ever intend for this code to get programs from the machine it is running on then obviously that is not an issue  :Smilie: 

Oh and you said it gets _almost_ all icons - are there any that it doesn't get that do actually appear in Add/Remove Programs? I'm assuming there is and that these are icons where the file pointed to by the DisplayIcon registry value contains more than one icon, as you are just using ExtractAssociatedIcon which doesn't have any option to get a specific icon from the file. If you are bothered about this, you might want to try using the *NativeIcon.GetIconAtIndex* method from my Windows API library (download link in my signature) as this will let you get an icon at a specific index from the specified file (I believe the DisplayIcon registry values specify the index number of the icon that should be used after the file name)

----------


## elielCT

gotcha...

yea thats not the case with me....

the reason for me gettin involved in this whole mess is due to the development of a small app that requires list of installed progs in local pc, but the path of itemized exe's is an integral and essential part.. 

 :Smilie:  thanks again for sharin... a little moddin and i devote more time to the rest of the app instead...

----------


## elielCT

some.. 7zip icon does not show in either, but clipmagic icon shows in add/remove... 
ur edit should do the trick.. and while using ur edit the user should remove/alter the line that i used to search for icons with index values,  and remove the index.. utorrent didnt seem to care and the icon displays, but the others wont..

----------


## chris128

Ah yeah, you mean this bit:


```
If InstalledPrograms(i).IconPath.IndexOf(",") > 0 Then
                                theIconPath = InstalledPrograms(i).IconPath.Substring(0, InstalledPrograms(i).IconPath.IndexOf(","))
                            Else
                                theIconPath = InstalledPrograms(i).IconPath
                            End If
```

Anyway, glad the code helped you out a bit even if it wasn't 100&#37; what you were looking for  :Smilie:

----------


## javier_sa@yahoo.com

I am new to vb and I love your code, it looks awesome and I want to know how can I also pull the system information from the remote pc.  Things like computer type ram, hard disk, ect.  I want to put it on the same page.  Can you assist?

----------


## max_carpenter

You should be starting a new thread not replying to this one.

This is exactly what I am working on at the moment for our Internal Asset Management system. There are two ways to go about it:

1: (The route we are taking): Have a client peice of software on each machine that collects details and sends back to a central server. Useful if you just want a database of machines with details - this method can also get alot of information.

2: Use Remote WMI to get the information you want. Note you will need permissions to access the other machine. I have used remote WMI but only within a Domain Environment and I have Domain Admin rights.

There is probably other methods but these are the two ones that jump out at me.

Hope this helps. If you want to discuss it further create a new thread in the vb.net forum.

----------


## javier_sa@yahoo.com

Thanks I posted a new thread to show you what I am looking for.

----------


## chris128

> 2: Use Remote WMI to get the information you want. Note you will need permissions to access the other machine. I have used remote WMI but only within a Domain Environment and I have Domain Admin rights.


Yeah WMI can be a little unreliable because a lot of admins don't allow it through their workstation's firewalls or simply disable the WMI service.

The other method that I have used in the past is a variation on your first method - rather than having a piece of software actually installed and always running on the workstations, we just run the program from a network share via logon script that is assigned to all users. This way it runs whenever anyone logs on to a PC (so we also get a record of when anyone logged on and which PC they logged on to) and updates the general system info in a central SQL database

----------


## max_carpenter

That is similar to what I have built (and currently rebuilding) at my place although it doesnt run with logon scripts.

My old package ran as a windows service on each local machine and got all the information I required including last logged on user and lots of WMI information then compiled an XML sent it back to the server for a server service to read the xml and enter it into the database.

The new one is slightly differnts its a windows forms app that runs on startup for every user, still collects all the same information but then connects to the server using sockets to send all the info back. The reason its a windows forms app because italso acts as a server monitor for users to see status of all our services and for us to display messages to the user etc.

----------


## chris128

Yeah I do like the idea of having an app like that, that we could send messages to the users through or send it the path to a script/program to execute, but I always wonder about the security risks. I mean if you have an app that accepts incoming connections that give it commands to execute, it would be very easy for an attacker to exploit and make it do whatever they wanted, if you didn't have some decent security built in (more than just a hard coded password because someone could just decompile your client side app to see what the password is that it checks for). I know the probability of anyone actually trying to exploit a bespoke app you've written is very slim but I just think if it ever _did_ happen I would be in so much trouble if someone found out my application had put every user's computers at risk (and therefore the entire company's data) that its just not worth it. Plus, we use MS SCCM now so we can just use that to push out apps and scripts etc and it collects a fair bit of information from the workstations too, even down to when they last ran each EXE on their PC which comes in handy for identifying who isn't using software that we pay for licenses for so that we can redistribute that license instead of having to buy a new one  :Smilie:

----------


## max_carpenter

That does sound handy.

As for the bespoke app not only as you mention is it very very slim that someone will exploit it especially as its not out in the public for anyone to find but with our one it only accepts pre-programmend commands and not scripts or windows commands etc.

For instance it is programmed to accept the commands

SCAN
LOCATE
UPATE
AVSCAN
MESSAGE

etc

But I couldn't say run the following:

C:\tmp\MyScript.bat

My program would just see that as an error and disconnect the session.

----------


## chris128

Yeah that's fine and certainly secure, I just think it would be really useful with an app like that to be able to tell it to launch a custom action (e.g a script or executable) so that you don't have to recompile and redeploy the app whenever you want it to do something that it doesn't already do.

----------


## JFoushee

This code is great!... but with Server 2008 onward, Microsoft has changed the way the OS reports Installed Updates.

After some research, I'm able to get the non-OS Installed Updates (MS Office hotfixes, SQL Server Service Packs) using this code:

(This goes inside Private Shared Function InternalGetInstalledPrograms, between the lines "Next" and "'Close the registry keys")


```
'-- Windows 2008 - JFoushee (2/2012)
Dim key As RegistryKey = HklmPath.OpenSubKey("Software\Microsoft\Windows NT\CurrentVersion\")
Dim strTemp As String = key.GetValue("CurrentVersion").ToString()
If IsNumeric(strTemp) Then
    Dim dblReturn As Double = Convert.ToDouble(strTemp)

    If dblReturn >= 6.0 Then
        '-- Windows 2008 non-QFE Patches
        Dim Win2K8PatchKey As RegistryKey = HklmPath.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products")
        ProgramList = Get2008NonQFEPatches(Win2K8PatchKey, HklmPath, ProgramList)

        'not sure how to get QFEs by Registry

    End If

End If
```

and this is a new function to go in the "Private Methods" region:


```
Private Shared Function Get2008NonQFEPatches(ByVal SystemKey As RegistryKey, ByVal HklmRootKey As RegistryKey, ByVal ExistingProgramList As List(Of InstalledProgram)) As List(Of InstalledProgram)
    ' addition provided by JFoushee (2/2012). No warranties/guarantees.

    If Not SystemKey Is Nothing Then

        Dim LmProductGuids() As String = SystemKey.GetSubKeyNames
        For Each LmProductGuid As String In LmProductGuids

            Dim InstallPropKey As RegistryKey = HklmRootKey.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\" & LmProductGuid & "\InstallProperties")

            If InstallPropKey IsNot Nothing Then

                If Not CInt(InstallPropKey.GetValue("SystemComponent", 0)) = 1 Then
                    Dim ParentName As String = CStr(InstallPropKey.GetValue("DisplayName", String.Empty))
                    Dim Vendor As String = CStr(InstallPropKey.GetValue("Publisher", String.Empty))

                    Dim PatchesKey As RegistryKey = HklmRootKey.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\" & LmProductGuid & "\Patches")

                    If PatchesKey IsNot Nothing Then
                        Dim LmPatchGuids() As String = PatchesKey.GetSubKeyNames
                        For Each LmPatchGuid As String In LmPatchGuids

                            If Not CInt(PatchesKey.OpenSubKey(LmPatchGuid).GetValue("State", 1)) = 2 Then
                                Dim Name As String = CStr(PatchesKey.OpenSubKey(LmPatchGuid).GetValue("DisplayName", String.Empty))

                                Dim InstallDate As String = ""
                                Try
                                    InstallDate = CStr(PatchesKey.OpenSubKey(LmPatchGuid).GetValue("Installed", String.Empty))
                                Catch ex As Exception
                                End Try

                                If Not Name = String.Empty AndAlso Not IsProgramInList(Name, ExistingProgramList) Then
                                    ExistingProgramList.Add(New InstalledProgram(Name, ParentName, True, "1", Vendor, "", "", InstallDate))
                                End If

                            End If

                        Next
                    End If
                End If
            End If

        Next

    End If
    Return ExistingProgramList
End Function
```

Now, if someone could figure out the Windows 2008 OS-level hotfixes from the Registry...
(Yes, I 'could' use WMI's Win32_QuickFixEngineering... but then I have two security points-of-failure if someone blocked WMI access.)

----------


## chris128

Cool thanks for the additions - I never spent much time on getting the updates (as mentioned in the original post) so even on XP it wasn't giving the correct results for that, as I was only ever really bothered about getting the installed programs rather than installed updates.

----------


## Midomoi

Miss list of update which have a "This update cannot be removed" info type.

----------


## WishWell

Is there a way to get more information about each product such as the publisher(or vendor) and installed location

----------


## chris128

> Miss list of update which have a "This update cannot be removed" info type.


Yeah like I said, I didn't spend much time on getting the update detection to work properly cos I was only interested in programs not updates

----------


## chris128

> Is there a way to get more information about each product such as the publisher(or vendor) and installed location


Yep, and I think someone else already asked in this thread about it. Just look at the source code and find the places where it is getting values like UninstallString, Version, SystemComponent etc etc from the registry and in there you need to get the Vendor value (or whatever its called, look in your own registry to find out).

----------


## jmb555

Hi!

I'm trying this one to check for OS Hotfixes(updates) that are installed on my machine. I found out that there are some missing updates and duplicate entry. 

Duplicate:


Missing:


Anyway, Cool app!  :Big Grin:

----------


## kitrafael

Hi Chris128,

Firstly, this app is really awesome! It helps me a lot when i need to trace what apps was installed in the labs.

I have questions here, still using the 'UserDataKeyName' and  im interested to populated the 'publisher' of all apps installed and how could we do so? if possible, i need to flag with a check box like what you did for 'include updates' and 'only show updates'.. then the 'publisher' data will shown in the Forms.ListView 

Can you help with this?

Hope to hear from you soon.

Thanks!
kitrafael

----------


## chris128

> Hi!
> 
> I'm trying this one to check for OS Hotfixes(updates) that are installed on my machine. I found out that there are some missing updates and duplicate entry. 
> 
> Duplicate:
> 
> 
> Missing:
> 
> ...


Yeah like I've said before, I didn't really ever concentrate on the update side of things as I only ever wanted the list of programs (not updates) - that was just a bonus really because it looked like it was fairly easy to do, but evidently its not working quite right. If you want it to reliably get all of the updates installed then you'd have to look at the code and see where it is going wrong and why it is picking up duplicates

----------


## chris128

> Hi Chris128,
> 
> Firstly, this app is really awesome! It helps me a lot when i need to trace what apps was installed in the labs.
> 
> I have questions here, still using the 'UserDataKeyName' and  im interested to populated the 'publisher' of all apps installed and how could we do so? if possible, i need to flag with a check box like what you did for 'include updates' and 'only show updates'.. then the 'publisher' data will shown in the Forms.ListView 
> 
> Can you help with this?
> 
> Hope to hear from you soon.
> ...


Like I've said before in this thread, if you want to get the publisher then just change the code so that as well as reading the DisplayName and Version registry values it also reads and stores the Publisher value. If you follow the code through its fairly obvious where it is actually reading and storing the display name and version values so just add another line in there to grab the publisher value.

*EDIT:* wow over 5000 downloads of this library now (and not many complaints). Hope it is helping people out  :Smilie:

----------


## meow

1st thanks a bunch chris128 its a great program.  Thanks again.  I have a question is it possible if I want to know 2-3 specific program such as microsoft office is it intalled or not , adobe pdf is installed or not?

If so can you please provide sample code ?

Best regards

----------


## chris128

Just loop through the list that my code gives you and check to see if it contains the program you are looking for. I'm not going to show you how to loop through a list, because that's very basic programming and if you're unsure how to do things like that then you should go through a few beginners tutorials

----------


## anoble1

Now is there a way to remove programs from this? I wonder how you could do that?

----------


## sachinnpanchal

Hi Chris,

   I looked into your example and working like exactly like Windows (I would say more better then windows because windows sometimes duplicates programs whereas your program didn't).

   But I have one problem, code is in VB.Net. I like to do same thing in c#. but when I convert your code from vb.net to c# (using online conversion tool) it gives wrong output in c# version. I compared both code it is same way written in c#. but although don't know why it is not correctly working. It would be great if you can upload C# version.

   I can't you DLL of your code (I gives prefect in c# if I use VB.Net dll). I need that class to be in c#.

   Any help appreciated.

Regards,
Sachin

----------


## chris128

> Hi Chris,
> 
>    I looked into your example and working like exactly like Windows (I would say more better then windows because windows sometimes duplicates programs whereas your program didn't).
> 
>    But I have one problem, code is in VB.Net. I like to do same thing in c#. but when I convert your code from vb.net to c# (using online conversion tool) it gives wrong output in c# version. I compared both code it is same way written in c#. but although don't know why it is not correctly working. It would be great if you can upload C# version.
> 
>    I can't you DLL of your code (I gives prefect in c# if I use VB.Net dll). I need that class to be in c#.
> 
>    Any help appreciated.
> ...


Sorry I don't really know C# at all so can't help you. You'd probably be better off asking on a C# forum than a VB forum  :Wink:

----------


## sachinnpanchal

Hi Chris, 

   I resolved problem in C# code. it was a problem in reverse function. If any one wants see please download attachment.

   One more thing chris, I want to develop one small utility to find out problems in Registry like (CCleaner). Do you any idea for this kind of thing?
   Any help would be appreciated.

Regards,
Sachin

InstalledProgram.cs

----------


## jariz_01

sir,need help for our thesis pruposal, we need to scan installed software from a network in just one clicking the scan button,,

can u help us? we cant find codes, we search night and day but we got lost :Frown:

----------


## Zeev

Thanks for the code, it is very helpful.
I understand that it takes information from the registry, which only contains program name. But is there any possible way to get also the executable path? Thanks!

----------


## stefan52a

> can u use this to uninstall programs? like in xp when you double click the program?
> 
> icons would be cool too


Just to say thanks for the code, I am not a vb wizard, but changed the test program a bit to include the uninstal strings, and added a sample program to uninstall 1 program based upon its display name.

----------


## stefan52a

> Just to say thanks for the code, I am not a vb wizard, but changed the test program a bit to include the uninstal strings, and added a sample program to uninstall 1 program based upon its display name.


O yeah, forgot to tell works fine in windows 8.1 as well, but have to run as administrator.

----------


## technonaut

Wow, this is fast and works in Windows 10 if you Run As administrator.  Unfortunately, for about 25% of my programs, it doesn't return the version number even though I can see the version number in the normal Apps & Features program list. 

The values can be found in 

```
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
```

 and I recommend retrieving them from there.  I checked and, in all cases where it was missing from what your program showed me, the version information could be found in the above registry section.

----------

