Piszemy sobie moduł: C# (1)

ModulBinarny_01Pisanie modułów skryptowych w PowerShellu to rzecz trywialna dla każdego, komu zdarzyło się w PowerShellu "popełnić" kilka skryptów. W ostateczności moduł taki możemy utworzyć wrzucając kilka funkcji do pliku z rozszerzeniem psm1. Jeśli mamy skrypty stanowiące swoiste biblioteki funkcji może wręcz wystarczyć zmiana nazwy pliku. Wreszcie – importować w ten sposób można również pliki z rozszerzeniem ps1, nic nie stoi więc na przeszkodzie by zwyczajny skrypt zacząć traktować jako moduł skryptowy. Dlatego właśnie postanowiłem przyjrzeć się dwóm pozostałym opcjom. Na pierwszy ogień idzie moduł binarny. A ponieważ z języków, w jakich można moduł taki napisać, najbliższy memu sercu jest C# – z niego właśnie w trakcie pisania modułu skorzystam.

Witaj świecie!

Czy zaskoczy kogokolwiek fakt, że pierwszym naszym poleceniem witać będziemy cały, otaczający nas świat? Gwoli ścisłości: o tym kogo będziemy witać zdecyduje nasz użytkownik. My jedynie zaoferujemy mu stosownego do tego narzędzia.

Moduł tworzyć będę w Visual Studio: pewnie da się prościej/ szybciej/ wygodniej, ale mi jakoś zawsze najłatwiej było zmusić do współpracy właśnie tę aplikację. Na początek stworzyć musimy projekt, wybierając jako nasz cel utworzenie biblioteki – tym właśnie są moduły w PowerShellu. Nazwa projektu stanie się docelowo nazwą naszego modułu.

TworzenieProjectu-ModulPowerShell

Kolejna rzecz, o którą musimy zadbać, to dodać do projektu "System.Management.Automation.dll". W chwili obecnej najlepszym sposobem osiągnięcia tego celu jest zainstalowanie odpowiedniego pakietu NuGet:

Find-Package -Id Microsoft.PowerShell. | 
    Where-Object Id -match ReferenceAssemblies            
Install-Package Microsoft.PowerShell.5.ReferenceAssemblies

Zainstalowanie pakietu doda również wspomnianą bibliotekę do listy referencji.

DodajemyReferencjePowerShell5

Pozostaje nam więc jedynie dopisać ją do przestrzeni nazw, z których nasz kod będzie korzystał. Zanim przystąpimy jednak do poprawiania samego kodu, warto zmienić też nazwę pliku/ opisywanej klasy. Następnie zmienimy opisywaną przez nas klasę: dopiszemy do niej atrybut Cmdlet, w którym umieścimy informację o tworzonym poleceniu. Zadbamy też o to, by tworzona przez nas klasa dziedziczyła po klasie PSCmdlet:

using System.Management.Automation;

namespace Przywitania
{
    [Cmdlet(VerbsCommunications.Write, "Hello")]
    public class WriteHelloCommand : PSCmdlet
    {

Przyjrzyjmy się temu, co w klasie takiej powinno się znaleźć

Deklarujemy parametry

Definiowanie parametrów w ramach funkcji zaawansowanych znamy dość dobrze: zaczynamy od (opcjonalnego) atrybutu Parameter(), następnie podajemy aliasy, wszelkiego rodzaju walidacje (w naszym wypadku: wymóg, by imię zaczynało się od wielkiej litery). W ramach atrybutu możemy uwzględnić informację, że parametr akceptuje wartości przekazywane przez potok, jest wymagany, możemy też podać pomoc wyświetlaną w momencie dopytywania o wartość parametru. Możemy też podać typ tworzonej zmiennej:

    [Parameter(            
        ValueFromPipeline,            
        ValueFromPipelineByPropertyName,            
        Mandatory,            
        HelpMessage = 'Imię osoby, którą witamy'            
    )]            
    [Alias("Imię")]            
    [ValidatePattern('(?-i)^\p{Lu}')]            
    [String]$Name            

Definicja w C# różnić się będzie nieznacznie. Po prostu w pewnych elementach wymagane będzie podanie informacji jawnie. Oprócz tego wewnątrz klasy stosować będziemy prywatną właściwość, która zastąpi publiczny parametr:

        [Parameter(
            ValueFromPipeline = true,
            ValueFromPipelineByPropertyName = true,
            Mandatory = true,
            HelpMessage = "Imię osoby, którą witamy",
            Position = 0
            )]
        [Alias("Imię")]
        [ValidatePattern(@"(?-i)^\p{Lu}")]
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        private string _name;

Parametr mamy już przygotowany, pora go użyć.

Bloki kodu

W ramach funkcji w PowerShellu poszczególne bloki kodu, odpowiedzialne za etapy pracy potoku, umieszczamy w begin, process oraz end. Każdy blok jest do pewnego stopnia niezależny. W pierwszym na ogół tworzyć będziemy pomocnicze funkcje i zmienne wykorzystywane w ciele funkcji. Blok process ograniczamy do wszystkiego, co korzystać będzie z danych spływających w potoku. Wreszcie blok end, którego zadanie to usuwanie pozostałości po blokach poprzednich.

Tworząc cmdlety korzystać będziemy z podobnej procedury, z tym że zamiast tworzyć poszczególne bloki kodu, nadpiszemy metody BeginProcessing, ProcessRecord i EndProcessing naszą własną implementacją. Różnica polega na tym, że w przypadku klas metody te nie są nam niezbędne do tworzenia właściwości (zmiennych) i metod (funkcji) widocznych w całym cmdlecie: zamiast tego tworzyć możemy prywatne właściwości i metody. Nasze polecenie tego jednak nie wymaga, zaimplementujemy w nim po prostu poszczególne metody:

        protected override void BeginProcessing()
        {
            WriteVerbose("Zaczynamy!");
        }

        protected override void ProcessRecord()
        {
            WriteVerbose($"Przetwarzamy: {_name}");
            WriteObject($"Witaj, {_name}!");
        }

        protected override void EndProcessing()
        {
            WriteVerbose("Kończymy!");
        }

Tym sposobem kończymy definiowanie naszego polecenia. Naturalnie, w przypadku prawdziwego polecenia dodalibyśmy jeszcze obsługę błędów, ale w naszym wypadku założymy, że błędy nie wystąpią. Pozostaje więc tylko zbudować nasz projekt i spróbować go użyć.

Budowanie, odrobaczanie

Tworząc projekty w Visual Studio, warto skorzystać z oferowanego przezeń debugera i w ten sposób sprawdzić, czemu nasze polecenie (z założenia bezbłędne) błąd jednak wygenerowało. Najprościej efekt ten osiągnąć konfigurując odpowiednio opcję debugowania w ramach naszego projektu:

KonfigurujemyDebuggowanieWPowerShell

Precyzujemy więc, że uruchamiać będziemy PowerShella, a wśród parametrów skorzystamy z opcji NoProfile (oszczędzi nam to czekania na załadowanie wszystkich elementów naszego profilu) NoExit (dzięki czemu po wykonaniu poleceń okno pozostanie otwarte, ułatwiając nam eksperymenty) i opcji Command (w której załaduje świeżo utworzony moduł). Pozwoli nam to na dokładną analizę modułu, łącznie z wykorzystaniem punktów zatrzymania:

PunktZatrzymaniaModul

Możemy się witać z całym światem przy pomocy skompilowanego cmdletu. Czemu jednak chcielibyśmy w ogóle to robić i co dalej z tak pięknie rozpoczętym modułem? O tym postaram się napisać w drugiej części.

~ - autor: Bartek Bielawski w dniu 16 marca, 2016.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: