Applications make your business go. They're the tools every office worker needs to create useful data that ultimately pays your salary. Yet while installing those applications is a fairly easy process, keeping track on them after they're installed can be an administrative headache.

Part of the reason for this headache lies in the fact that neither Windows nor Active Directory keep an easily-findable store of installed applications. Neither is there a native tool you can run to determine installed applications across every computer on the network all at once.

Inventorying the applications across your entire infrastructure requires getting a little creative with the breadcrumbs of data Windows leaves behind. The process can start in either of two locations: The registry or WMI.

Finding Apps by Querying the Registry

You know that every installed application stores information about itself in the computer's Programs and Features Control Panel. Bringing up this Control Panel (see Figure 1), you can easily see which applications have been installed to the local computer. Also there, you can Repair or Uninstall any application you find.

Figure 1: Programs and Features Control Panel

But where is the source of this information? Where does this Control Panel query to discover the information it presents in Figure 1? Much of that information can be found in the registry, specifically in subkeys of the computer's HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall key.

Take a look at the image you see in Figure 2. This image was taken from a computer which has been installed with the popular 7-Zip application. Notice down at the bottom of the window where the key path is displayed. Upon installation, 7-Zip populated this key with a GUID that references the application. It also populates the key with information about the application itself, along with the command line executable that is needed to uninstall the application. That command line you'll find next to the value name ModifyPath.

Figure 2: Registry Keys

Included also all this information is the name of the application itself, found next to DisplayName. Figure 2 shows the display name to be 7-Zip 4.65 (x64 edition). For many (but not all) applications, the value found next to DisplayName should be enough information to help you understand to which application this GUID corresponds.

Obviously, gathering this information one-at-a-time from each computer is a lengthy process. What you need is a script that can interrogate every subkey to record the DisplayName value. Even better would be a script that could accomplish this activity across every computer on the network.

There are a couple of ways to accomplish this. One of which is through the use of a VBScript that activates as part of you logon script. Properly coded, such a VBScript will be able to identify the correct subkeys of the Uninstall key, then gather the DisplayName values from each subkey. Once collected, such a script might then deposit that information into a file in a central storage location. Running as a logon script means that the script will execute every time a user logs on, updating data on a regular basis. One such script in VBScript format was submitted by Michael Weigert and can be found at

Finding Apps by Querying WMI

There's a problem, however, with the registry-based approach. Not every application deposits the correct (or, in many cases, any!) information to that location in the registry. In my case, while 7-Zip did indeed store its information in that location, I found no record of my installed copy of Camtasia, for example. That's why getting a complete record of every application means also looking elsewhere. A second option is to see what's been stored in the computer's WMI store. Many applications, when installed, add information about themselves to the WMI class called Win32_Product.

Figure 3: Enumerating Win32_Product

Accessing Win32_Product is easy if I switch to using PowerShell as my scripting language. As you can see in Figure 3, executing the Get-WMIObject (shortened there to just 'gwmi') against the Win32_Product class results in a far longer list of installed applications. This is obviously a better idea than the Uninstall key, because it presents a much better glimpse of what's installed.

But I'm still only partially there, because the gwmi Win32_Product command only sends back results from my local computer. I need to run this against all the computers in my domain to gather that inventory of applications. One option is to create a very simple one-liner script that, like the one above, runs as part of a logon script. That script will need to run the Win32_Product query and output its contents to a file on the network somewhere. Such a script could be accomplished with only a single line:

gwmi Win32_Product | out-file (\\server1\share $env:HOSTNAME '.txt')

The script above gathers the information from Win32_Product and then pipes the result to the location \\server1\share. It then enumerates the hostname for the computer on which the script is being run and uses that hostname as the name of the file to write.

Yet even though this does a better job of enumerating the installed software across my range of computers, it's going to take a while for me to get back that data. I don't want to have to wait for every user to process a logon script before I can see their installed applications. There's got to be a better way!

A third solution can be to use the remoting capabilities of PowerShell to query WMI across a range of computers. By fanning out my queries from a single location, I can just kick off the script and know that when it's done I'll have the data I need. One way to do that starts by creating a list of computers I want to query. I'll store that list in a regular text file with each computer name listed one per line. Using PowerShell's Get-Content cmdlet, I can query those computers one at a time to run my WMI query. That script might look like this:

gwmi Win32_Product -comp (Get-Content computers.txt) | Out-File inventory.txt

By enclosing the Get-Content command in parenthesis, I'm forcing PowerShell to execute its content first. That content gets fed into the Get-WMIObject cmdlet with its results eventually being spit out by Out-File. I can use a local file here, because I'm gathering every computer all at once.

Gosh, but a couple of problems still remain with this tactic. First, running gwmi Win32_Product in this way means querying all those computers serially, one at a time. If I've got a hundred computers to check, that script could take a long, long time.

I could speed up the process a bit by using PowerShell's 'AsJob parameter. With a little extra script handiness, I can run jobs in parallel to speed up the process. Yet there's one tiny little problem that's keeping me from going too much farther with this. That problem relates to some really big issues with querying Win32_Product itself.

Check out Microsoft knowledgebase article 974524, found here: Turns out that any time the Win32_Product class is queried, the act of querying it initiates a consistency check of every package that's installed on the system. Such a consistency check could modify the software that's installed, attempting to 'repair' any problems it sees. Yikes! That means my attempts to just discover my software could accidentally also change my configuration, or even break the software itself!

Not to mention that some of Microsoft's applications aren't even enumerated in the WMI store. What seems like it should be a simple process is only now getting harder and harder as I keep digging further in. Even Microsoft's knowledgebase article noted above suggests using a different WMI class that isn't even natively found on my systems unless I purchase a software inventory tool like System Center Configuration Manager.

Leave Software Inventory to Software Inventorying Tools

All the while, nothing in this process has even started building the database of items that I really want out of a software inventory solution. Even in the best of worlds, one where every consistency check is a good thing, the best I'm getting is a list of software like this'

IdentifyingNumber : {A3FF5CB2-FB35-4658-8751-9EDE1D65B3AA}
Name''''''''''''' : VMware Workstation
Vendor''''''''''' : VMware, Inc.
Version'''''''''' :
Caption'''''''''' : VMware Workstation
IdentifyingNumber : {0C682623-8F66-46A8-B9B3-93FE1E66A001}
Name''''''''''''' : iTunes
Vendor''''''''''' : Apple Inc.
Version'''''''''' :
Caption'''''''''' : iTunes
IdentifyingNumber : {1374CC63-B520-4f3f-98E8-E9020BF01CFF}
Name''''''''''''' : Windows XP Mode
Vendor''''''''''' : Microsoft Corporation
Version'''''''''' : 1.3.7600.16423
Caption'''''''''' : Windows XP Mode

'where what I really want is a searchable database of software, along with dates, times, and computers where it is installed. Even more useful would be a solution that would enable me to take actions based on the values in that database such as remote the computer or uninstall the software.

That's why leaving the software inventory process to dedicated tools that are built for software inventory is often the best approach. While I can indeed build a minimal software inventory script that gets me some basic information, having something that's integrated into a configuration database of other useful asset information is much more useful. If you're finding yourself in the need of a real software inventory, this is one example where you might consider buying a solution rather than building one from scratch.