*EDITED

mały tutorial o przetwarzaniu przekazywanych wartości, który może radykalnie zmienić czas przetwarzania skryptu. wiadomka – pipelining jest podstawowym mechanizmem PS a przy przetwarzaniu wielu elementów, czasem wręcz odruchowo, wpisuje się ‘%’ czyli foreach-object. przykładowo – znajdź wszystkich, którzy mają nazwisko na ‘x’ i wyślij im wiadomość:

get-aduser -filter 'surname -like "x*"' -properties mail|%{Send-MailMessage -To $_.mail -Body 'are you Asian?' -SmtpServer 10.10.10.10}

a teraz standardowy scenariusz z życia codziennego: dostajemy jakieś listy w postaci pliqw tekstowych. załóżmy, że są w nim nazwy kont, z którymi coś trzeba zrobić. szkielet jest trywialny:

cat .\list.txt|%{ do-something -name $_ }

…mija czas i okazuje się, że nasze ‘do-something’ rozbudowaliśmy do postaci skryptu, ponieważ ilość wykonywanych czynności rośnie i oneliner jest zbyt mało wygodny. wartości można do skryptu przekazywać na dwa sposoby – bezpośrednio, jako parametr lub – tak jak powyżej – przez pipelining. druga metoda nie działa ‘od ręki’, wymaga włączenia poprzez specjalny parametr: ‘valueFromPipeline‘ [lub valueFromPipelineByPropertyName… ale to na inny wpis].

żeby pokazać co się będzie działo załóżmy, że mamy listę komputerów i operacja wymaga uwierzytelnienia się do zdalnego zasobu:

do-something.ps1:

param( [string[]]$identities )

$creds=get-credential

foreach($id in $identities) {
    #tu skomplikowany kod
    Get-WmiObject -ComputerName $id -credential $creds -Class win32_computersystem
}

w takim przypadq, aby uruchomić skrypt dla całej listy, należy przekazać listę przez parametr:

.\do-something.ps1 -identities (cat .\computerlist.txt)

polecenie w nawiasach ‘( )’ zostanie przetworzone, listing pliq zwraca tablicę stringów string[] i to zostanie przetworzone w skrypcie. jak na razie oczywiste oczywistości. nuda… ale zadanie nad którym pracujemy chcemy coraz bardziej automatyzować, cały proces do którego piszemy skrypty jest dość rozbudowany, a więc nasze skrypty muszą ze sobą gadać – output z jednego chcemy móc przekierować na kolejny. czyli trzeba włączyć pipelining:

do-something.ps1:

[cmdletbinding()]
param(
[parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string[]]$identities
)

$creds=get-credential

foreach($id in $identities) {
    Get-WmiObject -ComputerName $id -credential $creds -Class win32_computersystem
    write-host $id
}

jeśli przekażemy wartości przez parametr tak, jak do tej pory, to nic się nie zmieni. ale jeśli przekażemy przez pipe … coś nie halo:

(1) cat .\computernames.txt|do-something.ps1

..wyświetli tylko dla ostatniego elementu /: [zwróć uwagę, że skrypt ‘do-something’ wywołany jest bez parametru – valueFromPipeline oznacza ‘jeśli potok wyjściowy jest przekierowany, przekaż go do tej zmiennej oznaczonej valueFromPipeline’ ]

ah! no oczywiście! zapomnieliśmy foreach:

(2) cat .\computernames.txt|%{do-something.ps1 $_ }

[tutaj już musi być parametr – $_ . a to dla tego, że nawiasy klamrowe ‘{ }’ definiują blok danych, separując potok od polecenia. niby niuans ale pokazuje, że totalnie zmienia się mechanika pod spodem]

hmm… no ale mamy kolejny problem – teraz skrypt przed każdym wywołaniem prosi o dane uwierzytelniające. lama poradzi sobie dodając parametr ‘credential’ i przekazując go podczas wywołania. ale to nie jest zawsze dobry sposób i dobrze wiedzieć czemu tak się dzieje. błąd został popełniony w momencie dodania ‘foreach’ (2). parametr ‘identities’ w skrypcie został sprytnie zadeklarowany jako tablica, a więc powinien tablicę przyjąć. ale dodając ‘foreach’, rozbijamy tablicę na poszczególne elementy i uruchamiamy skrypt wiele razy, przekazując każdą kolejną wartość jako parametr wywołania. a nie o to chodziło.

rozwiązaniem jest dodanie składni ‘begin-process-end’ do skryptu. standardowo skrypt czeka na pojedynczą wartość, po dodaniu tego bloq tworzy ‘pułapkę’ zawartą w bloq ‘process’, która będzie się uruchamiać dla każdego kolejnego elementu. co więcej, ‘process’ zostanie wykonany dla każdego przekazywanego elementu dokładnie tak, jak ‘foreach’, a więc można się pozbyć tego fragmentu:

do-something.ps1

[cmdletbinding()]
param(
    [parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string[]]$identities
)

begin {
    write-host 'begin'
    $creds=get-credential
}

process {
    #foreach($id in $identities) { <- już niepotrzebne
        write-host 'jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element'
        Get-WmiObject -ComputerName $identities -credential $creds -Class win32_computersystem
        write-host $identities
    #} <- juz niepotrzebne
}

end {
    write-host 'end.'
}

teraz jeśli uruchomi się ‘do-sthelse | do-something’ – zostaniemy zapytanie o credsy tylko raz, po czym wykonana zostanie część ‘process’, a na koniec jednokrotnie wykona się klauzula ‘end’. wyrzucam ‘gwmi’ żeby pokazać przetwarzanie i zostawiam tylko write-host:

cat .\computerlist.txt | .\do-something.ps1

begin
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name1
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name2
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name3
[...]
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name1oo
end.

o to chodziło.

co zapamiętać

czym innym jest przekazanie tablicy a czym innym kolejne przekazywanie parametrów tablicy – czyli foreach. jeśli skrypt jest duży, to można dużo oszczędzić. dla tego przy pokaźnych operacjach warto zajrzeć do pomocy, opisu parametrów skryptu i sprawdzić czy deklaracja:

  • pozwala na przekazanie tablicy – np. [string[]]
  • pozwala na przekazanie przez pipe – valueFromPipeline

ponadto trzeba pamiętać, że ponieważ ‘valueFromPipeline’ jest pewnego rodzaju wskaźnikiem ‘TU PRZEKIERUJ WSZYSTKO CO WPADA’ – to może być zadeklarowane tylko raz. zadeklarowanie wielokrotnie nie spowoduje błędu składniowego, skrypt uruchomi się, ale wyniki będą mocno niedeterministyczne…

eN.

Spread the love

Comments (5)

  1. peki

    Odpowiedz

    W tego typu przetwarzaniach mozna tez polecieć zaawansowąną funkcją. Jest o tyle wygodniej, że foreach leci tak szybko jak spływają dane ze źródła, a nie czeka na załadowanie całości. Nie musimy też trzymać całej tablicy obiektow w pamięci, co przy bardzo duzych dancyh jest problemem.
    Korzystając ze snippetów w ise nawet nie pisze się trudno.
    W sumie sam bym nie wiedzieł o tym sposobie gdyby ne koniecznośc poprowadzenia kiedys kursu powesehllowego ; )
    https://technet.microsoft.com/en-us/library/hh413265.aspx

  2. Odpowiedz

    valueFromPipeline to jest mechanizm advanced function. możesz zaproponować rozwiązanie bo nie wiem o który konkretnie mechanizm AF chodzi?
    o. a link prowadzi do do przykładu niemal identycznego. nawet nazwa funkcji taka sama. LoL.

  3. peki

    Odpowiedz

    [cmdletbinding()]
    param(
    [parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string]$identities
    )

    begin {
    write-host ‘begin’
    $creds=get-credential
    }

    process {
    “output $identities”
    }

    end {
    write-host ‘end.’
    }

  4. peki

    Odpowiedz

    komenda process implikuje foreach, wiec mozemy przekazac sam parametr nie jako tablice, tylko pojedynczy parametr.
    Przy czym jedna i druga skladnia ma dokladnie to samo dialanie

Zostaw komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Time limit is exhausted. Please reload CAPTCHA.