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

This Area is Widget-Ready

You can place here any widget you want!

You can also display any layout saved in Divi Library.

Let’s try with contact form: