Compare-PSObject
Haben Sie nicht auch immer mal wieder den Bedarf, zwei Objekte in der PowerShell miteinander zu vergleichen? Mit passiert das Regelmäßig, z.B.: bei Receive Connectoren, Send-Connectoren, AD-Objekten etc. Für den Vergleich von Dateien gibt es FC.EXE, Windiff und andere Tools aber mit dem PowerShell Commandlet "Compare-Object" bin ich nicht zurecht gekommen. Daher habe ich mit ein Compare-PSObject selbst geschrieben.
Compare-Object
Wie schon im Vorwort geschrieben, möchte ich z.B. zwei Einstellungen miteinander vergleichen. Nehmen wir mal an, es gibt zwei Exchange Server und ich möchte die Einstellungen zweier Receive Connectoren vergleichen. Ich hole mit also die Connectoren in eine Variable und vergleiche sie:
$RC=Get-ReceiveConnector Compare-Object $RC[0] $RC[1] InputObject SideIndicator ----------- ------------- EX01\Default Frontend EX01 => EX01\Default EX01 <= Compare-Object $b[0] $b[1] -Property * Compare-Object : Wildcard characters are not allowed in "*".
Vielleicht habe ich mich zu ungeschickt angestellt, aber der erste Vergleich sag mir einfach, dass die Objekte irgendwie unterschiedlich sind und der Versuch den Vergleich auf Properties zu stellen, scheitert ebenfalls.
- Compare-Object
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/compare-object?view=powershell-7.3
Coding
Mein Wunsch wäre eigentlich gewesen, Dass ich als Ergebnis eine Tabelle bekomme, in der die erste Spalte der Name des Property ist und die nächste Spalte den Vergleich und optional die Werte in Spalte 3 und 4. Ich würde die beiden zu vergleichenden Objekte einfach als Parameter oder Optional in der Pipeline übergeben und vielleicht noch die Parameter filtern. Die Anforderungen sind eigentlich gar nicht so groß und wenn Sie an die Konsole denken, an der Sie von einem Befehl dann zwei Objekte bekommen.
In der ersten Stufe ist es eine einfache PS1-Datei, die Sie einfach aufrufen und entweder zwei Objekte per Pipeline oder als Parameter an das Skript übergeben. Das Skript sammelt dann erst von beiden Objekten alle Properties ein und sammelt danach zu jedem Property die beiden Werte ein, vergleich Sie und schreibt sie in CustomObject.
Beispiel
Ein einfaches Beispiel ist der vergleich von zwei Prozessen. Zuerst holen ich mit die Liste der Prozesse um dann die ersten beiden zu vergleichen.
PS C:\> (get-process)[1..2] | .\Compare-PSObject.ps1 Property SideIndicator 1 -------- ------------- - PrivilegedProcessorTime <> 00:00:00.5312500 NonpagedSystemMemorySize64 <> 13720 PagedSystemMemorySize <> 197160 StandardOutput == MainModule <> System.Diagnostics.ProcessModule (ai.exe) PrivateMemorySize64 <> 20320256 SessionId == 1 PagedSystemMemorySize64 <> 197160 NonpagedSystemMemorySize <> 13720 MachineName == . MinWorkingSet == 204800 Handle <> 3696 Id <> 89764 HasExited == False SafeHandle <> Microsoft.Win32.SafeHandles.SafeProcessHandle WorkingSet64 <> 16883712 StandardInput == ExitCode == StartTime <> 31.10.2023 13:58:08 PriorityBoostEnabled == True PeakVirtualMemorySize <> 198295552 ProcessName <> ai PeakWorkingSet <> 35028992 PrivateMemorySize <> 20320256 SynchronizingObject == PeakWorkingSet64 <> 35028992 Site == PeakPagedMemorySize64 <> 21282816 MaxWorkingSet == 1413120 MainWindowHandle == 0 EnableRaisingEvents == False ExitTime == MainWindowTitle == Threads <> {88080, 8896, 70040, 74420…} PeakVirtualMemorySize64 <> 2203516518400 HandleCount <> 185 StandardError == PagedMemorySize64 <> 20320256 WorkingSet <> 16883712 PagedMemorySize <> 20320256 PeakPagedMemorySize <> 21282816 Modules <> {System.Diagnostics.ProcessModule (ai.exe), System.Diagnostics.ProcessModule … VirtualMemorySize <> 189902848 StartInfo == VirtualMemorySize64 <> 2203508125696 TotalProcessorTime <> 00:00:01.8906250 Responding == True UserProcessorTime <> 00:00:01.3593750 BasePriority == 8 PriorityClass == Normal Container == ProcessorAffinity == 255
Natürlich kann ich mit einem "Select" dazwischen auch Felder filtern und die Ausgabe kann in "Out-GridView" ebenfalls betrachtet werden:
Ich kann sogar drei und mehr Objekte gegenüberstellen.
Der Vergleich erfolgt aber immer nur zwischen den beiden ersten Objekten.
Genauso gut kann ich zwei Dateien vergleichen. Wobei hier dann darauf zu achten ist, dass die Objekte und nicht der Inhalt verglichen wird.
PS C:> (get-item c:\windows\*.*)[2..4] | .\Compare-PSObject.ps1 | ft
Auch diese Ausgabe können Sie über Out-Gridview anzeigen oder natürlich über die Pipeline an andere PowerShell-Funktionen weitergeben.
Ein letztes Beispiel ist der Vergleich von Receive Connectoren in Exchange OnPremises, die ich manuell oder mit Copy-ReceiveConnector von einem Server auf die anderen Server übertragen habe. Ich hole mit die Connectoren einer Umgebung und filtere sie so, dass ich nur die bekomme, die auch gleich sein sollen und lasse mir einfach nur die Unterschiede anzeigen.
Get-ReceiveConnector ` | Where-Object {$_.id -like "*\Default frontend*"} ` | .\compare-psobject.ps1 -ExludeEqual
Die Ausgabe können Sie natürlich sowohl als Tabelle oder mit Out-GridView anzeigen.
Einschränkungen
Es ist einfach, Binär-Dateien 1:1 zu vergleichen. Selbst Textdateien können zumindest zeilenweise verglichen werden und mit etwas Unschärfe auch Leerzeilen oder zusätzliche Zeilen erkannt werden. Bei einem PSObject ist nicht nicht immer sichergestellt ,dass die Eigenschaften "Vergleichbar" sind. Das können ihrerseits ja wieder Objekte sein mit weiteren Eigenschaften.
Das Skript nutzt die PowerShell-Konvertierung von Properties in einen String und vergleich diese Texte. Für einfache Typen funktioniert das recht gut aber nicht für komplexe oder selbstdefinierte Typen. Manchmal wird hier dann einfach der Typename und nicht der Inhalt zurückgegeben, was einen Vergleich unmöglich macht. Diese Unterschiede werden nicht erkannt.
Wenn das Property ein Array ist, dann kann schon eine andere Sortierung dafür sorgen, dass der resultierende String unterschiedlich ist und das Skript vielleicht irrtümlich einen Unterschied meldet, obwohl im Grund die Inhalte identisch sind. Das passiert leider bei den "RemoteIPRanges" von Connectoren schon mal.
Zusammenfassung
Mit dem Skript habe ich mein Problem gelöst, welches ich mit Compare-Object nicht lösen konnte. Ich kann zwei oder mehr Objekte an das Skript übergeben, es sammelt alle Properties zusammen und vergleicht jedes Objekte anhand jedem einzelnen Property.