Seriál Windows PowerShell – Vylepšený Where-Object (část 41.)

Rovnou na začátku říkám, že název článku je trochu zavádějící. Nebudeme se dnes bavit o cmdletu Where-Object, ale o jeho trochu skryté náhradě. Pro použití následujících technik musíte mít nainstalován PowerShell v4.

Před několika dny jsem se vrátil z MVP Summitu v Redmodu, kde jsem strávil několik dní v přítomnosti dalších MVPků z různých produktových skupin. V rámci mé PowerShell oblasti byly nejzajímavější přednášky na témata již existujících funkcionalit. Několik z nich jsme ale dostali rozebrané do naprostého detailu. V rámci prezentace o DSC (Desired State Configuration) – již jsem o tomto tématu psal – jsme se dostali k zajímavé vlastnosti, o kterou bych se s vámi dnes rád podělil.

V rámci konfigurace a výběru jednotlivých nodů můžete určovat, kde se bude nastavení aplikovat. Vezměme si následující příklad:

Configuration CloudService
{
    Node $AllNodes.Where("Role -eq Web").NodeName {
        WindowsFeature IIS
        {
             Ensure = "Present"
             Name = $Node.RolesToBePresent
        }
    }
}

Důležitý je pro nás třetí řádek, kde říkáme, že ze všech dostupných nodů vyberu pomocí where pouze ty, které mají mít roli web a z těchto si zapamatuji jejich jméno. Zajímavá je právě konstrukce .Where(„Role –eq Web“) . Tuto konstrukci můžeme použít i v konzoli, např.:

PS C:\> (Get-Process).Where({$_.name -eq "svchost"})

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
    397 14 3576 9984 45 0.27 592 svchost
    327 14 2384 5852 28 0.19 620 svchost
    418 32 9100 13172 638 6.25 672 svchost
    497 21 11368 15796 70 1.08 732 svchost
   1128 45 12876 27032 192 3.56 764 svchost
    462 26 4588 11056 82 0.28 800 svchost
    522 33 6908 15024 1129 0.36 872 svchost
    352 32 8724 10844 55 0.23 988 svchost
    681 26 62800 77052 191 4.95 1224 svchost
    252 15 2512 7340 47 0.06 1324 svchost

Pro porovnání, toto je standardní zápis předchozí konstrukce:

PS C:\> Get-Process | where name -eq 'svchost'
PS C:\> Get-Process | where { $_.name -eq 'svchost' }

První z nich lze použít od verze 3. Jedná se o tzv. zjednodušenou syntaxi. Poslední zápis funguje již od první verze PowerShellu. Jen pro zajímavost. Pokud bych potřeboval předchozí akci provést narychlo v konzoli, asi bych použil následující zápis:

PS C:\> gps|? name -eq svchost

Vidíte, že kolik adminů, tolik různých zápisů. Nicméně …

Na první pohled asi vypadá nová syntaxe trochu divná. V DSC má své opodstatnění, ale v konzoli se jedná o vůbec nejdelší zápis. Výhodou je ovšem rychlost běhu.

Uložíme si všechny tři varianty do proměnné:

PS C:\> $c = '(gps).Where({$_.name -eq "svchost"})','gps | where { $_.name -eq "svchost" }','gps | where name -eq "svchost"'

Nyní si můžeme všechny příkazy spustit:

PS C:\> $c |% { Write-Host $_ -fore Yellow; iex $_ }
(gps).Where({$_.name -eq "svchost"})

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
    394 14 3524 9964 44 0.27 592 svchost
    301 13 2216 5784 26 0.19 620 svchost
    419 32 9072 13176 638 6.28 672 svchost
    483 20 11312 15944 68 1.11 732 svchost
   1112 45 12804 27048 191 3.69 764 svchost
    429 26 4560 10784 81 0.28 800 svchost
    526 33 6964 15048 1130 0.36 872 svchost
    344 32 8692 10788 54 0.23 988 svchost
    677 26 64500 82328 190 8.64 1224 svchost
    252 15 2512 7340 47 0.09 1324 svchost
gps | where { $_.name -eq "svchost" }
    394 14 3524 9964 44 0.27 592 svchost
    301 13 2216 5784 26 0.19 620 svchost
    419 32 9072 13176 638 6.28 672 svchost
    483 20 11312 15944 68 1.11 732 svchost
   1112 45 12804 27048 191 3.69 764 svchost
    429 26 4560 10784 81 0.28 800 svchost
    526 33 6964 15048 1130 0.36 872 svchost
    344 32 8692 10788 54 0.23 988 svchost
    677 28 64500 82328 190 8.66 1224 svchost
    252 15 2512 7340 47 0.09 1324 svchost
gps | where name -eq "svchost"
    394 14 3524 9964 44 0.27 592 svchost
    301 13 2216 5784 26 0.19 620 svchost
    419 32 9072 13176 638 6.28 672 svchost
    483 20 11312 15944 68 1.11 732 svchost
   1112 45 12804 27048 191 3.69 764 svchost
    429 26 4560 10784 81 0.28 800 svchost
    526 33 6964 15048 1130 0.36 872 svchost
    344 32 8692 10788 54 0.23 988 svchost
    677 26 64500 82328 190 8.69 1224 svchost
    252 15 2512 7340 47 0.09 1324 svchost

Zde jsem si schválně dovolil použití méně známého aliasu. Iex je alias pro Invoke-Expression. Tento cmdlet je schopen převzít text na vstupu a spustit jej jako příkaz PowerShellu. Ve smyčce jsem tedy prošel všechny tři varianty zápisu a pomocí Invoke-Expression je spustil. Pro oddělení jsem použil Write-Host, abychom viděli, v které fázi se nacházíme.

Nyní si pojďme porovnat rychlost všech tří variant.

PS C:\> $c|% { "{0,-40} : {1}" -f $_, $((Measure-Command { 1..100 |% {iex $_} }).Milliseconds) }
(gps).Where({$_.name -eq "svchost"}) : 46
gps | where { $_.name -eq "svchost" } : 93
gps | where name -eq "svchost" : 128

Možná bychom nejprve mohli rozklíčovat zápis. Opět procházíme všechny varianty, ale jejich výpis nyní zobrazujeme pomocí F operátoru. Pokud o něm chcete vědět víc, můžete se podívat na mé staré články. První část zobrazuje text a druhá vlastní čas běhu. Pro měření běhu jsem využil cmdlet Measure-Command a poté zobrazil pouze jeho vlastnost Milliseconds. Je vidět, že rozdíl v běhu je docela markantní. Největší režie je u posledního zápisu. V případě, že bychom filtrovali nějaké větší pole objektů, může nám nový zápis hodně zkrátit celkový čas skriptu.

Pojďme si tedy ukázat další možnosti daného zápisu. Where můžete řetězit za sebe. Můžete tedy vyzkoušet toto:

PS C:\> (gps).Where({$_.name -eq "svchost"}).where({$_.id -ge 800})

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
    458 26 4856 11300 82 0.28 800 svchost
    519 32 6936 15092 1129 0.38 872 svchost
    348 32 8848 10840 55 0.23 988 svchost
    675 26 64504 82368 190 12.67 1224 svchost
    252 15 2512 7340 47 0.13 1324 svchost

Můžete si nechat vypsat pouze první nebo poslední záznam:

PS C:\> (gps).Where({$_.name -eq "svchost"}, "last")

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
    252 15 2512 7340 47 0.16 1324 svchost

PS C:\> (gps).Where({$_.name -eq "svchost"}, "first")

Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
    394 14 3524 9968 44 0.27 592 svchost

Zajímavé je, že ve stejném tvaru můžete použít i foreach. Můžete tedy zkusit:

PS C:\> (gps).Where({$_.name -eq "svchost"}).foreach({$_.name})

svchost
svchost
svchost
svchost
svchost
svchost
svchost
svchost
svchost
svchost

Kdy pro každý z objektů vypisujeme pouze jeho jméno. Samozřejmě byste asi dokázali vymyslet užitečnější příklad. Zkuste si například vzít některý z vašich posledních použitých příkazů a převést jej na tuto syntaxi. Klidně si i změřte délku běhu.

Dnešním cílem nebylo přesvědčit vás na využívání této syntaxe. Chtěl jsem vám ukázat jednu z možných cest a je na vás, abyste se rozhodli. Určitě je dobré o této variantě vědět a v případě, že budete mít pocit, že by se vám mohla hodit, vyzkoušejte ji. Nebo budete alespoň vědět, že jste to již někde viděli, pokud se v budoucnu dostanete k článku, kde bude tento zápis použit.

Ještě jednou připomínám, že pro možnost použití tohoto tvaru, musíte mít instalován PowerShell v4.

David Moravec, MVP
Mainstream Technologies