Use PowerShell to Quickly Find Installed Software


Summary: Learn how to use Windows PowerShell to quickly find installed software on local and remote computers.

 

Microsoft Scripting Guy Ed Wilson here. Guest Blogger Weekend concludes with Marc Carter. The Scripting Wife and I were lucky enough to attend the first PowerShell User Group meeting in Corpus Christi, Texas. It was way cool, and both Marc and his wife Pam are terrific hosts. Here is what Marc has to say about himself.

I am currently a senior systems administrator with the Department of the Army. I started in the IT industry in 1996 with DOS and various flavors of *NIX. I was introduced to VBScript in 2000, and scripting became a regular obsession sometime in 2005. In 2008, I made the move to Windows PowerShell and have never looked back. My daily responsibilities keep me involved with Active Directory, supporting Microsoft Exchange, SharePoint, and various ASP.NET applications. In 2011, I founded the Corpus Christi PowerShell User Group and try to help bring others up to speed on Windows PowerShell. 

Take it away, Marc!

 

One of the life lessons I have learned over the years working in the IT field as a server administrator is that there are often several different valid responses to a situation. It’s one of the things that makes work interesting. Finding the “best” solution to a problem is one of the goals that I think drives many people who are successful at what they do. Occasionally, the best solution is the path of least resistance.

This is one things I love most about working with Windows PowerShell (and scripting in general) is that most problems have more than one solution. Sometimes the “right” way to do something comes down to a matter of opinion or preference. However, sometimes the best solution is dictated by the environment or requirements you are working with.

For instance, let us talk about the task of determining which applications are installed on a system. If you’re familiar with the Windows Management Instrumentation (WMI) classes and the wealth of information that can be gathered by utilizing the Get-WmiObject cmdlet, an obvious choice might be referencing the Win32_product class. The Win32_Product represents products as they are installed by Windows Installer. It is a prime example of many of the benefits of WMI. It contains several useful methods and a variety of properties. At first glance, Win32_Product would appear to be one of those best solutions in the path of least resistance scenario. A simple command to query Win32_Product with the associated output is shown in the following image.

Image of command to query Win32_Product

The benefits of this approach are:

  • This is a simple and straightforward query: Get-WmiObject -Class Win32_Product.
  • It has a high level of detail (for example, Caption, InstallDate, InstallSource, PackageName, Vendor, Version, and so on).

However, because we are talking about alternative routes, let us look at another way to get us to arrive at the same location before we burst the bubble on Win32_Product. Remember, we are simply looking for what has been installed on our systems, and because we have been dealing with WMI, let’s stay with Get-WmiObject, but look at a nonstandard class, Win32Reg_AddRemovePrograms

What is great about Win32Reg_AddRemovePrograms is that it contains similar properties and returns results noticeably quicker than Win32_Product. The command to use this class is shown in the following figure.

Image of command to use Win32Reg_AddRemovePrograms

Unfortunately, as seen in the preceding figure, Win32Reg_AddRemovePrograms is not a standard Windows class. This WMI class is only loaded during the installation of an SMS/SCCM client. In the example above, running this on my home laptop, you will see the “Invalid class” error if you try querying against it without an SMS/SCCM client installation. It is possible (as Windows PowerShell MVP Marc van Orsouw points out) to add additional keys to WMI using the Registry Provider, and mimic what SMS/SCCM does behind the scenes. Nevertheless, let us save that for another discussion.

One other possibly less obvious and slightly more complicated option is diving into the registry. Obviously, monkeying with the registry is not always an IT pro’s first choice because it is sometimes associated with global warming. However, we are just going to query for values and enumerate subkeys. So let’s spend a few moments looking at a method of determining which applications are installed courtesy of another Windows PowerShell MVP and Honorary Scripting Guy Sean Kearney (EnergizedTech). In a script that Sean uploaded to the Microsoft TechNet Script Center Repository, Sean references a technique to enumerate through the registry where the “Currently installed programs” list from the Add or Remove Programs tool stores all of the Windows-compatible programs that have an uninstall program. The key referred to is HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall. The script and associated output are shown in the following figure.

Image of script and associated output

Here are the various registry keys:

#Define the variable to hold the location of Currently Installed Programs
  $UninstallKey=”SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall” 
#Create an instance of the Registry Object and open the HKLM base key
   $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey(‘LocalMachine’,$computername) 
#Drill down into the Uninstall key using the OpenSubKey Method
  $regkey=$reg.OpenSubKey($UninstallKey) 
#Retrieve an array of string that contain all the subkey names
  $subkeys=$regkey.GetSubKeyNames() 
#Open each Subkey and use the GetValue Method to return the string value for DisplayName for each

At this point, if you are anything like me, you are probably thinking, “I’ll stick with a one-liner and use Win32_Product.” But this brings us back to why we started looking at alternatives in the first place. As it turns out, the action of querying Win32_Product has the potential to cause some havoc on your systems. Here is the essence of KB974524.

The Win32_product class is not query optimized. Queries such as “select * from Win32_Product where (name like ‘Sniffer%’)” require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause:,

  • This process initiates a consistency check of packages installed, and then verifying and repairing the installations.
  • If you have an application that makes use of the Win32_Product class, you should contact the vendor to get an updated version that does not use this class.

On Windows Server 2003, Windows Vista, and newer operating systems, querying Win32_Product will trigger Windows Installer to perform a consistency check to verify the health of the application. This consistency check could cause a repair installation to occur. You can confirm this by checking the Windows Application Event log. You will see the following events each time the class is queried and for each product installed:

Event ID: 1035
Description: Windows Installer reconfigured the product. Product Name: <ProductName>. Product Version: <VersionNumber>. Product Language: <languageID>. Reconfiguration success or error status: 0.

Image of Event ID 1035

 

Event ID: 7035/7036
Description: The Windows Installer service entered the running state.

Image of Event ID 7036

 

Windows Installer iterates through each of the installed applications, checks for changes, and takes action accordingly. This would not a terrible thing to do in your dev or test environment. However, I would not recommend querying Win32_Product in your production environment unless you are in a maintenance window. 

So what is the best solution to determine installed applications? For me, it is reading from the registry as it involves less risk of invoking changes to our production environment. In addition, because I prefer working with the ISE environment, I have a modified version of Sean’s script that I store in a central location and refer back to whenever I need an updated list of installed applications on our servers. The script points to a CSV file that I keep up to date with a list of servers from our domain. 

$computers = Import-Csv “D:\PowerShell\computerlist.csv”

$array = @()

foreach($pc in $computers){

    $computername=$pc.computername

    #Define the variable to hold the location of Currently Installed Programs

    $UninstallKey=”SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall” 

    #Create an instance of the Registry Object and open the HKLM base key

    $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey(‘LocalMachine’,$computername) 

    #Drill down into the Uninstall key using the OpenSubKey Method

    $regkey=$reg.OpenSubKey($UninstallKey) 

    #Retrieve an array of string that contain all the subkey names

    $subkeys=$regkey.GetSubKeyNames() 

    #Open each Subkey and use GetValue Method to return the required values for each

    foreach($key in $subkeys){

        $thisKey=$UninstallKey+”\\”+$key 

        $thisSubKey=$reg.OpenSubKey($thisKey) 

        $obj = New-Object PSObject

        $obj | Add-Member -MemberType NoteProperty -Name “ComputerName” -Value $computername

        $obj | Add-Member -MemberType NoteProperty -Name “DisplayName” -Value $($thisSubKey.GetValue(“DisplayName”))

        $obj | Add-Member -MemberType NoteProperty -Name “DisplayVersion” -Value $($thisSubKey.GetValue(“DisplayVersion”))

        $obj | Add-Member -MemberType NoteProperty -Name “InstallLocation” -Value $($thisSubKey.GetValue(“InstallLocation”))

        $obj | Add-Member -MemberType NoteProperty -Name “Publisher” -Value $($thisSubKey.GetValue(“Publisher”))

        $array += $obj

    } 

}

$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | ft -auto

 

My modified version of Sean’s script creates a PSObject to hold the properties I am returning from each registry query, which then get dumped into an array for later use. When I am done, I simply output the array and pass it through a Where-Object to display only those entries with something in the DisplayName. This is handy because I can then refer back to just the array if I need to supply different output. Say I want to only report on a specific server. I’d change Where-Object to something like this:

Where-Object { $_.DisplayName -and $_.computerName -eq “thisComputer”} 

In conclusion, if you have added Windows PowerShell to your IT tool belt, you have plenty of go-to options when someone asks you, “What’s the best solution to a problem?”

 

Thank you, Marc, for writing this post and sharing with our readers

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy

 

 

Comments (45)

  1. Was not aware of the danger in querying Win32_Product class.

  2. Jonas,

    Thanks for the excellent comments and questions.

    1) First solution that comes to mind would be to build a simple array of both x64 and x32 locations and then loop through each.  You could throw in a search while enumerating through keys, say something like…

     Select-String -InputObject ($array | % { $_.DisplayName }) -pattern $($thisSubKey.GetValue("DisplayName"))

    And ignore results that match existing patterns within your array

    (Guessing there may be a better way to accomplish this task then enumerating through the entire array with every every subkey)

    2) I prefer the PowerShell ISE that comes with the download.  I've used a couple other editors and the one hang-up I have is that if you fat-finger a cmdlet and then backspace you no longer have the ability to do tab-completion unless you completely start over from the beginning of the cmdlet.  (And I do have fat fingers)  However, I don't have that issue with PowerShell ISE that came as part of WinRM 2.0.  Have you downloaded PowerShell 3.0 CTP1 that came with Windows Management Framework 3.0?  I believe there have been many new features added to the ISE.  I know I need to check it out.

    blogs.msdn.com/…/download-windows-powershell.aspx

    3) Sounds like this could be accomplished with a preliminary search like I mentioned previously.  

    Regards,

    -Marc

    Not a problem with the spelling (happens often) thanks for saying something!

  3. This is by far the best solution I have found, it's very quick, and doesn't trigger a repair.

    blogs.technet.com/…/how-to-not-use-win32-product-in-group-policy-filtering.aspx

    Create a custom MOF

  4. Anonymous says:

    I might be doing a very newbie mistake somewhere, but I can't get the script to run against remote computers.  It stops after running locally.  It's importing the CSV file okay though.

  5. jrv says:

    @DarwinS – that is a good reason for not using XP.

    On Vista and later it is very fast.  MS changed the installer support.

  6. Anonymous says:

    Why not use the registry PS provider? Like:

    Get-ChildItem -Path HKLM:SOFTWAREMicrosoftWindowsCurrentVersionUninstall |

       Get-ItemProperty |

       Sort-Object -Property DisplayName |

       Select-Object -Property DisplayName, DisplayVersion, InstallLocation

  7. Jeffrey,

    I'd +1 Jonas' comment too.  I was searching last night for .NET reference to the namespace that you would query but haven't had enough time to find what I'm looking for.  

    Klaus,

    You're welcome!

    And good call!  At the moment, I can't remember why I allowed those to be added to the array.  Probably some testing I was doing at one point that I never cleaned up and was cleaning up at the end with the Where-Object filter.

    Thanks!

    -Marc

  8. Mike Crowley says:

    Here are two simple lines:

    gci -recurse HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall | gp | select displayname

    gci -recurse HKLM:SoftwareWow6432NodeMicrosoftWindowsCurrentVersionUninstall | gp | select displayname

  9. jrv says:

    @Rob – other

    Your issue appears to be that you are not using a CSV file.  Look up CSV file and be sure you know how to create one.

  10. mredwilson says:

    @Knut I love the registry provider. Your solution would absolutely work. The nice thing about Windows PowerShell is that it allows you to work the way you want to work, and to use tools with which you are familiar. Thank you for your contribution.

  11. I'd like to +1 Jonas' comment, the x64 listing of software is a huge issue, wmi doesn't report everything nor does the registry. Does anyone have a solution for this?

  12. Jonas Mellquist says:

    Sorry, that should have been Marc, not Mark 🙂

    -Jonas, Denmark

  13. Jonas Mellquist says:

    Hi Mark and Ed

    I've just tried running the script on my local machine..

    It doesn't show anywhere near all the software that "Programs and Features" lists on my Windows 7 x64 machine..

    The stuff located under: HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall referred in the post above are (as far as I can tell) the generic 64 bit applications only.

    There is also software that goes under:

    HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionUninstall (maybe not too relevant, not much here, but still)

    And alot under

    HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall (32 bit programs installed on a 64 bit OS)

    I haven't figured out if there are duplicates in the three registry paths, I would assume not..

    *-*

    A few questions:

    1: How would you guys go about running through two registry (maybe even 3) without making another (more or less) copy-paste of the foreach loop?

    2: Which PowerShell ISE do you guys prefer? I don't think the default Windows PowerShell ISE does a good enough job when it comes to syntax highlighting/help and tab-completion. What do you recommend Ed?

    3: The Wow6432Node lists an awful lot of Office 2010 related updates unfortuanately, but I guess with a little scripting magic these could be ignored, how would you guys do this – maybe another csv-file with some program names (DisplayName)'s that should be ignored and therefore not added to $array.

    -Jonas, Denmark

  14. Klaus Schulte says:

    Hi Marc,

    thank you for providing this solution!

    It works for me on my WIN 7 x64 computer with the exception of the topics already mentioned by Jonas and Jeffrey.

    Apart from that there is one thing I would change:

    Instead of filtering the results of the query in the end "$array | Where-Object { $_.DisplayName } | …"

    I would recommend to add the following line

    if (-not $thisSubKey.getValue("DisplayName")) { continue }

    after

    $thisSubKey=$reg.OpenSubKey($thisKey)

    in the foreach loop!

    This saves creating 200 custom objects ( on my machine ) that are unused ( filtered ) later on!

    Klaus.

  15. mlm software noida says:

    thankyou………………the information on your blog is very useful

    <a href="http://www.ariestechsoft..net/mlm-software-development.html">mlm software development</a>

  16. Klaas says:

    I've always found it hard to get correct information about installed software. Knowing that after uninstallation not all programs are cleaned up the same way makes it even harder. Since there wasn't a consistent way of installing software in our company before, I had to combine WMI, registry, existing folders and files and SNMP (snmpwalk -v 1 -c communityname -O v computername hrSWInstalledName) in a script to find a fairly reliable inventory of installations.

    I've been using win32_product for 3 years and found it the slowest technology but never read about the MSI repair activities and the havoc Marc mentions here. I consider this a serious issue and will be more careful from now on.

    I'll dive into 'Powershell and WMI' right now and see what Richard has to say about this.

    Thank you, Marc, for the warning.

  17. Brent Challis says:

    Found both the article and comments very informative and useful.  From that I created a focussed function for testing for the presence of software that can be uninstalled by using the PSDrive for the registry:

    function Test-ProductCanBeUninstalled

    {

       Param

       (

           [string]$ProductDisplayName

       )

       $found = $false

       $uninstallKeys = Get-ChildItem HKLM:SOFTWAREMicrosoftWindowsCurrentVersionUninstall

       if (Test-Path HKLM:SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall)

       {

           $uninstallKeys += Get-ChildItem HKLM:SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall

       }

       foreach ($key in $uninstallKeys)

       {

          if ($key.GetValue("DisplayName") -like $ProductDisplayName)

          {

               $found = $true

          }

       }

       return $found

    }

    My thought was that is I found more registry hives to test I could simply add then with a Test-Path before adding to the collection.  A shortcoming of my approach, of course is that it only works on the running computer.

  18. goodgnu says:

    Also the computer name is not being printed on each line so that the query by computer will never work.

  19. Scott says:

    @knut – Love it!  

    Also added    | export-csv app_list.csv   to the end & have saved myself a heap of work!

  20. Nick W. says:

    Alright, took several hours of research and trial and error but i got the what I have found to be the best way to list all installed software on your computer. I'm sure i'm missing something somewhere but ATM this replicates exactly what is shown on my add or remove programs list! This script is a little raw but as is will give you a list of all uninstalled software

    $COTS.InstalledSoftware = Get-ChildItem HKLM:SOFTWAREMicrosoftWindowsCurrentVersionUninstall | Get-ItemProperty

    $COTS.InstalledSoftware += Get-ChildItem HKCU:SoftwareMicrosoftWindowsCurrentVersionUninstall | Get-ItemProperty

    IF (Test-path HKLM:SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall) {

       $COTS.InstalledSoftware += Get-ChildItem HKLM:SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall | Get-ItemProperty

    }

    ($Cots.InstalledSoftware | Where {$_.DisplayName -ne $Null -AND $_.SystemComponent -ne "1" -AND $_.ParentKeyName -eq $Null} |Select DisplayName).GetEnumerator() | Sort-Object {"$_"}

  21. Martin says:

    Hi

    Can I then further list the top 10 installed programs based on ALL computers installed programs?

  22. Chris Roong says:

    Hi,

    I have a question. The script above is very fascinating. It run it and it works great.

    However, what can I change or modify to target other computer than the one I am using?

    I mean other computers that are in the same local network. There are 20 computers in our network.

    And I am trying to figure out that programs they are installing with for an update information.

  23. Lyudmil says:

    Thanks a lot no wonder why Microsoft named this language POWERshell

  24. Yanni says:

    Thank you very much for sharing your knowledge.

  25. Darwin S says:

    Win32_Product is a very bad Class to enumerate – it cause MSI to run a validation process on every installed package – which is why it takes ages.  See here: csi-windows.com/…/win32product-replacement

  26. Roy says:

    Hi

    I created csv file with two example computers.

    In output I only see software listed from local computer (without server name info)

    What should I change to see software inventory from remote computers, with Computer name displayed?

    Thanks

    This is example:

    PS C:WindowsSystem32WindowsPowerShellv1.0> C:scriptssoftware.ps1

    ComputerName DisplayName                                                               DisplayVersion Publisher                    

    ———— ———–                                                               ————– ———                    

                Microsoft Project Professional 2010                                       14.0.6029.1000 Microsoft Corporation        

                Microsoft Visio Premium 2010                                              14.0.6029.1000 Microsoft Corporation        

                Microsoft Office Professional Plus 2013                                   15.0.4420.1017 Microsoft Corporation    

  27. Rob says:

    Roy

    I was having the same issue.  All I did was comment out $computername=$pc.computername and then change $obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername to $obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $pc.

    Hope this helps!

  28. Rob says:

    Forgot my last comment…  This appears to run but only queries the local computer then outputs the data to make it look as though it ran against the remote computers.  Can anyone help with this?

  29. Brandon says:

    Would there be a way to exclude the security updates for each server?

    I've added | export-csv filename -NoTypeInformation but so many rows are wasted with installed updates for each server.

  30. Gary says:

    Thanks for the solution, but in my case it fails for lack of credentials. Is there a way to pass a credentials object with this?

  31. john c says:

    Nice Ed! You've nailed it yet again! Only problem is, as Jonas points out, we need to capture whats under that Wow6432Node registry key, as that is where the good deal of/ majority of software appears to be. Anyway to somehow capture all subkeys under that to display for use? Or is it just a matter of changing the $UninstallKey variable to point to the Wow6432Node key and all subkeys below it..? Any help would be GREATLY appreciated as we just need a simple list of what software is on the machine including 64 bit apps.

  32. John/all-

    With all the great alternative solutions and comments this subject has spawned I think it's worth revisiting the issue.  Keep an eye out for "Use PowerShell to Quickly Find Installed Software 2.0" in the upcoming weeks ahead.  

  33. Guido Pinamonti says:

    The powershell code above does not run as written. There’s no computer name property on the PC object.: $computername=$pc.computername.

  34. Updated Script says:

    Part #1 of 3 – Posting limit is 3072 characters. $comments = @’ Author: Kevin Higgins Date: 1/24/2014 Purpose: Remote pull a list of all programs installed on a windows-based computer. Misc Info: This script is rewritten from the code in the URL below.
    Difference include: 1. Fixes original code issue of $computername ($computername = $pc.computername) not working. 2. Added checking for 64-bit programs. 3. Added a "32 or 64 bit" column 4. Additional "search" example for -match & -eq 5. Clears the $reg variable
    before processing each computer. (Prevents programs installed on ComputerA from showing as installed on ComputerB if ComputerB is inaccessible.) 6. Skips loop processing if a computer’s registry is NOT accessible. (this is the ‘trap’. I got tired of seeing
    red text due to inaccessible computers.) Reference URL: http://blogs.technet.com/b/heyscriptingguy/archive/2011/11/13/use-powershell-to-quickly-find-installed-software.aspx Prerequisite1: Remote registry services running. If not, the from admin cmd prompt,
    run these two commands sc \computername config remoteregistry start= auto sc \computername start remoteregistry Prerequisite2: ComputersToCheck.csv Cell A1 = computername (The text "computername".) Cell A2 = DESKTOP01 (The name of a real computer.) Cell
    A3 = DESKTOP02 (The name of a real computer.) Registry keys checked: HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall ‘@

  35. Kevin Higgins says:

    Part #1 of 3 – Posting limit is 3072 characters. $comments = @’ Author: Kevin Higgins Date: 1/24/2014 Purpose: Remote pull a list of all programs installed on a windows-based computer. Misc Info: This script is rewritten from the code in the URL below.
    Difference include: 1. Fixes original code issue of $computername ($computername = $pc.computername) not working. 2. Added checking for 64-bit programs. 3. Added a "32 or 64 bit" column 4. Additional "search" example for -match & -eq 5. Clears the $reg variable
    before processing each computer. (Prevents programs installed on ComputerA from showing as installed on ComputerB if ComputerB is inaccessible.) 6. Skips loop processing if a computer’s registry is NOT accessible. (this is the ‘trap’. I got tired of seeing
    red text due to inaccessible computers.) Reference URL: http://blogs.technet.com/b/heyscriptingguy/archive/2011/11/13/use-powershell-to-quickly-find-installed-software.aspx Prerequisite1: Remote registry services running. If not, the from admin cmd prompt,
    run these two commands sc \computername config remoteregistry start= auto sc \computername start remoteregistry Prerequisite2: ComputersToCheck.csv Cell A1 = computername (The text "computername".) Cell A2 = DESKTOP01 (The name of a real computer.) Cell
    A3 = DESKTOP02 (The name of a real computer.) Registry keys checked: HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall ‘@

  36. Kevin Higgins says:

    Part #2 of 3 – Posting limit is 3072 characters. clear-host #Note: Edit the two lines below. $computers = Import-Csv "C:ComputersToCheck.csv" $DomainName = ".YourDomain.internal" $array = @() foreach($pc in $computers) { #$pc = $computers[0] $computername
    = $pc.computername $computernameFQDN = $computername + $DomainName; write-host -f cyan "`nStarting scan on computer " -nonewline; write-host -f green "$computername." #Define the variable to hold the location of Currently Installed Programs $UninstallKey32
    = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $UninstallKey64 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" #Create an instance of the Registry Object and open the HKLM base key $reg = $null trap {; Continue}; [microsoft.win32.registrykey]::OpenRemoteBaseKey(‘LocalMachine’,$computernameFQDN)
    | out-null if ($? -eq $False) {write-host -f red "`t Error accessing $computername."; continue} $reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey(‘LocalMachine’,$computernameFQDN) #Drill down into the Uninstall key using the OpenSubKey Method $regkey32
    = $null $regkey64 = $null $regkey32 = $reg.OpenSubKey($UninstallKey32) $regkey64 = $reg.OpenSubKey($UninstallKey64) #Retrieve an array of string that contain all the subkey names $subkeys32 = $null $subkeys64 = $null $subkeys32 = $regkey32.GetSubKeyNames()
    $subkeys64 = $regkey64.GetSubKeyNames() #32bit Open each Subkey and use GetValue Method to return the required values for each foreach($key in $subkeys32){ $thisKey = $UninstallKey32+"\"+$key $thisSubKey = $reg.OpenSubKey($thisKey) #if (-not $thisSubKey.getValue("DisplayName"))
    { continue } $obj = New-Object PSObject $obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername $obj | Add-Member -MemberType NoteProperty -Name "32 or 64 bit" -Value " 32-bit" $obj | Add-Member -MemberType NoteProperty -Name "DisplayName"
    -Value $($thisSubKey.GetValue("DisplayName")) $obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion")) $obj | Add-Member -MemberType NoteProperty -Name "InstallLocation" -Value $($thisSubKey.GetValue("InstallLocation"))
    $obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher")) $array += $obj }

  37. Kevin Higgins says:

    Part #3 of 3 – Posting limit is 3072 characters. #64bit Open each Subkey and use GetValue Method to return the required values for each foreach($key in $subkeys64){ $thisKey = $UninstallKey64+"\"+$key $thisSubKey = $reg.OpenSubKey($thisKey) #if (-not
    $thisSubKey.getValue("DisplayName")) { continue } $obj = New-Object PSObject $obj | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $computername $obj | Add-Member -MemberType NoteProperty -Name "32 or 64 bit" -Value " 64-bit" $obj | Add-Member
    -MemberType NoteProperty -Name "DisplayName" -Value $($thisSubKey.GetValue("DisplayName")) $obj | Add-Member -MemberType NoteProperty -Name "DisplayVersion" -Value $($thisSubKey.GetValue("DisplayVersion")) $obj | Add-Member -MemberType NoteProperty -Name "InstallLocation"
    -Value $($thisSubKey.GetValue("InstallLocation")) $obj | Add-Member -MemberType NoteProperty -Name "Publisher" -Value $($thisSubKey.GetValue("Publisher")) $array += $obj } } 1..5 | % {write-host "`n"} #See all installed programs #$array | Where-Object { $_.DisplayName
    } | select ComputerName, "32 or 64 bit", DisplayName, DisplayVersion, Publisher | ft -auto #Search for "iTunes", "Google", "Google Chrome" #$array | Where-Object { $_.DisplayName -match "iTunes"} | select ComputerName, "32 or 64 bit", DisplayName, DisplayVersion,
    Publisher | ft -auto #$array | Where-Object { $_.DisplayName -match "Google"} | select ComputerName, "32 or 64 bit", DisplayName, DisplayVersion, Publisher | ft -auto #Check for a specific installation from "Microsoft Updates" #$array | Where-Object { $_.DisplayName
    -match "KB2687455"} | select ComputerName, "32 or 64 bit", DisplayName, DisplayVersion, Publisher | ft -auto #Search for EXACT text of "Google Chrome" $array | Where-Object { $_.DisplayName -eq "Google Chrome"} | select ComputerName, "32 or 64 bit", DisplayName,
    DisplayVersion, Publisher | ft -auto

  38. Kevin Higgins says:

    Sorry it shows up as one long sentence.

  39. Jacob Fischlein says:

    Instead of using a file with computer names, you can take them from AD: $computers = Get-ADComputer -SearchBase "OU=Computers, DC=Internal, DC=YourDomain" -Filter * | Select -Expand Name

  40. Ayan Bhattacharyya says:

    A compact solution to find all the installed applications whehter it exists under 32 bit or 64 bit registry key, can be found here –

    https://gallery.technet.microsoft.com/scriptcenter/Search-for-Installed-1a70096d

  41. RP_ says:

    Might be a stupid question, but mine doesn’t output anything – anyone any idea why? Sorry I’m quite new to PS

  42. Brian S says:

    The first line on the .csv should be "ComputerName"

  43. John Walls says:

    Hi Guys, Thanks for taking the time to write these blogs they are very informative!

    John (http://justanotheritblog.co.uk)

Skip to main content