Gyakran találkozunk olyan objektumokkal, amelyek nagyon sok tulajdonsággal rendelkeznek. Ilyenek például a WMI objektumok vagy az ActiveDirectory-ban található objektumok. A sok tulajdonság között nehéz észrevenni, hogy például melyekben található meg a számítógépünk neve vagy melyekben találhatók e-mailcím jellegű adatok. Készítsünk tehát egy olyan függvényt, ami segít nekünk a tulajdonságok közti keresésben!
Első próbálkozásként ezt raktam össze:
function Search-Property1 {
param(
[string] $pattern = ".",
[Parameter(ValueFromPipeline = $true)] $object
)
process{
$object.psobject.properties |
Where-Object {$_.name -match $pattern -or $_.value -match $pattern} |
Select-Object -Property @{n="Object"; e={$object.tostring()}}, Name, Value
}
}
Két paraméterem van: $pattern az a minta, amit keresünk és a $object amiben keresünk. A $pattern alaphelyzet szerinti értéke „.”, ami azt jelenti, hogy csak azokat a tulajdonságokat kapom meg, amelyekben legalább 1 bármilyen karakter van. Az $object érkezhet a csövön keresztül is, így a függvényen belül a process blokkot kell alkalmazni.
Itt az $object tulajdonságain keresztül ($object.psobject.properties) egy Where-Object-tel szűröm, hogy melyek azok a tulajdonságok, ahol vagy a tulajdonság neve vagy az értéke illeszkedik a megadott mintára. Ha találok ilyet, akkor abból csinálok egy egyedi objektumot, ami első oszlopként tartalmazza magának az objektumnak a szöveggé konvertált változatát, majd a tulajdonság nevét és végül a tulajdonság értékét:
PS C:\>
get-date | Search-Property1 -pattern Date
Object Name Value
------ ---- -----
2023. 01.
21. 13:44:45 DisplayHint DateTime
2023. 01.
21. 13:44:45 DateTime 2023. január
21., szombat 13:44:45
2023. 01.
21. 13:44:45 Date
2023. 01. 21. 0:00:00
A fenti példában a Date mintára kerestem a get-date által kibocsátott objektumban. Látszik, hogy mind a tulajdonságok nevei között, mind az értékei között megtalálta a mintát.
Gyorsan bővítsük a függvényünket 3 kapcsoló paraméterrel!
function Search-Property2 {
param(
[string] $pattern = ".",
[Parameter(ValueFromPipeline = $true)] $object,
[switch] $SearchInPropertyNames,
[switch] $NotSearchInValues,
[switch] $Literal
)
begin{
if($Literal){
$pattern = [regex]::Escape($pattern)
}
}
process{
$object.psobject.properties |
Where-Object {($SearchInPropertyNames -and $_.name -match $pattern) -or (!$NotSearchInValues -and $_.value -match $pattern)} |
Select-Object -Property @{n="Object"; e={$object.tostring()}}, Name, Value
}
}
A három kapcsoló közül az utolsó a $Literal. Ha ezt használjuk, akkor a begin blokkban átalakítom a mintát annak eszképelt változatára, azaz szó szerinti kereséseket tudok végrehajtani, nem pedig regex minta alapján. Az új változatban alapban a tulajdonságok neveiben nem keresünk, csak ha a $SearchInPropertyNames kapcsolót használjuk. Ha viszont pont csak a nevekben akarunk keresni, akkor a $NotSearchInValues kapcsolót használjuk. Ezeket a kapcsolókat integráltam a Where-Object feltételrendszerébe.
Az alábbi példában a szó szerinti „pöttyre” keresve alapban minden tulajdonságot megkapnék, viszont a ‑Literal kapcsolóval ezt kapom:
PS C:\>
Get-Date | Search-Property2 -pattern "." -Literal
Object Name Value
------ ---- -----
2023. 01.
21. 18:11:13 DateTime 2023. január 21.,
szombat 18:11:13
2023. 01.
21. 18:11:13 TimeOfDay 18:11:13.0798403
Ha a „Date”-re keresünk, akkor más-más eredményt kapunk annak függvényében, hogy a és kapcsolókat használjuk:
PS C:\> Get-Date | Search-Property2 -pattern Date
Object Name Value
------ ---- -----
2023. 01. 21. 18:20:16 DisplayHint DateTime
PS C:\> Get-Date | Search-Property2 -pattern Date -SearchInPropertyNames
Object Name Value
------ ---- -----
2023. 01. 21. 18:20:20 DisplayHint DateTime
2023. 01. 21. 18:20:20 DateTime 2023. január 21., szombat 18:20:20
2023. 01. 21. 18:20:20 Date 2023. 01. 21. 0:00:00
PS C:\> Get-Date | Search-Property2 -pattern Date -SearchInPropertyNames -NotSe
archInValues
Object Name Value
------ ---- -----
2023. 01. 21. 18:20:22 DateTime 2023. január 21., szombat 18:20:22
2023. 01. 21. 18:20:22 Date 2023. 01. 21. 0:00:00
Mivel lehetne még fejleszteni a függvényt? Például lehetne szűrni, hogy mely tulajdonságokban akarunk eleve keresni. Ezt két újabb paraméterrel tudjuk megadni, a $Property és az $ExcludeProperty szövegtömbökkel:
function Search-Property3 {
param(
[string] $pattern = ".",
[Parameter(ValueFromPipeline = $true)] $object,
[switch] $SearchInPropertyNames,
[switch] $NotSearchInValues,
[switch] $Literal,
[string[]] $Property = "*",
[string[]] $ExcludeProperty
)
begin{
if($Literal){
$pattern = [regex]::Escape($pattern)
}
}
process{
$object.psobject.properties |
Where-Object {
$propname = $_.Name
($Property | Where-Object {$propname -like $_}) -and
!($ExcludeProperty | Where-Object {$propname -like $_}) -and
(($SearchInPropertyNames -and $_.name -match $pattern) -or (!$NotSearchInValues -and $_.value -match $pattern))
} | Select-Object -Property @{n="Object"; e={$object.tostring()}}, Name, Value
}
}
Ezekkel a Where-Object feltételrendszerét kell kibővíteni. Mivel mindkét paraméter tömb, így két beágyazott Where-Object kifejezéssel kell vizsgálni, hogy az adott tulajdonságnév szerepel-e a kért tulajdonságok között ($Property) és nem szerepel a nem kértek között ($ExcludeProperty). Mivel kavarodás lenne a $_ használatából a kétszintű csővezetékeknél, így az aktuális tulajdonság nevét külön elmentem a $propname változóba. Mindkét paraméter esetében a -like operátorral vizsgálom az egyezést, így az ott használatos „*” és „?” wildcard karaktereket használhatjuk. Nézzük a példákat:
PS C:\> Get-Date | Search-Property3 -pattern 2023
Object Name Value
------ ---- -----
2023. 01. 21. 18:52:11 DateTime 2023. január 21., szombat 18:52:11
2023. 01. 21. 18:52:11 Date 2023. 01. 21. 0:00:00
2023. 01. 21. 18:52:11 Year 2023
PS C:\> Get-Date | Search-Property3 -pattern 2023 -Property Date*
Object Name Value
------ ---- -----
2023. 01. 21. 18:52:27 DateTime 2023. január 21., szombat 18:52:27
2023. 01. 21. 18:52:27 Date 2023. 01. 21. 0:00:00
PS C:\> Get-Date | Search-Property3 -pattern 2023 -Property Date* -ExcludePrope
rty *Time
Object Name Value
------ ---- -----
2023. 01. 21. 18:52:33 Date 2023. 01. 21. 0:00:00
Az első kifejezésben a „2023”-at tartalmazó tulajdonságokat kerestem, három találatot kaptam. A másodikban ezen belül csak a „Date”-tel kezdődőkre vagyok kíváncsi, itt már csak 2 találatom lett. Az utolsó kifejezésben a „Time” végűekre nem vagyok kíváncsi.
Mivel lehetne még gazdagítani a függvényt? Például azzal, hogy a mintánkban lehessen hivatkozni egy tulajdonság értékére. Például: keresem azokat a tulajdonságokat, amelyekben megtalálható az XY tulajdonságban tárolt érték. Nézzük példaként a Win32_ComputerSystem CIM objektumot:
PS C:\> Get-CimInstance -ClassName Cim_ComputerSystem | fl *
AdminPasswordStatus : 0
BootupState : Normal boot
ChassisBootupState : 3
KeyboardPasswordStatus : 0
PowerOnPasswordStatus : 0
PowerSupplyState : 3
PowerState : 0
FrontPanelResetStatus : 0
ThermalState : 3
Status : OK
Name : STLENO
PowerManagementCapabilities :
PowerManagementSupported :
Caption : STLENO
Description : AT/AT COMPATIBLE
…
Rengeteg tulajdonsága van, itt most csak az első néhányat másoltam ide. Látható, hogy a Name és Caption tulajdonság is tartalmazza a gépem nevét, de még több ilyen is van. Keresem az összes olyan tulajdonságot, ahol a Name tulajdonság értéke szerepel:
function Search-Property4 {
param(
[string] $pattern = ".",
[Parameter(ValueFromPipeline = $true)] $object,
[switch] $SearchInPropertyNames,
[switch] $NotSearchInValues,
[switch] $Literal,
[string[]] $Property = "*",
[string[]] $ExcludeProperty
)
begin{
if($Literal){
$pattern = [regex]::Escape($pattern)
}
$origpattern = $pattern
}
process{
if(!$LiteralSearch -and $origpattern -match "<[^>]+>"){
$pattern = [regex]::Replace($origpattern, "<([^>]+)>", {[regex]::Escape($object.($args[0].value -replace "<|>"))})
}
$object.psobject.properties |
Where-Object {
$propname = $_.Name
($Property | Where-Object {$propname -like $_}) -and
!($ExcludeProperty | Where-Object {$propname -like $_}) -and
(($SearchInPropertyNames -and $_.name -match $pattern) -or (!$NotSearchInValues -and $_.value -match $pattern))
} | Select-Object -Property @{n="Object"; e={$object.tostring()}}, Name, Value
}
}
Ez a változat nem igényel újabb paramétert. Az eddigi $pattern paraméterben, ha egy „<tulajdonságnév>” kifejezést szerepeltetek (kacsacsőrök között a tulajdonság neve), akkor azt a függvény behelyettesíti a „tulajdonságnév” alatti tulajdonság értékével. Mivel így a minta dinamikusan kell, hogy változzon, ha több objektumot is betöltök a csőből, így el kell tároljam ez eredeti mintát a $origpattern változóba, ezt a begin részben teszem meg, majd egy jópofa regex kifejezéssel történik meg a dinamikus csere. Ezt láthattuk a „ 1.4.7.16 Csere .NET osztállyal ” fejezetben. Itt még annyi a trükk, hogy a dinamikus csere közben még egy [regex]::Escape metódust is meghívok.
Nézzük ezt használat közben:
PS C:\> Get-CimInstance -ClassName Cim_ComputerSystem | Search-Property4 -patte
rn "<Name>"
Object Name Value
------ ---- -----
Win32_ComputerSystem: STLENO (Name = "STLENO") Caption STLENO
Win32_ComputerSystem: STLENO (Name = "STLENO") Name STLENO
Win32_ComputerSystem: STLENO (Name = "STLENO") DNSHostName STLeno
Win32_ComputerSystem: STLENO (Name = "STLENO") UserName STLeno...
Win32_ComputerSystem: STLENO (Name = "STLENO") CimInstanceProperties {Capti...
PS C:\> Get-CimInstance -ClassName Cim_ComputerSystem | Search-Property4 -patte
rn "^<Name>$"
Object Name Value
------ ---- -----
Win32_ComputerSystem: STLENO (Name = "STLENO") Caption STLENO
Win32_ComputerSystem: STLENO (Name = "STLENO") Name STLENO
Win32_ComputerSystem: STLENO (Name = "STLENO") DNSHostName STLeno
Az első esetben a Name tulajdonság értékét keresem, a találatokban az is szerepel, ahol a gépem neve, mint rész-sztring szerepel. A második példában csak teljes egyezést keresek.
Az utolsó verzióban néhány esztétikai módosítást teszek még. Egyrészt képessé teszem a függvényt, hogy a több objektumot ne csak a csőből, hanem az $object paraméterbe tömbként is át lehessen adni. Ehhez a $object paraméter elé teszem a [PSObject[]] típusjelölőt, másrészt a processz blokkban egy foreach ciklussal megyek végig az elemeken és ennek megfelelően a cikluson belül a $o ciklusváltozóra hivatkozom:
function Search-Property {
param(
[string] $pattern = ".",
[Parameter(ValueFromPipeline = $true)] [PSObject[]] $object,
[switch] $SearchInPropertyNames,
[switch] $NotSearchInValues,
[switch] $Literal,
[string[]] $Property = "*",
[string[]] $ExcludeProperty
)
begin{
if($Literal){
$pattern = [regex]::Escape($pattern)
}
$origpattern = $pattern
}
process{
foreach($o in $object){
if(!$LiteralSearch -and $origpattern -match "<[^>]+>"){
$pattern = [regex]::Replace($origpattern, "<([^>]+)>", {[regex]::Escape($o.($args[0].value -replace "<|>"))})
}
$o.psobject.properties |
Where-Object {
$propname = $_.Name
($Property | Where-Object {$propname -like $_}) -and
!($ExcludeProperty | Where-Object {$propname -like $_}) -and
(($SearchInPropertyNames -and $_.name -match $pattern) -or (!$NotSearchInValues -and $_.value -match $pattern))
} | Select-Object -Property @{n="Object"; e={$o.tostring()}}, Name, Value
}
}
}
Akkor erre is nézzünk néhány példát:
PS C:\> Get-ChildItem C:\PSTools\d* | Search-Property -pattern "<extension>"
Object Name Value
------ ---- -----
C:\PSTools\data-20230102.csv PSPath Microsoft.PowerShell.Core\FileSyst...
C:\PSTools\data-20230102.csv PSChildName data-20230102.csv
C:\PSTools\data-20230102.csv VersionInfo File: C:\PSTools\data-...
C:\PSTools\data-20230102.csv Name data-20230102.csv
C:\PSTools\data-20230102.csv FullName C:\PSTools\data-20230102.csv
C:\PSTools\data-20230102.csv Extension .csv
C:\PSTools\dummy.ps1 PSPath Microsoft.PowerShell.Core\FileSyst...
C:\PSTools\dummy.ps1 PSChildName dummy.ps1
C:\PSTools\dummy.ps1 VersionInfo File: C:\PSTools\dummy...
C:\PSTools\dummy.ps1 Name dummy.ps1
C:\PSTools\dummy.ps1 FullName C:\PSTools\dummy.ps1
C:\PSTools\dummy.ps1 Extension .ps1
PS C:\> $files = Get-ChildItem C:\PSTools\d*
PS C:\> Search-Property -object $files -pattern "<extension>"
Object Name Value
------ ---- -----
C:\PSTools\data-20230102.csv PSPath Microsoft.PowerShell.Core\FileSyst...
C:\PSTools\data-20230102.csv PSChildName data-20230102.csv
C:\PSTools\data-20230102.csv VersionInfo File: C:\PSTools\data-...
C:\PSTools\data-20230102.csv Name data-20230102.csv
C:\PSTools\data-20230102.csv FullName C:\PSTools\data-20230102.csv
C:\PSTools\data-20230102.csv Extension .csv
C:\PSTools\dummy.ps1 PSPath Microsoft.PowerShell.Core\FileSyst...
C:\PSTools\dummy.ps1 PSChildName dummy.ps1
C:\PSTools\dummy.ps1 VersionInfo File: C:\PSTools\dummy...
C:\PSTools\dummy.ps1 Name dummy.ps1
C:\PSTools\dummy.ps1 FullName C:\PSTools\dummy.ps1
C:\PSTools\dummy.ps1 Extension .ps1
Az első kifejezésben keresem a fájljaimnak azon tulajdonságait, ahol az extension tulajdonságuk értéke szerepel. Ezt itt a csövön keresztül hajtom végre. Az ezt követő példában ezeket a fájlokat előbb berakom a $files változóba, majd hagyományos módon adom át ezt a tömböt a $object paraméternek.