Scelta del pacchetto NuGet di PowerShell appropriato per il progetto .NET

Oltre ai pacchetti eseguibili pwsh pubblicati con ogni versione di PowerShell, il team di PowerShell gestisce anche diversi pacchetti disponibili in NuGet. Questi pacchetti consentono di specificare PowerShell come piattaforma API di destinazione in .NET.

Essendo un'applicazione .NET che fornisce API e prevede il caricamento delle librerie .NET che implementano le proprie (moduli binari), è essenziale che PowerShell sia disponibile sotto forma di pacchetto NuGet.

Esistono attualmente diversi pacchetti NuGet che forniscono una rappresentazione della superficie di attacco dell'API PowerShell. Non è sempre stato chiaro quale pacchetto usare con un determinato progetto. Questo articolo fornisce informazioni su alcuni scenari comuni per i progetti .NET destinati a PowerShell e illustra come scegliere il pacchetto NuGet appropriato come destinazione per il progetto .NET orientato a PowerShell.

Hosting e riferimenti

Alcuni progetti .NET cercano di scrivere il codice da caricare in un runtime di PowerShell preesistente (ad esempio pwsh, powershell.exe, la console integrata di PowerShell o ISE), mentre altri vogliono eseguire PowerShell nelle proprie applicazioni.

  • I riferimenti vengono usati quando un progetto, in genere un modulo, deve essere caricato in PowerShell. Deve essere compilato con le API fornite da PowerShell per consentire l'interazione, ma l'implementazione di PowerShell viene fornita dal processo di caricamento di PowerShell. Per i riferimenti, un progetto può usare gli assembly di riferimento o gli assembly di runtime effettivi come destinazione della compilazione, ma è necessario assicurarsi che nessuno di questi venga pubblicato con la relativa build.
  • L'hosting viene usato quando un progetto necessita di una propria implementazione di PowerShell, in genere perché si tratta di un'applicazione autonoma che deve eseguire PowerShell. In questo caso, non si possono usare gli assembly di riferimento puri. È invece necessaria una dipendenza da un'implementazione di PowerShell concreta. Poiché deve essere usata un'implementazione di PowerShell concreta, è necessario scegliere una versione specifica di PowerShell per l'hosting. Una singola applicazione host non può specificare come destinazione più versioni di PowerShell.

Pubblicazione di progetti che specificano PowerShell come riferimento di destinazione

Nota

Il termine pubblicare in questo articolo viene usato per fare riferimento all'esecuzione di dotnet publish, che inserisce una libreria .NET in una directory con tutte le relative dipendenze, pronta per la distribuzione in un determinato runtime.

Per evitare la pubblicazione delle dipendenze del progetto che vengono usate solo come destinazioni di riferimento per la compilazione, è consigliabile impostare l'attributo PrivateAssets:

<PackageReference Include="PowerShellStandard.Library" Version="5.1.0.0" PrivateAssets="all" />

Se si dimentica di eseguire questa operazione e si usa un assembly di riferimento come destinazione, è possibile che si verifichino problemi relativi all'uso dell'implementazione predefinita dell'assembly di riferimento invece che dell'implementazione effettiva. Potrebbe quindi verificarsi un'eccezione NullReferenceException, perché gli assembly di riferimento spesso simulano l'API di implementazione semplicemente restituendo null.

Tipi chiave di progetti .NET destinati a PowerShell

Anche se qualsiasi libreria o applicazione .NET può incorporare PowerShell, esistono alcuni scenari comuni che usano le API di PowerShell:

  • Implementazione di un modulo binario di PowerShell

    I moduli binari di PowerShell sono librerie .NET caricate da PowerShell che devono implementare le API di PowerShell, ad esempio i tipi PSCmdlet o CmdletProvider per esporre rispettivamente i cmdlet o i provider. Poiché vengono caricati, i moduli provano a eseguire la compilazione in base ai riferimenti a PowerShell senza pubblicarli nella build. È anche frequente che i moduli vogliano supportare più piattaforme e versioni di PowerShell, idealmente con un sovraccarico minimo di spazio su disco, complessità o implementazione ripetuta. Vedere Informazioni sui moduli per altre informazioni sui moduli.

  • Implementazione di un host di PowerShell

    Un host di PowerShell fornisce un livello di interazione per il runtime di PowerShell. È una forma specifica di hosting, in cui PSHost viene implementato come nuova interfaccia utente per PowerShell. ConsoleHost di PowerShell, ad esempio, fornisce un'interfaccia utente di terminale per i file eseguibili di PowerShell, mentre l'host di PowerShell Editor Services e l'host ISE forniscono entrambi un'interfaccia utente parzialmente grafica integrata nell'editor in PowerShell. Anche se è possibile caricare un host in un processo di PowerShell esistente, è molto più comune che un'implementazione host funga da implementazione di PowerShell autonoma che ridistribuisce il motore di PowerShell.

  • Chiamata in PowerShell da un'altra applicazione .NET

    Come qualsiasi applicazione, PowerShell può essere chiamato come sottoprocesso per l'esecuzione di carichi di lavoro. Trattandosi tuttavia di un'applicazione .NET, è anche possibile richiamare PowerShell In-Process per ottenere gli oggetti .NET completi da usare all'interno dell'applicazione chiamante. Si tratta di una forma più generale di hosting, in cui l'applicazione contiene la propria implementazione di PowerShell per uso interno. Ne sono esempi un servizio o un daemon che esegue PowerShell per gestire lo stato del computer o un'applicazione Web che esegue PowerShell su richiesta, ad esempio per gestire le distribuzioni cloud.

  • Testing unità di moduli di PowerShell da .NET

    Anche se i moduli e le altre librerie progettate per esporre le funzionalità a PowerShell devono essere testati principalmente da PowerShell (è consigliabile usare Pester), a volte è necessario eseguire unit test delle API scritte per un modulo di PowerShell da .NET. In questa situazione il codice del modulo prova a specificare come destinazione diverse versioni di PowerShell, mentre il test deve eseguirlo in implementazioni specifiche e concrete.

Riepilogo dei pacchetti NuGet di PowerShell

In questo articolo verranno descritti i pacchetti NuGet seguenti che espongono le API di PowerShell:

  • PowerShellStandard.Library: assembly di riferimento che consente di compilare un singolo assembly che può essere caricato da più runtime di PowerShell.
  • Microsoft.PowerShell.SDK: modo per specificare come destinazione ed effettuare il rehosting dell'intero PowerShell SDK
  • Pacchetto System.Management.Automation: implementazione del motore e del runtime di PowerShell di base, che può essere utile nelle implementazioni minime ospitate e per gli scenari con versioni di destinazione specifiche.
  • Assembly di riferimento di Windows PowerShell: modo per specificare come destinazione ed effettuare in modo efficace il rehosting di Windows PowerShell (PowerShell versione 5.1 e precedenti).

Nota

Il pacchetto NuGet di PowerShell non è affatto un pacchetto di librerie .NET, ma fornisce l'implementazione dello strumento globale dotnet di PowerShell. Questo pacchetto non deve essere usato da nessun progetto, perché fornisce solo un file eseguibile.

PowerShellStandard.Library

La libreria di PowerShell Standard è un assembly di riferimento che acquisisce l'intersezione delle API di PowerShell versione 7, 6 e 5.1. Fornisce un'area API controllata in fase di compilazione in base alla quale compilare il codice .NET, consentendo ai progetti .NET di specificare come destinazione le versioni 7, 6 e 5.1 di PowerShell senza rischiare di chiamare un'API che non sarà disponibile.

PowerShell Standard è progettato per la scrittura di moduli di PowerShell o di altro codice che dovrà essere eseguito solo dopo averlo caricato in un processo di PowerShell. Poiché si tratta di un assembly di riferimento, PowerShell Standard non contiene implementazioni, quindi non fornisce funzionalità per le applicazioni autonome.

Uso di PowerShell Standard con runtime .NET diversi

PowerShell Standard ha come destinazione il runtime .NET Standard 2.0, ovvero un runtime di facciata progettato per fornire una superficie di attacco comune condivisa da .NET Framework e .NET Core. In questo modo è possibile specificare come destinazione un singolo runtime per produrre un singolo assembly che funzionerà con più versioni di PowerShell, ma con le conseguenze seguenti:

  • L'istanza di PowerShell che carica il modulo o la libreria deve eseguire almeno .NET 4.6.1. .NET 4.6 e .NET 4.5.2 non supportano .NET Standard. Si noti che una versione più recente di Windows PowerShell non implica una versione di .NET Framework più recente. Windows PowerShell 5.1 può essere eseguito in .NET 4.5.2.
  • Per usare un'istanza di PowerShell che esegue .NET Framework 4.7.1 o versione precedente, è necessaria l'implementazione di NETStandard.Library di .NET 4.6.1 per fornire netstandard.dll e altri assembly shim nelle versioni di .NET Framework precedenti.

PowerShell 6+ fornisce i propri assembly shim per l'inoltro dei tipi da .NET Framework 4.6.1 (e versioni successive) a .NET Core. Se quindi un modulo usa solo le API esistenti in .NET Core, PowerShell 6+ può caricarlo ed eseguirlo quando è stato compilato per .NET Framework 4.6.1 (la destinazione del runtime net461).

I moduli binari che usano PowerShell Standard per specificare come destinazione più versioni di PowerShell con una singola DLL pubblicata hanno quindi due opzioni:

  1. Pubblicazione di un assembly compilato per il runtime di destinazione net461, Questo implica:

    • Pubblicazione del progetto per il runtime net461
    • Compilazione in base al runtime netstandard2.0 (senza usare l'output di compilazione) per assicurarsi che tutte le API usate siano presenti anche in .NET Core.
  2. Pubblicazione di un assembly compilato per il runtime di destinazione netstandard2.0, che richiede:

    • Pubblicazione del progetto per il runtime netstandard2.0
    • Acquisizione e copia delle dipendenze net461 di NETStandard.Library nel percorso di pubblicazione dell'assembly del progetto, in modo che il tipo dell'assembly venga inoltrato correttamente in .NET Framework.

Per compilare moduli o librerie di PowerShell che hanno come destinazione versioni di .NET Framework meno recenti, può essere preferibile specificare come destinazione più runtime di .NET. In questo modo verrà pubblicato un assembly per ogni runtime di destinazione e l'assembly corretto dovrà essere caricato durante il caricamento del modulo (ad esempio con un file psm1 di piccole dimensioni come modulo radice).

Test di progetti PowerShell Standard in .NET

Quando si tratta di testare il modulo in strumenti di esecuzione dei test per .NET, ad esempio xUnit, tenere presente che i controlli in fase di compilazione possono arrivare solo fino a un certo punto. È necessario testare il modulo con le piattaforme di PowerShell pertinenti.

Per testare le API compilate in base a PowerShell Standard in .NET, è consigliabile aggiungere Microsoft.Powershell.SDK come dipendenza di test con .NET Core (con la versione impostata in modo che corrisponda alla versione di PowerShell desiderata) e gli assembly di riferimento di Windows PowerShell appropriati con .NET Framework.

Per altre informazioni su PowerShell Standard e su come usarlo per scrivere un modulo binario che funzioni in più versioni di PowerShell, vedere questo post di blog. Vedere anche il repository standard di PowerShell in GitHub.

Microsoft.PowerShell.SDK

Microsoft.PowerShell.SDK è un metapacchetto che esegue il pull di tutti i componenti di PowerShell SDK in un singolo pacchetto NuGet. Un'applicazione .NET autonoma può usare Microsoft.PowerShell.SDK per eseguire funzionalità di PowerShell arbitrarie senza dipendere da alcuna installazione o libreria di PowerShell esterna.

Nota

PowerShell SDK fa riferimento a tutti i pacchetti di componenti che costituiscono PowerShell e che possono essere usati per lo sviluppo .NET con PowerShell.

Una determinata versione di Microsoft.Powershell.SDK contiene l'implementazione concreta della stessa versione dell'applicazione PowerShell. La versione 7.0 contiene l'implementazione di PowerShell 7.0 e l'esecuzione di comandi o script con questa versione sarà molto simile all'esecuzione in PowerShell 7.0.

L'esecuzione dei comandi di PowerShell dall'SDK è molto simile, anche se con alcune differenze, all'esecuzione da pwsh. Start-Job, ad esempio, dipende attualmente dalla disponibilità del file eseguibile pwsh e quindi non funzionerà con Microsoft.Powershell.SDK per impostazione predefinita.

Specificare come destinazione Microsoft.Powershell.SDK da un'applicazione .NET consente l'integrazione con tutti gli assembly di implementazione di PowerShell, ad esempio System.Management.Automation, Microsoft.PowerShell.Management e altri assembly del modulo.

La pubblicazione di un'applicazione che ha come destinazione Microsoft.Powershell.SDK includerà tutti questi assembly e le dipendenze necessarie per PowerShell. Includerà anche altri asset richiesti da PowerShell nella build, ad esempio i manifesti per i moduli Microsoft.PowerShell.* e la directory ref richiesta da Add-Type.

Microsoft.Powershell.SDK, grazie alla sua completezza, è ideale per:

  • Implementazione degli host di PowerShell.
  • Test xUnit delle librerie che hanno come destinazione gli assembly di riferimento di PowerShell.
  • Chiamata a PowerShell In-Process da un'applicazione .NET.

Microsoft.PowerShell.SDK può essere usato anche come destinazione di riferimento quando un progetto .NET deve essere usato come modulo o altrimenti caricato da PowerShell, ma dipende dalle API presenti solo in una determinata versione di PowerShell. Si noti che un assembly pubblicato in base a una versione specifica di Microsoft.PowerShell.SDK potrà essere caricato e usato in tutta sicurezza solo in tale versione di PowerShell. Per avere come destinazione più versioni di PowerShell con API specifiche, sono necessarie più build, ognuna delle quali ha come destinazione la propria versione di Microsoft.Powershell.SDK.

Nota

PowerShell SDK è disponibile solo per PowerShell versione 6 e successive. Per fornire funzionalità equivalenti con Windows PowerShell, usare gli assembly di riferimento di Windows PowerShell illustrati di seguito.

System.Management.Automation

Il pacchetto System.Management.Automation è il cuore di PowerShell SDK. Esiste in NuGet, principalmente come asset per il pull di Microsoft.Powershell.SDK. Può tuttavia essere usato anche direttamente come pacchetto per gli scenari di hosting minori e per i moduli che specificano una versione come destinazione.

Il pacchetto System.Management.Automation, in particolare, può essere un provider migliore di funzionalità di PowerShell nei casi seguenti:

  • Si vogliono solo usare le funzionalità del linguaggio di PowerShell (nello spazio dei nomi System.Management.Automation.Language), ad esempio il parser di PowerShell, AST e le API AST visitor (ad esempio, per l'analisi statica di PowerShell).
  • Si vogliono eseguire solo comandi specifici dal modulo Microsoft.PowerShell.Core ed è possibile eseguirli in uno stato sessione creato con il metodo factory CreateDefault2.

System.Management.Automation è anche un assembly di riferimento utile nei casi seguenti:

  • Si vogliono specificare come destinazione API presenti solo in una versione specifica di PowerShell
  • Non si dipenderà da tipi che si verificano all'esterno dell'assembly System.Management.Automation (ad esempio, i tipi esportati dai cmdlet in moduli Microsoft.PowerShell.*).

Assembly di riferimento di Windows PowerShell

Per PowerShell versione 5.1 e precedenti (Windows PowerShell), non sono disponibili SDK che forniscono un'implementazione di PowerShell perché l'implementazione di Windows PowerShell fa parte di Windows.

Gli assembly di riferimento di Windows PowerShell forniscono invece sia le destinazioni di riferimento che un modo per effettuare il rehosting di Windows PowerShell, esattamente come fa PowerShell SDK per le versioni 6 e successive.

Invece di distinguersi in base alla versione, gli assembly di riferimento di Windows PowerShell hanno un pacchetto diverso per ogni versione di Windows PowerShell:

Per informazioni su come usare gli assembly di riferimento di Windows PowerShell, vedere Windows PowerShell SDK.

Esempi reali dell'uso di questi pacchetti NuGet

I progetti basati su strumenti di PowerShell hanno come destinazione pacchetti NuGet di PowerShell diversi a seconda delle esigenze. Ecco alcuni esempi significativi.

PSReadLine

PSReadLine, il modulo di PowerShell che offre gran parte dell'esperienza avanzata della console di PowerShell, ha come destinazione PowerShell Standard come dipendenza invece che una versione specifica di PowerShell e ha come destinazione il runtime .NET net461 in csproj.

PowerShell 6+ fornisce i propri assembly shim che consentono a una DLL che ha come destinazione il runtime net461 "semplicemente di funzionare" quando viene caricata (reindirizzando il binding a mscorlib.dll di .NET Framework all'assembly .NET Core pertinente).

Questo semplifica considerevolmente il layout e la distribuzione del modulo PSReadLine perché PowerShell Standard garantisce che le uniche API usate saranno presenti sia in PowerShell 5.1 che in PowerShell 6+, consentendo allo stesso tempo di distribuire il modulo con un singolo assembly.

La destinazione .NET 4.6.1 significa che Windows PowerShell in esecuzione in .NET 4.5.2 e .NET 4.6 non è supportato.

PowerShell Editor Services

PowerShell Editor Services (PSES) è il back-end per l'estensione PowerShell per Visual Studio Code ed è in realtà una forma di modulo di PowerShell che viene caricato da un file eseguibile di PowerShell e quindi subentra a tale processo per effettuare il rehosting di PowerShell al suo interno, fornendo al tempo stesso anche il protocollo del servizio di linguaggio e le funzionalità di adattatore di debug.

PSES fornisce destinazioni di implementazione concreta per consentire a netcoreapp2.1 di specificare come destinazione PowerShell 6+ (perché il runtime netcoreapp3.1 di PowerShell 7 è compatibile con le versioni precedenti) e a net461 di specificare come destinazione Windows PowerShell 5.1, ma contiene la maggior parte della logica in un secondo assembly che ha come destinazione netstandard2.0 e PowerShell Standard. Ciò gli consente di eseguire il pull delle dipendenze necessarie per le piattaforme .NET Core e .NET Framework, semplificando al tempo stesso la maggior parte della codebase dietro un'astrazione uniforme.

Essendo basato su PowerShell Standard, PSES richiede un'implementazione di runtime di PowerShell per poter essere testato correttamente. A tale scopo, i test xUnit di PSES eseguono il pull di Microsoft.PowerShell.SDK e Microsoft.PowerShell.5.ReferenceAssemblies per fornire un'implementazione di PowerShell nell'ambiente di test.

Come per PSReadLine, PSES non può supportare .NET 4.6 e versioni precedenti, ma esegue un controllo in fase di esecuzione prima di chiamare una delle API che potrebbero causare un arresto anomalo dei runtime di .NET Framework precedenti.

PSScriptAnalyzer

PSScriptAnalyzer, il linter per PowerShell, deve avere come destinazione gli elementi sintattici introdotti solo in determinate versioni di PowerShell. Poiché il riconoscimento di questi elementi sintattici viene eseguito implementando una classe AstVisitor2, non è possibile usare PowerShellStandard e anche implementare i metodi AST visitor per le sintassi di PowerShell più recenti.

Invece PSScriptAnalyzer specifica come destinazione ogni versione di PowerShell come configurazione della build e produce una DLL separata per ognuna di esse. In questo modo aumentano le dimensioni e la complessità della build, ma è possibile:

  • Usare come destinazione l'API specifica della versione
  • Implementare le funzionalità specifiche della versione sostanzialmente senza costi di runtime
  • Supportare completamente Windows PowerShell fino a .NET Framework 4.5.2

Riepilogo

In questo articolo sono stati elencati e illustrati i pacchetti NuGet disponibili per l'impostazione della destinazione quando si implementa un progetto .NET che usa PowerShell e i motivi per cui usarne uno invece di un altro.

Se si è passati direttamente al riepilogo, tenere presenti questi suggerimenti generali:

  • I moduli di PowerShell devono essere compilati in base a PowerShell Standard se richiedono solo API comuni a versioni diverse di PowerShell.
  • Gli host e le applicazioni di PowerShell che devono eseguire PowerShell internamente devono avere come destinazione PowerShell SDK per PowerShell 6+ o gli assembly di riferimento di Windows PowerShell pertinenti per Windows PowerShell.
  • I moduli di PowerShell che necessitano di API specifiche della versione devono avere come destinazione gli assembly di riferimento di PowerShell SDK o di Windows PowerShell per le versioni di PowerShell richieste, usandoli come assembly di riferimento (ovvero senza pubblicare le dipendenze di PowerShell).