31 января 2009

IIS и все-все-все

Так мне и не удалось добраться до блога чтоб закончить предыдущий пост... Грустно, но что делать, такова жизнь=) Думаю все еще получится.
Вчера у начальства возникла необходимость чтобы при обращении к любым сайтам установленным на IIS осуществлялся редирект с http на https. Ну ладно, идея в конце-концов благая, правильная, а то до этого у нас месяц примерно клиенты попадали не на сайт, а в черную дыру, т.к. запросы по http просто никто не обрабатывал, не до того было. А вчера значит понадобилось, у меня сервак стоит, срочно запускать надо, а им нет жизни без редиректа. Ладно, проехали...
Вообще-то я уже пытался это сделать раньше, но мне не хватило терпенья, а когда начальство берет за... и требует сделать за час, в общем все как-то само получается.
В общем есть несколько способов.
Первый - запретить обращение к сайту не по https и вместо страницы 404.3 IIS'a повесить свою собственную страничку, которая будет редиректить клиента (описано например тут, там вообще-то для exchange, но схема приемлима для многих вариантов). Но тут я столкнулся с такой фигней, что моя 404.3 страница никак не хотела появляться вместо "родной". К тому же тут используется asp + vbs, а это скучно.
На основе этого решения я попытался сделать следующее. Вешаем на 80 порт сайт с default.asp примерно с таким кодом

<%
If Request.ServerVariables("SERVER_PORT")=80 Then
Dim strSecureURL
strSecureURL = "https://"
strSecureURL = strSecureURL & Request.ServerVariables("SERVER_NAME")
strSecureURL = strSecureURL & "/{NameOfSecureDirectory}"
Response.Redirect strSecureURL
End If
%>

Нов моем случае пришлось бы допиливать код, т.к. редиректило меня на https://hostname/default.asp а это неприемлемо.

В общем все это хорошо, но выглядит, честно говоря не очень.
Во время блужданий по интернету я набрел на такую статью на iis.net.
Вкратце - самописный модуль для IIS. Я раньше пытался использовать этот сценарий, но у меня не получилось т.к. я пытался поместить модуль непосредственно в приложение (в моем случае это SharePoint и CRM) и там запросы обрабатывались не так как надо. Например CRM отказывался проводить аутентификацию пользователей и т.п. Но впрочем это и не удивительно, лишнее доказательство что в случае с MS лучше не добавлять ничего самописного в их продукты.

Тогда я сделал отдельный каталог со следующим наполнением:
каталог App_Code c redir.cs
index.html просто пустой файл, дань традиции=)
web.config в котором указано использовать данный модуль.

На 80 порту висит сайт с этим каталогом, а на 443 - собственно продакшн сайт. Для того чтобы модуль нормально работал сайт с модулем должен быть запущен в Application Pool'е в Integrated Mode. Собственно код есть по ссылкам, приводить его тут не вижу смысла, так же как и выкладывать исходники. Если не прав - напишите.

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 и сделать обработчик ошибок, если сделаю (а я думаю что сделаю) и доберусь до блога (а вот с этим уже сложнее), то вывешу обновленный вариант и даже кину куда-нить полные исходники.