Objektumok tulajdonságainak kiolvasása (Get-Property)

Lehetnek olyan objektumaink, amik elég mély tulajdonság-hierarchiát tartalmaznak, például REST API hívások eredményei gyakran ilyenek. Viszont ezek az objektumok eléggé változékony szerkezetet mutathatnak: egy service desk rendszerből lekérdezett kérés objektuma tartalmazhat különböző típusú és számú elemet, így ha „strict mode” kompatibilis módon akarjuk megvizsgálni egyes mélyen fekvő tulajdonságok jelenlétét és értékét, akkor elég hosszú IF feltétel-láncot kell használjunk. Nézzünk egy bonyolultabb hashtáblát:

$ticket = @{

    Number = "REQ1234"

    Approver = @{

        Name = "Tibor Soós"

        EmployeeId = 99123

        Manager = @{

            Name = "Ferenc Főnök"

            EmployeeID = 87871

        }

    }

    items = @(

        @{

            Number = "RITM332211"

            Description = "Valami jó"

            ItemCost = @{

                Price = 1234

                Currency = "HUF"

            }

        }, @{

            Number = "RITM332212"

            Description = "Ingyenes"

            ItemCost = $null

        }

    )

}

Látható, hogy az első elem az items-ben rendelkezik költséggel (ItemCost), de a második elem nem. Ha ezt egy szkriptben próbáljuk megvizsgálni, ráadásul strict mode-dal (lásd 2.4.6.2 Lépésenkénti végrehajtás és szigorú változókezelés (set-psdebug, set-strictmode) ), akkor hibát kapunk:

PS C:\> Set-StrictMode -Version latest

PS C:\> $ticket.items[1].ItemCost.Price

The property 'Price' cannot be found on this object. Verify that the property

 exists.

At line:1 char:1

+ $ticket.items[1].ItemCost.Price

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : NotSpecified: (:) [], PropertyNotFoundExceptio

   n

    + FullyQualifiedErrorId : PropertyNotFoundStrict

 Ahhoz, hogy elkerüljük az ilyen jellegű hibát és tűrje a szkriptünk a szigorú üzemmódot ezt az If feltételrendszert kellene használjuk:

PS C:\> if($ticket.ContainsKey('items') -and $ticket.items.count -ge 2 -and $ticket.items[1] -and $ticket.items[1].ContainsKey('ItemCost') -and $ticket.items[1].ItemCost -and $ticket.items[1].ItemCost.ContainsKey('Price')){$ticket.items[1].ItemCost.Price}

Ez ugye nem ad vissza semmit, mert ennek az elemnek nincs ára, de ellenpróbaként nézzük meg az első elem árát hogyan tudnánk kiolvasni:

PS C:\> if($ticket.ContainsKey('items') -and $ticket.items.count -ge 1 -and $ticket.items[0] -and $ticket.items[0].ContainsKey('ItemCost') -and $ticket.items[0].ItemCost -and $ticket.items[0].ItemCost.ContainsKey('Price')){$ticket.items[0].ItemCost.Price}

1234

Nem túl egyszerű, és ezek a kifejezések nehezen módosíthatók. Na pont ezért csináltam a Get-Property függvényt:

function Get-Property {

<#

.Synopsis

   Returns the value of object(s) that is available under the path(s) specified.

.DESCRIPTION

   This function gets the value of a property or key under a hierarchy of properties and keys and/or under the index of collections.

.EXAMPLE

    Get-ChildItem C:\Windows\system32\*.exe | Get-Property -PropPath "VersionInfo.CompanyName", "PSDrive.Provider.Name" -ObjectNameProperty Name

 

    Gets the VersionInfo.CompanyName and PSDrive.Provider.Name properties of all EXE files under c:\windows\system32 folder. The result will have the Name of each files under the Object column.

.EXAMPLE

   $h = @{Name = "MyHashTable"; Array = @{n = 'First'; data = 'Text1'}, @{n = 'Second'; data = 'Text2'}}; Get-Property -Object $h -PropPath 'Array[1].data' -ValueOnly

 

   In this example we get the 'Text2' from hashtable $h. In this case the -PropPath contains an index as well and because we used the -ValueOnly switch only the value of 'data' is returned.

.INPUTS

   hashtables or psobjects

.OUTPUTS

   Collection of custom objects having an Object, PropertyPath, PropertyExists and Value properties, or only the value of the addressed property if the -ValueOnly switch is used.

#>

[cmdletbinding()]

param(

    # Input object to get its property

    [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object[]] $Object,

    # Path(s) of the value to query. This path is the full path to the value, including property names and key names and indexes.

    [Parameter(Mandatory = $true)] [string[]] $PropPath,

    # Property or key that can be used to reference the object. If not specified then the resullt of the ToString() method will be used.

    [string] $ObjectNameProperty,

    # Return only the addressed property/key value, not the complete custom object.

    [switch] $ValueOnly

)

 

process{

    foreach($obj in $Object){

        if($null -eq $obj){

            continue

        }

 

        foreach($pp in $PropPath){

            $props = $pp -split "\.|(?<=.)(?=\[)"

 

            $currentObj = $obj

 

            $exists = $true

 

            foreach($p in $props){

                if($p -match "\[(\d+)\]"){

                    $index = [int] $Matches[1]

                }

                elseif($p -match '\[["'']([^"'']+)["'']\]'){

                    $p = $p -replace '\[["'']([^"'']+)["'']\]', '$1'

                    $index = $null

                }

                else{

                    $index = $null

                    if($p -match '^([''"]).*\1$'){

                        $p = $p -replace "^.(.*).$", '$1'

                    }

                }

 

                if($null -ne $index){

                    if($currentObj.count -gt $index){

                        $currentObj = $currentObj[$index]

                    }

                    else{

                        $currentObj = $null

                        $exists = $false

                        break

                    }                   

                }

                elseif($null -ne $currentObj -and ((($currentObj -is [hashtable] -or $currentObj -is [System.Collections.Specialized.OrderedDictionary]) -and $currentObj.containskey($p)) -or ($currentObj.psobject.properties.count -and $currentObj.psobject.properties.name -contains $p))){

                    $currentObj = $currentObj.$p

                    if($null -eq $currentObj){

                        $exists = $false

                        break

                    }

                }

                else{

                    $exists = $false

                    $currentObj = $null

                    break

                }

            }

 

            if($ValueOnly){

                $currentObj

            }

            else{

                [pscustomobject]@{

                    Object = if($ObjectNameProperty){$obj.$ObjectNameProperty}else{$obj.tostring()}

                    PropertyPath = $pp

                    PropertyExists = $exists

                    Value = if($exists){$currentObj}

                }

            }

        }

    }

}

}

Nézzük ennek a használatát a fenti problémára:

PS C:\> Get-Property -Object $ticket -PropPath "items[1].ItemCost.Price"

 

Object                       PropertyPath            PropertyExists Value

------                       ------------            -------------- -----

System.Collections.Hashtable items[1].ItemCost.Price          False

A visszatérési érték egy egyedi objektum. Az első oszlopban alaphelyzetben az objektum sztring reprezentációja szerepel. A második oszlopban a tulajdonság elérési útja, a harmadik oszlop egy igaz/hamis mező, ami jelzi, hogy létezik-e ez az elérési út, a negyedik meg a tulajdonság értéke. Ez a második elemre (1-es index) természetesen ez üres.

Az első elemre már kicsit más az eredmény:

PS C:\> Get-Property -Object $ticket -PropPath "items[0].ItemCost.Price"

 

Object                       PropertyPath            PropertyExists Value

------                       ------------            -------------- -----

System.Collections.Hashtable items[0].ItemCost.Price           True  1234

Itt már létezik ez az elérési út és van érték is. Ha csak az értékre vagyunk kíváncsiak, akkor használhatjuk a -ValueOnly kapcsolót:

PS C:\> Get-Property -Object $ticket -PropPath "items[0].ItemCost.Price" -ValueOnly

1234

 Ha az első oszlopban valami értelmesebb információt szeretnénk látni, akkor hivatkozhatunk egy olyan tulajdonságra, ahol valami értelmesebb azonosító van, pl. a jegynek a száma (Number):

PS C:\> Get-Property -Object $ticket -PropPath "items[0].ItemCost.Price" -ObjectNameProperty Number

 

Object  PropertyPath            PropertyExists Value

------  ------------            -------------- -----

REQ1234 items[0].ItemCost.Price           True  1234

A -PropPath-nak nem is csak egy, hanem több elérési utat is adhatunk:

PS C:\> Get-Property -Object $ticket -PropPath Approver.Manager.Name, items[1].ItemCost.Price, items[0].ItemCost.Price, items[2], items[0].ItemCost

 

Object                       PropertyPath            PropertyExists Value

------                       ------------            -------------- -----

System.Collections.Hashtable Approver.Manager.Name             True Ferenc...

System.Collections.Hashtable items[1].ItemCost.Price          False

System.Collections.Hashtable items[0].ItemCost.Price           True 1234

System.Collections.Hashtable items[2]                         False

System.Collections.Hashtable items[0].ItemCost                 True {Price...

Végig szigorú módban voltam, de mégsem kaptam egyszer sem hibát, annak ellenére, hogy hivatkoztam nemlétező indexre is (items[2]), a nemlétező tulajdonság mellett is.

Ez a függvény támogatja a hashtábla kulcsainak indexszerű hivatkozását is:

PS C:\> Get-Property -Object $ticket -PropPath "['items'][0]['ItemCost']['Price']"

 

Object                       PropertyPath                      PropertyExists

------                       ------------                      --------------

System.Collections.Hashtable ['items'][0]['ItemCost']['Price']           True

Természetesen támogatja ez a függvény a csőből való táplálást is:

PS C:\> Get-ChildItem C:\Windows\*.exe | Get-Property -PropPath "VersionInfo.CompanyName", "PSDrive.Provider.Name" -ObjectNameProperty Name

 

Object       PropertyPath            PropertyExists Value

------       ------------            -------------- -----

bfsvc.exe    VersionInfo.CompanyName           True Microsoft Corporation

bfsvc.exe    PSDrive.Provider.Name             True FileSystem

explorer.exe VersionInfo.CompanyName           True Microsoft Corporation

explorer.exe PSDrive.Provider.Name             True FileSystem

HelpPane.exe VersionInfo.CompanyName           True Microsoft Corporation

HelpPane.exe PSDrive.Provider.Name             True FileSystem

hh.exe       VersionInfo.CompanyName           True Microsoft Corporation

hh.exe       PSDrive.Provider.Name             True FileSystem

notepad.exe  VersionInfo.CompanyName           True Microsoft Corporation

notepad.exe  PSDrive.Provider.Name             True FileSystem

regedit.exe  VersionInfo.CompanyName           True Microsoft Corporation

regedit.exe  PSDrive.Provider.Name             True FileSystem

splwow64.exe VersionInfo.CompanyName           True Microsoft Corporation

splwow64.exe PSDrive.Provider.Name             True FileSystem

winhlp32.exe VersionInfo.CompanyName           True Microsoft Corporation

winhlp32.exe PSDrive.Provider.Name             True FileSystem

Itt tehát több objektumra futtattuk a függvényt, objektumonként meg több tulajdonságra.



Word To HTML Converter