Downloads
102818.zip

Executive Summary:
It's important to regularly audit the applications installed on the computers in a network for a variety of reasons. You could spend some of your IT budget on purchasing a third-party application—or you can use the free Windows PowerShell script named Get-InstalledApp.ps1. Learn how to use this script and take a peek at how it works.

 

System administrators often need to identify the software that's installed on the computers in their networks. It's important to regularly audit the applications installed on those computers for a variety of reasons. Administrators might install a software package, forget to document it, and later learn that a particular software package contains security vulnerabilities. End users might have the administrative permissions that allow them to install software on their computers. There's also the potential that rogue administrators can install unauthorized software and open the organization to legal liabilities.

There are third-party applications that can audit installed software, but small to mid-sized businesses might not have the resources to implement such a solution. My company didn't have a software solution in place, so I wrote the Windows PowerShell script Get-InstalledApp.ps1. Before I explain how to use this script and how it works, I want to describe two techniques for detecting installed software from a script and why I chose one technique over the other.

Win32_Product Class vs. the Registry


Windows Management Instrumentation (WMI) provides a Win32_Product class that lets you enumerate the applications installed on a computer. This is easy in PowerShell. For example, the command

Get-WmiObject Win32_Product | select name

lists all the applications installed on the current computer. However, the Win32_Product class has some limitations:

  • It retrieves only the names of applications installed using the Windows Installer service. It doesn't retrieve the names of applications installed by other tools. This means that you can't use the Win32_Product class to perform a general software audit unless you use Windows Installer packages exclusively, which is an unrealistic assumption on most networks.
  • Retrieving instances of the Win32_Product class is very slow.
  • It's not always possible to retrieve Win32_Product class instances from remote computers. For example, when I try to do this on my own network, I get the error message Generic failure.

All these problems limit the usefulness of the WMI Win32_Product class. Fortunately, you can retrieve information about installed applications from the registry instead. The HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall registry key contains information about applications installed on a computer. Each subkey under the Uninstall key represents an installed application, and the values in each subkey contain information about the application, as Figure 1 shows. So, to get a list of applications, you can enumerate the Uninstall key and read the data in each subkey underneath the Uninstall key.

 

Figure 1: An example of a subkey under the Uninstall key (click to enlarge)

 

PowerShell's registry provider lets you use the Get-ChildItem cmdlet to list the names of the applications installed on the current computer using the command

Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
 ForEach-object \\{ (Get-ItemProperty Microsoft.PowerShell.Core\Registry::$_).DisplayName \\}

However, the Get-ChildItem cmdlet can't access the registry provider on a remote computer in PowerShell 1.0, so you need to use WMI's registry management class, StdRegProv, for this purpose. The StdRegProv class provides a useful set of methods that make reading the registry simple no matter whether you're reading the data from the local machine or a remote computer. (For more information about the StdRegProv class, see MSDN's StdRegProv Class web page.)

How to Use Get-InstalledApp.ps1


Get-InstalledApp.ps1 reads information about installed applications from the registry, then returns either a list of all installed applications or a list of applications matching the criteria you specify. The script uses the following command-line syntax:

Get-InstalledApp \\[-computername \\]
 \\[-appID \\] \\[-appname \\]
 \\[-publisher \\] \\[-version \\] \\[-matchall\\]

You use the -computername parameter to list a single computer name or an array of computer names. The -computername parameter is the first positional parameter, so you can omit -computername and just include the name of the computer to shorten the command. If you omit this parameter entirely, the script assumes you want to access the local computer.

You use the -appID parameter to search for applications by their application ID. An application's ID is its registry subkey underneath the Uninstall key. In Figure 1, the application ID is selected in the left pane. For Windows Installer-based applications, the application ID is equivalent to the application's product ID globally unique identifier (GUID). Using the -appID parameter is the best way to find specific applications installed by the Windows Installer service.

You use the -appname parameter to search for applications by their display name. The display name is the application's name as it appears in the Add/Remove Programs list or the DisplayName value in the application's subkey. For example, in Figure 1, the selected application's display name is OpenOffice.org 2.4.

You use the -publisher parameter to search for applications by publisher. If you're not sure of an application's publisher, you can find it in the Publisher entry of that application's subkey. For example, the publisher of the selected application in Figure 1 is OpenOffice.org.

You use the -version parameter to search for applications by their version. If you're not sure of an application's version, you can find it in the DisplayVersion entry of that application's subkey. For example, the version of the application selected in Figure 1 is 2.4.9286.16.

Including the -matchall parameter causes Get-InstalledApp.ps1 to list all matching applications instead of stopping after the first match. For example, the command

Get-InstalledApp -publisher "Microsoft Corporation" -matchall

returns a list of all Microsoft applications on the current computer. If you omit the -matchall parameter, the script will stop searching after the first match.

The -appID, -appname, -publisher, and -version parameters all support wildcard matching, and the parameters' arguments are case insensitive. For example, the command

Get-InstalledApp -appname "*office*" -matchall

lists all applications on the current computer that have the word office in their name. Also, you can combine any of the -appID, -appname, -publisher, and -version parameters for a more specific search. For example, the command

Get-InstalledApp sales01 -appname "*.NET Framework*" -version "2*"

outputs the first application that contains the words .NET Framework in the application's display name whose version number starts with 2. If there are no matches, the script doesn't output anything. For more information about wildcards in PowerShell, run the command

Get-Help about_wildcard

Get-InstalledApp.ps1 outputs objects that contain the ComputerName, AppID, AppName, Publisher, and Version properties, so you can use PowerShell cmdlets to select, sort, and format the output to suit your needs. For example, the command

Get-InstalledApp | Select-Object AppName,Version |
 Sort-Object AppName

outputs a list of applications and each application's version, sorted by application name. If you want to create a comma-separated value (CSV) report of all software installed on each computer named in the file Computers.txt, you'd use the command

Get-InstalledApp (Get-Content Computers.txt) |
 Export-CSV Report.csv -notypeinformation

(The Export-CSV cmdlet's -NoTypeInformation parameter suppresses the type information in the CSV output.) The command

Get-InstalledApp (Get-Content Computers.txt)
 -appID "\\{7131646D-CD3C-40F4-97B9-CD9E4E6262EF\\}" |
 Select-Object ComputerName

lists the computers named in Computers.txt that have the .NET Framework 2.0 installed. If you want to output a sorted list of Microsoft applications and their versions on the current computer, sorted by application name, you'd use the command

Get-InstalledApp -publisher "Microsoft Corporation" -matchall |
 Select-Object AppName,Version | Sort-Object AppName

Inside Get-InstalledApp.ps1


Now that you know how to use Get-InstalledApp.ps1, let's look at how it works. The script first uses a param statement to declare the command-line parameters, then it creates a pair of global variables it will use later with the StdRegProv class. After this, it declares the usage function and the main function. The last line of the script calls the main function.

The main function contains the main body of the script. It first determines if the -help parameter exists on the command line. If it does, the main function calls the usage function, which outputs a usage message and ends the script.

Next, the main function creates an empty hashtable and stores it in the $propertyList variable, as Listing 1 shows.

 

Listing 1: Code That Creates a Hash Table Containing the Requested Application Properties

 

If any of the -appID, -appname, -publisher, or -version parameters exist on the command line, the function adds a key to the hashtable corresponding to the parameter's name and sets that key's value to the parameter's argument. For example, the command

Get-InstalledApp -appname "Windows Support Tools"

causes the main function to populate the hashtable with an AppName key that has the value Windows Support Tools.

The main function then checks to see if the -computername parameter is empty. If it's empty, the function uses the local computer name, which it gets from the COMPUTERNAME environment variable. If it's not empty, the function iterates through the array of computer names using a foreach loop. (The loop executes only once if the -computername parameter's argument is a single computer name.)

Inside the foreach loop, the main function declares the $err variable and sets its value to $NULL. After this, the function uses a trap scriptblock to trap WMI errors, as callout A in Listing 2 shows. If an error occurs, the trap scriptblock updates the error variable to the current error record (i.e., $ERROR\\[0\\]) and continues to the statement following the error.

 

Listing 2: Code That Traps Errors

 

Next, the main function connects to the StdRegProv class on the current computer using the \\[WMIClass\\] type accelerator for the Windows .NET Framework's System.Management.ManagementClass. (If you're unfamiliar with PowerShell's type accelerators, see "Type Accelerators: A Useful But Undocumented Feature in PowerShell 1.0".) If an error occurs, it gets trapped by the trap scriptblock shown in callout B in Listing 2. If the $err variable is not $NULL, this means the trap scriptblock was triggered. In this case, the main function uses the Write-Error cmdlet to output the error record, then it uses the continue statement to skip the remainder of the foreach loop and continue to the next computer name in the array.

After this, the main function uses the StdRegProv class's EnumKey method to enumerate the subkeys in the Uninstall key. The EnumKey method's result—an array of subkey names—is stored in the sNames property. The function then iterates through the array of subkey names using another foreach loop.

For each subkey name in the array, the main function uses the GetStringValue method of the StdRegProv class to retrieve the registry key's DisplayName property. The function uses the Join-Path cmdlet to append the subkey's name to the Uninstall key. The resulting registry path is used as an argument for the GetStringValue method, which returns a registry entry's value. (It must be a string value.) The method takes three arguments: a registry hive, the subkey path, and the name of the entry. GetStringValue returns an object that contains two properties: the ReturnValue property, which contains a value indicating the success or the failure of the method call, and the sValue property, which contains the string value. The main function assigns the sValue property's value to the $name variable.

If the $name variable is not $NULL, the main function creates a custom output object that contains four properties: ComputerName, AppID, AppName, Publisher, and Version. It updates the object's ComputerName property with the current computer name, the object's AppID property with the current subkey name, and the object's AppName property with the current application's display name. The function then uses the GetStringValue method twice more to retrieve the current application's Publisher and DisplayVersion entry values and updates the corresponding properties in the custom object with these values.

At this point, the main function has completed reading the information about the current application, so it checks if the $propertyList hashtable contains any keys by checking its Keys collection's Count property. If the hashtable doesn't contain any keys (i.e., if none of the -appID, -appname, -publisher, and -version parameters exist on the command line), then the function simply outputs the custom object.

If the hashtable contains one or more keys, the main function needs to determine which of the command-line parameters' arguments match the property values in the custom object. Listing 3 shows how the main function accomplishes this.

 

Listing 3: Code That Matches All Named Properties

 

First, it sets the $matches variable to 0, then it iterates through the $propertyList hashtable's Keys collection. For each key in the $propertyList hashtable, the function uses PowerShell's -like operator to see if the custom object's property matches the corresponding value from the $propertyList hashtable. If they match, the function increments the $matches variable. If the number of matches is the same as the number of keys in the hashtable, the main function outputs the custom object. Lastly, if the -matchall parameter doesn't exist on the command line, the function uses the break statement to break out of the foreach loop used to iterate through the registry subkeys.

Know What's Installed on Your Computers


The Get-InstalledApp.ps1 PowerShell script, which you can obtain by clicking the Download the Code Here button near the top of the page, provides an easy way to list applications installed on one or more computers. And it can even search for specific applications. This easy-to-use script might be all you need to audit the software that's installed on the computers in your network.