Comment charger des assemblys dans PowerShell?

153

Le code PowerShell suivant

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

Donne le message d'erreur suivant:

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

Chaque réponse sur Internet écrit que je dois charger l'assemblage - bien sûr que je peux lire cela à partir du message d'erreur :-) - la question est:

Comment charger l'assemblage et faire fonctionner le script?

Baxter
la source

Réponses:

179

LoadWithPartialNameest obsolète. La solution recommandée pour PowerShell V3 consiste à utiliser l' Add-Typeapplet de commande, par exemple:

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

Il existe plusieurs versions différentes et vous souhaiterez peut-être choisir une version particulière. :-)

Keith Hill
la source
1
D'accord, j'utilise PowerShell3 - ces commandes d'inclusion semblent très compliquées. Je m'attendrais juste à quelque chose comme "inclure le nom de fichier".
Baxter
6
PowerShell est insensible à la casse (sauf si vous lui indiquez qu'il est sensible à la casse avec des opérateurs tels que -cmatch, -ceq). Ainsi, la casse des noms et des paramètres de commande n'a pas d'importance.
Keith Hill
5
Oui. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx Voir la note en haut - This API is now obsolete. Bien sûr, cela n'empêche pas les gens de l'utiliser.
Keith Hill
2
Bien qu'il soit techniquement correct que ce LoadWithPartialNamesoit obsolète, les raisons (comme indiqué dans blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) ne s'appliquent clairement pas à une session Powershell interactive. Je vous suggère d'ajouter une note indiquant que l'API convient à une utilisation interactive de Powershell.
Micha Wiedenmann
La plupart du temps, je n'ai aucun problème avec l'assemblage SMO mais parfois je dois tuer PowerShell et quand je le fais, je commence à avoir des problèmes de chargement SMO. L'ajout de add-type -Path corrige ce problème.
Nicolas de Fontenay
73
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
Shay Levy
la source
8
C'est trop utile pour être obsolète sans remplacement! Mon équipe utilise un mélange d'outils clients 2008 et 2012. C'est le seul moyen de faire fonctionner mes scripts PowerShell pour toute mon équipe sans inclure une logique de version-fallback maladroite.
Iain Samuel McLean Elder
4
Vous pouvez diriger la sortie vers Out-Nullsi vous ne voulez pas que le GAC fasse écho.
Iain Samuel McLean Elder
3
@Baxter - vous devriez accepter cette réponse ou celle de Keith et laisser la réponse à cette question.
Jaykul
3
J'utilise [void] [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.Smo")
Soeren L. Nielsen
@IainElder "logique de repli de version maladroite" Vous dites cela jusqu'à ce que vous rencontriez une incompatibilité de version! Ce n'est pas si difficile à dire Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
Bacon Bits
44

La plupart des gens savent maintenant que System.Reflection.Assembly.LoadWithPartialNamec'est obsolète, mais il s'avère que Add-Type -AssemblyName Microsoft.VisualBasic cela ne se comporte pas beaucoup mieux queLoadWithPartialName :

Plutôt que de tenter d'analyser votre requête dans le contexte de votre système, [Add-Type] examine une table interne statique pour traduire le «nom partiel» en «nom complet».

Si votre "nom partiel" n'apparaît pas dans leur tableau, votre script échouera.

Si plusieurs versions de l'assemblage sont installées sur votre ordinateur, il n'y a pas d'algorithme intelligent pour choisir entre elles. Vous obtiendrez celui qui apparaît dans leur tableau, probablement le plus ancien et obsolète.

Si les versions que vous avez installées sont toutes plus récentes que la version obsolète du tableau, votre script échouera.

Add-Type n'a pas d'analyseur intelligent de "noms partiels" comme .LoadWithPartialNames.

Qu'est - ce que Microsoft dit que vous êtes réellement censé faire quelque chose comme ceci:

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

Ou, si vous connaissez le chemin, quelque chose comme ceci:

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

Ce nom long donné pour l'assembly est connu sous le nom de nom fort , qui est à la fois unique à la version et à l'assembly, et est également parfois appelé nom complet.

Mais cela laisse quelques questions sans réponse:

  1. Comment déterminer le nom fort de ce qui est réellement chargé sur mon système avec un nom partiel donné?

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

Ceux-ci devraient également fonctionner:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Si je veux que mon script utilise toujours une version spécifique d'un .dll mais que je ne peux pas être certain de l'endroit où il est installé, comment déterminer quel est le nom fort du .dll?

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

Ou:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Si je connais le nom fort, comment puis-je déterminer le chemin .dll?

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

  2. Et, dans le même ordre d'idées, si je connais le nom de type de ce que j'utilise, comment savoir de quel assemblage il provient?

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

  3. Comment voir quels assemblages sont disponibles?

Je suggère le module GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNamefonctionne plutôt bien.

  1. Comment puis-je voir la liste qui Add-Typeutilise?

C'est un peu plus complexe. Je peux décrire comment y accéder pour n'importe quelle version de PowerShell avec un réflecteur .Net (voir la mise à jour ci-dessous pour PowerShell Core 6.0).

Tout d'abord, déterminez de quelle bibliothèque Add-Typeprovient:

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

Ouvrez la DLL résultante avec votre réflecteur. J'ai utilisé ILSpy pour cela parce que c'est FLOSS, mais n'importe quel réflecteur C # devrait fonctionner. Ouvrez cette bibliothèque et regardez dedans Microsoft.Powershell.Commands.Utility. En dessous Microsoft.Powershell.Commands, il devrait y en avoir AddTypeCommand.

Dans le code inscripteur pour cela, il y a une classe privée, InitializeStrongNameDictionary(). Cela répertorie le dictionnaire qui mappe les noms courts aux noms forts. J'ai regardé près de 750 entrées dans la bibliothèque.

Mise à jour: maintenant que PowerShell Core 6.0 est open source. Pour cette version, vous pouvez ignorer les étapes ci-dessus et voir le code directement en ligne dans leur référentiel GitHub . Cependant, je ne peux pas garantir que ce code correspond à une autre version de PowerShell.

Lardons
la source
3ème question sans réponse: que faire si je ne souhaite pas exiger une version spécifique?
jpmc26
1
@ jpmc26 Eh bien, vous pouvez simplement utiliser Add-Typeou LoadWithPartialName(), mais vous devez être conscient que la première ne sera pas cohérente à 100% entre les versions et la seconde est une méthode obsolète. En d'autres termes, .Net veut que vous vous souciez de la version de la bibliothèque que vous chargez.
Bacon Bits
@BaconBits La réponse complète à la question de jpmc26 est que, selon que vous êtes sur PowerShell 5 ou PowerShell 6, l'assembly chargé peut être différent. JSON.NET a ce problème avec les fonctions Azure PS.
John Zabroski
@BaconBits C'est une plongée vraiment fantastique dans PowerShell. Vous devriez écrire un livre.
John Zabroski
1
@KolobCanyon Parce que dans ce cas, vous devriez généralement utiliser Add-Type -Path, qui est le deuxième code mentionné, ou Assembly.LoadFrom()qui résout les dépendances pour vous (et, pour autant que je sache, est ce qui Add-Type -Pathutilise). Le seul moment où vous devriez utiliser Assembly.LoadFile()est si vous devez charger plusieurs assemblys qui ont la même identité mais des chemins différents. C'est une situation étrange.
Bacon Bits
23

Si vous souhaitez charger un assembly sans le verrouiller pendant la durée de la session PowerShell , utilisez ceci:

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

$storageAssemblyPathest le chemin de fichier de votre assembly.

Ceci est particulièrement utile si vous avez besoin de nettoyer les ressources de votre session. Par exemple dans un script de déploiement.

Martin Brandl
la source
1
👍 👍 👍 Fantastique. Parce que dans Visual Studio, lors du débogage de Powershell, la session PS se bloque après l'exécution (via PowerShellToolsProcessHost). Cette approche corrige cela. Merci.
CJBS
15

Voici quelques articles de blog avec de nombreux exemples de façons de charger des assemblys dans PowerShell v1, v2 et v3.

Les moyens comprennent:

  • dynamiquement à partir d'un fichier source
  • dynamiquement à partir d'un assemblage
  • en utilisant d'autres types de code, par exemple F #

v1.0 Comment charger des assemblys .NET dans une session PowerShell
v2.0 à l' aide de code CSharp (C #) dans des scripts PowerShell 2.0
v3.0 à l' aide d' assemblys .NET Framework dans Windows PowerShell

Ralph Willgoss
la source
10

Vous pouvez charger l'ensemble de l'assembly * .dll avec

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

Aucune des réponses ne m'a aidé, donc je poste la solution qui a fonctionné pour moi, tout ce que j'avais à faire est d'importer le module SQLPS, je m'en suis rendu compte lorsque par accident j'ai exécuté la commande Restore-SqlDatabase et j'ai commencé à travailler, ce qui signifie que l'assemblage a été référencé dans ce module d'une manière ou d'une autre.

Exécutez simplement:

Import-module SQLPS

Remarque: merci Jason d'avoir noté que SQLPS est obsolète

à la place, exécutez:

Import-Module SqlServer

ou

Install-Module SqlServer
dim_user
la source
2
Pour toute personne utilisant cette approche, FYI qui sqlpsest obsolète en faveur du modulesqlserver
Jason
2

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") travaillé pour moi.

Tez Kurmala
la source
2

Vous pourriez utiliser LoadWithPartialName. Cependant, cela est obsolète comme ils l'ont dit.

Vous pouvez en effet accepter Add-Type, et en plus des autres réponses, si vous ne souhaitez pas spécifier le chemin complet du fichier .dll, vous pouvez simplement faire:

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

Pour moi, cela a renvoyé une erreur, car SQL Server n'est pas installé (je suppose), cependant, avec cette même idée, j'ai pu charger l'assembly Windows Forms:

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

Vous pouvez trouver le nom d'assembly précis appartenant à la classe particulière sur le site MSDN:

Exemple de découverte du nom d'assembly appartenant à une classe particulière

ThomasMX
la source
2

Assurez-vous que les fonctionnalités ci-dessous sont installées dans l'ordre

  1. Types de CLR système Microsoft pour SQL Server
  2. Objets de gestion partagée Microsoft SQL Server
  3. Extensions Microsoft Windows PowerShell

Vous devrez peut-être également charger

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"
shadi eftekhari
la source
J'ai passé une semaine à essayer de charger l'assemblage et je n'ai vu aucune sortie de l'instruction qui les chargeait, mais lorsque j'ai essayé de l'utiliser, j'ai eu l'erreur.Lorsque j'ai installé ces trois choses, cela a fonctionné. - merci
pparas
0

Ajoutez les références d'assemblage en haut.

#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
Amrita Basu
la source
pourriez-vous en faire un exemple?
endo.anaconda
1
Vous devez simplement l'ajouter au début du script PowerShell. Par exemple: Créer une sauvegarde d'une base de données: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SmoExtended") | Out-Null $ SQLServer = Read-Host -Prompt 'Nom du serveur SQL (facultatif)' IF ([string] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host -Prompt ' Nom de la base de données SQL (facultatif) 'IF ([string] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Read-Host -Prompt' Login '
Amrita Basu