Mittels folgenden Zeilen Powershell Code, können alle ProductCodes von MSI Installationsdateien in einem Verzeichnis und allen Unterverzeichnissen ausgelesen und im Unterschied zum letzten Artikel in einer GridView angezeigt werden.
ValidateSet
Das eigentliche Auslesen des ProductCodes geschieht mittels der Funktion „get-property“. Diese beinhaltet einen Parameter, bzw. ein sogenanntes ValidateSet. Damit wird eingeschränkt, was abgefragt werden kann und somit zurück gegeben werden kann.
[ValidateSet('ProductCode','ProductVersion','ProductName')]
In diesem Fall besteht eine Einschränkung auf die drei Properties „ProductCode“, „ProductVersion“ und „ProductName“. Selbstverständlich kann das erweitert oder ganz weggelassen werden.
Alle MSI auslesen
Mittels Get-ChildItem und dem Parameter -Filter werden nun alle Dateien mit der Endung .msi in dem gewünschten Verzeichnis gesucht und anschliessend in einer Foreach Schlaufe abgearbeitet. Dabei wird für jede gefundene Datei die Funktion „get-Property“ aufgerufen und das Property „ProductCode“ sowie „ProductName“ abgefragt.
Get-ChildItem -Path $strSearchDirectory -Recurse -Filter *.msi | ForEach-Object { #read productcode [string]$strProdName = get-Property -Path $_ -Property ProductName [string]$strProdName = "$_, $strProdName" [string]$strProdCode = get-Property -Path $_ -Property ProductCode If(!($AllProdCodes.ContainsKey($strProdName))) { $AllProdCodes.Add($strProdName,$strProdCode) } else { $i++ [string]$strProdName = [string]$strProdName + "_MULTIPLEVALUE_$i" $AllProdCodes.Add($strProdName,$strProdCode) } [string]$strProdName = $null [string]$strProdCode = $null }
Die gefundenen ProductNames und ProductCodes werden in die Hashtable $AllProductCodes geschrieben. Bevor das stattfindet, wird auf mehrfach vorhandene Werte überprüft. Falls vorhanden, werden diese zuerst umbenannt.
Ausgabe in GridView
Am Schluss wird das ganze mittels Out-GridView ausgegeben.
#output to gridview $AllProdCodes.Keys | Select-Object @{l='MSI,PackageName';e={$_}},@{l='ProductCode';e={$AllProdCodes.$_}} | Out-GridView
Die Performance ist in diesem Beispiel nicht so berauschend und bestimmt verbesserungswürdig. Wieso denn die Ausgabe mit Out-GridView? Meiner Meinung nach ist die GridView, einmal geladen und angezeigt, ein wunderbares Hilfsmittel um Daten zu suchen und zu sortieren. Des weiteren hat es einfach Spass gemacht, das so umzusetzen 🙂
Das komplette Script
# --------------------------------------------------------- # purpose: loop through folder and get all msi productcodes # author: clearbyte # date: 06.04.2017 # version: 2.0 # getestet unter: ps 5.0, ps 2.0 # --------------------------------------------------------- # Functions ----------------------------------------------- Function get-Property() { param( [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.IO.FileInfo]$Path, [parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [ValidateSet('ProductCode','ProductVersion','ProductName')] [string]$Property ) Process { try { #read property from msi $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0)) $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'" $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query)) [void]($View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)) $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null) $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1) #commit db and close view [void]($MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)) [void]($View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)) $MSIDatabase = $null $View = $null #return value return $Value } catch { Write-Warning -Message $_.Exception.Message ; break } } End { #run garbage collection & release comobject [System.Runtime.InteropServices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null [System.GC]::Collect() } } # End Functions ------------------------------------------- # processing ---------------------------------------------- #declaration ---- [string]$strSearchDirectory = '<MYDIR>' [string]$strProdCode = $null [string]$strProdName = $null $AllProdCodes = @{} #read msi and collect productcodes Get-ChildItem -Path $strSearchDirectory -Recurse -Filter *.msi | ForEach-Object { #read productcode [string]$strProdName = get-Property -Path $_ -Property ProductName [string]$strProdName = "$_, $strProdName" [string]$strProdCode = get-Property -Path $_ -Property ProductCode If(!($AllProdCodes.ContainsKey($strProdName))) { $AllProdCodes.Add($strProdName,$strProdCode) } else { $i++ [string]$strProdName = [string]$strProdName + "_MULTIPLEVALUE_$i" $AllProdCodes.Add($strProdName,$strProdCode) } [string]$strProdName = $null [string]$strProdCode = $null } #output to gridview $AllProdCodes.Keys | Select-Object @{l='MSI,PackageName';e={$_}},@{l='ProductCode';e={$AllProdCodes.$_}} | Out-GridView