Jak utworzyć moduł binarny biblioteki standardowej

Niedawno miałem pomysł na moduł, który chciałem zaimplementować jako moduł binarny. Muszę jeszcze utworzyć jedną przy użyciu standardowej biblioteki programu PowerShell, więc czułem się jak dobra okazja. Użyto przewodnika Tworzenie międzyplatformowego modułu binarnego w celu utworzenia tego modułu bez żadnych przeszkód. Będziemy chodzić ten sam proces i dodam trochę dodatkowe komentarze po drodze.

Uwaga

Oryginalna wersja tego artykułu pojawiła się na blogu napisanym 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ę do języka C#, możesz przejść do przerzucania obciążenia dodanego przez program 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ł zawiera już wiele stylów języka C# programu PowerShell. Dzięki temu konwersja modułu binarnego jest czysta i prosta. Przekonwertowając moduł binarny 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 mieszać za pomocą zaawansowanych funkcji programu PowerShell. Wszystkie informacje o modułach skryptów są stosowane w taki sam sposób. psm1 Pusty plik jest dołączany, aby później dodać inne funkcje programu PowerShell.

Prawie wszystkie skompilowane polecenia cmdlet utworzone przeze mnie zostały uruchomione jako funkcje programu PowerShell. Wszystkie nasze moduły binarne są naprawdę hybrydowymi modułami.

Skrypty kompilacji

Trzymałem skrypt kompilacji prosty tutaj. Zazwyczaj używam dużego Invoke-Build skryptu w ramach potoku ciągłej integracji/ciągłego wdrażania. Robi to więcej magii, takich jak uruchamianie testów Pester, uruchamianie PSScriptAnalyzer, zarządzanie przechowywaniem wersji i publikowanie w programie 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 tego modułu polega na utworzeniu src folderu dla kodu języka C# i utworzeniu struktury pozostałej części, 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, aby rozpocząć pracę. W tej sekcji przedstawiono przykłady z przewodnika Tworzenie międzyplatformowego modułu binarnego. Zapoznaj się z tym przewodnikiem, jeśli potrzebujesz więcej szczegółów lub masz jakiekolwiek problemy.

Najpierw chcemy sprawdzić wersję zainstalowanego zestawu SDK dotnet Core. 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 src folderu dla 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 zamierzam określić element 2.1.0. Użyj 2.0.0 polecenia , jeśli używasz zestawu SDK w wersji 2.0.

dotnet new globaljson --sdk-version 2.1.0

Dodaj pakiet NuGet bibliotekiStandardowej programu PowerShell 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.

Kompilowanie binarnego polecenia cmdlet

Musimy zaktualizować element , src\Class1.cs aby zawierał następujące początkowe polecenie 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 nową bibliotekę DLL, aby załadować nowe polecenie 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 międzyplatformowego 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

Warto zaimportować bibliotekę DLL i skorzystać z modułu roboczego. Lubię kontynuować chodzenie z nim i tworzyć manifest modułu. Potrzebujemy manifestu, jeśli chcemy opublikować go później w galerii 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 elementu 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 output\$module\bin folderze. Następnie kopiuje inne pliki modułów do miejsca.

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"

W tym miejscu możemy usunąć .\Output\$module folder do naszego $env:PSModulePath katalogu i automatycznie ładuje nasze polecenie zawsze wtedy, gdy tego potrzebujemy.

Aktualizacja: dotnet new PSModule

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

Wszystkie kroki opisane powyżej są nadal prawidłowe, ale ten szablon wycina wiele z nich. Nadal jest to dość nowy szablon, który wciąż dostaje na nim trochę polski. Spodziewaj się, że będzie coraz lepiej stąd.

W ten sposób używasz instalowania i używania szablonu modułu 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 co najmniej opłacalny szablon zajmuje się dodawaniem zestawu .NET SDK, biblioteki Programu PowerShell w warstwie Standardowa i utworzeniem przykładowej klasy w projekcie. Możesz go skompilować i uruchomić od razu.

Ważne informacje

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 naprawdę zwolnić. Plik DLL jest zablokowany do czasu jego zwolnienia. 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ść pracy dewelopera programu PowerShell 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 w górnej części mojej listy.

Zagnieżdżone sesje programu PowerShell

Jedną z innych opcji jest posiadanie dobrego pokrycia testowego Pester. Następnie możesz dostosować skrypt build.ps1, aby rozpocząć 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 została załadowana, musisz go polować i zamknąć. Jest to mniej problem podczas instalowania z programu PSGallery, ponieważ przechowywanie wersji modułu powoduje umieszczenie nowego w innym folderze.

Możesz skonfigurować lokalną usługę PSGallery i opublikować ją w ramach kompilacji. Następnie wykonaj instalację lokalną z tej galerii PSGallery. Brzmi to jak wiele pracy, ale może to być tak proste, jak uruchomienie kontenera platformy Docker. Omówiono sposób, aby to zrobić w moim wpisie w temacie Using a NuGet server for a PSRepository (Używanie serwera NuGet dla repozytorium PSRepository).

Ostatnie myśli

Nie dotknąłem składni języka C# do tworzenia polecenia cmdlet, ale w zestawie SDK programu Windows PowerShell znajduje się wiele dokumentacji. To na pewno coś warto eksperymentować z jako krokowy kamień do poważniejszego C#.