Een binaire module van een standaardbibliotheek maken

Ik had onlangs een idee voor module die ik wilde implementeren als een binaire module. Ik moet er nog een maken met behulp van de PowerShell Standard-bibliotheek , dus dit voelde als een goede kans. Ik heb de handleiding voor het maken van een platformoverschrijdende binaire module gebruikt om deze module zonder obstakels te maken. We gaan datzelfde proces doorlopen en ik zal onderweg een beetje extra commentaar toevoegen.

Notitie

De oorspronkelijke versie van dit artikel verscheen op het blog geschreven door @KevinMarquette. Het PowerShell-team bedankt Kevin voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.

Wat is de Standaardbibliotheek van PowerShell?

Met de PowerShell Standard-bibliotheek kunnen we platformoverschrijdende modules maken die werken in Zowel PowerShell als Windows PowerShell 5.1.

Onze module plannen

Het plan voor deze module is het maken van een src map voor de C#-code en het structureren van de rest zoals ik voor een scriptmodule zou doen. Dit omvat het gebruik van een buildscript om alles in een output map te compileren. De mapstructuur ziet er als volgt uit:

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

Aan de slag

Ik gebruik normaal gesproken een gipssjabloon, maar mijn huidige sjabloon heeft nog geen binaire moduleondersteuning. Niet erg. Ik maak dit deze keer met de hand.

Eerst moet ik de map maken en de Git-opslagplaats maken. Ik gebruik $module als tijdelijke aanduiding voor de modulenaam. Dit maakt het voor u gemakkelijker om deze voorbeelden zo nodig opnieuw te gebruiken.

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

Maak vervolgens de mappen op hoofdniveau.

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

Binaire module instellen

Dit artikel is gericht op de binaire module, zodat we beginnen. In deze sectie worden voorbeelden opgehaald uit de handleiding voor het maken van een platformoverschrijdende binaire module . Raadpleeg deze handleiding als u meer informatie nodig hebt of problemen hebt.

Het eerste wat we willen doen, is de versie controleren van de dotnet core SDK die we hebben geïnstalleerd. Ik gebruik 2.1.4, maar je moet 2.0.0 of nieuwer hebben voordat je doorgaat.

PS> dotnet --version
2.1.4

Ik werk uit de src map voor deze sectie.

Set-Location 'src'

Maak met de dotnet-opdracht een nieuwe klassebibliotheek.

dotnet new classlib --name $module

Hiermee is het bibliotheekproject gemaakt in een submap, maar ik wil geen extra nestniveau. Ik ga die bestanden naar een hoger niveau verplaatsen.

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

Stel de .NET Core SDK-versie voor het project in. Ik heb de 2.1 SDK, dus ik ga 2.1.0 opgeven. Gebruik 2.0.0 als u de 2.0 SDK gebruikt.

dotnet new globaljson --sdk-version 2.1.0

Voeg het NuGet-pakket PowerShell Standard Library toe aan het project. Zorg ervoor dat u de meest recente versie gebruikt die beschikbaar is voor het compatibiliteitsniveau dat u nodig hebt. Ik zou standaard de nieuwste versie gebruiken, maar ik denk niet dat deze module gebruikmaakt van nieuwere functies dan PowerShell 3.0.

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

We moeten een src-map hebben die er als volgt uitziet:

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

Nu zijn we klaar om onze eigen code toe te voegen aan het project.

Een binaire cmdlet bouwen

We moeten de src\Class1.cs cmdlet bijwerken die deze starter-cmdlet bevat:

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();
        }
    }
}

Wijzig de naam van het bestand zodat deze overeenkomt met de klassenaam.

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

Dan kunnen we onze module bouwen.

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

We kunnen de nieuwe DLL aanroepen Import-Module om onze nieuwe CMDlet te laden.

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

Als het importeren op uw systeem mislukt, probeert u .NET bij te werken naar 4.7.1 of hoger. De handleiding voor het maken van een platformoverschrijdende binaire module bevat meer informatie over .NET-ondersteuning en -compatibiliteit voor oudere versies van .NET.

Modulemanifest

Het is cool dat we de DLL kunnen importeren en een werkende module kunnen hebben. Ik ga er graag mee door en maak een modulemanifest. We hebben het manifest nodig als we later naar de PSGallery willen publiceren.

Vanuit de hoofdmap van ons project kunnen we deze opdracht uitvoeren om het modulemanifest te maken dat we nodig hebben.

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

Ik ga ook een lege hoofdmodule maken voor toekomstige PowerShell-functies.

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

Hierdoor kan ik zowel normale PowerShell-functies als binaire cmdlets in hetzelfde project combineren.

De volledige module bouwen

Ik compileer alles samen in een uitvoermap. Hiervoor moeten we een buildscript maken. Ik zou dit normaal gesproken toevoegen aan een Invoke-Build script, maar we kunnen het eenvoudig houden voor dit voorbeeld. Voeg dit toe aan de build.ps1 hoofdmap van het project.

$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"

Met deze opdrachten wordt ons DLL-bestand gebouwd en in onze output\$module\bin map geplaatst. Vervolgens worden de andere modulebestanden gekopieerd.

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

Op dit moment kunnen we onze module importeren met het psd1-bestand.

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

Van hieruit kunnen we de .\Output\$module map in onze $env:PSModulePath map neerzetten en de opdracht wordt automatisch geladen wanneer we deze nodig hebben.

Update: dotnet new PSModule

Ik heb geleerd dat het dotnet hulpprogramma een PSModule sjabloon heeft.

Alle stappen die ik hierboven heb beschreven, zijn nog steeds geldig, maar deze sjabloon knipt er veel uit. Het is nog steeds een vrij nieuwe sjabloon die er nog wat professioneel op wordt geplaatst. Verwacht dat het hier steeds beter wordt.

Dit is hoe u de PSModule-sjabloon installeert en gebruikt.

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

Deze minimaal haalbare sjabloon zorgt voor het toevoegen van de .NET SDK, De Standaardbibliotheek van PowerShell en het maken van een voorbeeldklasse in het project. U kunt het bouwen en direct uitvoeren.

Belangrijke informatie

Voordat we dit artikel beëindigen, vindt u hier enkele andere details die u kunt vermelden.

DLL's lossen

Zodra een binaire module is geladen, kunt u deze niet echt uitpakken. Het DLL-bestand is vergrendeld totdat u het laadt. Dit kan vervelend zijn bij het ontwikkelen, omdat het bestand vaak wordt vergrendeld wanneer u een wijziging aanbrengt en deze wilt bouwen. De enige betrouwbare manier om dit op te lossen is door de PowerShell-sessie te sluiten die het DLL-bestand heeft geladen.

Vensteractie voor opnieuw laden van VS Code

Ik doe het grootste deel van mijn PowerShell Dev-werk in VS Code. Wanneer ik aan een binaire module (of een module met klassen) werk, heb ik elke keer dat ik VS Code opnieuw laad. Ctrl+Verschuiving+P popt het opdrachtvenster en Reload Window staat altijd boven aan mijn lijst.

Geneste PowerShell-sessies

Een andere optie is om een goede Pester-testdekking te hebben. Vervolgens kunt u het build.ps1-script aanpassen om een nieuwe PowerShell-sessie te starten, de build uit te voeren, de tests uit te voeren en de sessie te sluiten.

Geïnstalleerde modules bijwerken

Deze vergrendeling kan vervelend zijn bij het bijwerken van uw lokaal geïnstalleerde module. Als er een sessie is geladen, moet u deze opsporen en sluiten. Dit is minder een probleem bij het installeren vanuit een PSGallery, omdat moduleversiebeheer de nieuwe in een andere map plaatst.

U kunt een lokale PSGallery instellen en daar publiceren als onderdeel van uw build. Voer vervolgens uw lokale installatie uit vanaf die PSGallery. Dit klinkt veel werk, maar dit kan net zo eenvoudig zijn als het starten van een Docker-container. Ik bedek een manier om dat te doen in mijn bericht over Het gebruik van een NuGet-server voor een PSRepository.

Waarom binaire modules?

Ik sprong meteen in het maken van een binaire module en noemde niet waarom je er een wilt maken. In werkelijkheid schrijft u C# en geeft u eenvoudige toegang tot PowerShell-cmdlets en -functies. Dat is de belangrijkste reden waarom ik niet eerder naar binaire modules ben verschoven.

Maar als u een module maakt die niet afhankelijk is van veel andere PowerShell-opdrachten, kan het prestatievoordeel aanzienlijk zijn. Door in C# te komen, kunt u de overhead die door PowerShell wordt toegevoegd, kwijtgeworpen. PowerShell is geoptimaliseerd voor de beheerder, niet voor de computer, en dat voegt wat overhead toe.

Op het werk hebben we een kritiek proces dat veel werkt met JSON en Hashtables. We hebben de PowerShell zo veel mogelijk geoptimaliseerd, maar dit proces werd nog 12 minuten uitgevoerd. De module bevat al veel C#-stijl PowerShell. Hierdoor is de conversie naar een binaire module schoon en eenvoudig te doen. Onze conversie heeft dat proces van meer dan 12 minuten tot minder dan vier minuten afgekapt.

Hybride modules

U kunt binaire cmdlets combineren met geavanceerde PowerShell-functies. Dat is precies wat ik in deze gids doe. U kunt alles wat u weet over scriptmodules gebruiken en het is allemaal op dezelfde manier van toepassing. Het lege psm1 bestand dat ik vandaag heb gemaakt, is er alleen zodat u later andere PowerShell-functies kunt neerzetten.

Bijna alle gecompileerde cmdlets die we hebben gemaakt, zijn eerst begonnen als een PowerShell-functie. Al onze binaire modules zijn echt hybride modules.

Scripts bouwen

Ik heb het buildscript hier eenvoudig gehouden. Ik gebruik over het algemeen een groot Invoke-Build script als onderdeel van mijn CI/CD-pijplijn. Het doet meer magie als het uitvoeren van Pester-tests, het uitvoeren van PSScriptAnalyzer, het beheren van versiebeheer en het publiceren naar de PSGallery. Zodra ik een buildscript voor mijn modules heb gebruikt, kon ik veel dingen vinden om er aan toe te voegen.

Laatste gedachten

Binaire modules zijn eenvoudig te maken. Ik heb de C#-syntaxis voor het maken van een cmdlet niet aangeraakt, maar er is veel documentatie over in de Windows PowerShell SDK. Het is zeker iets waar je mee kunt experimenteren als een stapsteen in serieuzere C#.