J3qx

information archive

Кастомизация RDweb в Windows Server 2008R2

Posted by j3qx на Октябрь 1, 2013

Кастомизация RDweb в Windows Server 2008R2

В данном топике я поделюсь своим опытом модифицирования RDweb для Windows Server 2008R2.

До интеграции 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 дружу мало, большинство функций написано перебором и чтением мануалов.

Спасибо за потреченное время. Надеюсь всё это сделано не зря.

Реклама

Добавить комментарий

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

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

 
%d такие блоггеры, как: