Adding Wake-on-LAN to MS RemoteApps

BY IN WOL NO COMMENTS YET

I like RD Web Access and RD GatewayUntitled

If you haven’t read up on it much, RD Gateway allows you to connect to Remote Desktop (either a server or workstation) through a https / SSL tunnel. Once you set things up, the default web interface gives you a login screen, a RemoteApps tab and a Remote Desktop tab.

RemoteApp is brilliant – we’re using it together with App-V for RDS to bring our line-of-business apps out in an accessible but secure way for off-campus staff. Applications run as if they’re running on your desktop, even though they’re tunneling through https, and running on an internal RDS server behind several firewalls.

Of course, you can disable the Remote Desktop tab, and you can limit who can connect and what they connect to through the RD Gateway. We’ve recently been experimenting, adding a few desktops to the security groups so staff can get access to their full desktop. But, of course, we don’t want to leave them powered up all the time. That’s where WOL comes in handy.

The Plan

We’re running SCCM 2007 R3, which has power-management features built in. And does wake-on-lan, but only for advertisements it’s scheduled. I didn’t want to go messing with it by adding entries to the database for it to process. So I went for a more straight-forward approach:

  • User enters computer name, clicks Check Power button
  • Xmlhttp request goes off from the page to the RD web server. A simple ASP page forwards this request to the SCCM Management Server (in a handy place firewall-wise); this sends one ping off to the given computer.
  • Javascript in page processes this response. If power is off, a button is presented to send a Wake-up packet.
  • Wake-up button works in similar way; request is proxied and processed in ASP on the SCCM server. This looks up the computer in the Network_DATA table in the ConfigManager database, gets its MAC address and IP address and sends the packet with an open-source executable.
  • Javascript interval timer checks when the computer starts pinging and updates the status on the page. A timeout after 100 secs tells the user to give up.

The Code

The javascript below was added to Desktops.aspx (c:\Windows\Web\RDWeb\Pages\en-US on the RDWeb server). It should be pretty obvious what it does:

var checkTimeHandle;
function checkPing(){
   var pcName = document.getElementById('MachineName').value;
   xmlhttp=new XMLHttpRequest();
   xmlhttp.open("GET","wol.asp?action=ping&host="+pcName,false);

   xmlhttp.onreadystatechange=function()
   {
      if (xmlhttp.readyState==4) {
         var statEl = document.getElementById('PCstatus');

         if(xmlhttp.responseText=='1'){
            statEl.style.color = "green";
            statEl.innerText = "Switched on";
         }else if(xmlhttp.responseText=='0'){
            statEl.style.color = "red";
            if(statEl.innerText.indexOf('aking up')>-1){
               statEl.innerText += '.';
            }else{
               statEl.innerText = 'Switched Off';
               document.getElementById('wolBtn').style.display='';
            }
         }else{
            statEl.style.color = "red";
            statEl.innerText = "PC Not found. Check spelling.";
         }

         if(statEl.innerText.indexOf('aking up')==-1) document.getElementById('checkBtn').style.display = 'none';
      }
   };
   xmlhttp.send();
}

function sendWOL(){
   var pcName = document.getElementById('MachineName').value;
   var statEl = document.getElementById('PCstatus');
   xmlhttp=new XMLHttpRequest();
   xmlhttp.open("GET","wol.asp?action=wol&host="+pcName,false);

   xmlhttp.onreadystatechange=function(){
      if (xmlhttp.readyState==4) {
         document.getElementById('wolBtn').style.display='none';
         if(xmlhttp.responseText.indexOf('sent')>-1){
            statEl.innerText = 'Waking up';
            document.getElementById('checkBtn').style.display = '';
            checkTimeHandle=setInterval('checkPing()', 3000);
            setTimeout('wolTimeout();', 101000);
         }else{
            statEl.innerText = 'Wake-up failed';
         }
      }
   };
   xmlhttp.send();
}

function wolTimeout(){
   if(checkTimeHandle!='') clearInterval(checkTimeHandle);
   if(document.getElementById('PCstatus').innerText.indexOf('aking up')>-1) document.getElementById("PCstatus").innerText="Wake-up failed. Did you switch off at the wall??";
}

You’ll need to add a couple of buttons after the MachineName input:

<button id="checkBtn" style="display: none;" onclick="jscript:checkPing();return false;" type="button">Check Power</button>
<button id="wolBtn" style="display: none;" onclick="jscript:sendWOL();return false;" type="button">Send Wake-up</button>

…and modify the checkKey function:

function checkKey(obj)
{
   if ((window.event.keyCode == 13) && (obj.value.length >= 1))
   {
      window.event.cancelBubble = true;
      window.event.returnValue = false;
      BtnConnect();
   }
   else if ((window.event.keyCode == 13) && (obj.value.length == 0))
   {
      document.getElementById("ButtonConnect").disabled = true;
      var L_errMsgServerInvalid2_Text = "The server name specified is invalid.";
      var retval = TSMsgBox(L_errMsgServerInvalid2_Text, vbCritical, L_sTitle_Text);
      window.event.cancelBubble = true;
      window.event.returnValue = false;
   }
   else
   {
      document.getElementById('PCstatus').innerText='';
      document.getElementById('checkBtn').style.display='';
      if(checkTimeHandle!='') clearInterval(checkTimeHandle);
      obj.style.color="black";
      document.getElementById("ButtonConnect").disabled = false;
      window.event.cancelBubble = true;
   }
}

Of course, the above isn’t perfect. Just a proof of concept, really. Use at your own risk!

As for the actual pinging and WOLing, here’s the back-end script. Note that this is designed to be called with an XMLHTTP request that passes a username and password (for DB connection and execute permissions), as well as filtering the incoming text at bit more.

<%
Dim sIP, oShell, oExec, sCommand, sOutput
sIP = request.querystring("host")
if instr(sIP, " ")>0 or instr(sIP, "'")>0 or instr(sIP, "^")>0 then response.end

action = request.querystring("action")

if action = "ping" then
    sCommand = "%comspec% /c @echo off & for /f ""tokens=7"" %q in ('ping -n 1 -w 25 " & sIP & "^|find /i ""Received =""') do echo %q"
    Set oShell = CreateObject("WScript.Shell")
    Set oExec = oShell.Exec(sCommand)
    sOutput = left(oExec.StdOut.ReadAll, 1)
elseif action = "wol" then
        strConnection = "Driver={SQL Server};Server=localhost;Database=SMS_UOB;"

        Set conn = Server.CreateObject("ADODB.Connection")
        conn.Open strConnection

        Set rs = Server.CreateObject("ADODB.recordset")
        strSQL = "SELECT * FROM Network_DATA WHERE IPAddress0 != 'NULL' AND DNSHostName00 = '"&sIP&"'"
        rs.open strSQL, conn, 3,3

        rs.MoveFirst
        WHILE NOT rs.EOF
            mac = rs("MACAddress0")
            if instr(rs("IPAddress0"), ",")>0 then
                ip = split(rs("IPAddress0"), ",")(0)
            else
                ip = rs("IPAddress0")
            end if
            rs.MoveNext
        WEND

        if mac <> "" then
            sCommand = "C:\inetpub\wwwroot\wol\wolcmd.exe "&mac&" "&ip&" 255.255.255.0"
            Set oShell = CreateObject("WScript.Shell")
            Set oExec = oShell.Exec(sCommand)
            op = sCommand & vbcrlf & oExec.StdOut.ReadAll
            if instr(op, "sent")>0 then sOutput = "WOL sent"
        end if
end if

response.write sOutput
%>

And there it is; users can log in and easily wake their computer from anywhere, on the same page as the remote desktop gateway link. I won’t give you the code to the ‘wol.asp’ page – it just filters inputs and passes a GET request to the sccm server. I will recommend that you add something like the following to the top, though:

if(Request.ServerVariables("REMOTE_USER") = "") then
   response.redirect "simpleLogin.aspx"
   response.end
end if

So, what do you think ?