*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