Category: najlepsze praktyki
Scripting Games 2012–Odliczanie: 8…
| 2012-03-25 | Posted by Bartek Bielawski under najlepsze praktyki, Polskie blogi IT, ScriptingGames, SG2012, zaawansowane funkcje |
|

Zegar tyka…
Już tylko 8 dni…
Dwa dni temu przedstawiłem moje propozycje: co moim zdaniem powinno się robić, by sędziowie byli zadowoleni z pisanych przez Was skryptów. Oczywiście, każdy sędzia jest nieco inny i co innego będzie miało dla niego znaczenie, ale sądzę, że lepiej wiedzieć czego się spodziewać, nawet jeśli gdzieś może się trafić Hiszpańska Inkwizycja. Jej nie spodziewa się nikt. ![]()
O czym dziś będzie mowa, już wspominałem:
8 rzeczy, których należy unikać
Są pewne rzeczy, które na PowerShellowych “purystów” działają jak płachta na byka. Dziś zamierzam wymienić te, które mi osobiście wadzą najbardziej i które, z własnego doświadczenia jako uczestnika, najboleśniej odpokutowałem. ![]()
8… Write-Host.
O tym cmdlecie napisano już wiele. Problem z nim polega na tym, że często jest nadużywany do rzeczy, do których go nie stworzono. Można go czasem użyć, by wyświetlić na ekranie bajecznie kolorowe komunikaty o statusie (z tą bajecznością to niestety lekko przesadzam, jesteśmy zamknięci w 16-kolorowej klatce). Ale nigdy, przenigdy nie używajmy go do wyświetlania istotnych wyników. Taki wynik, jak sama nazwa cmdletu wskazuje, zostanie “pożarty” przez naszego hosta. Jeśli nasz skrypt/ funkcja mają komukolwiek posłużyć to nie mogą ograniczać się do jednego “ujścia”. Przekierowanie takiego wyniku gdziekolwiek (do innego polecenia, do pliku, na drukarkę) jest już praktycznie niemożliwe. Idealne ujścia dla informacji o statusie bieżącej operacji wewnątrz naszego skryptu, fatalne dla ostatecznego wyniku jego działania. A ponieważ niektórzy nabawili się przez to alergii na cmdlet w ogóle, proponuję status wyświetlać opcjonalnie (Write-Verbose/ Debug), lub używając poleceń Write-Progress (które mają tę przewagę, że łatwo je aktualizować nie zalewając użytkownika informacją).
7… Miksowanie typów na wylocie.
Pisałem, że na wyjściu naszej funkcji powinny znajdować się obiekty. Ale muszą to być obiekty jednego typu – mieszanie różnych typów zwykle kończy się fatalnie i powoduje nieoczekiwane rezultaty. Źródła “mieszania” są w zasadzie trzy głównie:
- osadzone w treści stringi. Ładnie wyglądają, ale niestety lądują w wyniku funkcji.
- korzystanie z metod .NET bez dbania o ich rezultat: często zwracają one jakieś liczby (indeks) lub wartości logiczne ($true/ $false)
- założenie, że funkcja zwraca tylko to, co znajduje się po “return”, wiąże się z poprzednimi dwoma…
Dla przykładu:
function Get-List { param ( [Object[]]$Member ) $Collection = New-Object System.Collections.ArrayList "Dodajemy elementy do kolekcji..." foreach ($Item in $Member) { $Collection.Add($Item) } , $Collection } $List = Get-List -Member Alfa, Beta, Gamma $List.Count
Wynik “powinien” być 1 (temu służy przecinek poprzedzający $Collection, w przeciwnym razie PowerShell “rozbije” nam kolekcję i zwróci zwykłą listę). Nie ma tu nawet jednego return, a mimo to wynik jest 5: mamy w wynikowej kolekcji stringa (nasz komunikat), liczby całkowite (rezultat użycia metody ‘Add’) i naszą kolekcję. Naturalnie, kolekcja nie ma nic wspólnego z ArrayList, ten typ jest tylko jednym z jej elementów. A wystarczyłoby:
- użyć Write-Host lub jeszcze lepiej: –Verbose, –Debug lub –Progress zamiast wrzucania zwykłego stringa
- przekierować metody .NET na $null, Out-Null, czy w inny sposób je “uciszyć”
- używać return tylko do tego, do czego w PowerShellu służy (wcześniejsze wyjście z funkcji w oparciu o jakiegoś ifa np.)
6… Pisanie własnej pomocy.
Intencje są słuszne (pomoc jest przydatna, pisałem już o tym dwa dni temu). Rozwiązanie – fatalne. Dodanie parametru ‘?’ do naszej funkcji nie jest konieczne od wersji drugiej PowerShella. Funkcja, która coś takiego obsłuży będzie i tak albo zbyt wylewna, albo zbyt powściągliwa. Nie da nam możliwości (łatwej) podania parametrów takich jak Full, czy Examples. Napracujemy się bardzo, a i tak nikt tego nie doceni. Dlaczego? Ponieważ jeśli ktoś nie używa narzędzi skutecznych i wbudowanych to udowadnia, że ich nie zna. A to nigdy nie świadczy na naszą korzyść. Zamiast obszernej funkcji – kilka linii komentarza. Prościej, czytelniej i skuteczniej.
5… Samodzielne walidowanie parametrów.
Znów: prawdopodobnie w 9 przypadkach na 10 skutek nieznajomości funkcji języka. PowerShell umożliwia nam walidowanie parametrów na wiele różnych sposobów, włącznie z walidowaniem przy pomocy własnego skryptu. Jeśli więc początek Twoich funkcji to banda ifów sprawdzających wartość parametrów, to prawdopodobnie robisz to źle. Jest tylko jeden przypadek, gdy taka walidacja ma sens: jeśli walidować musimy kilka parametrów jednocześnie ($Pensja –gt $Wydatki). Tego PowerShell nie umożliwia, musimy więc go wyręczyć. Ale to właśnie te 1 na 10 przypadków. A może nawet 1 na 100…?
Jeśli więc chcecie walidować parametry (a robić to warto, by nie doprowadzić do nieoczekiwanych rezultatów), polecam lekturę about_Functions_Advanced_Parameters. Można też sięgnąć do mojego starszego postu, gdzie wymieniam dostępne opcje. Tu – prosty przykład. Podajemy funkcji ścieżkę do pliku, który musi istnieć by funkcja zadziałała, korzystając więc z ValidateScript upewniamy się, że tak jest w istocie:
function Get-FileStatistic { param ( [ValidateScript( { Test-Path -Path $_ })] [string]$Path ) Get-Content $Path | Measure-Object -Word -Line -Character | Select-Object Words, Lines, Characters }
Przykład bardzo prosty ale obrazuje, jak niewiele trzeba, by nasz parametr miał sensowną wartość.
4… Aliasy, szczególnie mało czytelne.
Alias w konsoli to błogosławieństwo, w skrypcie – przekleństwo. Przede wszystkim może zmniejszyć jego czytelność. O ile aliasy takie jak ‘select’, ‘where’, czy ‘measure’ są dość powszechnie używane i zrozumiałe, o tyle rzadziej stosowane (sl?) mogą sprawić kłopot nawet osobom, które z PowerShellem pracują każdego dnia. Odradzam też powszechne skrótowce “%” i “?” – oczywiście, wiele osób zna je już dobrze, ale w skrypcie moim zdaniem są przeszkodą w jego łatwym odczytaniu i zrozumieniu. A jeśli ktoś posłuży się naszym skryptem jako inspiracją i zechce go zmodyfikować, to może się na nich “potknąć”. Pisząc skrypt nie musimy oszczędzać palców: piszemy go raz i później (przynajmniej z założenia) głównie korzystamy. Więc zysk jest iluzoryczny, lepiej jednak postarać się i użyć pełnej nazwy komendy. Jest też jeszcze jedno zagrożenie: polegamy w naszym skrypcie na istnieniu aliasu, który ktoś świadomie na swoim komputerze usunął. Wówczas użytkownik naszego skryptu może spędzić sporo czasu próbując ustalić, gdzie tkwi błąd. Na tyle dużo, że potencjalny sędzia uzna po prostu, że Wasz skrypt nie działa wcale. Po co ryzykować? ![]()
3… Niezrozumiałe nazwy zmiennych.
Ocenianie skryptów czasem musi się ograniczyć do ich przejrzenia, testowanie wszystkich może się okazać niemożliwe. Używając nazw zmiennych, które trudno później śledzić (a, b, c, pc, pr) oszczędzacie nieco czasu w czasie pisania, ale praktycznie uniemożliwiacie rozumienie logiki “na pierwszy rzut oka”. Skrypt nie przyspieszy od nazwania zmiennej krótszą nazwą, a zarówno Wam, jak i osobom czytającym Wasz skrypt będzie łatwiej, jeśli zmienne będą miały sensowne nazwy. Oczywiście, zalecam nazwy angielskie… Nie dlatego, że nie lubię mowy ojczystej. Po prostu język angielski lepiej lub gorzej znamy (niemal) wszyscy, nikt więc nie będzie miał wątpliwości co kryje się pod zmienną “Path”. “Sciezka” może kogoś zbić z pantałyku… ![]()
2… Łamanie linii przy pomocy backticka.
W PowerShellu koniec linii na ogół tożsamy jest z końcem wyrażenia, czego skutkiem jest jego wykonanie. By tego uniknąć często w skryptach korzysta się ze znaku “odwołującego” tę właściwość końca linii, “`”. Osobiście odradzam tę technikę. Wystarczy że ktoś nieumyślnie wstawi spację tuż po tym znaku i działająca komenda nagle zaczyna sprawiać problemy. Zamiast tego proponuję łamać linie na znakach, które tego feleru nie mają:
- | –> czyli następna komenda w “rurce” ląduje w kolejnej linii
- ( –> czyli dalsza część prostego wyrażenia przenosi się do linii kolejnej
- { –> rozpoczynamy ScriptBlock w pierwszej linii, kontynuujemy w następnej
- , –> lista można w prosty sposób “rozrzucić” na kilka linii
Gorzej, gdy pojedyncza komenda ma tak wiele parametrów, że można albo mieć linię o szerokości 300 znaków, albo użyć backticka. W takiej sytuacji – polecam metodę nazywaną “splatting”:
# Podajemy wartość "na sztywno" ale mogłaby być parametrem... $ComputerName = '.' $Opcje = @{ LogName = 'System' Newest = 10 After = (Get-Date).AddDays(-1) EntryType = 'Error' Source = 'Service Control Manager' ComputerName = $ComputerName } Get-EventLog @Opcje | select Message, TimeGenerated
Pozwala ona też prosto “współdzielić” parametry między komendami, jeśli są jakieś punkty wspólne. Jest też wiele innych korzyści wynikających z korzystania ze splattingu. Korzyści z backticka nie ma w zasadzie żadnych, przynajmniej do łamania linii (w innych miejscach nie niesie ze sobą takich zagrożeń).
1… Komentowanie rzeczy oczywistych.
Komentarze w funkcjach to rzecz dobra, warto czasem opatrzyć jakieś nieoczywiste operacje komentarzem, by osoba przeglądająca skrypt wiedziała dlaczego postępujemy tak, a nie inaczej. Ale trzeba wystrzegać się pisania rzeczy trywialnych i oczywistych. Prosty przykład:
# Czytam zawartość pliku... $Content = Get-Content -Path $Path # Filtruje tylko te linie, które zawierają słowo "error" $Errors = $Content | where { $_ -match 'error' } # Zapisuje do pliku $OutPath Set-Content -Path $OutPath
Pomijając jakość samego kodu: czy choć jeden komentarz coś wnosi? Wyjaśnia logikę naszego działania? Informuje nas o czymś, czego nie da się wyczytać ze samej składni? Teraz porównajmy to z przykładem pozytywnym…:
# Używam -FilterXml: tylko 2008 R2 i nowsze pozwala na użycie HashTable. Get-WinEvent -FilterXml $SomeXML -ComputerName $Computer
I już nie przyjdzie mi do głowy pytanie: czemu nie użyto łatwiejszego do zbudowania –FilterHashTable…
Skoro skrypt ma zadziałać na serwerze 2008, to nie można użyć parametru, którego ta wersja serwera nie akceptuje.
Podsumowanie.
Wiemy już czego unikać, co robić. Pytanie kolejne: po co w ogóle mam sobie zawracać tym głowę. Już pojutrze: 6 powodów, by uczestniczyć.
Scripting Games 2012–Odliczanie: 10…
| 2012-03-23 | Posted by Bartek Bielawski under najlepsze praktyki, Polskie blogi IT, ScriptingGames, SG2012, zaawansowane funkcje |
|

Za dziesięć dni rozpocznie się święto miłośników PowerShella – Scripting Games. Dwa tygodnie przepełnione pisaniem skryptów (uczestnicy) i ich czytaniem/ testowaniem (sędziowie). W tym roku mam zaszczyt znajdować się w tej drugiej kategorii, nie mam więc szans, by obronić tytuł z zeszłego roku. Aby więc laur zwycięzcy pozostał w kraju nad Wisłą postanowiłem się podzielić moimi spostrzeżeniami odnośnie Scripting Games. Przede wszystkim: wyedukować, czy raczej – podzielić się kilkoma uwagami, które mogą pomóc uniknąć błędów. Później: zachęcić, wszak każdy lubi, gdy jego wysiłek jest stosownie (wy)nagrodzony. Wreszcie: pomóc wybrać odpowiedni poziom. Ale po kolei. Dziś:
10 rzeczy, które powinno się robić.
Dla wielu spośród uczestników będą to banały, ale czasem lepiej pewne rzeczy powtórzyć, bo a nuż widelec komuś się to czy owo “zapomniało”…
10… Zaawansowane funkcje.
W kategorii “początkujących” może się to wydawać lekką przesadą. I naturalnie, nie ma sensu “pakować” w stosowne dekoracje kodu, który zajmuje jedną linię. Z drugiej strony [CmdletBinding()] to tylko 17 znaków, a dają nam sporo w zamian. Naprawdę, funkcja korzystająca z $args trąci myszką. PowerShell daje nam tę elastyczność, ale jeśli piszemy coś na konkurs, to lepiej pokazać, że znamy się na temacie. Kundelki są śliczne, ale na wystawę psów ich nikt (chyba?) nie prowadzi. Zaawansowana funkcja, to funkcja “rasowa”. W rękach wprawnego skrypciarza – medalowa wręcz. ![]()
9… Obiekt na wyjściu.
Funkcja w PowerShellu zwraca obiekty. KROPKA. Jeśli napisałeś ostatnio funkcję, która robi coś innego – przepisz to zdanie 100 razy na tablicy. Jeśli funkcja nie zwraca obiektów, to jest jednorazowa. Jeśli zwróci obiekt – można ją wpleść w zgrabną rurkę i mieć z niej pożytek jeszcze wiele razy. Warto też przyjrzeć się parametrom, szczególnie w kategorii zaawansowanej. Jeśli jest sens, by jakiś parametr “pobierał” obiekty (lub też ich właściwości) z rurki – grzechem zaniedbania będzie pominięcie tego. Znów: troszkę się trzeba z tym “naklikać”, ale późniejsze użycie może się okazać po wielekroć przyjaźniejsze użytkownikowi.
8… Comment Based Help.
Jeśli piszesz funkcję i wymagasz, by wszyscy przejrzeli Twój skrypt by się dowiedzieć co robi (lub co gorsza: zakładasz, że na pewno odgadną co autor miał na myśli), to właśnie strzeliłeś komuś w stopę. Co gorsza: tym kimś możesz być Ty, za kilka miesięcy, o trzeciej nad ranem desperacko próbujący rozwiązać jakiś problem przy pomocy tej właśnie funkcji. Jak myślisz, co pomyślisz o tym leniu, któremu zabrakło energii, by funkcję wyposażyć w zrozumiałą i kompletną pomoc? Przecież to raptem kilka linii komentarza. W zasadzie podstawowy opis, do tego kilka przykładów – i pomoc gotowa. Get-Help to o wiele bardziej przyjazna metoda zapoznawania się z komendami w PowerShellu, niż notepad.
7… Write-Verbose i Write-Debug.
Mamy funkcję zaawansowaną (mamy, prawda?), więc skorzystajmy z tego, co ona nam oferuje. Robisz w funkcji coś “przełomowego”, gdzie potencjalnie może coś się nie udać? Daj szansę użytkownikowi wyświetlić informację, co próbujesz zrobić. Wyrzucenie informacji Verbose/ Debug zawierającej wartości zmiennych, parametrów, czy po prostu informujące o stopniu zaawansowania przydadzą się nie tylko w czasie testowania skryptu, ale również później, gdy nagle coś przestanie działać. Ponieważ domyślnie te informacje są ukryte, to nie zaśmiecamy nikomu konsoli. Dopiero gdy sam o to poprosi – robimy się wylewni. Złoty środek. ![]()
6… Obsługa błędów.
Błędy czasem można przewidzieć. Jeśli przewidujesz błąd – obsłuż go. Straszenie ludzi, że dzieje się coś złego, gdy w istocie dzieje się coś przewidywanego i naturalnego jest marnym pomysłem. Narzędzia zbyt hałaśliwe i zbyt “płochliwe” mogą zniechęcić każdego. Jeśli więc da się obsłużyć jakiś błąd – zróbmy to. I nie mam tu bynajmniej na myśli:
try { # kod naszej funkcji } catch {}
To nie obsługa błędów, to proszenie się o kłopoty. ![]()
5… Testuj na “gołym” PowerShellu.
Maszyna miłośnika skryptów to często swoisty bazar. Moduły, funkcje, skrypty i profile pałętają się po całym twardym dysku i nigdy nie wiadomo, który akurat “wyręczy nas” przy definiowaniu elementów wykorzystywanych w naszym skrypcie. Problem polega na tym, że sędziowie skrypt będą raczej testować na innym komputerze i nagle skrypt zasypie ich stronami niezrozumiałych błędów. Jak tego uniknąć? powershell.exe –noprofile, to najlepsza możliwa odpowiedź. Najlepiej (na wszelki wypadek) uruchomić go ponownie tuż przed ostateczną kontrolą. Jeśli jak ja – od dawna macie na swoim komputerze PowerShella 3 – możecie zechcieć dorzucić tam też przełącznik “-version 2”. Chyba że faktycznie chcecie skrypt pisać pod wersję trzecią – pamiętajcie wtedy jednak o parametrze #requires. Pamiętajcie też: skrypt raz wrzucony na poshcode pozostanie niezmienny, zawsze więc przed opublikowaniem tam: testujemy, testujemy, testujemy.
4… KISS.
Prosto. Jeśli da się cmdletem – nie twórzmy klas w .NET. Jeśli da się parametrem – nie filtrujmy przy pomocy Where-Object. Dobry skrypt nie musi być długi, więcej nawet – często to właśnie te krótsze są o wiele lepsze, gdyż łatwiej je ogarnąć i zrozumieć ich logikę. Sam wiele razy popełniałem ten błąd, więc wiem co mówię. Nie dać się ponieść, skupić się na zadaniu, upraszczać by zwiększyć przejrzystość. Prosto do celu.
3… Stosować cmdlety, jeśli istnieją.
Troszkę wpisuję się to w ideę KISS, ale tu bardziej chodzi o nie odkrywanie na nowo koła. Pisanie funkcji, która zrobi to samo co istniejący cmdlet (tylko gorzej) mija się z celem. Po to Bozia dała nam Get-Command, byśmy mogli w razie czego poszukać gotowego narzędzia, które wykona za nas najcięższą robotę. Nikt nie nagrodzi nas za napisanie na nowo Get-Member, szczególnie gdy nasze zadanie dotyczy kompletnie innych rzeczy.
2… Brać sobie do serca komentarze/ artykuły sędziów.
Skrypt opatrzony przez sędziego komentarzem daje nam możliwość pracowania nad swoimi umiejętnościami. Zakładam oczywiście, że komentarz ma sens (co jest raczej regułą). Dodatkowo w zeszłym roku kilku sędziów w czasie trwania SG publikowało na swoich blogach wskazówki, które pomagały uniknąć błędów innych (przynajmniej w kolejnych zadaniach). W tym roku pewnie będzie podobnie. Te dwa źródła informacji mogą pomóc tu i teraz (przy kolejnych konkursowych skryptach) jak i w przyszłości (w czasie pisanie skryptów “produkcyjnych”).
1… Porównać swoje rozwiązanie z rozwiązaniami innych.
Takie porównanie pozwala często dostrzec rysy we własnej logice, oraz wskazać inne drogi (być może skuteczniejsze, bądź wydajniejsze) prowadzące do tego samego celu. Warto przyjrzeć się zarówno skryptom uznanym za najlepsze, jak i tym, które uzyskały noty najniższe: to pozwoli nam samodzielnie ocenić: na którym miejscu na skali znajdują się obecnie nasze umiejętności. Na koniec gier pojawiają się też rozwiązania ekspertów. I choć sam kilka razy nie do końca zgadzałem się z rozwiązaniami, które tam można było znaleźć, to dobrze jest móc porównać swoją pracę, z pracą ekspertów. Dzięki temu wyciśniemy z Scripting Games ostatnie soki. ![]()
Podsumowanie
Czas leci… Dziś przyjrzeliśmy się temu, co robić należy. Za dwa dni: 8 rzeczy, których należy unikać. Naturalnie, jeśli niebo nie zawali mi się na głowę.
PowerShell–efektywnie(j), część 9
| 2012-03-17 | Posted by Bartek Bielawski under formatowanie, moduły, najlepsze praktyki, Początki, Polskie blogi IT |
|
Dziś wracamy do tematu modułów w PowerShellu. Uznałem, że jeśli teraz nie przysiądę i nie dokończę tego cyklu – to nie zrobię tego w najbliższej, dającej się przewidzieć przyszłości. Poprzednio opisałem podstawy tworzenia modułów, dziś – pora na manifest i pliki definiującej typy i format ich wyświetlania.
Manifest to prościutki skrypt PowerShella. W zasadzie zawiera on jedną tablicę skrótów z kluczami odpowiadającymi właściwościom/ ustawieniom modułu. Korzystamy przy tym z rozszerzenia .psd1, które wykorzystywane jest w zasadzie tylko w tym celu.
Nasz manifest
Manifest najprościej jest stworzyć przy pomocy polecenia New-ModuleManifest. Polecenie to spyta nas w zasadzie o wszystkie elementy, które do stworzenia manifestu są niezbędne. Większość kluczy ma dość jednoznaczne nazwy, zatrzymać się chciałbym w zasadzie przy dwóch: TypesToProcess i FormatsToProcess.
Oba klucze przyjmują tablicę stringów, w której zawrzeć możemy ścieżkę do wszystkich plików .ps1xml dotyczących naszego modułu. Pliki te służą definiowaniu dla istniejącego typu (czy to typu “prawdziwego”, czy też “wirtualnego”, stworzonego przez nas na potrzeby modułu) dodatkowych właściwości i metod (TypesToProcess) oraz sposobów wyświetlania (FormatsToProcess). Pliki .ps1xml to całkowicie odrębny temat, w dodatku dość obszerny. W wielkim skrócie można napisać, że są to pliki XML o zdefiniowanym schemacie, dzięki którym możemy “centralnie” przeprowadzać operacje podobne do tych, które wykonujemy przy pomocy poleceń takich jak Format-Table, czy Add-Member.
Manifest pozwoli nam powiązać te pliki z naszym modułem, dzięki czemu obiekty tworzone przy jego pomocy będą wyglądać tak, jak my sobie tego zażyczymy. By jednak obiekty przez nas generowane wyróżnić z tłumu innych, zwłaszcza jeśli nie chcemy dodawać “prawdziwego” typu poleceniem Add-Type, musimy skorzystać z jednej, dość prostej techniki, by nasze obiekty miały stosowną “metkę”.
Pseudo-typ i pliki PS1XML.
Skuteczne korzystanie z plików typów/ formatów wymaga od nas trzech rzeczy. Po pierwsze: funkcje tworzące nasz wymarzony obiekt muszą go odpowiednio “oznakować”:
function Get-Foo { param ( [Parameter(ValueFromPipeline = $true)] [string]$Bar ) process { $Out = New-Object PSObject -Property @{ One = 1 Two = $Bar } $Out.PSTypeNames.Insert(0, 'Typ.Wymarzony' ) $Out } }
Druga rzecz, to dodać ten wirtualny typ do naszych plików PS1XML w odpowiednim miejscu, zarówno w pliku opisującym formatki:
<Configuration>
<ViewDefinitions>
<View>
<Name>Wymarzony.FT</Name>
<ViewSelectedBy>
<TypeName>Typ.Wymarzony</TypeName>
</ViewSelectedBy>
… jak i pliku opisującym rozszerzenia naszego typu:
<?xml version="1.0" encoding="utf-8" ?>
<Types>
<Type>
<Name>Typ.Wymarzony</Name>
<Members>
Jak widać nie wymagało to wielkiej pracy, a efekt jest zwykle zadowalający. Zamiast dość losowego wyniku i obiektu bez żadnych “specjalnych” właściwości i metod możemy uzyskać zwierza, który wyświetlany będzie w sposób dla nas wygodny i będzie gotowy wykonać dla nas wybrane przez nas zadania:
To wszystko można oczywiście zrobić również bez modułu, ale moduł czyni te operacje o wiele prostszymi i daje nam (przy pomocy pliku manifestu) bardzo dokładną kontrolę.
$Env:PSModulePath czyli o instalacji modułu
Moduły można, jak widać na załączonym zrzucie ekranu, importować bez większego trudu z dowolnego miejsca na dysku. Istnieje jednak zmienna środowiskowa, która definiuje kilka folderów, z których import będzie o wiele prostszy. PSModulePath przypomina nieco zmienną PATH: ma identyczną składnię, lista folderów jest rozdzielona średnikami:
Poprawna struktura:
Ponieważ zdarzyć się może, że zrobimy literówkę w nazwie pliku lub folderu (a raczej ze względu na to, że sam kiedyś zmarnowałem sporo czasu próbując ustalić, czemu jeden z moich modułów nie działał poprawnie) stworzyłem wieki temu funkcję, która sprawdzi poprawność tych nazw i jeśli się na to zdecydujemy – błędy te naprawi. Na tym kończę serię dotyczącą efektywnego PowerShella. Troszkę się zeszło… ![]()
Powershell – efektywniej, część 8.
| 2011-12-17 | Posted by Bartek Bielawski under moduły, najlepsze praktyki, Polskie blogi IT |
|
Zgodnie z obietnicą – w tej części zajmę się modułami. Czym są moduły w PowerShellu? Moduł to rozszerzenie. Może to być zarówno rozszerzenie binarne – w formie skompilowanej biblioteki dll, jak i skryptowe – w najbardziej podstawowej formie plik zawierający odpowiedni kod PowerShella z rozszerzeniem .psm1. Nas oczywiście interesować będzie moduł skryptowy.
Po co?
Czemu moglibyśmy chcieć stworzyć moduł? Moduły nadają się świetnie do zmieniania środowiska, pozwalają zebrać funkcje/ zmienne/ aliasy związane z jakimś zagadnieniem (np. obsługa lokalnych kont na stacjach roboczych; obsługa AD w danym, lokalnym OU). Moduł sprawdza się lepiej niż wrzucanie wszystkiego do profilu – dzięki temu kod staje się bardziej przenośny, a jeśli chcemy zmienić coś w zestawie funkcji dotyczących konkretnej technologii/ zagadnienia, to nie musimy ich szukać na piechotkę. Moduł pozwala też oddzielić elementy “prywatne” od “publicznych”. Ta izolacja pozwala uniknąć sytuacji, gdy dwa narzędzia będą ze sobą walczyć o jakąś zmienną. W ilu skryptach używamy gdzieś zmiennych typu $i, albo $sum(a)? No cóż, moduły mogą pracować w swoim wewnętrznym zakresie z dowolnymi, kolidującymi zmiennymi. Jeśli moduły nie “wypluwają” ich na zewnątrz (dojdziemy do tego jak to zrobić, domyślnie żadna zmienna nie wychodzi poza moduł) – do kolizji zmiennych nigdy nie dojdzie. Nie da się tego powiedzieć o dotsourcingu – tam zawartość skryptu wpada do naszego głównego zakresu, przez co możemy czasem wylać dziecko z kąpielą.
To wszystko?
Moduł to jednak dużo, dużo więcej. Kompletny moduł, oprócz tworzenia zestawu funkcji potrafi również:
- zmieniać sposób wyświetlania obiektów
- dodawać do istniejących/ definiowanych typów właściwości i metody skryptowe
- tworzyć wielojęzyczną pomoc do naszego modułu
- odpowiednio opisać metadane modułu (autora, wersję) – do tego służy manifest
Pierwsze dwa punkty realizowane są przy pomocy plików XML o specjalnej strukturze. Wszystkie pliki tego typu muszą mieć rozszerzenie .ps1xml. Ich stosowanie to jednak całkiem osobny temat. Poza tym takie modyfikacje przydają się zwykle w bardziej zaawansowanych modułach.
Pomoc dla modułu też raczej nie ma większego sensu jeśli tworzymy moduł w miarę prosty. Prosty moduł to zestaw funkcji, a funkcje można łatwo opisać posługując się pomocą w komentarzach. Oczywiście taka pomoc nie będzie wielojęzyczna, ale w przypadku, gdy z modułu nie będzie korzystało wielu ludzi – język nie jest istotną kwestią.
Ostatni punkt, czyli manifest, można w zasadzie zastosować do każdego modułu. Pomaga on śledzić wersje modułu, co z czasem może okazać się przydatne (gdy np. pracujemy na kilku komputerach i zechcemy moduł uaktualnić), informację o autorze, opis modułu… A ponieważ jego tworzenie nie jest bardzo skomplikowane (służy do tego cmdlet New-ModuleManifest), nic nie stoi na przeszkodzie, by moduł (nawet najprostszy) w taki manifest wyposażyć.
Tworzymy moduł: krok 1
Pierwszy krok to zebranie wszystkich funkcji, które nasz moduł ma udostępniać i zapisanie ich w pliku z odpowiednim rozszerzeniem. Taki moduł zwykle można od razu zacząć używać (wystarczy go zaimportować: Import-Module). Jeśli jednak chcemy by nasz moduł zawierał również funkcje pomocnicze (które nie powinny być widoczne na zewnątrz) bądź dodawał do sesji zmienne i aliasy (domyślnie wszystkie aliasy i zmienne definiowane w module nie są eksportowane poza moduł), musimy pójść krok dalej. Podstawowa metoda to wyeksportowanie “ręcznie” funkcji, zmiennych i aliasów. Trzeba jednak pamiętać, że eksportując dowolny element w ten sposób wyłączamy domyślny mechanizm eksportujący wszystkie funkcje z modułu. Popatrzmy na konkretny przykład, trzy proste moduły, bardzo podobne do siebie:
function Get-Foo { Write-Host foo } function pomoc { Write-Host Pomagam } New-Alias -Name foo -Value Get-Foo $Foo = 'Bar'
Taki moduł udostępnia zarówno funkcję Get-Foo (która ma być widoczna) jak i funkcję pomoc – która w naszym przykładzie jest pomocnicza. Co jeśli dodamy Export-ModuleMember i zapomnimy o funkcjach…:
function Get-Foo { Write-Host foo } function pomoc { Write-Host Pomagam } New-Alias -Name foo -Value Get-Foo $Foo = 'Bar' Export-ModuleMember -Alias foo -Variable Foo
Alias ‘foo’ zadziała, ale skutkiem będzie błąd (funkcja Get-Foo nie będzie zdefiniowana, więc użyć się jej bezpośrednio nie da). Działać poprawnie będzie w zasadzie tylko zmienna $Foo. Prawidłowe użycie cmdletu Export-ModuleMember:
function Get-Foo { Write-Host foo } function pomoc { Write-Host Pomagam } New-Alias -Name foo -Value Get-Foo $Foo = 'Bar' Export-ModuleMember -Function Get-Foo -Alias foo -Variable Foo
Po zaimportowaniu tego modułu widoczna będzie tylko potrzebna nam funkcja (Get-Foo), alias, który do niej prowadzi (foo) oraz zmienna, którą chcemy zdefiniować ($Foo).
Tworzymy moduł: krok 2.
Zainicjowaliśmy więc moduł. Jak go testować? Import-Module działa jednorazowo, jeśli moduł w sesji już funkcjonuje – ponowny import nie da żadnego rezultatu. Na etapie tworzenia bardzo przydatny może się okazać parametr –Force – pozwala on zaimportować moduł wielokrotnie, dzięki czemu wprowadzone zmiany będą widoczne. Na etapie “produkcji” warto jest po każdej poważniejszej zmianie dokonać importu by przekonać się, czy nasz moduł nie przestał działać.
Warto też na tym etapie zastanowić się nad skutecznym mechanizmem, który polecenia i zmienne eksportowane z naszego modułu pozwoli wyróżnić. Prefiks ułatwi zarówno używanie modułu, jak i eksport poleceń/ zmiennych. Dlaczego? Otóż polecenie Export-ModuleMember obsługuje symbole wieloznaczne. Zamiast więc pojedynczo eksportować polecenia i zmienne – możemy na końcu modułu użyć:
Export-ModuleMember -Function *-Prefiks* -Variable Prefiks* -Alias *
Naturalnie, prefiks to raczej 2-3 literki.
Gdzie zaś ułatwienie użytkowania? O ile prościej jest (posługując się TABem np.) dopełniać polecenie Get-Prefiks[TAB] zamiast przypominania sobie za każdym razem, jakich rzeczowników użyliśmy w naszych funkcjach? Get-Command –Module NaszModuł nieco sprawę upraszcza, ale nie wydaje mi się to najefektywniejszą metodą.
Co dalej?
W kolejnej części zamierzam opisać dokładniej manifest modułu i to, co on nam daje. Oprócz tego zamierzam napisać jak moduły przygotować do dystrybucji i jak “instalować” je, by zamiast składni: Import-Module .\Nazwa.psm1 móc skorzystać ze składni: Import-Module Nazwa. To w zasadzie drobiazg, ale myślę że warto o nim wspomnieć, na wypadek gdyby ktoś tego jednak nie wiedział… ![]()
PowerShell – efektywniej, część 7.
| 2011-11-29 | Posted by Bartek Bielawski under najlepsze praktyki, Polskie blogi IT, pomoc, zaawansowane funkcje |
|
Minął prawie miesiąc od ostatniego wpisu… dla mnie był to miesiąc wyjątkowo długi. Kończymy powoli wdrażanie Windows 7 w naszej firmie, a wiadomo jak to jest – końcówki zwykle są najgorsze, szczególnie gdy innych spraw również nie można odłożyć na później. Skutek jest taki jak widać – blog musiał nieco poczekać… Dziś zamknąć zamierzam kwestie związane z zaawansowanymi funkcjami/ skryptami. Nasza zaawansowana (choć niezbyt funkcjonalna) ma już niemal wszystko. Brakuje tylko jednego – pomocy. W przypadku cmdletów pomoc jest tworzona przy pomocy plików maml. Pisanie ich na piechotę nie należy do wdzięcznych czynności, można to sobie znacznie uprościć korzystając z narzędzia Cmdlet Help Editor. Pisząc zaawansowane funkcje mamy zadanie znacznie uproszczone. W zasadzie całość sprowadza się do umieszczenia odpowiednio sformatowanego komentarza w odpowiednim miejscu. Zasady przypominają nieco regulamin dotyczący spalonego w piłce nożnej: niby wszystko jest proste, ale jak się zacznie wyjaśniać komuś, robi się coraz mniej “prosto”.
W zasadzie budowanie pomocy sprowadza się do dodania komentarza w formacie:
<#
.Nagłówek
Treść
.Dwuczłonowy Nagłówek
Treść
#>
Dokładny opis wszystkich elementów znajduje się w pomocy (about_comment_based_help), ja wspomnę o najistotniejszych (moim zdaniem):
- Synopsis – krótki opis naszej funkcji
- Description – opis obszerniejszy, bardziej szczegółowy
- Example – przykłady, każdy przykład umieszczamy w osobnej sekcji Example a sekcji takich możemy definiować ile chcemy (nawet jeśli istnieje jakiś limit, to trudno będzie do niego “dobić”
- Notes – dodatkowe informacje, które nie pojawią się w widoku domyślnym
- Parameter MojParametr – opis konkretnego parametru
Ale to dopiero początek, dalej zaczyna się cała litania innych warunków, po kolei więc:
- można użyć zarówno komentarzy blokowych (<# #>), “tradycyjnych” (# jako pierwszy znak w linii) oraz mieszanki obu
- komentarz do funkcji można umieścić “w ciele”, lub tuż nad słowem kluczowym “function”
- umieszczając pomoc nad funkcją, możemy zostawić maksymalnie jedną linię pustą pomiędzy końcem komentarza a początkiem funkcji.
- umieszczając pomoc w ciele funkcji, musimy pamiętać by umieścić go przed blokiem param () i [CmdletBinding()]
- w przypadku pomocy do skryptu – musimy umieścić komentarz na jego początku lub końcu
- jeśli chcemy dodać linie #requires na początku skryptu – musimy odseparować ją pustą linią od bloku zawierającego pomoc
- umieszczenie pomocy na końcu skryptu i podpis elektroniczny wzajemnie się wykluczają (niedopatrzenie
) – podpis jest komentarzem, więc automatycznie blok pomocy nie jest ostatni i przestaje być obsługiwany - jeśli chcemy pomoc skryptu umieścić na jego początku musimy zadbać o to, by pomoc nie “przykleiła” nam się do pierwszej funkcji znajdującej się w skrypcie
- jakakolwiek literówka w nagłówkach pomocy (n.p.: .Synosis) automatycznie sprawi, że pomoc się nie pojawi w ogóle
- każdy nagłówek musi być w osobnej linii, sekcja musi zaczynać się w linii następnej i kończyć w linii poprzedzającej następny nagłówek
- komentarz musi stanowić jedną całość – jeśli będą przerwy między częściami, brana pod uwagę będzie tylko ta część, której położenie zgadza się z zasadami wymienionymi powyżej
- opis parametrów można umieszczać zarówno tuż nad nimi, jak i w treści “głównej” pomocy
Jak widać – miejsc na potencjalne pomyłki nie brakuje. Próbowałem sobie wyłapywanie błędów (szczególnie literówek) ułatwić jakiś czas temu (dokładnie w czasie Scripting Games 2010) – wtedy też napisałem prostą funkcję, która miała mi w wyłapywanie błędów pomóc. Bazuje ona na wyrażeniach regularnych i pewnie wymagałaby poprawek – pisałem ją w czasach gdy o tych ostatnich wiedziałem zdecydowanie mniej niż teraz. Ale na pewno może się ona przydać od czasu do czasu, by wyłapać najbardziej bagatelne błędy…
Tyle zasady. A jak najlepiej budować taką pomoc? Synopsis i Description to moim zdaniem absolutne minimum. Dobrze jest też umieścić choć kilka przykładów użycia naszej funkcji. Odnośnie parametrów – preferuję umieszczania pomocy do nich bezpośrednio na nimi, w bloku param. Dzięki temu, moim zdaniem, łatwo tę pomoc “wyłapać” w czasie modyfikowania skryptu/ funkcji. A i dodając nowy parametr możemy od razu wzbogacić go o odpowiednią pomoc. Wyjątkiem będą tu parametry dodawane w zaawansowanych funkcjach automatycznie, jak WhatIf, Confirm, Debug i Verbose.
Pozostałe elementy pomocy używam niezmiernie rzadko. Możliwości jest sporo, więc najlepiej korzystać z nich z głową, by nie przesadzić… Jak pomoc będzie zbyt przeładowana – nikomu nie będzie się chciało jej czytać. ![]()
Na koniec nasza funkcja, tym razem już odpowiednio uzbrojona w system pomocy:
function Get-Foo { <# .Synopsis Wyświetla literki i cyferki. .Description Funkcja wyświetla literki i cyferki, w zależnosci od potrzeb. Liczby są formatowane jako waluta (C4), procenty (P4) i zwykłe liczby (N4). W przypadku słów nie jest używane żadne specjalne formatowanie. .Example Get-Foo Slowo Slowo to moje slowo Wyświetla wybrany string. .Example Get-Foo -Liczba .24 -Format P4 Moja liczba: 24.0000 % Wyświetla liczbę sformatowaną w wybrany sposób. .Parameter WhatIf Dodawany automatycznie. Pomoc do niego możemy umieścić jedynie w taki sposób. .Parameter Confirm I tu podobnie... #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Domyslny' )] param ( # Liczba, którą chcemy wyświetlić. [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Liczby' )] [double]$Liczba, # Pojedyncze słowo, które chcemy wyświetlić. [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'Domyslny' )] [ValidatePattern('^\w+$')] [string]$Slowo, # Format, który zostanie użyty do wyświetlenia podanej liczby. [Parameter( Position = 1, ParameterSetName = 'Liczby', Mandatory = $true, HelpMessage = 'Pomozcie mi!' )] [ValidateSet('P4','N4','C4')] [string]$Format ) begin { Write-Verbose "Format: $Format" Write-Verbose "Liczba: $Liczba" Write-Verbose "Slowo: $Slowo" if ($Format) { Write-Debug "Wygląda na to, że używamy liczby." } } process { if ($psCmdlet.ShouldProcess( "Cel: $Slowo $Liczba", "Operacja: Wyświetl" )) { switch ($psCmdlet.ParameterSetName) { Domyslny { "{0} to moje slowo" -f $Slowo } Liczby { "Moja liczba: {0:$Format}" -f $Liczba } } } } }
W następnej części zamierzam zająć się modułami. Ponieważ temat ten jest dość obszerny, nie wykluczone że rozbiję go na kilka wpisów. Byleby tylko czasu i zapału na pisanie nie zabrakło… ![]()
PowerShell–efektywniej, część 6.
| 2011-11-06 | Posted by Bartek Bielawski under najlepsze praktyki, Początki, Polskie blogi IT, Skrypty, zaawansowane funkcje |
|
W poprzedniej części opisałem składnię, dzięki której funkcja zmienia się w funkcję zaawansowaną. Dziś skupię się na tym, co z tego wynika przy używaniu takiej funkcji.
Debug i Verbose
W wielu językach – zarówno programowania jak i skryptowych – pierwszą metodą sprawdzania co poszło nie tak jest wyświetlanie na ekran informacji, które mogą pomóc ustalić, gdzie popełniliśmy błąd. To generuje dwa problemy:
- nie usunięte komunikaty pojawiają się w najmniej odpowiednim momencie
- ponowne “włączenie” komunikatów po ich usunięciu jest niemożliwe
PowerShell ma bardzo przydatną właściwość: generuje nieco więcej strumieni niż domyślne wyjście standardowe i wyjście błędów: mamy dodatkowo ostrzeżenia, informacje rozszerzone (verbose) i komunikaty pomagające debugować (debug). Komunikaty verbose i debug można z powodzeniem pozostawić (jeśli są cenzuralne
) a w razie potrzeby – włączyć na żądanie. W funkcjach zwykłych wymagałoby to zmiany $DebugPreference i $VerbosePreference na poziomie sesji. Funkcje zaawansowane dają nam możliwość użycia po prostu jednego z parametrów: –Debug lub –Verbose. Dzięki temu automagicznie zmienia się wartość odpowiedniej zmiennej i wszystkie komunikaty pojawiają się w odpowiednich strumieniach. Dodajmy więc komunikaty dotyczące zawartości naszych parametrów:
Write-Verbose "Format: $Format" Write-Verbose "Liczba: $Liczba" Write-Verbose "Slowo: $Slowo" if ($Format) { Write-Debug "Wygląda na to, że używamy liczby." }
Begin, Process, End
Funkcja w PowerShellu może mieć trzy odseparowane części, co ułatwia zdecydowanie pracę w potokach i dzięki czemu każda funkcja może działać podobnie jak prawdziwy cmdlet (jeśli chodzi o funkcjonalność, nie o wydajność). Mamy więc:
- begin gdzie można zainicjować połączenia, zdefiniować funkcje pomocnicze, przetworzyć zmienne podawane “w linii”
- process stanowiący serce funkcji działającej w rurce: tu możemy sięgnąć do parametrów pobieranych z poprzedniej komendy i przekazać informację dalej
- end który sprząta na koniec: zabija połączenia, czyści pamięć ze zbędnych elementów
Jeśli zamierzamy korzystać tylko z parametrów podawanych w linii – nasza funkcja może mieć jedno, zbiorcze “ciało”. Jeśli jednak chcemy skorzystać z pipe’a – absolutnym minimum jest dodanie części process, pozostałe dwie pozostają wówczas opcjonalne. Begin ma sens wtedy, gdy potrzebujemy pewną operację przeprowadzić dokładnie raz na początku, a wykonywanie jej dla każdego elementu wpadającego w rurkę jest stratą czasu, czy wręcz może doprowadzić do nieoczekiwanych rezultatów. End działa podobnie – tylko operacja musi mieć miejsce dokładnie raz na końcu. Cała reszta – powinna trafić do bloku process. Zaawansowane funkcje tracą wiele jeśli pozbawimy ich tej troistości.
PSCmdlet
Zaawansowane funkcja ma jeszcze jedną wielką zaletę: w jej wnętrzu dostępna jest automatyczna zmienna ($PSCmdlet), która w zasadzie daje nam dostęp do tych samych metod i właściwości, jakie ma programista piszący cmdlet w C# lub VB.NET. Na blogu zespołu PowerShell jakiś czas temu opublikowana była prosta funkcja, która pozwala zajrzeć do wnętrza tej zmiennej, sprowadza się to właściwie do:
function Test-PSCmdlet { [CmdletBinding()] param() $p = $PSCmdlet function prompt { "Test-PSCmdlet> " } $host.EnterNestedPrompt() }
Dzięki temu możemy poeksperymentować ze zmienną $p (która jest kopią $PSCdmlet). W praktyce na ogół wykorzystuje się dwa jej elementy:
- właściwość ParameterSetName – pozwala ustalić, który zestaw parametrów jest wykorzystywany
- metody ShouldProcess i ShouldContinue – pozwalają zastosować parametry WhatIf oraz Confirm.
Ponieważ budowana przez nas zaawansowana funkcja wspiera ShouldProcess (a więc metody Shoud* będą w niej dostępne), oraz założyliśmy kilka różnych zestawów parametrów, więc wykorzystamy i jedno, i drugie:
process { if ($psCmdlet.ShouldProcess( "Cel: $Slowo $Liczba", "Operacja: Wyświetl" )) { switch ($psCmdlet.ParameterSetName) { Domyslny { "{0} to moje slowo" -f $Slowo } Liczby { "Moja liczba: {0:$Format}" -f $Liczba } } } }
Ponieważ część parametrów zamierzamy opcjonalnie pobierać z rurki – możemy do nich sięgać dopiero w bloku process. Jeśli mamy dwa zestawy parametrów na ogół wystarczy zwykły if – jeśli zestawów jest więcej, opcjonalnym rozwiązaniem może okazać się switch. Jak widać operacja przeprowadzana nie jest specjalnie niebezpieczna, ale czasem jednak funkcja napisana przez nas może nieść z sobą pewne ryzyko – warto wtedy wiedzieć jak zaimplementować parametry –WhatIf i –Confirm.
I tym optymistycznym akcentem kończę temat zaawansowanych funkcji. Jeszcze tylko nasza zaawansowana funkcja w pełnej krasie:
function Get-Foo { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'Domyslny' )] param ( [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Liczby' )] [double]$Liczba, [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'Domyslny' )] [ValidatePattern('^\w+$')] [string]$Slowo, [Parameter( Position = 1, ParameterSetName = 'Liczby', Mandatory = $true, HelpMessage = 'Pomozcie mi!' )] [ValidateSet('P4','N4','C4')] [string]$Format ) begin { Write-Verbose "Format: $Format" Write-Verbose "Liczba: $Liczba" Write-Verbose "Slowo: $Slowo" if ($Format) { Write-Debug "Wygląda na to, że używamy liczby." } } process { if ($psCmdlet.ShouldProcess( "Cel: $Slowo $Liczba", "Operacja: Wyświetl" )) { switch ($psCmdlet.ParameterSetName) { Domyslny { "{0} to moje slowo" -f $Slowo } Liczby { "Moja liczba: {0:$Format}" -f $Liczba } } } } }
Następną część zamierzam poświęcić kolejnej funkcjonalności, przydatnej zarówno w funkcjach (również zaawansowanych) jak i w skryptach: pomocy generowanej w oparciu o komentarze.
PowerShell – efektywnie(j), część 5.
| 2011-10-25 | Posted by Bartek Bielawski under najlepsze praktyki, Polskie blogi IT, Skrypty, zaawansowane funkcje |
|
W wersji drugiej PowerShell został dość mocno rozbudowany o nowe możliwości: zdalne uruchamianie kodu, praca w tle, moduły… Sam język też zyskał bardzo wiele, przede wszystkim – zaawansowane funkcje. Czy może raczej: zaawansowane scriptblocki. Jest bowiem prawdą, że niezależnie od tego jak nasz blok skryptu jest zapisany: w postaci funkcji w pamięci, w postaci zmiennej typu [ScriptBlock] czy też w postaci pliku z rozszerzeniem ps1 – może on w każdej z tych postaci być zaawansowany. Tą część cyklu zamierzam poświęcić w całości temu, w czym objawia się to “zaawansowanie” i jak je wykorzystać.
Spytaj PowerShella.
Parafrazując mój ulubiony zespół punk-rockowy: “Spytaj PowerShella, on ci prawdę powie, spytaj PowerShella – on ci wskaże drogę”. W tym wypadku pytanie brzmieć powinno: get-help about_functions_advanced*, pytanie pomocnicze: get-help about_functions_CmdletBindingAttribute. Postaram się opisać najważniejsze cechy funkcji zaawansowanych w tym artykule w tym, oraz następnym artykule, ale na pewno coś mi umknie. PowerShell (przynajmniej z założenia) powinien dostarczyć nam komplet informacji. Zacznijmy więc od początku. Zbudujmy od zera zaawansowaną funkcję. Nie będzie ani troszkę funkcjonalna, ale za to bardzo, bardzo zaawansowana. ![]()
CmdletBinding
Od tego wszystko się zaczyna. PowerShell jest domyślny, więc w wielu sytuacjach ten element może się okazać zbędny. Jednak warto od niego zaczynać każdą funkcję. Nic to nie kosztuje, a daje kilka korzyści. Należy jednak pamiętać, że konstrukcja ta wymaga posiadania bloku param (może on być pusty) i może być rozbudowana o kilka opcji:
- SupportsShouldProcess – jeśli chcemy by nasza funkcja rozumiała –WhatIf i –Confirm, dopuszczalne wartości: $true, $false
- ConfirmImpact – jeśli zależy nam na tym, by pytanie o zgodę pojawiało się częściej, lub zawsze, dopuszczalne wartości: High, Medium, Low
- DefaultParameterSetName – jeśli definiujemy kilka zestawów parametrów i chcemy mieć pewność, że jeden z nich będzie używany domyślnie, dopuszczalne wartości: nazwy wszystkich użytych zestawów parametrów
W 9/10 przypadków [CmdletBinding()] w zupełności wystarczy, dobrze jednak wiedzieć, że to nie wszystko co ma do zaoferowania ta konstrukcja. Nasza funkcja oczywiście zalicza się do tych 10%, wszystko-w-sobie-mających:
function Get-Foo { [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'Domyslny' )] param (
Parameter
Wchodzimy do bloku definiowania parametrów i zaczynamy je zdobić. [Parameter()] to podstawa: pozwala nam zdefiniować parametry niezbędne (Mandatory), uzupełnić parametry o pomoc (HelpMessage), zdecydować czy będą “konsumować” rurkę i jak będą to robić (ValueFromPipeline, ValueFromPipelineByPropertyName), jaką pozycję mogą zajmować (Position), który zestaw parametrów opisujemy (ParameterSetName) i wreszcie – że parametr połknie całą resztą argumentów, do których nikt inny się nie przyznaje (ValueFromRemainingArguments). Należy pamiętać, że jeśli mamy kilka zestawów parametrów a opisywany parametr ma pojawiać się tylko w niektórych – to trzeba to wyraźnie zdefiniować (kilka bloków [Parameter()]). Jeśli parametr pojawia się wszędzie i wszędzie ma zachowywać się identycznie – wystarczy jedna deklaracja. My zdefiniujemy sobie dwa zestawy parametrów: liczba wraz z odpowiednim formatowaniem, lub po prostu słowo:
param ( [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipeline = $true, Position = 0, ParameterSetName = 'Liczby' )] [double]$Liczba, [Parameter( Mandatory = $true, HelpMessage = 'Pomocy!', ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'Domyslny' )] [ValidatePattern('^\w+$')] [string]$Slowo, [Parameter( Position = 1, ParameterSetName = 'Liczby', Mandatory = $true, HelpMessage = 'Pomozcie mi!' )] [ValidateSet('P4','N4','C4')] [string]$Format )
Jak widać – można mieć dwa parametry o tej samej pozycji. O tym, który zostanie wykorzystany, decyduje zestaw parametrów: domyślnie nawet liczba zostanie potraktowana jako string. Jeśli jednak dodamy –Format (występujący tylko w zestawie z liczbami) PowerShell spróbuje pozycyjny parametr przerobić na [double]:
Jest wyjątek od tej reguły: jeśli typ będzie idealnie pasował do oczekiwanego (1.123123) to PowerShell wybierze ten zestaw, który pobiera zmienną danego typu.
Walidacja na wejściu
Kolejny efekt pisania funkcji jako zaawansowanej to możliwość walidacji tego, co użytkownik próbuje do funkcji wrzucić. Zgodnie z regułą GIGO (nie wiem czy polska wersja, choć bardziej dosadna, nie oddaje bardziej powagi sytuacji) – walidacja wejścia ma sens i należy jej używać zawsze wtedy, gdy zła informacja na wejściu może spowodować nieoczekiwane (na ogół bolesne) rezultaty. Sposobów walidacji mamy kilka:
- zestaw do wyboru: ValidateSet
- odpowiednia ilość elementów: ValidateCount
- odpowiednia matryca z wyrażeń regularnych: ValidatePattern
- odpowiednia długość: ValidateLength
- odpowiedni zakres wartośći: ValidateRange
- nie jest ‘zerowy’: ValidateNotNull
- nie jest ‘zerowy’ ani pusty: ValidateNotNullOrEmpty
- i najbardziej elastyczny: ValidateScript
Wszystkie te elementy ograniczają nasz element, jednak jeśli nasz argument jest wymagany (Mandatory) czasem konieczne może się okazać przymknięcie oka na pewne niedoskonałości (zwłaszcza jeśli element wpada nam z “rurki”):
- zezwól na wartości zerowe: AllowNull
- zezwól na pusty string: AllowEmptyString
- zezwól na puste kolekcje: AllowEmptyCollection
Jak widać jest w czym wybierać. Mój ulubiony to ValidateScript, który akceptuje parametr jeśli wynik skryptu będzie $true, odrzuci – jeśli wynik będzie $false, albo wyskoczy nam jakiś wyjątek. Dzięki temu i komendzie ‘throw’ możemy uczynić komunikat o błędzie bardziej zrozumiałym (zwłaszcza jeśli lokalizacja do języka narodowego nadal nie istnieje
).
Aliasy dla parametrów.
Aliasy mają dwa główne cele:
- umożliwienie wykorzystania w naszej funkcji różnych właściwości obiektów (ComputerName, Name, Nazwa)
- ułatwienie pracy z komendą “interaktywnie” (CN, NK)
Wydaje mi się, że druga ewentualność jest dość jasna. Co do pierwszej: jeśli zdarzy się, że jakaś komenda wyrzuca z siebie obiekty o właściwości Foo, która nam odpowiada a inna wyrzuca obiekty o właściwości Bar, która niczym się nie różni od Foo, to mamy idealnego kandydata na alias. Nazywając parametr Foo i nadając mu alias Bar zyskamy możliwość pobierania obiektów z obu komend. Wielokrotnie stosowałem tę metodę by móc “połykać” obiekty dotyczące komputerów i przypisywać wartość właściwości Name parametrowi ComputerName. Bez aliasów byłoby to trudne, lub wręcz niemożliwe…
I w ten oto sposób zamknęliśmy blok param () – przed nami wszystko to, co kryje się w automatycznie tworzonej w zaawansowanych funkcjach zmiennej $psCmdlet oraz kilka słów o Write-* w funkcji zaawansowanej. Zamierzam też wspomnieć, po co i gdzie używać konstrukcji begin, process i end. I choć wielka trójca była już dostępna w PowerShellu w wersji 1, to nie sposób pominąć tego tematu przy omawianiu zaawansowanych funkcji.
PowerShell – efektywnie(j), część 4.
| 2011-10-23 | Posted by Bartek Bielawski under najlepsze praktyki, Początki, Polskie blogi IT, Skrypty, zaawansowane funkcje |
|
Troszkę to trwało nim udało mi się przysiąść do tego artykułu. Przyczyna jest prozaiczna: temat jest dość obszerny i nie wiem jak go ugryźć. Pisanie skryptów w PowerShellu nie wymaga wielkiego wysiłku, ale jeśli chcemy by nasz skrypt był przydatny dla innych i dla nas za pół roku – trzeba się nieco przyłożyć w czasie jego tworzenia. Uznałem jednak, że rzecz sama się nie zrobi. W najgorszym wypadku po prostu rozpiszę się nieco bardziej niż zwykle (ha, ha, ha…
)
Od czego zacząć?
W zasadzie pierwsze pytanie jakie powinniśmy sobie zadać powinno brzmieć: czy chcemy napisać coś, co wykona serię operacji i da nam na koniec jakiś wynik (skrypt) czy bardziej interesuje nas biblioteka narzędzi (moduł). Dalej jest już prościej: dzielimy zadanie na proste czynności (funkcje), które następnie połączymy w całość. I o ile w przypadku modułów sprawa wydaje się “czysta” i dość naturalna, o tyle w wypadku skryptów musimy zwalczyć pokusę umieszczenia operacji w jednym, wielki worze.
Kod wielokrotnego użycia
Skrypty lubią rosnąć niby hydra: dodajemy do nich kolejne funkcjonalności, jednocześnie nie specjalnie dbając o to, by kod można było później wykorzystać. To moim zdaniem spory błąd: utrudnia ponowne wykorzystanie napisanego kodu w innym narzędziu, czyni narzędzie dość statycznym i dość silnie uwiązanym do wykorzystanej technologii. Może prosty przykład, by pokazać o co mi chodzi…
Naturalna kolej rzeczy: nasz skrypt może przyjąć jako parametr samą listę (przekazaną przez rurkę), OU w AD, plik txt/ csv z listą komputerów itd. Na wyjściu – podamy ścieżkę do pliku .csv w którym dane zostaną zapisane. To co się dzieje w środku zależy już od nas. Możemy więc albo zrobić to w formie funkcji, które następnie ustawimy w jednej “rurce” i uzyskamy pożądanych efekt. Albo wszystko zrobić za jednym zamachem, bez dziabania kodu na poszczególne fragmenty. Co tracimy?
- przejrzystość kodu (logika się “zlewa”)
- utrudnienia przy wymianie komponentu
- rozwiązując podobny problem musimy praktycznie kopiować cały skrypt, albo zacząć pisanie od początku
Problem, jaki może się pojawić, to konieczność zdefiniowania funkcji zanim ich użyjemy. Jak to w prosty sposób rozwiązać? Mój ulubiony trik to definiowanie na początku funkcji, która ma za zadanie tylko opisać zakładaną logikę – czy inaczej – szkic tego co chciałbym osiągnąć. Na ogół wygląda to tak:
#requires -version 2.0 <# Pomoc do skryptu #> function Invoke-Main { param ( $Parametr = $Script:Parametr, $DrugiParametr = $Script:DrugiParametr, $Path = $Script:Path ) Get-Foo -Parametr $Parametr | Test-Foo | ConvertTo-Bar -Drugi $DrugiParametr | Export-Csv -Path $Path } <# Tu definiujemy: * Get-Foo * Test-Foo * ConvertTo-Bar #> Invoke-Main
Jak widać – definicje funkcji następują przed jej wywołaniem (które ma miejsce na samym końcu skryptu) ale sposób ich wywołania znany jest od początku (zdefiniowany w pierwszej funkcji w skrypcie). Jeśli zechcę kiedyś zmienić finalny obiekt na ‘Boo’ zamiast funkcji ConvertTo-Bar zdefiniuję ConvertTo-Boo, zmienię nieco Invoke-Main i już – skrypt gotowy do użycia.
Funkcja w rurce – awansujemy.
Patrząc na składnię moich hipotetycznych funkcji od razu widać jedną rzecz: wszystkie “klocki” radzą sobie w rurce. To podstawa: pisząc funkcję niezdolną do pracy w środku pipe’a strzelamy sobie w stopę. By jednak współpraca przebiegała bezboleśnie – należy z całą pewnością przyzwyczaić się do pisania zaawansowanych funkcji. Wymagają one nieco dekoracji na wstępie, ale wartość dodaną trudno moim zdaniem przecenić. Druga rzecz, która być może w skryptach nie jest tak cenna, ale w modułach moim zdaniem absolutnie niezbędna: pomoc do funkcji. Wymaga to maciupeńkich nakładów pracy (odpowiednio skomponowany komentarz) a oszczędza konieczności czytania definicji funkcji za każdym razem. Myślę, że sam temat zaawansowanych funkcji jest zbyt obszerny, by go streścić tutaj, zostawię więc to na piątą część cyklu. Na rozbudzenie apetytu: malutka funkcja, która wykorzystuje część możliwości, które dają zaawansowane funkcje:
function Test-IsAlive { [CmdletBinding()] param ( [Parameter( ValueFromPipelineByPropertyName = $true, Mandatory = $true, HelpMessage = 'Nazwa komputera, ktory testujemy' )] [ValidatePattern('(?# Bez spacji w nazwach komputerow!)^\S+$')] [Alias('Name','CN','Nazwa')] [string]$ComputerName, [Parameter(ValueFromPipeline = $true)] [Alias('IO')] [PSObject]$InputObject ) process { Write-Verbose "Testuje komputer: $ComputerName" if (Test-Connection -ComputerName $ComputerName -Quiet -Count 1) { if (!$InputObject) { Write-Verbose 'Nic na wejsciu, tworzymy sami.' $InputObject = New-Object PSObject -Property @{ ComputerName = $ComputerName } } $InputObject | Add-Member NoteProperty IsAlive $true -PassThru } } }
Skutek? Mogę przez tą funkcję przepuścić dowolny obiekt, który posiada właściwość ComputerName, Name, Nazwa lub CN i jeśli właściwość ta nie zawiera spacji (ValidatePattern, z komentarzem, by nie straszyć ludzi niezrozumiałym regexpem) i da się ją “pingnąć” (preferowane więc będą obiekty pobrane z AD, lub innego zawierającego nazwę komputera) to na wyjściu dostanę ten sam obiekt “wzbogacony” o właściwość IsAlive. Jeśli spróbuję uruchomić funkcję bez parametru – poprosi mnie ona o wartość ComputerName. I rzecz nie bez znaczenia: dodanie wtrąceń typu Write-Verbose/ Debug działa bez potrzeby implementacji. [CmdletBinding()] daje mi te opcje za darmo. Mogę więc uruchomić funkcję z parametrem –Debug lub –Verbose i dowiedzieć się więcej o tym, co dzieje się w środku.
Wejście – wyjście.
Skrypty mają to do siebie, że czasem wolelibyśmy nie musieć pamiętać co do nich trzeba włożyć, oraz co byśmy chcieli z nich wyjąć. Z drugiej strony – czasem jednak chcemy mieć wpływ na to, co się stanie… PowerShell pozwala pogodzić te dwie sprzeczności. Rozwiązanie to wyciągnąć jak najwięcej elementów na zewnątrz skryptu (w postaci parametrów) i jednocześnie przypisać im wartość domyślną (by nie być zmuszonym podawać ich za każdym razem). Z wyjściem jest gorzej – optymalne rozwiązanie to parametr typu [switch], który na wyjściu da nam “żywe” obiekty. To co z nimi zrobimy dalej będzie zależeć już tylko od nas. I wilk syty, i owca cała.
PowerShell – efektywnie(j), część 3.
| 2011-10-08 | Posted by Bartek Bielawski under konsola, najlepsze praktyki, Polskie blogi IT |
|
W trzeciej części jeszcze troszkę popastwię się nad konsolową pracą z PowerShellem. Opiszę kilka trików, które mogą nam pomóc oszczędzić nieco czasu i w miarę szybko dojść do pożądanych rezultatów.
Komentarze
Wydawać się może, że w interaktywnej pracy komentarze są zbędne. I faktycznie, raczej nie ma sensu korzystać z nich w celach dla których zostały stworzone. Czasem jednak zdarzy się, że zaczniemy pisać jakąś komendę (np. przenosimy komputer w AD do odpowiedniego kontenera). Mamy już niemal gotową rurkę, gdy zdajemy sobie sprawę, że nie pamiętamy całej ścieżki. Do wyboru mamy:
- [Esc], sprawdzamy, piszemy od początku
- [Home], wstawiamy #, [Enter], sprawdzamy, strzała w górę, usuwamy #, kończymy.
I choć drugie zdanie jest dłuższe – to jednak zwykle to jednak pierwsza opcja jest bardziej czasochłonna. Zwłaszcza, że w międzyczasie może zadzwonić telefon, szef wpadnie z niezapowiedzianą wizytą, pamięć nam się zresetuje… a Get-History zwykle nie zapomina. A przynajmniej nie od razu.
Drugi sposób twórczego wykorzystania komentarzy: w PowerShellu komentarz blokowy może znaleźć się dosłownie wszędzie, przetestujcie sami:
ls <# czyli dir #> -r <# czyli -Recurse #> -fi <# znaczy filtr #> *.ps1 -EA 0
Oczywiście, to samo mogłoby się znaleźć w jednej linii i nadal by działało. Jakie to może mieć praktyczne zastosowanie? Powiedzmy, że napisaliśmy jakiś zagmatwany filtr do Where-Object, który nie daje żadnych rezultatów… Wystarczy czasem “wyłączyć” na moment jego fragment (odpowiednio zamykając go w <# #>) i sprawdzić, która część nie daje oczekiwanego efektu. W ostateczności można wyłączyć Where-Object a całość przepuścić przez Get-Member by upewnić się, czy nie pozajączkowała nam się nazwa właściwości.
Historia
Wspominałem już o jednym z trików związanych z historią. Jest ich więcej, ale sama historia domyślnie jest dość krótka: $MaximumHistoryCount, która za długość historii odpowiada, domyślnie ma wartość 64. Zwykle zwiększam ją do wartości maksymalnej.
Gdy już mamy jakąś historię istnieje kilka opcji, by z niej skorzystać. Przede wszystkim możemy użyć kombinacji # z tabulatorem. #Numer przywoła komendę o danym ID. #Ciąg Znaków przywoła wszystkie komendy, które pasują do wzorca (ciąg znaków nie musi znajdować się na początku).
Można też uruchomić polecenie bezpośrednio z historii. Invoke-History pobiera polecenie o podany –Id i wykonuje je ponownie. By jednak efektywnie z niego korzystać dobrze jest wiedzieć jakie id miało każde z wykonanych wcześniej poleceń. Bardzo w tym pomaga odpowiednio przygotowany prompt (mój typ to dzieło Jaykula, o którym wspominałem w cyklu o profilach).
ScriptBlock jako argument
Gdy to pierwszy raz zobaczyłem, najzwyczajnie w świecie powaliło mnie z nóg. Otóż jeśli jakieś polecenie potrafi pobierać informacje z rurki, to możemy “nakarmić” je scriptblokiem. Oczywiście – jeśli scriptblock to nasze możliwości właśnie stały się niemal nieograniczone. Zwykle jednak nie jest nam potrzebny 20-linijkowy skrypt, starcza lekki retusz obiektu wpadającego z innego polecenia. Jest jeden warunek: musi być to pobieranie z rurki imienne, nie na podstawie typu (oczywiście nie wadzi jeśli możliwe są oba rozwiązania). Prosty przykład:
ls *.ps1 | Rename-Item -NewName { $_.BaseName + '.psm1' } -WhatIf
I nagle wszystkie skrypty w bieżącym katalogu zmieniły(by) się w moduły.
Inne zastosowania to już kwestia naszej kreatywności. Akurat ten przykład (ze zmianą nazw plików) jest moim ulubionym.
Podsumowanie
Praca w konsoli jest tym przyjemniejsza, im swobodniej się w niej poruszamy. Myślę, że warto poświęcić nieco czasu na jej wybór, dostosowanie, zapoznać się z jej możliwościami (i ograniczeniami). Później odpowiednio ją dopasować “pod siebie” przy pomocy profilu i/ lub modułów.
W kolejnych kilku częściach tego cyklu zajmę się funkcjami, skryptami, modułami… Pewnie rozwlecze się to na kilka łaaadnych artykułów, bo to temat-rzeka. Do konsoli wrócę jeszcze na koniec, gdy opiszę wędrówkę z konsoli do skryptu i z powrotem.
PowerShell – efektywnie(j), część 2.
| 2011-10-04 | Posted by Bartek Bielawski under konsola, najlepsze praktyki, Początki, Polskie blogi IT |
|
W części pierwszej tego cyklu opisałem ogólnie to, jak moim zdaniem praca w PowerShellu różni się w zależności od naszej aktualnej aktywności. Skupiłem się na różnicach między trybem pracy interaktywnej i trybem tworzenia skryptów. Dziś, zgodnie z obietnicą, poznęcam się nieco nad pracą w konsoli.
Badacz
PowerShell daje nam możliwości, których próżno szukać w VBS. Trudno je też znaleźć w cmd, choć temu ostatniemu nieco bliżej do interaktywnej natury PowerShella. W VBS byliśmy skazani na pisanie od zera, sklejanie skryptów z klocków, żmudne testy bez prostej możliwości wglądu w działanie skryptu. PowerShell pozwala każdy obiekt zbadać, obejrzeć, obrócić, przeskanować, rozłożyć na czynniki pierwsze. Nie korzystając z tych możliwości ograniczamy sobie pole działania. Wielu rzeczy o PowerShellu dowiadywałem się po prostu klepiąc TABem na różnych obiektach. Get-Member też zwykle służy pomocą. To wszystko sprawia, że PowerShell jest wręcz stworzony do zgłębiania świata .NET. Swoją drogą to dumam, czy programistom zdarza się uruchamiać PowerShella tylko po to, by sobie szybciutko pogmerać w jakimś skompilowany kodzie…
Oprócz tego możemy pracując obserwować cały czas rezultaty i nasze zachowanie dostosowywać do wyników. Jeśli jakaś metoda wyrzuca błąd – możemy sprawdzić jej definicję, zmodyfikować, uruchomić ponownie… aż do skutku.
Z pewną taką nieśmiałością…
Pisałem już o tym chyba kiedyś, ale ta cecha PowerShella niezmiennie mnie zachwyca. Pięknie o tym mówi w czasie swoich prezentacji o PowerShellu Jeffrey Snover. Spróbuję przetłumaczyć
Odwieczny dylemat administratora:
Czy ta komenda jest poprawna? Czy jeśli ją wykonam, to stracę pracę? Czy przez moją pomyłkę Buffy i Puffy nie pójdą na studia? Może lepiej sprawdzą Co Jeśli…
-WhatIf i –Confirm – dwa parametry, które pewnie nie raz uratują adminowi jego cztery litery, lub przynajmniej pozwolą uniknąć 16-godzinnej dniówki. Korzystać z nich można, należy. Co więcej – korzystanie z –Confirm można w prosty (stosunkowo) sposób wymusić: wystarczy ustawić $ConfirmPreference na odpowiednio niską wartość. Domyślnie jest High (czyli tylko komendy z ConfirmImpact równym High będą wymuszać potwierdzenie). Ustawienie tego parametru na ‘Low’ może sprawić, że PowerShell będzie nas pytał o niemal wszystko.
WhatIf też można wymusić ($WhatIfPrerence = 1) ale to chyba już przesada… Zamiast coś robić, będziemy tylko dostawać informację co by było gdyby… Za to raczej nam nikt nie zapłaci.
A odwrócić to można tylko używając składni:
Komenda –WhatIf:$false
Niezbyt przyjazne, jeśli mam być szczery…
Klocki lego
Pisanie pełnej, “wypasionej” komendy od zera to troszkę jak malowanie obrazu bez szkiców… PowerShell to idealny szkicownik: możemy wykonać jakąś “rurkę”, sprawdzić czy rezultat nas zadowala, dodać kolejną komendę do potoku, sprawdzić. Zapisać w zmiennej, sprawdzić zawartość, przepuścić ją przez foreach ()… I tak, krok po kroku, dojść od koncepcji do pełnego rozwiązania. Czasem oczywiście nie jest to konieczne: łatwiej nam będzie zmodyfikować coś, co już kiedyś w pocie czoła stworzyliśmy. Ale na początku lepiej właśnie posłużyć się techniką “małych kroczków”. PowerShell, przez swoją interaktywność oraz fakt, że składnia w skrypcie niczym się nie różni od tej pisanej w konsoli, daje nam taką możliwość. Warto z niej skorzystać by nie napracować się nad jakimś skryptem tylko po to, by na koniec odkryć, że obiekt który zapowiadał się obiecująco kompletnie nie nadaje się do rozwiązania postawionego przed nami problemu. Budując rozwiązanie stopniowo unikniemy takich frustrujących i zniechęcających przygód.
Krótkie podsumowanie
Na dziś kończę. W następnej części skupię się na “trikach”, które często w konsoli wykorzystuję. Większość z nich opisałem w moim gościnnym artykule na blogu Hey, Scripting Guy! Część jednak dość lakonicznie, tu postaram się nieco bardziej rozpisać…
PowerShell – efektywnie(j), część 1.
| 2011-09-24 | Posted by Bartek Bielawski under najlepsze praktyki, Początki, Polskie blogi IT |
|
Wszystkie narzędzia używane przez człowieka mają swoje ograniczenia. Wkręt można wbić młotkiem, ale śrubokręt zwykle daje lepszy rezultat. Tak samo naturalnie jest z PowerShellem: można walić nim jak młotem, można subtelnie trafiać idealnie w problem. W tej serii postaram się opisać to, co moim zdaniem może pracę w PowerShellu usprawnić, uprościć i dostosować do poziomu, na którym obecnie pracujemy.
Wyróżniłbym dwa skrajne poziomy, wraz z pewnym obszarem pośrednim:
Innymi zasadami kierowałbym się w każdym z tych obszarów. Dlaczego? Bo życie należy sobie ułatwiać. Na początek postaram się w skrócie przedstawić różnice pomiędzy oboma trybami (tryb pośredni pozostawię sobie na deser) a następnie zagłębie się w to, co moim zdaniem może uczynić pracę na obu poziomach bardziej efektywną, czasem również efektówną.
Aliasy
Pracując interaktywnie korzystamy z aliasów: po to zostały stworzone. W skrypcie – każdy alias to potencjalne źródło problemów, błędów i dodatkowo – element pogarszający czytelność skryptu. Przykład z życia wzięty: doskonały moduł ShowUI w jednym z przykładowych skryptów korzysta z aliasu “ps”. Długo musiałem dochodzić do tego, czemu ten konkretny przykład u mnie nie działał. Przyczyna? Usuwam w profilu w/w alias, mam dysk PS:, do niego odpowiednią funkcję, która działa podobnie do a:, c:, d: – alias mi uniemożliwiał szybkie dopełnianie nazwy tabem, więc go usunąłem. Skrypt, mimo że napisany poprawnie, stracił swą funkcjonalność.
Pisałem o czytelności… Dla początkującego użytkownika PowerShella taki kod będzie mało czytelny…:
h|?{$_.Id-gt5}|%{ $_.CommandLine-replace'^.(\w+)','$1'}
Przykładów dowodzących zasadności używania aliasów w shellu jest pewnie jeszcze więcej. Wystarczy porównać:
# Z aliasami... ls -r -fo -ea 0 -fi *.ps1 # I bez.Get-ChildItem -Recurse -Force ` -ErrorAction SilentlyContinue -Filter *.ps1
Aliasy są też bardzo przydatne, jeśli używacie czasem narzędzi nie przesiadujących w Waszej ścieżce: można albo dodać kolejny folder do $env:PATH, albo stworzyć alias, który będzie uruchamiał potrzebne narzędzie.
Profile
Pracując interaktywnie bez profilu tracimy czas – wiele funkcji i narzędzi definiujemy za każdym razem, zamiast zapisać je raz i później po prostu używać. Profil może za nas załadować wtyczki i moduły, może dodać wykorzystywane przez nas często zmienne, stworzyć dyski – możliwości jest bardzo wiele.
Skrypty dla odmiany powinny być kompletnie niezależne od profilu. Skrypt, jeśli jest napisany z głową, powinien sprawdzić czy wszystkie niezbędne do jego działania elementy są już w środowisku. Do testów najlepiej użyć możliwości, jakie daje powershell.exe – ma on parametr –noprofile, który świetnie nadaje się do uruchamiania “środowiska testowego” dla skryptów.
Host i UAC
Pracując interaktywnie korzystajmy raczej z hosta, który daje nam największy komfort. Dobrze poświęcić nieco czasu, “sprawdzić” dostępne opcje – w tym również opcje komercyjne (na ogół dostępne są wersje trial). Nie ma rozwiązania idealnego dla wszystkich, dlatego najlepiej “posmakować” dostępnych opcji i wybrać taką, w której będziemy się czuć najlepiej. Dodatkowo warto rozważyć ustawienie wybranego hosta tak, by uruchamiał się z “odpowiednimi” uprawnieniami. Osobiście uważam UAC za świetny wynalazek, nawet na serwerach go nie wyłączam. Ale póki PowerShell nie ma możliwości w prosty sposób “przeskoczyć” na wyższy poziom w trakcie pracy – wolę go od razu na tym wyższym poziomie uruchamiać.
Skrypty dla odmiany powinny “umieć się zachować”. Naturalnie, są takie, których użycie w innym hoście niż ten, w którym go tworzono, po prostu nie ma sensu. Ale jeśli skrypt nie jest z hostem bezpośrednio “powiązany” – dajmy użytkownikowi wybór. To oznacza, że powinniśmy przetestować nasz skrypt w kilku hostach, absolutne moim zdaniem minimum to PowerShell.exe i PowerShell ISE. Jeśli zaś chodzi o UAC – skrypt powinien wiedzieć, co będzie mu potrzebne. Powinien uprawnienia sprawdzić. I jeśli mu czegoś brakuje – uprzedzić użytkownika zanim spróbuje coś zmodyfikować.
Formalizm
Pracując interaktywnie tworzymy czasem krótkie funkcje, bloki skryptu, tymczasowe narzędzia. Ponieważ stworzyliśmy je przed chwilką – nie ma sensu ich dokumentować. Get-Help raczej na nich nie użyjemy. Nie ma też raczej powodu nadmiernie rozbudowywać blok przyjmujący parametry – wszelkie walidacje nie mają większego sensu, wiemy jak wygląda nasza funkcja, więc znamy jej ograniczenia, wiemy z jakimi założeniami została stworzona.
Skrypt dla odmiany powinien być dobrze udokumentowany. Korzystajmy z tego wszystkiego, co daje nam środowisko: pomoc w komentarzach, walidacja parametrów, zmienne z usztywnionymi typami, informacje typu warning, debug, verbose – to wszystko ułatwi nam modyfikowanie skryptu w przyszłości i używanie go bez konieczności otwierania go w edytorze za każdym razem. Koszt takiej operacji w wypadku skryptów używanych od czasu do czasu szybko się zwróci. A ewentualna rozbudowa o dodatkowe funkcje nie przyprawi nas o ból głowy.
Co dalej?
Ten wpis zaczyna pewien cykl. Dalej zagłębię się nieco bardziej w obu skrajnościach, na koniec zaś postaram się napisać, jak w prosty sposób można “przeskoczyć” z poziomu interaktywnego do skryptowego. Druga część będzie w całości poświęcona pracy interaktywnej w PowerShellu.