Как загрузить сборки в PowerShell?


153

Следующий код PowerShell

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

Выдает следующее сообщение об ошибке:

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

Каждый ответ в Интернете пишет, что я должен загрузить сборку - конечно, я могу прочитать это из сообщения об ошибке :-) - вопрос такой:

Как загрузить сборку и заставить скрипт работать?

Ответы:


179

LoadWithPartialNameбыл объявлен устаревшим Рекомендуемое решение для PowerShell V3 - использовать Add-Typeкомандлет, например:

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

Существует несколько разных версий, и вы можете выбрать конкретную версию. :-)


1
Хорошо, я использую PowerShell3 - эти команды включения кажутся очень сложными. Я бы просто ожидал что-то вроде «включить имя файла».
— Бакстер

6
PowerShell нечувствителен к регистру (если вы не скажете, что он чувствителен к регистру с такими операторами, как -cmatch, -ceq). Таким образом, регистр имен команд и параметров не имеет значения.
— Кит Хилл

5
Да. msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx См. примечание вверху - This API is now obsolete. Конечно, это не мешает людям использовать его.
— Кит Хилл

2
Хотя это технически правильно, что LoadWithPartialNameэто устарело, причины (как изложено в blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx ) явно не применимы для интерактивного сеанса Powershell. Я предлагаю вам добавить примечание, что API подходит для интерактивного использования Powershell.
— Миха Виденманн

Большую часть времени у меня нет проблем со сборкой SMO, но иногда мне нужно убить powershell, и когда я это делаю, у меня возникают проблемы с загрузкой SMO. Добавление add-type -Path исправляет это.
— Николас де Фонтене

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

8
Это слишком полезно, чтобы быть устаревшим без замены! Моя команда использует смесь клиентских инструментов 2008 и 2012 годов. Это единственный способ заставить мои скрипты PowerShell работать на всю мою команду, не включая неуклюжую логику восстановления версий.
— Иэн Самуэль Маклин, старейшина

4
Вы можете направить вывод, Out-Nullесли не хотите, чтобы GAC отображал что-либо.
— Иэн Самуэль Маклин, старейшина

3
@Baxter - вы должны принять этот ответ или ответ Кита, и пусть этот вопрос будет помечен как отвеченный.
— Джайкуль

3
Я использую [void] [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.Smo")
— Сёрен Л. Нильсен,

@IainElder "неуклюжая логика восстановления версий" Вы говорите это, пока не столкнетесь с несовместимостью версий! Это не так сложно сказать Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...].
— Беконные биты

44

Большинство людей к настоящему моменту знают, что System.Reflection.Assembly.LoadWithPartialNameэто устарело, но оказывается, что Add-Type -AssemblyName Microsoft.VisualBasic не ведет себя намного лучше, чемLoadWithPartialName :

Вместо того, чтобы пытаться анализировать ваш запрос в контексте вашей системы, [Add-Type] просматривает статическую внутреннюю таблицу, чтобы перевести «частичное имя» в «полное имя».

Если ваше «частичное имя» не отображается в их таблице, ваш сценарий потерпит неудачу.

Если на вашем компьютере установлено несколько версий сборки, интеллектуального алгоритма для выбора между ними не существует. Вы получите то, что появится в их таблице, вероятно, старое, устаревшее.

Если все версии, которые вы установили, являются более новыми, чем устаревшая в таблице, ваш скрипт завершится ошибкой.

Add-Type не имеет интеллектуального синтаксического анализатора типа «частичных имен» .LoadWithPartialNames.

Microsoft говорит, что на самом деле вы должны делать что-то вроде этого:

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

Или, если вы знаете путь, что-то вроде этого:

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

Это длинное имя, данное для сборки, называется строгим именем , которое уникально как для версии, так и для сборки, а также иногда называется полным именем.

Но это оставляет пару вопросов без ответа:

  1. Как определить строгое имя того, что на самом деле загружается в моей системе с данным частичным именем?

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

Они также должны работать:

Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Если я хочу, чтобы в моем сценарии всегда использовалась определенная версия .dll, но я не могу быть уверен в том, где он установлен, как определить, какое строгое имя используется в .dll?

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

Или:

Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
  1. Если я знаю строгое имя, как мне определить путь к .dll?

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

  2. И в том же духе, если я знаю имя типа того, что я использую, как я узнаю, из какой сборки это происходит?

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

  3. Как посмотреть какие сборки доступны?

Я предлагаю модуль GAC PowerShell . Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullNameработает довольно хорошо

  1. Как я могу увидеть список, который Add-Typeиспользует?

Это немного сложнее. Я могу описать, как получить к нему доступ для любой версии PowerShell с помощью отражателя .Net (см. Обновление ниже для PowerShell Core 6.0).

Сначала выясните, из какой библиотеки Add-Typeпроисходит:

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

Откройте полученную DLL вашим отражателем. Я использовал ILSpy для этого, потому что это FLOSS, но любой отражатель C # должен работать. Откройте эту библиотеку и посмотрите Microsoft.Powershell.Commands.Utility. Под Microsoft.Powershell.Commands, там должно быть AddTypeCommand.

В листинге кода для этого есть закрытый класс InitializeStrongNameDictionary(). Это перечисляет словарь, который отображает короткие имена в строгие имена. В библиотеке я посмотрел почти 750 записей.

Обновление: теперь, когда PowerShell Core 6.0 с открытым исходным кодом. Для этой версии вы можете пропустить описанные выше шаги и увидеть код прямо онлайн в их репозитории GitHub . Однако я не могу гарантировать, что этот код соответствует любой другой версии PowerShell.


3-й вопрос без ответа: что, если я не хочу требовать конкретную версию?
— jpmc26

1
@ jpmc26 Ну, вы можете просто использовать Add-Typeили LoadWithPartialName(), но вы должны знать, что первое не будет на 100% согласованным в разных версиях, а второе - устаревший метод. Другими словами, .Net хочет, чтобы вы заботились о версии загружаемой вами библиотеки.
— биты

@BaconBits Полный ответ на вопрос jpmc26 заключается в том, что в зависимости от того, используете ли вы PowerShell 5 или PowerShell 6, загружаемая сборка может отличаться. JSON.NET имеет эту проблему с функциями Azure PS.
— Джон Заброски

@BaconBits Это действительно фантастическое глубокое погружение в PowerShell. Вы должны написать книгу.
— Джон Заброски

1
@KolobCanyon Потому что в этом случае вы обычно должны использовать Add-Type -Path, который является вторым упомянутым кодом или Assembly.LoadFrom()который разрешает зависимости для вас (и, насколько я могу судить, это то, что Add-Type -Pathиспользует). Единственное время, которое вам следует использовать, Assembly.LoadFile()- это если вам нужно загрузить несколько сборок, которые имеют одинаковые идентификационные данные, но разные пути. Это странная ситуация.
— Бекон

23

Если вы хотите загрузить сборку без блокировки во время сеанса PowerShell , используйте это:

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

Где $storageAssemblyPathнаходится путь к файлу вашей сборки.

Это особенно полезно, если вам нужно очистить ресурсы в течение вашего сеанса. Например, в сценарии развертывания.


1
👍 👍 👍 Фантастика. Потому что в Visual Studio при отладке Powershell сеанс PS зависает после выполнения (через PowerShellToolsProcessHost). Такой подход исправляет это. Спасибо.
— CJBS

15

Вот некоторые сообщения в блоге с многочисленными примерами способов загрузки сборок в PowerShell v1, v2 и v3.

Способы включают в себя:

  • динамически из исходного файла
  • динамически из сборки
  • используя другие типы кода, например, F #

v1.0 Как загрузить сборки .NET в сеансе PowerShell
v2.0 Использование кода CSharp (C #) в сценариях PowerShell 2.0
v3.0 Использование сборок .NET Framework в Windows PowerShell


10

Вы можете загрузить всю сборку * .dll с помощью

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

3

Ни один из ответов не помог мне, поэтому я публикую решение, которое сработало для меня, все, что мне нужно было сделать, это импортировать модуль SQLPS. Я понял это, когда случайно запустил команду Restore-SqlDatabase и начал работать, то есть сборка упоминалась в этом модуле как-то.

Просто беги:

Import-module SQLPS

Примечание: спасибо Джейсону за то, что он отметил, что SQLPS устарел

вместо этого запустите:

Import-Module SqlServer

или

Install-Module SqlServer

2
Для любого , используя этот подход, FYI , который sqlpsявляется устаревшим в пользу модуляsqlserver
— Джейсон


2

Вы могли бы использовать LoadWithPartialName. Тем не менее, это устарело, как они сказали.

Вы действительно можете согласиться Add-Type, и в дополнение к другим ответам, если вы не хотите указывать полный путь к файлу .dll, вы можете просто сделать:

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

Для меня это вернуло ошибку, потому что у меня не установлен SQL Server (я думаю), однако, с этой же идеей я смог загрузить сборку Windows Forms:

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

Точное имя сборки, принадлежащее определенному классу, можно узнать на сайте MSDN:

Пример определения имени сборки, принадлежащей определенному классу


2

Убедитесь, что нижеуказанные функции установлены в порядке

  1. Типы Microsoft CLR для SQL Server
  2. Общие объекты управления Microsoft SQL Server
  3. Расширения Microsoft Windows PowerShell

Также вам может понадобиться загрузить

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"

Я потратил неделю, пытаясь загрузить сборку, и не увидел никакого вывода из оператора, который их загружал, но когда я попытался использовать его, я получил ошибку. Когда я установил эти три вещи, это сработало. - спасибо
— pparas

0

Добавьте ссылки на сборки вверху.

#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

не могли бы вы сделать из этого пример?
— endo.anaconda

1
Вам просто нужно добавить его в начале скрипта powershell. Например: создать резервную копию базы данных: [System.Reflection.Assembly] :: LoadWithPartialName ("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly] :: LoadWithPartialName («Microsoft.SqlServer.SmoExtended») | Out-Null $ SQLServer = Read-Host -Prompt 'Имя сервера SQL (необязательно)' IF ([строка] :: IsNullOrWhitespace ($ SQLServer)) {$ SQLServer = "XXX";} $ SQLDBName = Read-Host -Prompt ' Имя базы данных SQL (необязательно) 'IF ([строка] :: IsNullOrWhitespace ($ SQLDBName)) {$ SQLDBName = "XXX";} $ SQLLogin = Read-Host -Prompt' Login '
— Амрита Басу
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.