Monthly Archives: July 2014

PowerShell – query installed software on remote computers

You can probably find plenty of PowerShell scripts that will retrieve a list of installed software on a local or a remote computer. Most of them utilize two techniques for detecting installed software from a computer:

  • Registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall key
  • Win32_Product Class

Please note that the Win32_Product class has some limitations:

1. It retrieves only the names of applications installed using the Windows Installer service.
2. Retrieving instances of the Win32_Product class is very slow.
3. It’s not always possible to retrieve Win32_Product class instances from remote computers.
Luckily, we can obtain information about installed software from a computer’s registry instead. Please be aware that in some environments the RemoteRegistry service (enabled by default) would be disabled if the High Security Policy Template was applied, and in such case your script would need to enable this service first, and then enumerate the Uninstall key and read the data in each subkey underneath the Uninstall key.

My Example:  Query-SoftwareOnRemotePc.ps1

I need to regularly audit if the specific software (and its version) has been installed on the computers in the particular Active Directory OU that has High Security Policy Template applied, and export it to a list of software. For this reason, my script first enables RemoteRegistry service and then generates a list by querying the registry and returning the specific programs (note in the script the section where I query only specific subkeys: SEP and Flash Player) from remote computers. The script exports the list of software to .CSV file and creates a log file. The next time the script runs, it will query only the computers in Active Directory OU that could not be reached / queried in the previous attempt. Check the script below:


# Filename: Query-SoftwareOnRemotePc_ADSI.ps1 
# Date:     July, 2014
# Author:   Alex Dujakovic 
# Description: PowerShell script to get the list of software installed on remote computers
# In a loop, an array receives a lot of new items using the operator "+="
# to speed up this array operation, in this example I will use ArrayList
#------------------------------------------------------------------------------------------------------
$myOU = [ADSI]"LDAP://OU=AFL-Computers,OU=AFL,OU=Accounts,DC=Test,DC=com"
$logFile="C:\PScontainer\QueryInstalledSoftwareLog.log"
$csvFilePath = "C:\PScontainer\SoftwareList.csv"
# Check if .CSV file exist, and create empty one with column names of the software I am searching for
if (!(Test-Path -path $csvFilePath)) { ""|Select Name, SEP, SEPVersion, FlashPlayer, FPlayerVersion, FPPlugin, FPPluginVersion | 
 Export-Csv -Path $csvFilePath -NoTypeInformation}

$outputArray = New-Object -TypeName System.Collections.ArrayList

$PCcollection = ($myOU.psbase.Children | Where  -FilterScript{$_.ObjectCategory -Like "*Computer*"} | 
 Select-Object -ExpandProperty Name)
$reachedPCs = (Import-Csv -Path $csvFilePath | Select-Object -ExpandProperty Name)

$newQueryCollection = (Compare-Object -ReferenceObject $PCcollection -DifferenceObject $reachedPCs |
Where-Object -FilterScript {$_.SideIndicator -eq "<="} |Select-Object -ExpandProperty InputObject | Sort InputObject)

foreach ($pcItem In $newQueryCollection){
Write-Host "Pinging computer: $pcItem" # comment this line if you do not want to see the names of computers
If (Test-Connection -ComputerName $pcItem -ea silentlycontinue -Count 1) {
          try 
            { 
            $errorActionPreference = "Stop"
            Write-Host "Querying computer: $pcItem" -ForegroundColor Green # comment this line if you do not want to see the names of computers
            "$($pcItem) - Success" | Out-file -FilePath $logFile -Encoding ascii -Append
            (Get-WmiObject -computerName $pcItem Win32_Service -Filter "Name='RemoteRegistry'").StartService() | Out-Null
            $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$pcItem)
            $RegKeySEP= $Reg.OpenSubKey("SOFTWARE\\Symantec\\Symantec Endpoint Protection\\CurrentVersion")
            $VersionSEP = $RegKeySEP.GetValue("PRODUCTVERSION")
            $RegKeyFP= $Reg.OpenSubKey("SOFTWARE\\Macromedia\\FlashPlayer")
            $VersionFP = $RegKeyFP.GetValue("CurrentVersion")
            $RegKeyFPPlug= $Reg.OpenSubKey("SOFTWARE\\Macromedia\\FlashPlayerPlugin")
            $VersionFPlug = $RegKeyFPPlug.GetValue("Version")
            $row = ""|Select Name, SEP, SEPVersion, FlashPlayer, FPlayerVersion, FPPlugin, FPPluginVersion
            $row.Name = $pcItem.ToString()
            $row.SEP = "Symantec Endpoint Protection"
            $row.SEPVersion = $VersionSEP
            $row.FlashPlayer = "Flash Player"
            $row.FPlayerVersion = $VersionFP
            $row.FPPlugin = "Flash Player Plugin"
            $row.FPPluginVersion = $VersionFPlug
            $outputArray.Add($row)
            Remove-Variable Reg,VersionSEP, VersionFP, VersionFPlug
            }

         catch 
            { 
             "$($pcItem) -(ERROR)- Exception Type: $($_.Exception.GetType().FullName)" | 
               Out-file -FilePath $logFile -Encoding ascii -Append            
            }
       }
       else
        {"$($pcItem) -Ping Failed" | Out-file -FilePath $logFile -Encoding ascii -Append }    
}

$outputArray | Export-Csv -Path $csvFilePath -NoTypeInformation -Append
Write-Host "Script finished execution" -ForegroundColor Red

This is the .CSV file that is created by the script – picture below:

CSVFile

And this is the log file that lists all the results of the script execution – picture below:

LogFile

In the scrip above, I would like to draw your attention to the two following sections. The first one, if you have PowerShell module installed on your computer you will have option to Import-Module ‘ActiveDirectory’ and use the following section in this script:


Import-Module ActiveDirectory
$myOU = "OU=AFL-Computers,OU=AFL,OU=Accounts,DC=Test,DC=com"

$PCcollection = (Get-ADComputer -Filter {(Name -like "AFL-*")} -SearchBase $myOU -Properties Name |
 Select-Object -ExpandProperty Name |Sort)
$reachedPCs = (Import-Csv -Path $csvFilePath | Select-Object -ExpandProperty Name)

Otherwise you can use ADSI to get your computers collections as shown in the first script above.

The second one, it applies to an array in a loop. To speed up an array, which takes a lot of time using the operator “+=”, you should try to use ArrayList, as follows:


$outputArray = New-Object -TypeName System.Collections.ArrayList
$row = ""|Select Name, SEP, SEPVersion, FlashPlayer, FPlayerVersion, FPPlugin, FPPluginVersion
            $row.Name = $pcItem.ToString()
            $row.SEP = "Symantec Endpoint Protection"
            $row.SEPVersion = $VersionSEP
            $row.FlashPlayer = "Flash Player"
            $row.FPlayerVersion = $VersionFP
            $row.FPPlugin = "Flash Player Plugin"
            $row.FPPluginVersion = $VersionFPlug
$outputArray.Add($row)

The Query-SoftwareOnRemotePc.ps1 scripts (both versions: ADSI and ActiveDirectory module) could be found in the download section, under PowerShell.





Please note: Although the author has made every reasonable attempt to achieve complete accuracy of the content, he assumes no responsibility for errors or omissions. Also, you should use this information as you see fit, and at your own risk.