Auditing 32-Bit and 64-Bit Applications with PowerShell

Script deals with the complexities so you don’t have to

Bill Stewart

July 26, 2011

7 Min Read
ITPro Today logo in a gray background | ITPro Today


Get-InstalledApp.ps1 is a PowerShell script that outputs information (e.g., display name, version, publisher) about the applications installed on one or more computers in a network. When I wrote this script back in 2009, I was using PowerShell 1.0 and only had to access 32-bit Windows OSs (see the web-exclusive article “What Applications Are Installed on the Computers in Your Network?”). Fast forward to 2011. I’m now using the 64-bit version of Windows 7, which has PowerShell 2.0 built-in. I wanted to use Get-InstalledApp.ps1 to output information about the applications installed on 64-bit and 32-bit Windows computers and instantly discovered that when I ran the script from the 64-bit version of PowerShell 2.0, it output only 64-bit applications. I had to start a 32-bit instance of PowerShell to find 32-bit applications. Needless to say, I was unhappy with this limitation.

I’ve now written a new version of Get-InstalledApp.ps1. It addresses this limitation and adds some new features, some of which should be particularly useful for people managing both 32-bit and 64-bit applications. The new version of the script requires PowerShell 2.0 and provides the following enhancements:

  • Comment-based Help information instead of a home-grown help function. If you put the script in a directory in your path, the command

    Get-Help Get-InstalledAppdisplays Help information for the script.

  • The script allows pipeline input in place of the -ComputerName parameter.

  • Application architecture detection (32-bit or 64-bit).

Using the Script

To download Get-InstalledApp.ps1, scroll to the top of this page and click the 136129.zip hotlink. The script's command-line syntax is as follows:

Get-InstalledApp

[-ComputerName ]

[-AppID ]

[-AppName ]

[-Publisher ]

[-Version ]

[-Architecture ]

[-MatchAll]

The -ComputerName parameter is optional. If you omit it, the script outputs information about the applications installed on the local computer. If you pipe input to the script, the script uses the piped input for the -ComputerName parameter. Because it’s the first positional parameter, you can omit the parameter’s name (-ComputerName) if you specify a computer name (or a list of computer names) as the script's first parameter.

The optional -AppID parameter lets you search for a particular application by its ID. The application ID is the application's registry subkey name, which is under the Uninstall key in the registry. This parameter is particularly useful for searching for applications installed with Windows Installer, as the application ID is the same as the application's product code globally unique identifier (GUID). You can use wildcards in the -AppID parameter's value. If the value contains curly braces ({ }), you need to enclose it in double quotes (" "); otherwise, PowerShell will think you’re specifying a hash table.

The optional -AppName, -Publisher, and -Version parameters behave the same way as in the earlier version of the script. These parameters let you search for applications by name, publisher, and version, respectively. All three parameters support wildcard matching and are case insensitive.

You can specify one of two strings—64-bit or 32-bit—for the optional -Architecture parameter, depending on whether you want to search for 64-bit or 32-bit applications. If you omit the -Architecture parameter, Get-InstalledApp.ps1 outputs information about both 64-bit and 32-bit applications.

When you’re looking for only one match (e.g., searching for an application by its ID), it’s unnecessary to continue the registry enumeration. So, the script returns only the first match by default to minimize network and registry access. When you need all the matches, you can use the -MatchAll parameter. If you include this parameter, the script lists the information about all matching applications instead of stopping after the first match. For example, the command

Get-InstalledApp -Publisher *Microsoft*

outputs information about the first application whose publisher contains the string Microsoft, whereas the command

Get-installedApp -Publisher *Microsoft*-MatchAll

 

outputs information about all applications whose publisher contains the string Microsoft. (Although this command wraps here, you'd enter it all on one line in the PowerShell console. The same holds true for the other commands that wrap.)
 

 

 

For each application, Get-InstalledApp.ps1 outputs the properties listed in Table 1. Figure 1 shows where in the registry this information is retrieved from. In case you’re curious, I retrieve the application information from the registry rather than Windows Management Instrumentation’s (WMI’s) Win32_Product class because of the class’s limitations. You can read about some of the limitations in “What Applications Are Installed on the Computers in Your Network?” You can read about another Win32_Product class limitation in the Microsoft article “Event log message indicates that the Windows Installer reconfigured all installed applications.” Incidentally, the Win32Reg_AddRemovePrograms class mentioned in the Microsoft article seems to exist only on computers managed by Microsoft Systems Management Server (SMS).

 

Table 1: Application Properties Returned by Get-InstalledApp.ps1 

 

 

Exploring the Possibilities

Here are some sample commands that will give you an idea of the script's usefulness. To output all 64-bit applications installed on server1, you’d use the command

Get-InstalledApp server1

  -Architecture 64-bit

  -MatchAll

If you want to find all the applications that have the word "office" in their names, then sort them by architecture and version, you’d run the command

Get-Installedapp -AppName *office*

  -MatchAll | Select-Object AppName,

  Version,Architecture |

  Sort-Object Architecture,Version

If you want the script to check the computers listed in Computers.txt to see which machines have LibreOffice 3.3 installed, then display the names of those computers, you’d use the command

Get-Content Computers.txt |

  Get-InstalledApp -AppID

  "{1A97CF67-FEBB-436E-BD64-431FFEF72EB8}" |

  Select-Object ComputerName

Note that this command demonstrates the script’s new feature of using pipeline input in place of the -ComputerName parameter. It also demonstrates how to enclose the AppID parameter’s value in quotes when it contains curly braces.

 

Working with WOW64

As I mentioned previously, Get-InstalledApp.ps1 retrieves information about the installed applications from the registry. To enumerate the installed software, it reads the relevant subkeys and values under the HKLMSOFTWAREMicrosoftWindowsCurrentVersionUninstall registry subkey, as seen in Figure 1. However, this is where the original version of the script runs into problems when it’s run on 64-bit Windows. This subkey contains information about the 64-bit applications installed on a 64-bit system only; 32-bit application installation information resides in a different location in the registry due to WOW64.

 

Figure 1: Retrieving application information in the registry

 

WOW64 is the 32-bit emulator in 64-bit Windows that allows 64-bit Windows to seamlessly run 32-bit applications. When a 32-bit application accesses the registry, the WOW64 emulator redirects the application to the Wow6432Node subkey. For example, a 32-bit application that requests HKLMSOFTWARE is redirected to HKLMSOFTWAREWow6432Node. This means that 32-bit applications installed on a 64-bit system are found in the HKLMSOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall subkey.

When I first started revising the script, I retrieved the data from both registry locations, which works without problems when it’s run from 64-bit PowerShell. However, when I ran the script from 32-bit PowerShell on a 64-bit machine, I was disconcerted to find that it output every application twice. The reason is that WOW64 redirected the script's request to enumerate HKLMSOFTWAREMicrosoftWindowsCurrentVersionUninstall to the Wow6432Node location. The script then enumerated the Wow6432Node location again, causing duplicate output.

I then decided it would be useful to determine whether an application is 32-bit or 64-bit, based on the application's installation location in the registry. However, you can't tell (at least not easily) whether the WOW64 emulator is redirecting the registry to Wow6432Node when you’re running 32-bit PowerShell on 64-bit Windows.

The new version of Get-InstalledApp.ps1 works around these problems by storing the registry paths from both registry locations as .NET ArrayList objects, then comparing the leaf items from both locations. The leaf item of a location is its final element. For example, the leaf item of the registry path HKLMSOFTWAREMicrosoftWindowsCurrentVersionUninstall{23170F69-40C1-2702-0920-000001000000} is {23170F69-40C1-2702-0920-000001000000}. Listing 1 shows the compare-leafequality function that the script uses to check whether the two lists contain the exact same list of leaf items. If the function returns $TRUE, the script has detected the WOW64 registry redirection and ignores the Wow6432Node data.

 

Find Out What's Installed

The new version of Get-InstalledApp.ps1 can now find and differentiate between 32-bit and 64-bit applications. It also supports pipeline input in place of the -ComputerName parameter. This updated script might be all you need to find applications on computers on your network.

 

Listing 1: The compare-leafequality Function

# Returns $TRUE if the leaf items from both lists are equal.

# Otherwise, it returns $FALSE.

function compare-leafequality($list1, $list2) {

  # Create ArrayLists to hold the leaf items and build both lists.

  $leafList1 = new-object System.Collections.ArrayList

  $list1 | foreach-object { [Void] $leafList1.Add((split-path $_ -leaf)) }

  $leafList2 = new-object System.Collections.ArrayList

  $list2 | foreach-object { [Void] $leafList2.Add((split-path $_ -leaf)) }

  # If compare-object has no output, then the lists match.

  (compare-object $leafList1 $leafList2 | measure-object).Count -eq 0

}

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like