02 января 2009

asp.net + powershell

Нашел любопытный пост на Dev-Infra. Вкратце - запуск всеми любимого PowerShell с веб-страницы. Это может быть достаточно удобным в любой организации где администратор достаточно квалифицирован чтобы написать скрипт, а так же в любой организации где используется Exchange 2007 (правда я пока не нашел как прикрутить EMS, но думаю это дело времени=) ).
В общем-то дело достаточно простое. Что нам потребуется:

Windows SDK for Windows Server 2008 and .NET Framework 3.5

Visual Web Developer 2008 Express Edition


PowerShell на хостовой машине (где и как скачивать найдете и сами =) )

Механизм работы достаточно подробно описан на Dev-Infra. Что же касается конкретики... Например у меня ни на одной из машин (ни на домашней ни на рабочей) SDK не захотел интегрироваться в VWD Express Edition, так что пришлось добавлять Referenc'ы вручную (Add Reference на проекте и дальше добавляем все dll из C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0 ).
Вторые грабли с которыми я столкнулся - правильный вывод команд PowerShell. Т.к. мы получаем не стандартный вывод, а набор объектов, но после добавления следующей строчки:

pipe.Commands.Add("Out-String");

проблема решилась.
Последние грабли случились с IIS 7. Если запускать рабочий процесс в Integrated, а не в Classical mode AJAX работать у меня отказался. С IIS 5 (стоит на домашней машине), не было вообще никаких проблем.
И еще одно - PowerShell выполняется от имени того пользователя, под которым запущен процесс. Так что надо внимательно относиться к выбору пользователя от имени которого должны запускаться скрипты. И назначать ему соответствующие права.
Естественно код предоставляется без всяких гарантий и т.п. Разумеется доступ к этой странице (если вы решитесь ее использовать) следует строго ограничить.

Полный код aspx страницы выложен тут и я не вижу смысла на нем останавливаться.

А вот codebehind файл представляет больший интерес, поэтому его я выкладываю почти полностью. Во-первых потому что на Дев-Инфре он раскидан по всему посту, а во-вторых потому что он чуть-чуть отличается от оригинала.

Добавляем к стандартным пространствам имен следующие:
=====================================================

using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.IO;

=====================================================

=====================================================

protected void BtnExecuteScript_Click(object sender, EventArgs e)
{

string strCurrentId = System.Security.Principal.WindowsIdentity.GetCurrent().Name;

// Включаем таймер, блокируем кнопку, очищаем текстовое поле с прошлым результатом и сообщаем от кого выполняется скрипт

this.Timer1.Enabled = true;

this.BtnExecuteScript.Enabled = false;

this.TxtResult.Text = "";

Session["PowerTrace"] = "Выполняется от имени : " + strCurrentId + "\r\n";

// Получаем текст скрипта

string strContent = TxtPowerShellScript.Text;

// Преобразуем HTML в string и говорим выполниться скрипту с указанным текстом

StringWriter writer = new StringWriter();

Server.HtmlDecode(strContent, writer);

this.executePowerShellCode(strContent);

}

=====================================================

=====================================================

private void executePowerShellCode(string code)
{
// Открываем рабочее пространство, задаем строчку с кодом который необходимо выполнить и добавляем командлет который отдает не объекты, а строки, в общем приводим к привычному виду

runspace.Open();

pipe = runspace.CreatePipeline(code);

pipe.Commands.Add("Out-String");

// Закрываем ввод, добавляем обработчики событий и запускаем выполнение тела скрипта

pipe.Input.Close();

pipe.Output.DataReady += new EventHandler(Output_DataReady);

pipe.StateChanged += new EventHandler(pipe_StateChanged);

pipe.InvokeAsync();

}
void Output_DataReady(object sender, EventArgs e)
{
// Забираем объекты из вывода

PipelineReader reader = (PipelineReader)sender;

String strPowershellTrace = reader.Read().ToString();

Session["PowerTrace"] += strPowershellTrace + "\r\n";

}
void pipe_StateChanged(object sender, PipelineStateEventArgs e)
{
// Заканчиваем выполнение скрипта
if (pipe.PipelineStateInfo.State == PipelineState.Completed)
{
runspace.Close();
while ((Session["PowerTrace"] != null) && (Session["PowerTrace"].ToString().Length > 0))
{
}
Session.Remove("PowerTrace");
}
}
protected void Timer1_Tick(object sender, EventArgs e)

{
// Ну и наконец включаем КНОПКУ и сообщаем о конце выполнения скрипта
if (Session["PowerTrace"] == null)

{

this.BtnExecuteScript.Enabled = true;

Timer1.Enabled = false;

this.TxtResult.Text += "Конец скрипта";

}

else

{

String strPoshTrace = Session["PowerTrace"].ToString();

this.TxtResult.Text += strPoshTrace;

Session["PowerTrace"] = "";

}

}

=====================================================

В общем в результате получаем работающий PS в браузере на нужной машине.

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

Комментариев нет:

Отправить комментарий