Udostępnij za pośrednictwem


Jak utworzyć moduł binarny biblioteki standardowej

Niedawno miałem pomysł na moduł, który chciałem zaimplementować jako moduł binarny. Jeszcze nie utworzyłem niczego przy użyciu standardowej biblioteki PowerShell, więc wydaje się to dobrą okazją. Użyłem przewodnika Tworzenie wieloplatformowego modułu binarnego, aby stworzyć ten moduł bez żadnych trudności. Przejdziemy przez ten sam proces i dodam trochę dodatkowych komentarzy po drodze.

Notatka

Oryginalna wersja tego artykułu pojawiła się na blogu prowadzonym przez @KevinMarquette. Zespół programu PowerShell dziękuje Kevinowi za udostępnienie tej zawartości nam. Zapoznaj się ze swoim blogiem na PowerShellExplained.com.

Co to jest biblioteka standardowa programu PowerShell?

Biblioteka Standardowa programu PowerShell umożliwia tworzenie międzyplatformowych modułów, które działają zarówno w programie PowerShell, jak i programie Windows PowerShell 5.1.

Dlaczego moduły binarne?

Podczas pisania modułu w języku C# możesz zrezygnować z łatwego dostępu do poleceń cmdlet i funkcji programu PowerShell. Jeśli jednak tworzysz moduł, który nie zależy od wielu innych poleceń programu PowerShell, korzyść z wydajności może być znacząca. Program PowerShell został zoptymalizowany pod kątem administratora, a nie komputera. Przełączając się na C#, pozbywasz się narzutu dodanego przez PowerShell.

Na przykład mamy krytyczny proces, który wykonuje wiele pracy z formatem JSON i tabelami skrótów. Zoptymalizowaliśmy program PowerShell tak samo, jak to możliwe, ale proces nadal trwa 12 minut. Moduł już zawierał dużo PowerShell w stylu C#. Dzięki temu konwersja modułu binarnego jest czysta i prosta. Konwertując na moduł binarny, skróciliśmy czas procesu z ponad 12 minut do poniżej czterech minut.

Moduły hybrydowe

Polecenia cmdlet binarne można łączyć z zaawansowanymi funkcjami programu PowerShell. Wszystko, co wiesz o modułach skryptów, ma takie samo zastosowanie. Zostanie uwzględniony pusty plik psm1, aby później można było dodać inne funkcje programu PowerShell.

Prawie wszystkie skompilowane cmdlety, które stworzyłem, rozpoczęły się jako funkcje PowerShell. Wszystkie nasze moduły binarne są naprawdę hybrydowymi modułami.

Skrypty kompilacji

Utrzymałem prostotę skryptu kompilacji. Zazwyczaj używam dużego skryptu Invoke-Build w ramach potoku CI/CD. Oferuje więcej możliwości, takich jak uruchamianie testów Pester, uruchamianie PSScriptAnalyzer, zarządzanie wersjonowaniem i publikowanie do galerii PSGallery. Kiedy zacząłem używać skryptu kompilacji dla moich modułów, udało mi się znaleźć wiele rzeczy do dodania.

Planowanie modułu

Plan dla tego modułu polega na utworzeniu folderu src dla kodu w języku C#, a resztę struktury zrobię tak, jak w przypadku modułu skryptu. Obejmuje to użycie skryptu kompilacji w celu skompilowania wszystkiego w folderze Output. Struktura folderów wygląda następująco:

MyModule
├───src
├───Output
│   └───MyModule
├───MyModule
│   ├───Data
│   ├───Private
│   └───Public
└───Tests

Wprowadzenie

Najpierw muszę utworzyć folder i utworzyć repozytorium git. Używam $module jako symbolu zastępczego nazwy modułu. Powinno to ułatwić ponowne użycie tych przykładów w razie potrzeby.

$module = 'MyModule'
New-Item -Path $module -Type Directory
Set-Location $module
git init

Następnie utwórz foldery na poziomie głównym.

New-Item -Path 'src' -Type Directory
New-Item -Path 'Output' -Type Directory
New-Item -Path 'Tests' -Type Directory
New-Item -Path $module -Type Directory

Konfiguracja modułu binarnego

Ten artykuł koncentruje się na module binarnym, więc od tego zaczniemy. Ta sekcja zawiera przykłady z przewodnika Tworzenie wieloplatformowego modułu binarnego. Zapoznaj się z tym przewodnikiem, jeśli potrzebujesz więcej szczegółów lub masz jakiekolwiek problemy.

Najpierw chcemy sprawdzić wersję zestawu SDK platformy dotnet core, który został zainstalowany. Używam wersji 2.1.4, ale przed kontynuowaniem należy mieć 2.0.0 lub nowszą.

PS> dotnet --version
2.1.4

Pracuję z folderu src w ramach tej sekcji.

Set-Location 'src'

Za pomocą polecenia dotnet utwórz nową bibliotekę klas.

dotnet new classlib --name $module

Spowodowało to utworzenie projektu biblioteki w podfolderze, ale nie chcę tego dodatkowego poziomu zagnieżdżania. Przeniosę te pliki na wyższy poziom.

Move-Item -Path .\$module\* -Destination .\
Remove-Item $module -Recurse

Ustaw wersję zestawu .NET Core SDK dla projektu. Mam zestaw SDK 2.1, więc określę 2.1.0. Użyj 2.0.0, jeśli używasz zestawu SDK w wersji 2.0.

dotnet new globaljson --sdk-version 2.1.0

Dodaj standardową bibliotekę programu PowerShellpakiet NuGet do projektu. Upewnij się, że używasz najnowszej wersji dostępnej dla wymaganego poziomu zgodności. Domyślnie używałbym najnowszej wersji, ale nie sądzę, aby ten moduł używał żadnych funkcji nowszych niż program PowerShell 3.0.

dotnet add package PowerShellStandard.Library --version 7.0.0-preview.1

Powinniśmy mieć folder src, który wygląda następująco:

PS> Get-ChildItem
    Directory: \MyModule\src

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        7/14/2018   9:51 PM                obj
-a----        7/14/2018   9:51 PM             86 Class1.cs
-a----        7/14/2018  10:03 PM            259 MyModule.csproj
-a----        7/14/2018  10:05 PM             45 global.json

Teraz możemy dodać własny kod do projektu.

Budowanie binarnego cmdlet

Musimy zaktualizować src\Class1.cs, aby zawierała ten inicjujący cmdlet:

using System;
using System.Management.Automation;

namespace MyModule
{
    [Cmdlet( VerbsDiagnostic.Resolve , "MyCmdlet")]
    public class ResolveMyCmdletCommand : PSCmdlet
    {
        [Parameter(Position=0)]
        public Object InputObject { get; set; }

        protected override void EndProcessing()
        {
            this.WriteObject(this.InputObject);
            base.EndProcessing();
        }
    }
}

Zmień nazwę pliku, aby był zgodny z nazwą klasy.

Rename-Item .\Class1.cs .\ResolveMyCmdletCommand.cs

Następnie możemy skompilować nasz moduł.

PS> dotnet build

Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Restore completed in 18.19 ms for C:\workspace\MyModule\src\MyModule.csproj.
MyModule -> C:\workspace\MyModule\src\bin\Debug\netstandard2.0\MyModule.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.19

Możemy wywołać Import-Module w nowej bibliotece DLL, aby załadować nasz nowy cmdlet.

PS> Import-Module .\bin\Debug\netstandard2.0\$module.dll
PS> Get-Command -Module $module

CommandType Name                    Version Source
----------- ----                    ------- ------
Cmdlet      Resolve-MyCmdlet        1.0.0.0 MyModule

Jeśli importowanie nie powiedzie się w systemie, spróbuj zaktualizować program .NET do wersji 4.7.1 lub nowszej. Przewodnik Tworzenie wieloplatformowego modułu binarnego zawiera więcej szczegółowych informacji na temat obsługi i zgodności platformy .NET dla starszych wersji platformy .NET.

Manifest modułu

Fajnie, że możemy zaimportować bibliotekę DLL i mieć działający moduł. Lubię nadal nad tym pracować i tworzyć manifest modułu. Potrzebujemy manifestu, jeśli później chcemy opublikować w PSGallery.

W katalogu głównym projektu możemy uruchomić to polecenie, aby utworzyć potrzebny manifest modułu.

$manifestSplat = @{
    Path              = ".\$module\$module.psd1"
    Author            = 'Kevin Marquette'
    NestedModules     = @('bin\MyModule.dll')
    RootModule        = "$module.psm1"
    FunctionsToExport = @('Resolve-MyCmdlet')
}
New-ModuleManifest @manifestSplat

Zamierzam również utworzyć pusty moduł główny dla przyszłych funkcji programu PowerShell.

Set-Content -Value '' -Path ".\$module\$module.psm1"

Dzięki temu można mieszać zarówno normalne funkcje programu PowerShell, jak i polecenia cmdlet binarne w tym samym projekcie.

Kompilowanie pełnego modułu

Skompiluję wszystko razem w folderze wyjściowym. Aby to zrobić, musimy utworzyć skrypt kompilacji. Zwykle dodawałbym to do skryptu Invoke-Build, ale możemy zachować to proste w tym przykładzie. Dodaj to do build.ps1 w katalogu głównym projektu.

$module = 'MyModule'
Push-Location $PSScriptRoot

dotnet build $PSScriptRoot\src -o $PSScriptRoot\output\$module\bin
Copy-Item "$PSScriptRoot\$module\*" "$PSScriptRoot\output\$module" -Recurse -Force

Import-Module "$PSScriptRoot\Output\$module\$module.psd1"
Invoke-Pester "$PSScriptRoot\Tests"

Te polecenia tworzą naszą bibliotekę DLL i umieszczają ją w naszym folderze output\$module\bin. Następnie kopiuje inne pliki modułów na właściwe miejsce.

Output
└───MyModule
    ├───MyModule.psd1
    ├───MyModule.psm1
    └───bin
        ├───MyModule.deps.json
        ├───MyModule.dll
        └───MyModule.pdb

W tym momencie możemy zaimportować moduł z plikiem psd1.

Import-Module ".\Output\$module\$module.psd1"

Stąd możemy przenieść folder .\Output\$module do katalogu $Env:PSModulePath, a nasze polecenie będzie automatycznie ładowane, kiedy tylko tego potrzebujemy.

Aktualizacja: dotnet new PSModule

Dowiedziałem się, że narzędzie dotnet ma szablon PSModule.

Wszystkie kroki opisane powyżej są nadal prawidłowe, ale ten szablon pomija wiele z nich. Nadal jest to dość nowy szablon, który wciąż jest dopracowywany. Spodziewaj się, że będzie coraz lepiej od teraz.

W ten sposób instalujesz i używasz szablonu PSModule.

dotnet new -i Microsoft.PowerShell.Standard.Module.Template
dotnet new psmodule
dotnet build
Import-Module "bin\Debug\netstandard2.0\$module.dll"
Get-Module $module

Ten minimalnie funkcjonalny szablon zajmuje się dodawaniem .NET SDK, biblioteki PowerShell Standard oraz utworzeniem przykładowej klasy w projekcie. Możesz go skompilować i uruchomić od razu.

Ważne szczegóły

Przed zakończeniem tego artykułu warto wspomnieć o kilku innych szczegółach.

Zwalnianie bibliotek DLL

Po załadowaniu modułu binarnego, nie można go faktycznie rozładować. Plik DLL jest zablokowany do czasu jego odłączenia. Może to być irytujące podczas opracowywania, ponieważ za każdym razem, gdy wprowadzasz zmianę i chcesz ją skompilować, plik jest często zablokowany. Jedynym niezawodnym sposobem rozwiązania tego problemu jest zamknięcie sesji programu PowerShell, która załadowała bibliotekę DLL.

Akcja okna ponownego ładowania programu VS Code

Większość mojej pracy deweloperskiej związanej z PowerShell wykonuję w programie VS Code. Kiedy pracuję nad modułem binarnym (lub modułem z klasami), za każdym razem, gdy kompiluję, doszedłem do nawyku ponownego ładowania programu VS Code. Ctrl+Shift+P wyskakuje okno polecenia i Reload Window jest zawsze na szczycie mojej listy.

Zagnieżdżone sesje programu PowerShell

Inna opcja to zapewnienie dobrego pokrycia testami Pester. Następnie możesz dostosować skrypt build.ps1, aby uruchomić nową sesję programu PowerShell, wykonać kompilację, uruchomić testy i zamknąć sesję.

Aktualizowanie zainstalowanych modułów

To blokowanie może być irytujące podczas próby zaktualizowania zainstalowanego lokalnie modułu. Jeśli jakakolwiek sesja ma to załadowane, musisz to znaleźć i zamknąć. To jest mniej problematyczne podczas instalowania z galerii PSGallery, ponieważ wersjonowanie modułów umieszcza nową wersję w innym folderze.

Możesz skonfigurować lokalną usługę PSGallery i publikować do niej jako część procesu kompilacji. Następnie wykonaj instalację lokalną z PSGallery. Brzmi to jak wiele pracy, ale może to być tak proste, jak uruchomienie kontenera platformy Docker. Opisuję sposób, jak to zrobić w moim wpisie Using a NuGet server for a PSRepository.

Ostatnie myśli

Nie omawiałem składni języka C# do tworzenia poleceń cmdlet, ale w zestawie Windows PowerShell SDK istnieje wiele dokumentacji na ten temat. To zdecydowanie coś, z czym warto eksperymentować jako próg do bardziej zaawansowanego C#.