Como carregar montagens no PowerShell?

153

O seguinte código do PowerShell

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

Dá a seguinte mensagem de erro:

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

Todas as respostas na Internet escrevem que eu tenho que carregar o assembly - com certeza eu posso ler isso na mensagem de erro :-) - a pergunta é:

Como você carrega a montagem e faz o script funcionar?

Baxter
fonte

Respostas:

179

LoadWithPartialNamefoi descontinuado. A solução recomendada para o PowerShell V3 é usar o Add-Typecmdlet, por exemplo:

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

Existem várias versões diferentes e você pode escolher uma versão específica. :-)

Keith Hill
fonte
1
Ok, eu uso o PowerShell3 - estes comandos incluem parece muito complicado. Eu apenas esperaria algo como "incluir nome do arquivo".
Baxter
6
O PowerShell não diferencia maiúsculas de minúsculas (a menos que você diga que faz distinção entre maiúsculas e minúsculas com operadores como -cmatch, -ceq). Portanto, a inclusão de nomes e parâmetros de comando não importa.
Keith Hill.
5
Sim. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx Veja a nota no topo - This API is now obsolete. Obviamente, isso não impede as pessoas de usá-lo.
Keith Hill
2
Embora seja tecnicamente correto que LoadWithPartialNametenha sido descontinuado, os motivos (conforme descrito em blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) claramente não se aplicam a uma sessão interativa do Powershell. Sugiro que você adicione uma nota de que a API é adequada para o uso interativo do Powershell.
Micha Wiedenmann
Na maioria das vezes, não tenho problemas com o conjunto SMO, mas às vezes preciso interromper o PowerShell e, quando o faço, começo a ter problemas de carregamento do SMO. A adição de add-type -Path corrige isso.
Nicolas de Fontenay
73
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
Shay Levy
fonte
8
Isso é útil demais para ser preterido sem substituição! Minha equipe usa uma mistura de ferramentas de clientes de 2008 e 2012. Essa é a única maneira de fazer com que meus scripts do PowerShell funcionem para toda a minha equipe sem incluir a lógica desajeitada de fallback de versão.
Iain Samuel McLean Elder
4
Você pode canalizar a saída Out-Nullse não quiser que o GAC faça eco.
Iain Samuel McLean Elder
3
@Axter - você deve aceitar esta resposta ou a de Keith e deixar essa pergunta marcada como respondida.
Jaykul
3
Eu uso [vazio] [System.Reflection.Assembly] :: LoadWithPartialName ( "Microsoft.SqlServer.Smo")
Soeren L. Nielsen
@IainElder "lógica desajeitada de fallback de versão" Você diz isso até encontrar a incompatibilidade de versão! Não é tão difícil de dizer Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
Bacon Bits
44

A maioria das pessoas sabe até agora que System.Reflection.Assembly.LoadWithPartialNameestá obsoleta, mas acontece queAdd-Type -AssemblyName Microsoft.VisualBasic não se comporta muito melhor do queLoadWithPartialName :

Em vez de tentar analisar sua solicitação no contexto do seu sistema, o [Add-Type] analisa uma tabela interna estática para converter o "nome parcial" em um "nome completo".

Se o seu "nome parcial" não aparecer na tabela, o script falhará.

Se você tiver várias versões do assembly instaladas no seu computador, não há algoritmo inteligente para escolher entre elas. Você receberá o que aparecer na tabela deles, provavelmente o mais antigo e desatualizado.

Se as versões instaladas forem todas mais novas que as obsoletas da tabela, seu script falhará.

Add-Type não possui um analisador inteligente de "nomes parciais", como .LoadWithPartialNames.

O que a Microsoft diz que você realmente deve fazer é algo como isto:

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

Ou, se você conhece o caminho, algo como isto:

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

Esse nome longo dado para a montagem é conhecido como nome forte , que é exclusivo da versão e do assembly, e também é conhecido como nome completo.

Mas isso deixa algumas perguntas sem resposta:

  1. Como determino o nome forte do que realmente está sendo carregado no meu sistema com um nome parcial fornecido?

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

Estes também devem funcionar:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Se quiser que meu script sempre use uma versão específica de uma .dll, mas não tenha certeza de onde está instalado, como determino qual é o nome forte da .dll?

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

Ou:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Se eu souber o nome forte, como determino o caminho .dll?

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

  2. E, de maneira semelhante, se eu sei o nome do tipo do que estou usando, como sei de que montagem ele está vindo?

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

  3. Como vejo quais montagens estão disponíveis?

Sugiro o módulo GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNamefunciona muito bem.

  1. Como posso ver a lista que Add-Typeusa?

Isso é um pouco mais complexo. Posso descrever como acessá-lo para qualquer versão do PowerShell com um refletor .Net (consulte a atualização abaixo para o PowerShell Core 6.0).

Primeiro, descubra de que biblioteca Add-Typevem:

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

Abra a DLL resultante com seu refletor. Eu usei o ILSpy para isso porque é FLOSS, mas qualquer refletor em C # deve funcionar. Abra essa biblioteca e veja Microsoft.Powershell.Commands.Utility. Abaixo Microsoft.Powershell.Commands, deve haver AddTypeCommand.

Na lista de códigos para isso, há uma classe privada InitializeStrongNameDictionary(),. Isso lista o dicionário que mapeia os nomes abreviados para os nomes fortes. Há quase 750 entradas na biblioteca que eu olhei.

Atualização: Agora que o PowerShell Core 6.0 é de código aberto. Para essa versão, você pode pular as etapas acima e ver o código diretamente online no repositório do GitHub . Não posso garantir que esse código corresponda a qualquer outra versão do PowerShell, no entanto.

Pedaços de bacon
fonte
3ª pergunta sem resposta: e se eu não quiser exigir uma versão específica?
precisa saber é o seguinte
1
@ jpmc26 Bem, você pode simplesmente usar Add-Typeor LoadWithPartialName(), mas precisa estar ciente de que o primeiro não será 100% consistente entre as versões e o último é um método obsoleto. Em outras palavras, o .Net deseja que você se preocupe com a versão da biblioteca que você carrega.
Bacon Bits
@BaconBits A resposta completa para a pergunta do jpmc26 é que, dependendo de você estar no PowerShell 5 ou PowerShell 6, o assembly carregado pode ser diferente. O JSON.NET tem esse problema com as funções do Azure PS.
John Zabroski
@BaconBits Este é um mergulho profundo verdadeiramente fantástico no PowerShell. Você deveria escrever um livro.
John Zabroski
1
@KolobCanyon Porque, nesse caso, você geralmente deve usar Add-Type -Path, que é o segundo código mencionado ou Assembly.LoadFrom()que resolve dependências para você (e, até onde eu sei, é o que Add-Type -Pathusa). O único momento que você deve usar Assembly.LoadFile()é se você precisa carregar vários assemblies que têm a mesma identidade, mas caminhos diferentes. Essa é uma situação estranha.
Bacon Bits
23

Se você deseja carregar uma montagem sem travá-la durante a sessão do PowerShell , use o seguinte:

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

Onde $storageAssemblyPath está o caminho do arquivo da sua montagem.

Isso é especialmente útil se você precisar limpar os recursos da sua sessão. Por exemplo, em um script de implantação.

Martin Brandl
fonte
1
Fantástico. Como no Visual Studio, ao depurar o Powershell, a sessão do PS fica suspensa após a execução (via PowerShellToolsProcessHost). Essa abordagem corrige isso. Obrigado.
CJBS
10

Você pode carregar o assembly * .dll inteiro com

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

Nenhuma das respostas me ajudou, então, eu estou postando a solução que funcionou para mim. Tudo o que eu precisava fazer era importar o módulo SQLPS. Percebi isso quando, por acidente, executei o comando Restore-SqlDatabase e comecei a trabalhar, o que significa que a montagem foi referenciada nesse módulo de alguma forma.

Apenas corra:

Import-module SQLPS

Nota: Obrigado Jason por observar que o SQLPS está obsoleto

em vez disso, execute:

Import-Module SqlServer

ou

Install-Module SqlServer
dim_user
fonte
2
Para qualquer pessoa usando esta abordagem, FYI que sqlpsé depreciado em favor do módulosqlserver
Jason
2

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") trabalhou para mim.

Tez Kurmala
fonte
2

Você poderia usar LoadWithPartialName. No entanto, isso foi preterido como eles disseram.

Você pode acompanhar Add-Typee, além das outras respostas, se não desejar especificar o caminho completo do arquivo .dll, basta:

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

Para mim, isso retornou um erro, porque eu não tenho o SQL Server instalado (eu acho), no entanto, com essa mesma ideia, consegui carregar o assembly do Windows Forms:

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

Você pode descobrir o nome exato do assembly pertencente à classe específica no site MSDN:

Exemplo de localização do nome do assembly pertencente a uma classe específica

ThomasMX
fonte
2

Verifique se os recursos abaixo estão instalados em ordem

  1. Tipos de CLR do sistema Microsoft para SQL Server
  2. Objetos de gerenciamento compartilhado do Microsoft SQL Server
  3. Extensões do Microsoft Windows PowerShell

Também pode ser necessário carregar

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
fonte
Passei uma semana tentando carregar o assembly e não vi nenhuma saída da instrução que os carregava, mas quando tentei usá-lo, recebi o erro. Quando instalei essas três coisas, funcionou. - obrigado
pparas 03/03
0

Adicione as referências de montagem na parte superior.

#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
fonte
você poderia dar um exemplo disso?
Endo.anaconda
1
Você só precisa adicioná-lo no início do script do PowerShell. Por exemplo: Criar backup de um banco de dados: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Fora-nulo [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SmoExtended") | Out-Null $ SQLServer = Read-Host-Prompt 'Nome do SQL Server (opcional)' IF ([string] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host-Prompt ' Nome do banco de dados SQL (opcional) 'IF ([string] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Host-Read - Prompt' Login '
Amrita Basu