Gyakran adódik olyan helyzet, hogy nem egy konkrét tulajdonságra akarunk keresni, hanem két hasonló objektumot akarunk összehasonlítani és feltárni a tulajdonságaik közti különbségeket. Ugyan létezik a Compare-Object cmdlet, de ez a nevével ellentétben nem objektumokat, hanem gyűjteményeket hasonlít össze. Kellene készíteni egy Compare-ObjectProperty függvényt!
A nehézség abban rejlik, hogy hogy itt nem csak háromfajta eset lehetséges az összehasonlítás során (<=, ==, =>), hanem több is, ráadásul ezek nem is teljesen egyértelműek. Természetesen a két objektum azonos nevű tulajdonságai lehetnek egyformák (==) és jelöljük a nyilakkal (<=, =>) azt, ha csak az egyikben vagy csak a másikban van meg az adott tulajdonság. Viszont, ha mindkettőben megvan ugyanaz a tulajdonság, csak más az értékük, akkor azt jelölhetjük ezzel: <>.
Kezdjük tehát összerakni ezt az összehasonlító függvényt!
function Compare-ObjectProperty1 {
param(
$referenceobject,
$differenceobject
)
$allprops = @()
$rp = @()
$dp = @()
$rs = "r:"
$ds = "d:"
if($referenceobject){
$rp = $referenceobject.psobject.Properties |
Select-Object -ExpandProperty Name
$allprops = @($rp)
$rs += $referenceobject.tostring()
}
if($differenceobject){
$dp = $differenceobject.psobject.Properties |
Select-Object -ExpandProperty Name
foreach($p in $dp){
if($allprops -notcontains $p){
$allprops += $p
}
}
$ds += $differenceobject.tostring()
}
foreach($p in ($allprops | Sort-Object)){
$ra = $referenceobject.$p
$da = $differenceobject.$p
$rtype = if($null -eq $ra){"NULL"}else{$ra.gettype().fullname}
$dtype = if($null -eq $da){"NULL"}else{$da.gettype().fullname}
if($null -eq $ra -and $null -ne $da){
$equal = "=>"
}
elseif($null -ne $ra -and $null -eq $da){
$equal = "<="
}
elseif($rtype -ne $dtype){
$equal = "<>"
}
elseif($ra -is [collections.ilist]){
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da){
$equal = "<>"
}
else{
$equal = "=="
}
}
elseif($ra -is [collections.idictionary]){
$ra = @($ra.getenumerator())
$da = @($da.getenumerator())
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da -Property Key, Value){
$equal = "<>"
}
else{
$equal = "=="
}
}
else{
$equal = if($ra -eq $da){"=="}else{"<>"}
}
[pscustomobject]@{
Property = $p
Equal = $equal
$rs = $ra
$ds = $da
}
}
}
Két paraméterünk van egyelőre, a $referenceobject és a $differenceobject. Az $allprops-ban gyűjtöm az összes tulajdonságnevet, az $rp-ben a referencia tulajdonságneveit, a $dp-ben a differenciáét. Miután a végeredményből egy táblázatot szeretnék, az egyik oszlopnévben a differencia objektumra akarok hivatkozni, ennek az oszlopnak a neve „r:”-al kezdődik, hasonlóan a másik „d:”-al, ezeket az $rs és $ds változókba rakom, amiket majd megtoldom az adott objektumok sztringesített változatával.
A tulajdonságnevek gyűjtögetése utáni foreach ciklusban végig megyek a sorba rakott összes tulajdonságon és kitalálom, hogy vajon az adott tulajdonságok értékei a két objektumban vajon milyen viszonyban vannak egymással a fenti négy lehetőség közül. Az egyformaság egyik döntő kérdése, hogy vajon egyforma típusúak-e a tulajdonság értékei? Mert ha nem, akkor nem is nagyon kell tovább vizsgálgatni őket, hanem az eredmény „<>” lesz. Így az egyes típusokat az $rtype és $dtype változókba beteszem, itt külön eset, ha valamelyik $null, hiszen a semminek nincs tulajdonsága, ezért erre én „NULL”-t teszek oda be.
Előtte még attól függően, hogy a referencia vagy differencia objektum üres-e generálom a $equal változóba a „=>” vagy „<=” nyilakat. A típusok különbözőségének vizsgálata után már a következő elseif ágban már elég a referencia objektum típusára kitételeket tenni, hiszen a másik objektum is, ha már idáig eljutottunk, akkor ugyanolyan. Ha tehát egyik objektum tömbszerű ([collections.ilist]), akkor a Compare‑Object-el határozhatom meg az egyformaságukat. Ha a Compare-Object-nek van kimenete, akkor az azt jelenti, hogy nem egyforma a kettő, ha nincs kimenet, akkor egyforma.
Hasonlóan ehhez, a következő elseif-ben, ha szótárszerűek ([collections.idictionary]) az objektumok, akkor előbb tömbbé alakítom a szótárakat a GetEnumerator() metódussal és utána már szintén a Compare-Object-el hasonlítom őket össze.
A legvégén már a sima -eq operátorral hasonlítom össze őket. Kimenetként egyedi objektumokat bocsátok ki, melynek első tulajdonsága maga a vizsgált tulajdonság neve ($p), majd az összehasonlítás eredménye ($equal), majd a referencia objektum, végül a differencia objektum adott tulajdonságának értéke.
Nézzük meg működés közben is a függvényt! Ehhez kreálok két egyéni objektumot, amely rendelkezik mindenféle típusú tulajdonsággal:
$o1 = [pscustomobject]@{
értékegyforma = 1
értékkülönböző = "bla"
hashegyforma
= @{
key1 = "bla"
Key2 = "BlaBla"
}
hashkülönböző = @{
keyegy = 1
keykettő = 2
}
tömbegyforma
= 1,2,3
tömbkülönböző = 'a', 'b'
kétfajtasemmi = ""
semmi = $null
csakitt = "szöveg"
}
$o2 = [pscustomobject]@{
értékegyforma = 1
értékkülönböző = "kakukk"
hashegyforma
= @{
key1 = "bla"
Key2 = "BlaBla"
}
hashkülönböző = @{
keyegy = 5
keyhárom
= 3
}
tömbegyforma
= 1,2,3
tömbkülönböző = 'c', 'b'
kétfajtasemmi = @()
semmi = $null
}
Van itt minden, mint a búcsúban! Egyforma és különböző érték típusú tulajdonság, egyforma és különböző hashtábla, egyforma és különböző tömb, különböző és egyforma „semmi” és végül egy olyan tulajdonság, ami csak a referencia objektumban van. Ezeket összehasonlítva ezt kapjuk:
PS C:\> Compare-ObjectProperty1 -referenceobject $o1 -differenceobject $o2
Property Equal r: d:
-------- ----- -- --
csakitt <= szöveg
értékegyforma == 1 1
értékkülönböző <> bla kakukk
hashegyforma == {key1, Key2} {key1, Key2}
hashkülönböző <> {keykettő, keyegy} {keyegy, keyhárom}
kétfajtasemmi <> {}
semmi ==
tömbegyforma == {1, 2, 3} {1, 2, 3}
tömbkülönböző <> {a, b} {c, b}
Az eredmény a várakozásunknak megfelelő lett! Mivel lehetne ezt még továbbfejleszteni? Vegyünk fel további paramétereket, amelyekkel lehet szűrni az eredményeket:
$includeequal : alapban az egyformákat nem jelenítjük meg, csak ha ezt a kapcsolót használjuk
$excludedifferent : alapban a különbözőket jelenítjük meg, de ezzel a kapcsolóval elrejthetjük azokat
$property : csak mely tulajdonságok között keressen
$exclude : mely tulajdonságokat hagyjon ki
Különösebben nem is magyarázom a 2. verziót, nézzük a függvényt:
function Compare-ObjectProperty2 {
param(
$referenceobject,
$differenceobject,
[switch] $includeequal,
[switch] $excludedifferent,
[string[]] $property = "*",
[string[]] $exclude
)
$allprops = @()
$rp = @()
$dp = @()
$rs = "r:"
$ds = "d:"
if($referenceobject){
$rp = $referenceobject.psobject.Properties |
Select-Object -ExpandProperty Name
$allprops = @($rp)
$rs += $referenceobject.tostring()
}
if($differenceobject){
$dp = $differenceobject.psobject.Properties |
Select-Object -ExpandProperty Name
foreach($p in $dp){
if($allprops -notcontains $p){
$allprops += $p
}
}
$ds += $differenceobject.tostring()
}
$allprops = $allprops | Where-Object {
$pp = $_
($property | Where-Object {$pp -like $_}) -and !($exclude | Where-Object {$pp -like $_})
} | Sort-Object
foreach($p in ($allprops | Sort-Object)){
$ra = $referenceobject.$p
$da = $differenceobject.$p
$rtype = if($null -eq $ra){"NULL"}else{$ra.gettype().fullname}
$dtype = if($null -eq $da){"NULL"}else{$da.gettype().fullname}
if($null -eq $ra -and $null -ne $da){
$equal = "=>"
}
elseif($null -ne $ra -and $null -eq $da){
$equal = "<="
}
elseif($rtype -ne $dtype){
$equal = "<>"
}
elseif($ra -is [collections.ilist]){
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da){
$equal = "<>"
}
else{
$equal = "=="
}
}
elseif($ra -is [collections.idictionary]){
$ra = @($ra.getenumerator())
$da = @($da.getenumerator())
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da -Property Key, Value){
$equal = "<>"
}
else{
$equal = "=="
}
}
else{
$equal = if($ra -eq $da){"=="}else{"<>"}
}
if((!$excludedifferent -and $equal -ne '==') -or ($includeequal -and $equal -eq '==')){
[pscustomobject] @{
Property = $p
Equal = $equal
$rs = $referenceobject.$p
$ds = $differenceobject.$p
}
}
}
}
Nézzük, ez hogyan működik:
PS C:\>
Compare-ObjectProperty2 -referenceobject $o1 -differenceobject $o2
Property Equal r: d:
-------- ----- -- --
csakitt <=
szöveg
értékkülönböző
<> bla kakukk
hashkülönböző <>
{keykettő, keyegy} {keyegy, keyhárom}
kétfajtasemmi <> {}
tömbkülönböző <>
{a, b} {c, b}
Ebben a 2. verzióban tehát alapban csak a különbözőségek látszanak, vegyük hozzá az egyformákat is:
PS C:\> Compare-ObjectProperty2 -referenceobject $o1 -differenceobject $o2 -inc
ludeequal
Property Equal r: d:
-------- ----- -- --
csakitt <= szöveg
értékegyforma == 1 1
értékkülönböző <> bla kakukk
hashegyforma == {key1, Key2} {key1, Key2}
hashkülönböző <> {keykettő, keyegy} {keyegy, keyhárom}
kétfajtasemmi <> {}
semmi ==
tömbegyforma == {1, 2, 3} {1, 2, 3}
tömbkülönböző <> {a, b} {c, b}
Szűkítsük le az eredményeket az „é” vagy „t” betűkkel kezdődő tulajdonságokra, de azért a dupla „k” betűt tartalmazókat vegyük ki:
PS C:\> Compare-ObjectProperty2 -referenceobject $o1 -differenceobject $o2 -inc
ludeequal -property é*, t* -exclude *kk*
Property Equal r: d:
-------- ----- -- --
értékegyforma == 1 1
tömbegyforma == {1, 2, 3} {1, 2, 3}
tömbkülönböző <> {a, b} {c, b}
A végső verzióban vezessünk be egy újabb szűrési lehetőséget, ahol elrejtjük a „semmit” tartalmazó sorokat. Semmi szerepelhet az egyik objektumnál, vagy mindkettőnél, vegy lehet olyan is, hogy pont a semmiket tartalmazó sorokat akarjuk megjeleníteni. Itt a „semmi” nem csak a tényleges $null-t, hanem az üres sztringet, üres tömböt, üres hashtáblát és a [system.dbnull]-t is jelentheti. Azaz az új paraméter legyen az, hogy $hide, ennek lehetséges értékei lehetnek: 'None', 'Empty', 'NonEmpty', 'BothEmpty', alapban az értéke ’None’, azaz nem rejtünk el semmit. Ezt a kiterjesztett „semmit” ki kell számolni, úgyhogy a függvényünk ezekkel a részekkel bővül, plusz még kiszűröm az AliasProperty típusú tulajdonságokat, valamint a „=>” és „<=” eredményeket rendelem azokhoz az esetekhez, ahol az egyik tulajdonság ezen kiterjesztett „semmi”, de a másik nem az:
function Compare-ObjectProperty {
param(
$referenceobject,
$differenceobject,
[switch] $includeequal,
[switch] $excludedifferent,
[string[]] $property = "*",
[string[]] $exclude,
[ValidateSet('None','Empty','NonEmpty','BothEmpty')] $hide = 'None'
)
$allprops = @()
$rp = @()
$dp = @()
$rs = 'r:'
$ds = 'd:'
if($null -ne $referenceobject){
$rp = $referenceobject.psobject.Properties |
Where-Object {$_.membertype -ne 'AliasProperty'} |
Select-Object -ExpandProperty Name
$allprops = @($rp)
$rs = "r:" + $referenceobject.tostring()
}
if($null -ne $differenceobject){
$dp = $differenceobject.psobject.Properties |
Where-Object {$_.membertype -ne 'AliasProperty'} |
Select-Object -ExpandProperty Name
foreach($p in $dp){
if($allprops -notcontains $p){
$allprops += $p
}
}
$ds = "d:" + $differenceobject.tostring()
}
$allprops = $allprops | Where-Object {
$pp = $_
($property | Where-Object {$pp -like $_}) -and !($exclude | Where-Object {$pp -like $_})
} | Sort-Object
foreach($p in $allprops){
$ra = $referenceobject.$p
$da = $differenceobject.$p
$raempty = $null -eq $ra -or
'' -eq $ra -or
(($ra -is [collections.ilist] -or $ra -is [Collections.IDictionary]) -and $ra.count -eq 0) -or
$ra -is [System.DBNull]
$daempty = $null -eq $da -or
'' -eq $da -or
(($da -is [collections.ilist] -or $da -is [Collections.IDictionary]) -and $da.count -eq 0) -or
$ra -is [System.DBNull]
$rtype = if($null -eq $ra){"NULL"}else{$ra.gettype().fullname}
$dtype = if($null -eq $da){"NULL"}else{$da.gettype().fullname}
if($hide -eq 'Empty' -and ($raempty -or $daempty)){
continue
}
elseif($hide -eq 'BothEmpty' -and $raempty -and $daempty){
continue
}
elseif($hide -eq 'NonEmpty' -and (!$raempty -or !$daempty)){
continue
}
if(($raempty -and !$daempty) -or ($dp -contains $p -and $rp -notcontains $p)){
$equal = "=>"
}
elseif((!$raempty -and $daempty) -or ($rp -contains $p -and $dp -notcontains $p)){
$equal = "<="
}
elseif($rtype -ne $dtype){
$equal = "<>"
}
elseif($ra -is [collections.ilist]){
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da){
$equal = "=="
}
else{
$equal = "<>"
}
}
elseif($ra -is [collections.idictionary]){
$ra = @($ra.GetEnumerator())
$da = @($da.GetEnumerator())
if(Compare-Object -ReferenceObject $ra -DifferenceObject $da -Property Key, Value){
$equal = "<>"
}
else{
$equal = "=="
}
}
else{
$equal = if($ra -eq $da){"=="}else{"<>"}
}
if((!$excludedifferent -and $equal -ne '==') -or ($includeequal -and $equal -eq '==')){
[pscustomobject] @{
Property = $p
Equal = $equal
$rs = $referenceobject.$p
$ds = $differenceobject.$p
}
}
}
}
És akkor nézzünk néhány példát ennek a paraméternek a használatára is:
PS C:\> Compare-ObjectProperty -referenceobject $o1 -differenceobject $o2 -incl
udeequal -hide Empty
Property Equal r: d:
-------- ----- -- --
értékegyforma == 1 1
értékkülönböző <> bla kakukk
hashegyforma == {key1, Key2} {key1, Key2}
hashkülönböző <> {keykettő, keyegy} {keyegy, keyhárom}
tömbegyforma <> {1, 2, 3} {1, 2, 3}
tömbkülönböző == {a, b} {c, b}
Itt a „-hide Empty” opciót választottam, és így az eredményből ki is maradtak azok a sorok, ahol az egyik vagy másik tulajdonság „semmi” volt. A következő példában meg pont azokat rejtem el, ahol egyik sem „semmi”:
PS C:\> Compare-ObjectProperty -referenceobject $o1 -differenceobject $o2 -incl
udeequal -hide NonEmpty
Property Equal r: d:
-------- ----- -- --
kétfajtasemmi <> {}
semmi ==