*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.
peki
nExoR
peki
peki
nExoR