Jak ładować zestawy w PowerShell?

153

Poniższy kod programu PowerShell

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

Daje następujący komunikat o błędzie:

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

Każda odpowiedź w internecie pisze, że muszę załadować montaż - na pewno mogę to wyczytać z komunikatu błędu :-) - pytanie brzmi:

Jak ładujesz assembler i uruchamiasz skrypt?

Baxter
źródło

Odpowiedzi:

179

LoadWithPartialNamezostał wycofany. Zalecanym rozwiązaniem dla PowerShell V3 jest użycie polecenia Add-Typecmdlet np:

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

Istnieje wiele różnych wersji i możesz wybrać konkretną wersję. :-)

Keith Hill
źródło
1
Dobra, używam PowerShell3 - te polecenia obejmują wydają się bardzo skomplikowane. Spodziewałbym się po prostu czegoś w rodzaju „dołącz nazwę pliku”.
Baxter,
6
W programie PowerShell nie jest rozróżniana wielkość liter (chyba że w przypadku operatorów takich jak -cmatch, -ceq rozróżniasz wielkość liter). Zatem wielkość liter w nazwach poleceń i parametrach nie ma znaczenia.
Keith Hill
5
Tak. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx Zobacz uwagę u góry - This API is now obsolete. Oczywiście to nie powstrzymuje ludzi przed używaniem go.
Keith Hill
2
Chociaż jest to technicznie poprawne rozwiązanie, które LoadWithPartialNamezostało wycofane, przyczyny (opisane w blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) wyraźnie nie dotyczą interaktywnej sesji programu Powershell. Proponuję dodać uwagę, że API jest dobre do interaktywnego użycia Powershell.
Micha Wiedenmann
Przez większość czasu nie mam problemu z montażem SMO, ale czasami muszę wyłączyć PowerShell, a kiedy to zrobię, zaczynam mieć problemy z ładowaniem SMO. Dodanie add-type -Path rozwiązuje ten problem.
Nicolas de Fontenay,
73
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
Shay Levy
źródło
8
Jest to zbyt przydatne, aby wycofać je bez wymiany! Mój zespół używa różnych narzędzi klienckich z 2008 i 2012 roku. Jest to jedyny sposób, aby moje skrypty PowerShell działały dla całego mojego zespołu bez uwzględnienia niezgrabnej logiki zastępczej wersji.
Iain Samuel McLean Elder
4
Możesz potokować wyjście, Out-Nulljeśli nie chcesz, aby GAC odbijał echo.
Iain Samuel McLean Elder
3
@Baxter - powinieneś zaakceptować tę odpowiedź lub odpowiedź Keitha i pozwolić na zaznaczenie odpowiedzi na to pytanie.
Jaykul
3
Używam [void] [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.Smo")
Soeren L. Nielsen
@IainElder "niezdarna logika awaryjna wersji" Mówisz tak, dopóki nie napotkasz niezgodności wersji! Nie jest trudno powiedzieć Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
Bacon Bits
44

Większość ludzi już wie, że System.Reflection.Assembly.LoadWithPartialNamejest to przestarzałe, ale okazuje się, że Add-Type -AssemblyName Microsoft.VisualBasic nie zachowuje się dużo lepiej niżLoadWithPartialName :

Zamiast próbować analizować żądanie w kontekście systemu, [Add-Type] sprawdza statyczną, wewnętrzną tabelę, aby przetłumaczyć „częściową nazwę” na „pełną nazwę”.

Jeśli Twoja „częściowa nazwa” nie pojawi się w ich tabeli, skrypt zawiedzie.

Jeśli masz wiele wersji zestawu zainstalowanych na komputerze, nie ma inteligentnego algorytmu do wyboru między nimi. Otrzymasz dowolny, który pojawi się w ich tabeli, prawdopodobnie starszy, nieaktualny.

Jeśli wszystkie zainstalowane wersje są nowsze niż przestarzała w tabeli, skrypt nie powiedzie się.

Add-Type nie ma inteligentnego analizatora składni „nazw częściowych”, takich jak .LoadWithPartialNames.

Microsoft twierdzi, że faktycznie powinieneś zrobić coś takiego:

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

Lub, jeśli znasz ścieżkę, coś takiego:

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

Ta długa nazwa nadana zespołowi jest znana jako silna nazwa , która jest unikalna zarówno dla wersji, jak i zestawu, a czasami jest również nazywana pełną nazwą.

Ale to pozostawia kilka pytań bez odpowiedzi:

  1. Jak określić silną nazwę tego, co faktycznie jest ładowane w moim systemie z podaną nazwą częściową?

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

Te też powinny działać:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Jeśli chcę, aby mój skrypt zawsze korzystał z określonej wersji pliku .dll, ale nie mam pewności, gdzie jest zainstalowany, w jaki sposób mogę określić, jaka jest silna nazwa z pliku .dll?

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

Lub:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Jeśli znam silną nazwę, jak określić ścieżkę .dll?

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

  2. I w podobny sposób, jeśli znam nazwę typu tego, czego używam, skąd mam wiedzieć, z jakiego zespołu pochodzi?

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

  3. Jak mogę sprawdzić, jakie zestawy są dostępne?

Proponuję moduł GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNamedziała całkiem nieźle.

  1. Jak mogę zobaczyć listę, która Add-Typeużywa?

To jest trochę bardziej złożone. Mogę opisać, jak uzyskać do niego dostęp dla dowolnej wersji programu PowerShell z reflektorem .Net (zobacz aktualizację poniżej dotyczącą programu PowerShell Core 6.0).

Najpierw dowiedz się, z której biblioteki Add-Typepochodzi:

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

Otwórz wynikową bibliotekę DLL za pomocą reflektora. Użyłem ILSpy za to, bo to FLOSS, ale wszelkie C # reflektor powinien działać. Otwórz tę bibliotekę i zajrzyj do środka Microsoft.Powershell.Commands.Utility. Pod Microsoft.Powershell.Commandsspodem powinno być AddTypeCommand.

W kodzie witryny, że znajduje się prywatny klasy InitializeStrongNameDictionary(). Zawiera słownik, który odwzorowuje krótkie nazwy na silne nazwy. W bibliotece, którą przeglądałem, jest prawie 750 pozycji.

Aktualizacja: teraz, gdy program PowerShell Core 6.0 jest oprogramowaniem typu open source. W przypadku tej wersji możesz pominąć powyższe kroki i zobaczyć kod bezpośrednio online w ich repozytorium GitHub . Nie mogę jednak zagwarantować, że ten kod pasuje do dowolnej innej wersji programu PowerShell.

Kawałki bekonu
źródło
Trzecie pytanie bez odpowiedzi: co jeśli nie chcę wymagać określonej wersji?
jpmc26
1
@ jpmc26 Cóż, możesz po prostu użyć Add-Typelub LoadWithPartialName(), ale musisz mieć świadomość, że ta pierwsza nie będzie w 100% spójna we wszystkich wersjach, a druga jest przestarzałą metodą. Innymi słowy, .Net chce, abyś dbał o wersję biblioteki, którą ładujesz.
Bacon Bits
@BaconBits Pełna odpowiedź na pytanie jpmc26 jest taka, że ​​w zależności od tego, czy korzystasz z PowerShell 5 czy PowerShell 6, załadowany zestaw może być inny. JSON.NET ma ten problem z funkcjami Azure PS.
John Zabroski
@BaconBits To naprawdę fantastyczne, głębokie zagłębienie się w PowerShell. Powinieneś napisać książkę.
John Zabroski
1
@KolobCanyon Ponieważ w takim przypadku powinieneś generalnie użyć Add-Type -Path, który jest drugim wymienionym kodem lub Assembly.LoadFrom()który rozwiązuje za Ciebie zależności (i, o ile wiem, tego Add-Type -Pathużywa). Jedyny przypadek, którego powinieneś używać, Assembly.LoadFile()to ładowanie wielu zestawów, które mają tę samą tożsamość, ale różne ścieżki. To dziwna sytuacja.
Bacon Bits
23

Jeśli chcesz załadować zestaw bez blokowania go podczas trwania sesji programu PowerShell , użyj tego:

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

Gdzie $storageAssemblyPathjest ścieżka pliku twojego zespołu.

Jest to szczególnie przydatne, jeśli chcesz wyczyścić zasoby podczas sesji. Na przykład w skrypcie wdrażania.

Martin Brandl
źródło
1
👍 👍 👍 Fantastycznie. Ponieważ w programie Visual Studio podczas debugowania programu Powershell sesja PS zawiesza się po wykonaniu (za pośrednictwem PowerShellToolsProcessHost). Takie podejście rozwiązuje ten problem. Dzięki.
CJBS
10

Możesz załadować cały zestaw * .dll za pomocą

$Assembly = [System.Reflection.Assembly]::LoadFrom("C:\folder\file.dll");
Yanaki
źródło
3

Żadna z odpowiedzi mi nie pomogła, więc publikuję rozwiązanie, które działało dla mnie, wystarczyło zaimportować moduł SQLPS, zdałem sobie z tego sprawę, gdy przypadkowo uruchomiłem polecenie Restore-SqlDatabase i zacząłem działać, co oznacza, że zestaw był w jakiś sposób przywoływany w tym module.

Po prostu biegnij:

Import-module SQLPS

Uwaga: dziękujemy Jasonowi za zauważenie, że SQLPS jest przestarzały

zamiast tego biegnij:

Import-Module SqlServer

lub

Install-Module SqlServer
dim_user
źródło
2
Dla każdego, kto korzysta z tego podejścia, informacja o Twojej sqlpssqlserver
Jason
2

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") pracował dla mnie.

Tez Kurmala
źródło
2

Możesz użyć LoadWithPartialName. Jednak, jak powiedzieli, jest to przestarzałe.

Rzeczywiście możesz się zgodzić Add-Typei oprócz innych odpowiedzi, jeśli nie chcesz określać pełnej ścieżki do pliku .dll, możesz po prostu zrobić:

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

Mnie zwróciło to błąd, ponieważ nie mam zainstalowanego SQL Server (chyba), jednak z tym samym pomysłem udało mi się załadować zestaw Windows Forms:

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

Dokładną nazwę zestawu należącego do określonej klasy można znaleźć w witrynie MSDN:

Przykład znalezienia nazwy zestawu należącego do określonej klasy

ThomasMX
źródło
2

Upewnij się, że poniższe funkcje są zainstalowane w kolejności

  1. Typy środowiska CLR systemu Microsoft dla programu SQL Server
  2. Obiekty zarządzania współużytkowanego Microsoft SQL Server
  3. Rozszerzenia Microsoft Windows PowerShell

Konieczne może być również załadowanie

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
źródło
Spędziłem tydzień próbując załadować zestaw i nie widziałem żadnych danych wyjściowych z instrukcji, która je załadowała, ale kiedy próbowałem go użyć, pojawił się błąd. Kiedy zainstalowałem te trzy rzeczy, zadziałało. - dzięki
pparas
0

Dodaj odniesienia do zestawów u góry.

#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
źródło
czy mógłbyś zrobić z tego przykład?
endo.anaconda
1
Wystarczy dodać go na początku skryptu PowerShell. Na przykład: Utwórz kopię zapasową bazy danych: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SmoExtended") | Out-Null $ SQLServer = Read-Host -Prompt 'Nazwa SQL Server (opcjonalnie)' IF ([string] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host -Prompt ' Nazwa bazy danych SQL (opcjonalnie) 'IF ([string] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Read-Host -Prompt' Login '
Amrita Basu