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.