Bagaimana cara memuat rakitan di PowerShell?

153

Berikut kode PowerShell

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

Memberikan pesan kesalahan berikut:

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

Setiap jawaban di Internet menulis bahwa saya harus memuatnya - tentu saya bisa membacanya dari pesan kesalahan :-) - pertanyaannya adalah:

Bagaimana Anda memuat rakitan dan membuat skrip berfungsi?

Baxter
sumber

Jawaban:

179

LoadWithPartialNametelah ditinggalkan. Solusi yang disarankan untuk PowerShell V3 adalah dengan menggunakan Add-Typecmdlet misalnya:

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

Ada beberapa versi berbeda dan Anda mungkin ingin memilih versi tertentu. :-)

Keith Hill
sumber
1
Oke saya menggunakan PowerShell3 - ini termasuk perintah yang tampaknya sangat rumit. Saya hanya berharap sesuatu seperti "termasuk nama file".
Baxter
6
PowerShell bersifat case-insensitive (kecuali jika Anda mengatakannya peka terhadap operator seperti -cmatch, -ceq). Jadi casing pada nama perintah dan parameter tidak masalah.
Keith Hill
5
Iya. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx Lihat catatan di atas - This API is now obsolete. Tentu saja, itu tidak menghentikan orang untuk menggunakannya.
Keith Hill
2
Meskipun secara teknis sudah benar yang LoadWithPartialNametelah usang, alasannya (sebagaimana diuraikan dalam blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) jelas tidak berlaku untuk sesi Powershell interaktif. Saya sarankan Anda menambahkan catatan bahwa API baik untuk penggunaan Powershell interaktif.
Micha Wiedenmann
Sebagian besar waktu, saya tidak punya masalah dengan perakitan SMO tetapi kadang-kadang saya harus membunuh PowerShell dan ketika saya melakukannya, saya mulai mengalami masalah pemuatan SMO. Menambahkan add-type perbaikan -Path itu.
Nicolas de Fontenay
73
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
Shay Levy
sumber
8
Ini terlalu berguna untuk ditinggalkan tanpa penggantian! Tim saya menggunakan campuran alat klien 2008 dan 2012. Ini adalah satu-satunya cara untuk membuat skrip PowerShell saya berfungsi untuk semua tim saya tanpa menyertakan logika versi-mundur.
Iain Samuel McLean Penatua
4
Anda dapat menyalurkan output ke Out-Nulljika Anda tidak ingin GAC untuk mengulangi hal-hal.
Iain Samuel McLean Penatua
3
@ Baxter - Anda harus menerima jawaban ini atau Keith dan biarkan pertanyaan ini ditandai dijawab.
Jaykul
3
Saya menggunakan [void] [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.Smo")
Soeren L. Nielsen
@IainElder "logika kejanggalan versi-fallback" Anda mengatakan itu sampai Anda mengalami ketidakcocokan versi! Itu tidak sulit untuk dikatakan Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
Bacon Bits
44

Sebagian besar orang tahu saat System.Reflection.Assembly.LoadWithPartialNameini sudah usang, tetapi ternyata Add-Type -AssemblyName Microsoft.VisualBasic tidak berperilaku jauh lebih baik daripadaLoadWithPartialName :

Daripada melakukan upaya untuk menguraikan permintaan Anda dalam konteks sistem Anda, [Add-Type] melihat tabel internal yang statis untuk menerjemahkan "sebagian nama" menjadi "nama lengkap".

Jika "sebagian nama" Anda tidak muncul di tabel mereka, skrip Anda akan gagal.

Jika Anda memiliki beberapa versi rakitan yang diinstal pada komputer Anda, tidak ada algoritma cerdas untuk memilih di antara mereka. Anda akan mendapatkan mana yang muncul di meja mereka, mungkin yang lebih tua, yang sudah ketinggalan zaman.

Jika versi yang Anda instal semuanya lebih baru daripada versi usang di skrip, skrip Anda akan gagal.

Add-Type tidak memiliki parser cerdas "sebagian nama" seperti .LoadWithPartialNames.

Apa yang Microsoft mengatakan Anda benar-benar harus lakukan adalah sesuatu seperti ini:

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

Atau, jika Anda tahu jalannya, sesuatu seperti ini:

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

Nama panjang yang diberikan untuk majelis dikenal sebagai nama kuat , yang unik untuk versi dan majelis, dan kadang-kadang juga dikenal sebagai nama lengkap.

Tapi ini menyisakan beberapa pertanyaan:

  1. Bagaimana cara menentukan nama kuat dari apa yang sebenarnya dimuat di sistem saya dengan nama parsial yang diberikan?

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

Ini juga harus bekerja:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Jika saya ingin skrip saya selalu menggunakan versi tertentu dari .dll tapi saya tidak bisa memastikan di mana itu diinstal, bagaimana cara menentukan apa nama yang kuat dari.

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

Atau:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Jika saya tahu nama yang kuat, bagaimana cara menentukan jalur .dll?

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

  2. Dan, dengan nada yang sama, jika saya tahu nama tipe dari apa yang saya gunakan, bagaimana saya tahu dari mana asalnya?

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

  3. Bagaimana cara melihat majelis apa yang tersedia?

Saya menyarankan modul GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNamebekerja dengan cukup baik.

  1. Bagaimana saya bisa melihat daftar yang Add-Typemenggunakan?

Ini sedikit lebih rumit. Saya dapat menjelaskan cara mengaksesnya untuk versi PowerShell dengan reflektor .Net (lihat pembaruan di bawah ini untuk PowerShell Core 6.0).

Pertama, cari tahu dari perpustakaan mana Add-Type:

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

Buka DLL yang dihasilkan dengan reflektor Anda. Saya telah menggunakan ILSpy untuk ini karena FLOSS, tetapi reflektor C # apa pun harus berfungsi. Buka perpustakaan itu, dan lihat Microsoft.Powershell.Commands.Utility. Di bawah Microsoft.Powershell.Commands, seharusnya ada AddTypeCommand.

Dalam daftar kode untuk itu, ada kelas privat InitializeStrongNameDictionary(),. Itu mencantumkan kamus yang memetakan nama pendek ke nama kuat. Ada hampir 750 entri di perpustakaan yang pernah saya lihat.

Pembaruan: Sekarang PowerShell Core 6.0 adalah open source. Untuk versi itu, Anda dapat melewati langkah-langkah di atas dan melihat kode secara online di repositori GitHub mereka . Saya tidak dapat menjamin bahwa kode tersebut cocok dengan versi PowerShell lainnya.

Bit Bacon
sumber
Pertanyaan ke-3 yang belum terjawab: bagaimana jika saya tidak ingin memerlukan versi tertentu?
jpmc26
1
@ jpmc26 Yah, Anda bisa menggunakan Add-Typeatau LoadWithPartialName(), tetapi Anda harus menyadari bahwa yang pertama tidak akan 100% konsisten di seluruh versi dan yang terakhir adalah metode yang usang. Dengan kata lain, .Net ingin Anda peduli dengan versi perpustakaan yang Anda muat.
Bacon Bits
@BaconBits Jawaban lengkap untuk pertanyaan jpmc26 adalah, tergantung apakah Anda menggunakan PowerShell 5 atau PowerShell 6, rakitan yang dimuat mungkin berbeda. JSON.NET memiliki masalah ini dengan fungsi Azure PS.
John Zabroski
@BaconBits Ini adalah penyelaman mendalam yang sangat fantastis ke PowerShell. Anda harus menulis buku.
John Zabroski
1
@ KolobCanyon Karena dalam hal ini Anda harus menggunakan umumnya Add-Type -Path, yang merupakan kode kedua yang disebutkan, atau Assembly.LoadFrom()yang menyelesaikan dependensi untuk Anda (dan, sejauh yang saya tahu, adalah apa Add-Type -Pathgunanya). Satu-satunya waktu Anda harus menggunakan Assembly.LoadFile()adalah jika Anda perlu memuat banyak majelis yang memiliki identitas yang sama tetapi jalur yang berbeda. Itu situasi yang aneh.
Bacon Bits
23

Jika Anda ingin memuat unit tanpa menguncinya selama durasi sesi PowerShell , gunakan ini:

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

Di mana $storageAssemblyPathfilepath dari majelis Anda.

Ini sangat berguna jika Anda perlu membersihkan sumber daya dalam sesi Anda. Misalnya dalam skrip penerapan.

Martin Brandl
sumber
1
👍 👍 👍 Fantastis. Karena di Visual Studio, ketika men-debug Powershell, sesi PS hang setelah eksekusi (melalui PowerShellToolsProcessHost). Pendekatan ini memperbaikinya. Terima kasih.
CJBS
10

Anda dapat memuat seluruh rakitan * .dll dengan

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

Tidak ada jawaban yang membantu saya, jadi saya memposting solusi yang bekerja untuk saya, yang harus saya lakukan adalah mengimpor modul SQLPS, saya menyadari ini ketika secara tidak sengaja saya menjalankan perintah Restore-SqlDatabase dan mulai bekerja, yang berarti bahwa perakitan dirujuk dalam modul itu entah bagaimana.

Lari saja:

Import-module SQLPS

Catatan: Terima kasih Jason karena mencatat bahwa SQLPS sudah usang

alih-alih jalankan:

Import-Module SqlServer

atau

Install-Module SqlServer
dim_user
sumber
2
Bagi siapa pun yang menggunakan pendekatan ini, FYI yang sqlpssudah usang mendukung modulsqlserver
Jason
2

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

Tez Kurmala
sumber
2

Anda bisa menggunakannya LoadWithPartialName. Namun, itu sudah usang seperti yang mereka katakan.

Anda memang dapat mengikuti Add-Type, dan di samping jawaban lain, jika Anda tidak ingin menentukan path lengkap dari file .dll, Anda cukup melakukan:

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

Bagi saya ini menghasilkan kesalahan, karena saya tidak menginstal SQL Server (saya kira), namun, dengan ide yang sama ini saya dapat memuat perakitan Windows Forms:

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

Anda dapat menemukan nama perakitan yang tepat milik kelas tertentu di situs MSDN:

Contoh menemukan nama majelis milik kelas tertentu

ThomasMX
sumber
2

Pastikan Anda memiliki fitur di bawah ini agar diinstal

  1. Jenis Sistem CLR Microsoft untuk SQL Server
  2. Objek Manajemen Bersama Microsoft SQL Server
  3. Microsoft Windows PowerShell Extensions

Anda juga mungkin perlu memuat

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
sumber
Saya menghabiskan satu minggu mencoba memuat perakitan dan tidak melihat output dari pernyataan yang memuat mereka, tetapi ketika saya mencoba menggunakannya, saya mendapatkan kesalahan. Ketika saya menginstal tiga hal ini, itu berhasil. - terima kasih
pparas
0

Tambahkan referensi perakitan di bagian atas.

#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
sumber
Bisakah Anda membuat contoh dari itu?
endo.anaconda
1
Anda hanya perlu menambahkannya di awal skrip PowerShell. Untuk misalnya: Buat Cadangan Database: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SmoExtended") | Out-Null $ SQLServer = Read-Host -Prompt 'Nama SQL Server (opsional)' IF ([string] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host -Prompt ' Nama Database SQL (opsional) 'JIKA ([string] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Baca-Host -Prompt' Login '
Amrita Basu