PowerShell:掃描軟體版本(2022/01/26 Update)

#Update@2022/1/26:請詳見下方

最近對於Powershell又有新發現了!

原由:

因為公司某個內部網站需要安裝元件,而且常常會更新,有時候會因為元件版本過於老舊或是未安裝元件而無法使用。

當然比較先進的方式是用Agent的方式做檢查並且派送,但是很無奈的在外商公司很多東西都不是Local IT可以觸碰的。

而且很詭異的是總部的派送常常會失敗,大概因為距離很遙遠吧(Cyberjaya or Pargue???)…

科技始終來自於惰性,我又不想一個一個打電話去問去檢查,這很不科學。

所以我一開始本來想用WMI的方式


Get-WmiObject -Class Win32_Product

但是這會碰到兩個問題:

1.這效率非常不好,因為它會全部掃過一遍,他相對於資料庫查詢很像是 Select * from Win32_Product where (name like ‘XXXXX%’)。

2.根據我查到的資料,他會一併檢查軟體安裝的一致性,如果檢查到有問題還會一併觸發Windows Installer去做修復與重新設定。

原文說明: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.

參考1(新分頁):https://blogs.technet.microsoft.com/heyscriptingguy/2011/11/13/use-powershell-to-quickly-find-installed-software/

這樣的Script給User自己點選跑起來可能很久,還要擔心萬一發生其他想不到的問題。

後來發現了可以用掃描機碼的方式,

32位元:HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

64位元還要加上:HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

然後搭配Out-GridView這樣是不是淺顯易懂!

附上Powershell裡面的XXXXX請自由變化。

function Search-RegistryUninstallKey {
param($SearchFor,[switch]$Wow6432Node)
$results = @()
$keys = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
foreach {
$obj = New-Object psobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name GUID -Value $_.pschildname
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayName -Value $_.GetValue("DisplayName")
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayVersion -Value $_.GetValue("DisplayVersion")
if ($Wow6432Node)
{Add-Member -InputObject $obj -MemberType NoteProperty -Name Wow6432Node? -Value "No"}
$results += $obj
}

if ($Wow6432Node) {
$keys = Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
foreach {
$obj = New-Object psobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name GUID -Value $_.pschildname
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayName -Value $_.GetValue("DisplayName")
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayVersion -Value $_.GetValue("DisplayVersion")
Add-Member -InputObject $obj -MemberType NoteProperty -Name Wow6432Node? -Value "Yes"
$results += $obj
}
}
$results | sort DisplayName | where {$_.DisplayName -match $SearchFor}
}
[String]$OS = ((Get-CimInstance win32_OperatingSystem).version).split(".",2)[-2]
Switch($OS)
{
10
{
Search-RegistryUninstallkey -Wow6432Node -SearchFor "XXXXX" | Out-GridView -Wait
}
6
{
Search-RegistryUninstallkey -SearchFor "XXXXX" | Out-GridView -Wait
}
}


以下為2022/01/26新增:

由於之前的方式是將參數丟進Function裡讓他搜尋出來,如果想一隻Powershell一次呈現

Search-RegistryUninstallkey -Wow6432Node -SearchFor "XXXX" #| Out-GridView
Search-RegistryUninstallkey -Wow6432Node -SearchFor "YYYY" #| Out-GridView
Search-RegistryUninstallkey -Wow6432Node -SearchFor "ZZZZ" #| Out-GridView

這樣的結果會變成跳出三個GridView,感覺就很弱。因此稍微做了一些改變,先把搜尋到的陣列結果存成參數,然後相加起來,在一次丟到Gridview就可以了,以下是該部分程式碼:

10
    {
        $Result1 = Search-RegistryUninstallkey -Wow6432Node -SearchFor "Citrix" #| Out-GridView
        $Result2 = Search-RegistryUninstallkey -Wow6432Node -SearchFor  'Microsoft Visual C++ 2015-2019 Redistributable' #| Out-GridView
        $Result3 = Search-RegistryUninstallkey -Wow6432Node -SearchFor "CargoWise" #| Out-GridView
        $TotalResult = $Result1 + $Result2 + $Result3
        $TotalResult | Out-GridView -Wait 
    }

另外在搜尋軟體的時候發現搜尋到Microsoft Visual C++ 20XX這一系列軟體的時候,因為他與PowerShell的正則表示式(運算式?)衝突,所以會出現錯誤,所以我在搜尋結果裡使用Regular Expression方式了一下調整一下Where裡的搜尋的結果。以下為程式碼:

$results | sort DisplayName | where {$_.DisplayName -Match [regex]::escape($SearchFor)}

最後式全部完全修正版本,XXXX請自行變化:

function Search-RegistryUninstallKey {
param($SearchFor,[switch]$Wow6432Node)
$results = @()
$keys = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
foreach {
$obj = New-Object psobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name GUID -Value $_.pschildname
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayName -Value $_.GetValue("DisplayName")
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayVersion -Value $_.GetValue("DisplayVersion")
if ($Wow6432Node)
{Add-Member -InputObject $obj -MemberType NoteProperty -Name Wow6432Node? -Value "No"}
$results += $obj
}

if ($Wow6432Node) {
$keys = Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
foreach {
$obj = New-Object psobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name GUID -Value $_.pschildname
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayName -Value $_.GetValue("DisplayName")
Add-Member -InputObject $obj -MemberType NoteProperty -Name DisplayVersion -Value $_.GetValue("DisplayVersion")
Add-Member -InputObject $obj -MemberType NoteProperty -Name Wow6432Node? -Value "Yes"
$results += $obj
}
}
$results | sort DisplayName | where {$_.DisplayName -Match [regex]::escape($SearchFor)}
}
[String]$OS = ((Get-CimInstance win32_OperatingSystem).version).split(".",2)[-2]
Switch($OS)
{
10
{
10
    {
        $Result1 = Search-RegistryUninstallkey -Wow6432Node -SearchFor "Citrix" #| Out-GridView
        $Result2 = Search-RegistryUninstallkey -Wow6432Node -SearchFor  'Microsoft Visual C++ 2015-2019 Redistributable' #| Out-GridView
        $Result3 = Search-RegistryUninstallkey -Wow6432Node -SearchFor "CargoWise" #| Out-GridView
        $TotalResult = $Result1 + $Result2 + $Result3
        $TotalResult | Out-GridView -Wait 
    }
}
6
{
Search-RegistryUninstallkey -SearchFor "XXXXX" | Out-GridView -Wait
}
}

這樣就可以很彈性的加入多種軟體的搜尋了