Come caricare gli assiemi in PowerShell?


153

Il seguente codice PowerShell

#Get a server object which corresponds to the default instance
$srv = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Server
... rest of the script ...

Fornisce il seguente messaggio di errore:

New-Object : Cannot find type [Microsoft.SqlServer.Management.SMO.Server]: make sure 
the assembly containing this type is loaded.
At C:\Users\sortelyn\ ... \tools\sql_express_backup\backup.ps1:6  char:8
+ $srv = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Server
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

Ogni risposta su Internet scrive che devo caricare l'assembly - ben sicuro di poterlo leggere dal messaggio di errore :-) - la domanda è:

Come si carica l'assembly e si fa funzionare lo script?

Risposte:


179

LoadWithPartialNameè stato deprecato. La soluzione consigliata per PowerShell V3 è utilizzare il Add-Typecmdlet, ad esempio:

Add-Type -Path 'C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Smo.dll'

Esistono più versioni diverse e potresti voler scegliere una versione particolare. :-)


1
Va bene uso PowerShell3: questi comandi includono sembrano molto complicati. Mi aspetterei semplicemente qualcosa come "include nomefile".
Baxter,

6
PowerShell non fa distinzione tra maiuscole e minuscole (a meno che tu non dica che fa distinzione tra maiuscole e minuscole con operatori come -cmatch, -ceq). Quindi il case su nomi di comandi e parametri non ha importanza.
Keith Hill,

5
Sì. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx Vedi la nota in alto - This API is now obsolete. Naturalmente, ciò non impedisce alle persone di usarlo.
Keith Hill,

2
Sebbene sia tecnicamente corretto che LoadWithPartialNameè stato deprecato, i motivi (come indicato in blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) chiaramente non si applicano per una sessione interattiva Powershell. Ti suggerisco di aggiungere una nota che l'API va bene per l'utilizzo interattivo di Powershell.
Micha Wiedenmann,

Il più delle volte, non ho problemi con l'assemblaggio SMO ma a volte ho bisogno di uccidere PowerShell e quando lo faccio, comincio ad avere problemi di caricamento SMO. L'aggiunta di add-type -Path risolve questo problema.
Nicolas de Fontenay,

73
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")

8
Questo è troppo utile per essere deprecato senza una sostituzione! Il mio team utilizza una combinazione di strumenti client 2008 e 2012. Questo è l'unico modo per far funzionare i miei script PowerShell per tutto il mio team senza includere una logica goffa di fallback versione.
Iain Samuel McLean Elder,

4
È possibile reindirizzare l'output Out-Nullse non si desidera che GAC esegua l'eco delle cose.
Iain Samuel McLean Elder,

3
@Baxter - dovresti accettare questa risposta o quella di Keith e lasciare che questa domanda sia contrassegnata come risposta.
Jaykul,

3
Uso [void] [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.Smo")
Soeren L. Nielsen,

@IainElder "logica goffa di fallback versione" Lo dici fino a quando non si incontra l'incompatibilità della versione! Non è così difficile da dire Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
Bacon Bits

44

La maggior parte delle persone sa ormai che System.Reflection.Assembly.LoadWithPartialNameè deprecata, ma si scopre che Add-Type -AssemblyName Microsoft.VisualBasic non si comporta molto meglio diLoadWithPartialName :

Invece di tentare di analizzare la tua richiesta nel contesto del tuo sistema, [Add-Type] esamina una tabella interna statica per tradurre il "nome parziale" in un "nome completo".

Se il tuo "nome parziale" non appare nella loro tabella, lo script non funzionerà.

Se sul computer sono installate più versioni dell'assembly, non esiste un algoritmo intelligente tra cui scegliere. Stai per ottenere quello che appare nella loro tabella, probabilmente il più vecchio, obsoleto.

Se le versioni installate sono tutte più recenti di quelle obsolete nella tabella, lo script non funzionerà.

Add-Type non ha un parser intelligente di "nomi parziali" come .LoadWithPartialNames.

Quello che Microsoft dice che dovresti effettivamente fare è qualcosa del genere:

Add-Type -AssemblyName 'Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

Oppure, se conosci il percorso, qualcosa del genere:

Add-Type -Path 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.VisualBasic\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualBasic.dll'

Quel lungo nome fornito per l'assemblaggio è noto come il nome sicuro , che è sia unico per la versione che per l'assemblaggio, ed è talvolta noto anche come il nome completo.

Ma questo lascia un paio di domande senza risposta:

  1. Come posso determinare il nome sicuro di ciò che viene effettivamente caricato sul mio sistema con un determinato nome parziale?

    [System.Reflection.Assembly]::LoadWithPartialName($TypeName).Location; [System.Reflection.Assembly]::LoadWithPartialName($TypeName).FullName;

Questi dovrebbero anche funzionare:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Se voglio che il mio script usi sempre una versione specifica di un .dll ma non posso essere sicuro di dove sia installato, come posso determinare quale sia il nome sicuro del .dll?

    [System.Reflection.AssemblyName]::GetAssemblyName($Path).FullName;

O:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Se conosco il nome sicuro, come posso determinare il percorso .dll?

    [Reflection.Assembly]::Load('Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a').Location;

  2. E, allo stesso modo, se conosco il nome del tipo di ciò che sto usando, come faccio a sapere da quale assembly proviene?

    [Reflection.Assembly]::GetAssembly([Type]).Location [Reflection.Assembly]::GetAssembly([Type]).FullName

  3. Come posso vedere quali assembly sono disponibili?

Suggerisco il modulo GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNamefunziona abbastanza bene.

  1. Come posso vedere l'elenco che Add-Typeutilizza?

Questo è un po 'più complesso. Posso descrivere come accedervi per qualsiasi versione di PowerShell con un riflettore .Net (vedere l'aggiornamento di seguito per PowerShell Core 6.0).

Innanzitutto, scopri da quale libreria Add-Typeproviene:

Get-Command -Name Add-Type | Select-Object -Property DLL

Apri la DLL risultante con il tuo riflettore. Ho usato ILSpy per questo perché è FLOSS, ma qualsiasi riflettore C # dovrebbe funzionare. Apri quella libreria e guarda dentro Microsoft.Powershell.Commands.Utility. Sotto Microsoft.Powershell.Commands, ci dovrebbe essere AddTypeCommand.

Nel listato di codice per questo, c'è una classe privata, InitializeStrongNameDictionary(). Questo elenca il dizionario che associa i nomi brevi ai nomi forti. Ci sono quasi 750 voci nella biblioteca che ho visto.

Aggiornamento: ora che PowerShell Core 6.0 è open source. Per quella versione, puoi saltare i passaggi precedenti e vedere il codice direttamente online nel loro repository GitHub . Tuttavia, non posso garantire che quel codice corrisponda a qualsiasi altra versione di PowerShell.


Terza domanda senza risposta: cosa succede se non desidero richiedere una versione specifica?
jpmc26

1
@ jpmc26 Bene, puoi semplicemente usare Add-Typeo LoadWithPartialName(), ma devi essere consapevole che il primo non sarà coerente al 100% tra le versioni e il secondo è un metodo obsoleto. In altre parole, .Net desidera che ti interessi della versione della libreria che carichi.
Pezzi di pancetta

@BaconBits La risposta completa alla domanda di jpmc26 è che, a seconda che tu sia su PowerShell 5 o PowerShell 6, l'assembly caricato potrebbe essere diverso. JSON.NET presenta questo problema con le funzioni di Azure PS.
John Zabroski,

@BaconBits Questa è un'immersione profonda davvero fantastica in PowerShell. Dovresti scrivere un libro.
John Zabroski,

1
@KolobCanyon Perché in quel caso dovresti generalmente usare Add-Type -Path, che è il secondo codice menzionato, o Assembly.LoadFrom()che risolve le dipendenze per te (e, per quanto posso dire, è ciò che Add-Type -Pathusa). L'unica volta che dovresti utilizzare Assembly.LoadFile()è se devi caricare più assembly con la stessa identità ma percorsi diversi. È una situazione strana.
Pezzi di pancetta

23

Se si desidera caricare un assembly senza bloccarlo durante la durata della sessione di PowerShell , utilizzare questo:

$bytes = [System.IO.File]::ReadAllBytes($storageAssemblyPath)
[System.Reflection.Assembly]::Load($bytes)

Dov'è $storageAssemblyPathil percorso del file dell'assembly.

Ciò è particolarmente utile se è necessario ripulire le risorse all'interno della sessione. Ad esempio in uno script di distribuzione.


1
👍 👍 👍 Fantastico. Perché in Visual Studio, durante il debug di Powershell, la sessione PS si blocca dopo l'esecuzione (tramite PowerShellToolsProcessHost). Questo approccio lo risolve. Grazie.
CJBS,


10

È possibile caricare l'intero assembly * .dll con

$Assembly = [System.Reflection.Assembly]::LoadFrom("C:\folder\file.dll");

3

Nessuna delle risposte mi ha aiutato, quindi sto pubblicando la soluzione che ha funzionato per me, tutto quello che dovevo fare è importare il modulo SQLPS, me ne sono reso conto quando per caso ho eseguito il comando Restore-SqlDatabase e ho iniziato a lavorare, nel senso che l'assemblaggio è stato in qualche modo indicato in quel modulo.

Corri:

Import-module SQLPS

Nota: grazie a Jason per aver notato che SQLPS è obsoleto

invece esegui:

Import-Module SqlServer

o

Install-Module SqlServer

2
Per chiunque usi questo approccio, FYI che sqlpsè deprecato a favore del modulosqlserver
Jason,

2

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") ha funzionato per me.


2

Puoi usare LoadWithPartialName. Tuttavia, questo è deprecato come hanno detto.

Puoi davvero andare d'accordo Add-Typee, oltre alle altre risposte, se non vuoi specificare il percorso completo del file .dll, puoi semplicemente fare:

Add-Type -AssemblyName "Microsoft.SqlServer.Management.SMO"

A me questo ha restituito un errore, perché non ho installato SQL Server (suppongo), tuttavia, con questa stessa idea sono stato in grado di caricare l'assembly di Windows Form:

Add-Type -AssemblyName "System.Windows.Forms"

È possibile scoprire il nome preciso dell'assembly appartenente alla classe particolare sul sito MSDN:

Esempio di ricerca del nome dell'assembly appartenente a una particolare classe


2

Assicurati di avere le funzioni seguenti installate in ordine

  1. Tipi di CLR di sistema di Microsoft per SQL Server
  2. Oggetti di gestione condivisa di Microsoft SQL Server
  3. Estensioni di Microsoft Windows PowerShell

Inoltre potrebbe essere necessario caricare

Add-Type -Path "C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Smo.dll"
Add-Type -Path "C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.SqlWmiManagement.dll"

Ho trascorso una settimana cercando di caricare l'assembly e non ho visto alcun output dall'istruzione che li ha caricati, ma quando ho provato a usarlo, ho ricevuto l'errore. Quando ho installato queste tre cose, ha funzionato. - grazie
pparas il

0

Aggiungi i riferimenti dell'assieme in alto.

#Load the required assemblies SMO and SmoExtended.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null

potresti farne un esempio?
endo.anaconda,

1
Devi semplicemente aggiungerlo all'inizio dello script PowerShell. Ad esempio: Crea backup di un database: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SmoExtended") | Out-Null $ SQLServer = Read-Host -Prompt 'Nome SQL Server (opzionale)' IF ([stringa] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host -Prompt ' Nome database SQL (facoltativo) 'IF ([stringa] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Read-Host -Prompt' Login '
Amrita Basu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.