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.

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.

Compare-PSobject.ps1.txt

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.

Weitere Links