Как загрузить сборки в 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.