Alternative to Win32_Product?

匆匆过客 提交于 2019-11-28 02:04:39

I use the registry remotely, without PSRemoting. Here's the function I wrote and use daily to query software.

Function Get-RemoteSoftware{
<#
.SYNOPSIS 
Displays all software listed in the registry on a given computer.

.DESCRIPTION
Uses the SOFTWARE registry keys (both 32 and 64bit) to list the name, version, vendor, and uninstall string for each software entry on a given computer.

.EXAMPLE
C:\PS> Get-RemoteSoftware -ComputerName SERVER1
This shows the software installed on SERVER1.
#>

param (
    [Parameter(mandatory=$true,ValueFromPipelineByPropertyName=$true)][string[]]
    # Specifies the computer name to connect to
    $ComputerName
)

Process {
    foreach ($Computer in $ComputerName)
    {
        #Open Remote Base
        $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$Computer)

        #Check if it's got 64bit regkeys
        $keyRootSoftware = $reg.OpenSubKey("SOFTWARE")
        [bool]$is64 = ($keyRootSoftware.GetSubKeyNames() | ? {$_ -eq 'WOW6432Node'} | Measure-Object).Count
        $keyRootSoftware.Close()

        #Get all of they keys into a list
        $softwareKeys = @()
        if ($is64){
            $pathUninstall64 = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            $keyUninstall64 = $reg.OpenSubKey($pathUninstall64)
            $keyUninstall64.GetSubKeyNames() | % {
                $softwareKeys += $pathUninstall64 + "\\" + $_
            }
            $keyUninstall64.Close()
        }
        $pathUninstall32 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
        $keyUninstall32 = $reg.OpenSubKey($pathUninstall32)
        $keyUninstall32.GetSubKeyNames() | % {
            $softwareKeys += $pathUninstall32 + "\\" + $_
        }
        $keyUninstall32.Close()

        #Get information from all the keys
        $softwareKeys | % {
            $subkey=$reg.OpenSubKey($_)
            if ($subkey.GetValue("DisplayName")){
                $installDate = $null
                if ($subkey.GetValue("InstallDate") -match "/"){
                    $installDate = Get-Date $subkey.GetValue("InstallDate")
                }
                elseif ($subkey.GetValue("InstallDate").length -eq 8){
                    $installDate = Get-Date $subkey.GetValue("InstallDate").Insert(6,".").Insert(4,".")
                }
                New-Object PSObject -Property @{
                    ComputerName = $Computer
                    Name = $subkey.GetValue("DisplayName")
                    Version = $subKey.GetValue("DisplayVersion")
                    Vendor = $subkey.GetValue("Publisher")
                    UninstallString = $subkey.GetValue("UninstallString")
                    InstallDate = $installDate
                }
            }

            $subkey.Close()
        }
        $reg.Close()
    }
}  

}

That Get-RemoteSoftware works great--assuming remote registry service is started on the remote system. If not you will get an error. I always check if this is started and start it if not, then stop it when done. I wonder if this is why the otherwise-great function received down-votes.

Here is a slightly modified version that will check and start the remote registry service, and stop when finished.

Function Get-WmiCustom2([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15,[string]$whereclause='') 
{
#Function Get-WMICustom2 by MSFT's Daniele Muscetta 
#This is a modified version to add where clause parameter, optional
#Original function: http://blogs.msdn.com/b/dmuscett/archive/2009/05/27/get_2d00_wmicustom.aspx

$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace

$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions

try {
    $Scope.Connect()
} catch {
    $result="Error Connecting " + $_
    return $Result 
}

$querystring = "SELECT * FROM " + $class + " " + $whereclause
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope

trap { $_ } $result = $searcher.get()

return $result
}

Function Get-RemoteSoftware{
<#
.SYNOPSIS 
Displays all software listed in the registry on a given computer.

.DESCRIPTION
Uses the SOFTWARE registry keys (both 32 and 64bit) to list the name, version, vendor, and uninstall string for each software entry on a given computer.

.EXAMPLE
C:\PS> Get-RemoteSoftware -ComputerName SERVER1
This shows the software installed on SERVER1.
#>

param (
[Parameter(mandatory=$true,ValueFromPipelineByPropertyName=$true)][string[]]
# Specifies the computer name to connect to
$ComputerName
)

Process {
    foreach ($Computer in $ComputerName)
    {
        $ChangeStateBack=$False
        $RemoteRegistryObj=""
        $ServiceWMIObj=@(get-wmicustom2 -class "win32_service" -namespace "root\cimv2" -whereclause "WHERE name='RemoteRegistry'" -computername $computername –timeout 60 -erroraction stop)
        if ($ServiceWMIObj.Count -gt 0) {
        $RemoteRegistryObj =  $ServiceWMIObj[0]
        if ($RemoteRegistryObj.State -ne 'Running') {
                $ChangeStateBack=$true
                $RemoteRegistryObj.InvokeMethod("StartService",$null) | Out-Null
                Start-Sleep -m 1800     
                #give it a chance to actually start. 1.5 second delay
        }
}
    #Open Remote Base
    $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$Computer)

    #Check if it's got 64bit regkeys
    $keyRootSoftware = $reg.OpenSubKey("SOFTWARE")
    [bool]$is64 = ($keyRootSoftware.GetSubKeyNames() | ? {$_ -eq 'WOW6432Node'} | Measure-Object).Count
    $keyRootSoftware.Close()

    #Get all of they keys into a list
    $softwareKeys = @()
    if ($is64){
        $pathUninstall64 = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
        $keyUninstall64 = $reg.OpenSubKey($pathUninstall64)
        $keyUninstall64.GetSubKeyNames() | % {
            $softwareKeys += $pathUninstall64 + "\\" + $_
        }
        $keyUninstall64.Close()
    }
    $pathUninstall32 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    $keyUninstall32 = $reg.OpenSubKey($pathUninstall32)
    $keyUninstall32.GetSubKeyNames() | % {
        $softwareKeys += $pathUninstall32 + "\\" + $_
    }
    $keyUninstall32.Close()

    #Get information from all the keys
    $softwareKeys | % {
        $subkey=$reg.OpenSubKey($_)
        if ($subkey.GetValue("DisplayName")){
            $installDate = $null
            if ($subkey.GetValue("InstallDate") -match "/"){
                $installDate = Get-Date $subkey.GetValue("InstallDate")
            }
            elseif ($subkey.GetValue("InstallDate").length -eq 8){
                $installDate = Get-Date $subkey.GetValue("InstallDate").Insert(6,".").Insert(4,".")
            }
            New-Object PSObject -Property @{
                ComputerName = $Computer
                Name = $subkey.GetValue("DisplayName")
                Version = $subKey.GetValue("DisplayVersion")
                Vendor = $subkey.GetValue("Publisher")
                UninstallString = $subkey.GetValue("UninstallString")
                InstallDate = $installDate
            }
        }

        $subkey.Close()
    }
    $reg.Close()
    if ($ChangeStateBack){
                            $RemoteRegistryObj.InvokeMethod("StopService",$null)  | Out-Null
    }
}
}  
}

This is using a custom WMI get wrapper that someone at MSFT wrote, so if this snippet is copied in its entirety it will work as-is. You could modify it back to the standard get-wmiobject function, but there is no timeout built in to that. In some [not all that rare] situations the remote WMI responder will hang indefinitely with the default WMI so this adds a timeout. -Dane

I would recommend reading this scripting guy article for why Win32_Product is bad along with alternatives. I usually use the Win32Reg_AddRemovePrograms as we are using SCCM which installs this class. If you are not using SCCM stick with a registry query like the one @Chris N posted.

PS:\>Measure-Command {gwmi win32reg_addremoveprograms}
Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 162
Ticks             : 1623758
TotalDays         : 1.87934953703704E-06
TotalHours        : 4.51043888888889E-05
TotalMinutes      : 0.00270626333333333
TotalSeconds      : 0.1623758
TotalMilliseconds : 162.3758
marc carter

There's actually a follow-up Hey! Scripting Guys article Use PowerShell to Find Installed Software that discusses other more efficient ways to grab that information. Briefly, use one of two commands:

Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
    Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
    Format-Table –AutoSize
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
    Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
    Format-Table –AutoSize
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!