До интеграции Remote Desktop Services в организации была полная неразбериха с портами и перенаправлениями.
Передо мной стояла задача закрыть все порты и пустить всех через Remote Desktop Gateway. Но платить за лицензию TS никто не хотел, поэтому «просто» сделать через RemoteApp не получилось. Пришлось делать свой костыль.
0. Дано
В самом начале все порты были такими:
В стандартном RDweb есть поле для ввода хоста или адреса, после соединения будет предложено 2 раза ввести учетные данные и только после этого установится соединение с хостом.
Задача в том, что необходимо через RD WebAccess, через вкладку Remote Desktop пустить пользователя извне на свой компьютер в офисе. Причем пользователь не в курсе что такое IP или hostname. На выбор ему показываем только доступные ему компьютеры. Минимизируем количество кликов и отвлекающих кнопок. Похожая функциональность уже есть в SBS, но перенести его на 2008R2 практически невозможно.
Перейдем непосредственно к правке файла desktops.aspx, расположенного в С:\windows\web\Rdweb\pages\en-US\
1. Для начала заменим поле ввода на выпадающий список:
Вместо:
<input name="MachineName" maxlength="255" id="MachineName" class="textInputField" onfocusin="jscript:ClearTxt(this);" onblur="jscript:checkLen(this, 0);" type="text" onkeydown="jscript:checkKey(this);" onkeyup="jscript:checkLen(this, 1);" />
Вставляем:
<select id="MachineName" style="width: 270px" name="MachineName"> <option value=ex2010.somertile.local>Exchange</option> <option value=AD.somertile.local>AD</option> </select>
И ниже убираем параметр «disabled» из ButtonConnect
<button type="button" id="ButtonConnect" name="ButtonConnect" disabled class="formButton" onclick="BtnConnect()" accesskey=<%=L_ConnectAccessKey_Text %>><%=L_ConnectLabel_Text %></button>
И далее в функции GetParam меняем
switch(obj.tagName) { case "SELECT": return obj.selectedIndex; break; case "INPUT": if (obj.type == "checkbox") return ((obj.checked) ? 1 : 0); if (obj.type == "hidden") return obj.value; if (obj.type == "text") return obj.value; break; default: break; }
На
switch(obj.tagName) { case "SELECT": return obj.options[obj.selectedIndex].value; break; default: break; }
Получится должно что-то такое:
С выдачей компьтеров мы определились. Идем дальше.
Небольшое отступление.
Существенная разница между RemoteApp и RemoteDesktop заключается в том, что RemoteApp генерируется и подписывается RD сервером и тогда можно использовать SSO(singl sing-on). В случае RemoteDestkop RDP файл генерируется на лету и само собой подписи у него нету, что влечет за собой повторный ввод данных. Максимум что мы можем сделать это передать домен\логин в этот RDP файл. Что собственно сейчас и покажу.
Вся магия происходит в функции BtnConnect()
Добавим параметр
RDPstr += "username:s:"+"<%=strDomainUserName%>"+"\n";
Что видим в генерированом файле? Правильно, чертовщину.
Модифицируем код и определим:
public string strUserName = "";
И ниже:
strUserName = strDomainUserName.Substring(strDomainUserName.LastIndexOf('\\') + 1); // Cut domain\username to username
А в BtnConnect() добавляем:
RDPstr += "domain:s:somertile\n"; RDPstr += "username:s:"+"<%=strUserName%>"+"\n"; RDPstr += "promptcredentialonce:i:1\n"; //use same creds for GW and host
NB: В процессе работы импортировал эти пространства имен:
<% @Import Namespace="System.Data" %> <% @Import Namespace="System" %> <% @Import Namespace="System.ComponentModel" %> <% @Import Namespace="System.Runtime.InteropServices" %> <% @Import Namespace="System.Text" %>
Проверяем запуск — домен и логин вставляются верно.
При этом видим, что данные будут использованы и для GW и для хоста.
3. Последний пункт — права и доступ к станциям.
Конечно в идеале все надо было делать через AD и права, но в силу ограниченности времени сделал XML базу вида:
<users> <user username="administrator"> <host> <hostname>ex2010.somertile.local</hostname> <label>Exchange</label> </host> <host> <hostname>AD.somertile.local</hostname> <label>AD</label> </host> </user> </users>
Где определяется логин пользователя и список доступных хостов.
Объявляем
public struct ComputerHost { public string Hostname; public string Label; }
А также парсер этого XML базы
private System.Collections.ArrayList GetUserComputers(string username) { System.Collections.ArrayList hosts = new System.Collections.ArrayList(); System.Xml.XPath.XPathDocument xdoc = null; System.Xml.XPath.XPathNavigator xnav = null; System.Xml.XPath.XPathExpression xexp = null; System.Xml.XPath.XPathNodeIterator xi = null; try { xdoc = new System.Xml.XPath.XPathDocument(Server.MapPath("users.xml")); xnav = xdoc.CreateNavigator(); xexp = xnav.Compile(string.Format("/users/user[@username=\"{0}\"]/host", username.ToLowerInvariant())); xi = xnav.Select(xexp); while (xi.MoveNext()) { ComputerHost h; h.Hostname = xi.Current.SelectSingleNode("./hostname").Value; h.Label = xi.Current.SelectSingleNode("./label").Value; hosts.Add(h); } } catch (System.Xml.XmlException XmlEx) { //Do nothing } catch (Exception Ex) { //Do nothing } finally { //Do Nothing } return hosts; }
Вернемся к нашему выпадающему списку и заменим содержимое на:
<select id="MachineName" style="width: 270px" name="MachineName"> <% System.Collections.ArrayList computerHosts = GetUserComputers(strUserName); foreach (ComputerHost h in computerHosts) { Response.Write(String.Format("\t <option value={1}>{0}</option> \r\n", h.Label, h.Hostname)); } %> </select>
Проверяем — работает.
В дальнейшем можно убрать «лишние» ссылки в RDweb на RemoteApp, Configuration, Help. Модифицировать %L_Company_Text%. Вынести отвлекающие кнопки под опции.
В итоге клиент увидит сайт в таком виде:
4.P.S.
В RDP файле можно предопределить пароль строкой
password 51:b:%hash%
Где %hash% — результат обработки пароля библиотекой crypt32.dll
В этом блоге описан метод реализации кода на Delphi.
Однако даже используя его шифратор и явно определяя пароль в BtnConnect() мне не удалось добиться «подключения по одному клику». Решению данной проблемы буду благодарен.
P.P.S.
Статья написана исходя из поставленной мне задачи. С asp.net и html дружу мало, большинство функций написано перебором и чтением мануалов.
Спасибо за потреченное время. Надеюсь всё это сделано не зря.
ссылка на оригинал статьи http://habrahabr.ru/post/193408/
Добавить комментарий