Как я уже описывал тут и тут, мы избрали PowerShell в качестве языка для разработки сценариев управления инфраструктурой. Дело осталось за малым – научиться вызывать PowerShell скрипты из серверного приложения, написанного на C#.
Всё новое – это неизвестное старое…
На самом деле данная технология достаточно старая, но при этом она почему-то не особо распространена. Возможно, люди просто боятся её использовать. Может быть отчасти это связано с тем, что соответствующие статьи на MSDN`е до сих пор помечены вот таким не хитрым предупреждением:
Итак, вы всё-таки решились, невзирая на предупреждение Microsoft, добавить в ваше приложение интеграцию c PowerShell`ом. В таком случае ищите все необходимые классы для управления окружением PowerShell 2.0 и вызовом скриптов из .NET в пространстве имён System.Management.Automation, которое располагается в одноимённой сборке. Правда сначала вам придётся найти саму сборку
. Дело в том, что она отсутствует в списке .NET компонентов, доступных для подключения к вашему проекту:

В итоге вопрос "Где найти System.Management.Automation.dll?" – самый популярный среди разработчиков, впервые решивших использовать данную технологию.
В большинстве случаев вам придётся поставить Windows SDK, тогда она появится в каталоге "C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0". Но даже после этого, добавлять к проекту её придётся руками по прямой ссылке на файл.
Второй способ попадания сборки System.Management.Automation.dll в систему – это установка PowerShell 2.0. В этом случае её можно найти в GAC`е примерно здесь: C:\Windows\assembly\GAC_MSIL\System.Management.Automation
Добавим немного кода
На этом самая сложная часть осталась позади, потому что собственно код, позволяющий выполнить PowerShell скрипт из приложения на C#, прост до нельзя. Вот он:
Давайте разберёмся, что же здесь происходит. Для начала я создаю конфигурацию сессии, используя конфигурацию по-умолчанию в качестве шаблона, и добавляю к ней две глобальный переменные: ErrorActionPreference со значением Stop и Arguments со значением типа System.Hashtable, которое моя функция получает из вне.
Первая переменная меняет политику обработки ошибок в скриптах. PowerShell – это в первую очередь язык написания командных сценариев со всеми вытекающими отсюда особенностями. В частности, по-умолчанию он реагирует на ошибки так же, как командная оболочка Windows: выводит сообщение и продолжает выполнение скрипта. Изменив подобным образом значение переменной ErrorActionPreference в глобальном контексте, мы изменяем это поведение на противоположное: любая ошибка будет приводить к моментальному завершению и выбросу исключения, которое мы можем легко и не принуждённо поймать и обработать.
С помощью второй переменной, как, наверное, не сложно догадаться из названия, я передаю параметры моему скрипту. Мы экспериментировали с разными способами – этот оказался самый удобный. Обратите внимание на тип передаваемой переменной: в таком же виде она будет видна и скрипту, а именно, как переменная с именем Arguments и типом hashtable:
Затем я создаю и открываю командную оболочку PowerShell, представленную экземпляром класса System.Management.Automation.Runspaces.Runspace. К ней я присоединяю новый конвейер команд (pipeline) PowerShell`а:
Обрати особое внимание: класс System.Management.Automation.PowerShell представляет собой конвейер команд, а не само окружение PowerShell, как это могло бы показаться на первый взгляд. Это странное именование может ввести в заблуждение. Второй интересный момент: поскольку это именно "конвейер" команд, то все отдельно добавленные к нему команды или целые скрипты считаются объединёнными с помощью операции "|" (в простонародье "труба"). Именно эта особенность объясняет, почему я воспользовался банальной конкатенацией строк, чтобы добавить свою команду к скрипту, который я хочу выполнить, а не вызвал метод AddScript 2 раза:
Собственно добавленная команда стоит отдельного упоминания. Эта команда – ещё один способ сгладить скриптовую природу PowerShell. Она всего лишь запрещает использование не инициализированных переменных, что сильно упрощает поиск ошибок в скриптах.
Всё что осталось – это вызвать скрипт, прочитать результат и обработать ошибки:
Как не сложно понять из кода, результат выполнения скрипта возвращается в виде коллекции объектов типа System.Management.Automation.PSObject. А ошибки, благодаря глобальной переменной ErrorActionPreference, значение которой мы выставили в Stop, мы получаем, обработав исключение System.Management.Automation.RuntimeException.
Вместо заключения
Мы использовали этот метод для подключения PowerShell 2.0 к нашей системе управления инфраструктурой. С выходом Windows Server 2012 стал доступен PowerShell третьей версии. Честно признаюсь: пока что мы ещё не ставили экспериментов по интеграции его с приложением на C#. Сегодня я отбываю на MS TechEd 2012, где обязательно постараюсь задать вопрос Джефри Сноверу о самой возможности такой интеграции вообще.
Архив с полным исходным кодом проекта можно скачать здесь.
Всё новое – это неизвестное старое…
На самом деле данная технология достаточно старая, но при этом она почему-то не особо распространена. Возможно, люди просто боятся её использовать. Может быть отчасти это связано с тем, что соответствующие статьи на MSDN`е до сих пор помечены вот таким не хитрым предупреждением:
This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.Как следствие мало информации о самой возможности интегрирования PowerShell и .NET. Так или иначе, но мы наблюдаем это предупреждение уже около 3-ёх лет и спокойно пользуемся данными возможностями.
Итак, вы всё-таки решились, невзирая на предупреждение Microsoft, добавить в ваше приложение интеграцию c PowerShell`ом. В таком случае ищите все необходимые классы для управления окружением PowerShell 2.0 и вызовом скриптов из .NET в пространстве имён System.Management.Automation, которое располагается в одноимённой сборке. Правда сначала вам придётся найти саму сборку

В итоге вопрос "Где найти System.Management.Automation.dll?" – самый популярный среди разработчиков, впервые решивших использовать данную технологию.
В большинстве случаев вам придётся поставить Windows SDK, тогда она появится в каталоге "C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0". Но даже после этого, добавлять к проекту её придётся руками по прямой ссылке на файл.
Второй способ попадания сборки System.Management.Automation.dll в систему – это установка PowerShell 2.0. В этом случае её можно найти в GAC`е примерно здесь: C:\Windows\assembly\GAC_MSIL\System.Management.Automation
Добавим немного кода
На этом самая сложная часть осталась позади, потому что собственно код, позволяющий выполнить PowerShell скрипт из приложения на C#, прост до нельзя. Вот он:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Management.Automation.Runspaces;
using System.Management.Automation;
// ...
private IEnumerable<string> Call(string script, Hashtable args)
{
InitialSessionState state = InitialSessionState.CreateDefault();
state.Variables.Add(new SessionStateVariableEntry(
"ErrorActionPreference", "Stop", null));
state.Variables.Add(new SessionStateVariableEntry(
"Arguments", args, null));
using (Runspace runspace = RunspaceFactory.CreateRunspace(state))
{
runspace.Open();
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
shell.AddScript("Set-PSDebug -Strict\n" + script);
try
{
return new List<string>(
from PSObject obj in shell.Invoke()
where obj != null select obj.ToString());
}
catch (RuntimeException psError)
{
ErrorRecord error = psError.ErrorRecord;
return error.InvocationInfo == null
? FormatErrorSimple(error.Exception)
: FormatError(error.InvocationInfo, error.Exception);
}
}
}
}
using System.Collections.Generic;
using System.Collections;
using System.Management.Automation.Runspaces;
using System.Management.Automation;
// ...
private IEnumerable<string> Call(string script, Hashtable args)
{
InitialSessionState state = InitialSessionState.CreateDefault();
state.Variables.Add(new SessionStateVariableEntry(
"ErrorActionPreference", "Stop", null));
state.Variables.Add(new SessionStateVariableEntry(
"Arguments", args, null));
using (Runspace runspace = RunspaceFactory.CreateRunspace(state))
{
runspace.Open();
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
shell.AddScript("Set-PSDebug -Strict\n" + script);
try
{
return new List<string>(
from PSObject obj in shell.Invoke()
where obj != null select obj.ToString());
}
catch (RuntimeException psError)
{
ErrorRecord error = psError.ErrorRecord;
return error.InvocationInfo == null
? FormatErrorSimple(error.Exception)
: FormatError(error.InvocationInfo, error.Exception);
}
}
}
}
Давайте разберёмся, что же здесь происходит. Для начала я создаю конфигурацию сессии, используя конфигурацию по-умолчанию в качестве шаблона, и добавляю к ней две глобальный переменные: ErrorActionPreference со значением Stop и Arguments со значением типа System.Hashtable, которое моя функция получает из вне.
InitialSessionState state = InitialSessionState.CreateDefault();
state.Variables.Add(new SessionStateVariableEntry(
"ErrorActionPreference", "Stop", null));
state.Variables.Add(new SessionStateVariableEntry(
"Arguments", args, null));
state.Variables.Add(new SessionStateVariableEntry(
"ErrorActionPreference", "Stop", null));
state.Variables.Add(new SessionStateVariableEntry(
"Arguments", args, null));
Первая переменная меняет политику обработки ошибок в скриптах. PowerShell – это в первую очередь язык написания командных сценариев со всеми вытекающими отсюда особенностями. В частности, по-умолчанию он реагирует на ошибки так же, как командная оболочка Windows: выводит сообщение и продолжает выполнение скрипта. Изменив подобным образом значение переменной ErrorActionPreference в глобальном контексте, мы изменяем это поведение на противоположное: любая ошибка будет приводить к моментальному завершению и выбросу исключения, которое мы можем легко и не принуждённо поймать и обработать.
С помощью второй переменной, как, наверное, не сложно догадаться из названия, я передаю параметры моему скрипту. Мы экспериментировали с разными способами – этот оказался самый удобный. Обратите внимание на тип передаваемой переменной: в таком же виде она будет видна и скрипту, а именно, как переменная с именем Arguments и типом hashtable:
$DeviceMAC = $Arguments["DeviceMAC"]
Затем я создаю и открываю командную оболочку PowerShell, представленную экземпляром класса System.Management.Automation.Runspaces.Runspace. К ней я присоединяю новый конвейер команд (pipeline) PowerShell`а:
using (Runspace runspace = RunspaceFactory.CreateRunspace(state))
{
runspace.Open();
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
//...
}
}
{
runspace.Open();
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
//...
}
}
Обрати особое внимание: класс System.Management.Automation.PowerShell представляет собой конвейер команд, а не само окружение PowerShell, как это могло бы показаться на первый взгляд. Это странное именование может ввести в заблуждение. Второй интересный момент: поскольку это именно "конвейер" команд, то все отдельно добавленные к нему команды или целые скрипты считаются объединёнными с помощью операции "|" (в простонародье "труба"). Именно эта особенность объясняет, почему я воспользовался банальной конкатенацией строк, чтобы добавить свою команду к скрипту, который я хочу выполнить, а не вызвал метод AddScript 2 раза:
shell.AddScript("Set-PSDebug -Strict\n" + script);
Собственно добавленная команда стоит отдельного упоминания. Эта команда – ещё один способ сгладить скриптовую природу PowerShell. Она всего лишь запрещает использование не инициализированных переменных, что сильно упрощает поиск ошибок в скриптах.
Всё что осталось – это вызвать скрипт, прочитать результат и обработать ошибки:
try
{
return new List<string>(
from PSObject obj in shell.Invoke()
where obj != null select obj.ToString());
}
catch (RuntimeException psError)
{
ErrorRecord error = psError.ErrorRecord;
return error.InvocationInfo == null
? FormatErrorSimple(error.Exception)
: FormatError(error.InvocationInfo, error.Exception);
}
{
return new List<string>(
from PSObject obj in shell.Invoke()
where obj != null select obj.ToString());
}
catch (RuntimeException psError)
{
ErrorRecord error = psError.ErrorRecord;
return error.InvocationInfo == null
? FormatErrorSimple(error.Exception)
: FormatError(error.InvocationInfo, error.Exception);
}
Как не сложно понять из кода, результат выполнения скрипта возвращается в виде коллекции объектов типа System.Management.Automation.PSObject. А ошибки, благодаря глобальной переменной ErrorActionPreference, значение которой мы выставили в Stop, мы получаем, обработав исключение System.Management.Automation.RuntimeException.
Вместо заключения
Мы использовали этот метод для подключения PowerShell 2.0 к нашей системе управления инфраструктурой. С выходом Windows Server 2012 стал доступен PowerShell третьей версии. Честно признаюсь: пока что мы ещё не ставили экспериментов по интеграции его с приложением на C#. Сегодня я отбываю на MS TechEd 2012, где обязательно постараюсь задать вопрос Джефри Сноверу о самой возможности такой интеграции вообще.
Архив с полным исходным кодом проекта можно скачать здесь.
1 комментарий:
Отлично. Жаль, что нет архива по ссылке
Отправить комментарий