Wrote this article almost 6 months ago…finally got around to finishing it–Let me know what you think!
“Project AppBlast will provide the universal delivery of any application, including Windows-based applications, to any off-the-shelf browser or device supporting HTML 5, enabling instant remote access to non-HTML based applications.”
We discussed this at work the other day and we were excited at the possibilities with AppBlast, but since it’s not here now we’ll have to wait for a released product. Â One of my co-workers Chad Wintzer said well why wait? Â We dug around and came up with a working solution–and a damn good one after a few evenings grinding away at this. Â Having a fair amount of RDS experience already this is what we came up with:
Backend
The backend infrastructure is provided by Microsoft’s Remote Desktop Services: RemoteApp.  This feature was introduced in Windows Server 2008 and was improved upon even more in the R2 release.  What RemoteApp allows you to do is run applications on backend RDS (TS) servers, and establish an RDP session from a user to just that app.  The user is presented with a window over their local desktop and functions just about as if it was actually installed and running off the local desktop.  Users can run multiple applications in parallel and so long as the app behaves well in a RDS environment you can host 1 installation with multiple users.  The servers that run RemoteApps are called RD Session Hosts, to scale you can simply throw a bunch of RD Session Hosts together and strap a load balancer on top, however you will want to use RD Connection Broker(s) in place of a traditional load balancer to keep track of user sessions.  The Connection Broker(s) manage session information such as RD Session Host server, session state, session ID, and the user associated with the aforementioned bits.  For the security junkies it’s worth noting that RemoteApp is AD integrated so you can restrict access to various apps via Security Groups.  This was something added in R2 I believe as many folks were complaining about this when RemoteApp first came out.
If you don’t want to go that route you can look at Citrix’s XenApp offering, but we found RDS: RemoteApp performed exactly as we needed it to.
Troubleshooting Broken Apps in RDS
Keep in mind if you are having trouble getting an application to play nice in your RDS environment you can take advantage of various application virtualization technologies to sandbox them. Â Once sandboxed they almost always seem to work–your choices are:
- Â VMware’s ThinApp
- Cameyo – Free
- Evalaze – Free (for personal use)
Frontend
So now that you have your backend sorted out, lets talk frontend. Â You have a few options:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
remoteapp-mspublisher.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>RemoteApp</title> <link rel="stylesheet" href="tabs.css" /> <link rel="stylesheet" href="rdp.css" /> </head> <body onload=""><div id="main"><div id="header"><div class="logo"><img src="images/cloud_small.png" alt="icon" /></div><div class="menubutton">Cloud Remote App Session | Microsoft Publisher 2010</div></div><script type="text/javascript" src="rdp_min.js"></script><div style="height:30px;"></div><canvas id="remotectrl" width="1" height="1"></canvas><div id="remoteapp"> <img src="images/cloud-large.png"/> <div class="welcome">Cloud Remote App Session | Microsoft Publisher 2010</div> <div id="remoteappalert">Connecting to your remote application...</div></div><script type="text/javascript"> var hasCanvas = false; var msg = ""; try { document.createElement("canvas").getContext("2d"); hasCanvas = true; } catch (e) { msg = "This browser does not support Canvas.\n\n"; }; var noWebSocket = !("WebSocket" in window) && !("MozWebSocket" in window); var userAgent = navigator.userAgent; var isFirefox = userAgent.indexOf("Firefox") != -1; if (noWebSocket){ msg += "This browser doesn't support WebSocket.\n\n"; if (isFirefox){ msg += "Please update to Firefox 6 or later.\n\n"; } else if (userAgent.indexOf("Opera") != -1){ msg += "Please open 'opera:config#Enable WebSockets' (type it in the link field) make 'Enable WebSockets' selected and restart Opera.\n\n"; } else if (userAgent.indexOf("MSIE") != -1){ msg += "Please install Google Chrome Frame.\n\n"; } } var hasAudio = ("webkitAudioContext" in window) || ("AudioContext" in window) || isFirefox; if (!hasAudio && (userAgent.indexOf("Chrome") != -1)){ msg += "Please enable Web Audio by going to 'about:flags' (type it in the link field), enabling 'Web Audio', clicking the restart button at the bottom of the page.\n\n"; } if (msg.length > 0) alert(msg); var ready = !noWebSocket && hasCanvas; var width = Math.max(window.innerWidth, window.innerHeight); var height = Math.min(window.innerWidth, window.innerHeight); height = height-30; var protocol = ("https:" == location.protocol) ? "wss://" : "ws://"; // RemoteSpark URL var s = "ws://remotespark.requeny.local:8080/RDP?"; // RDS Session Host Server s += "server=rdsfarm.requeny.local&"; // RDS Session Host Port s += "port=3389&"; // Keyboard Language s += "keyboard=1033&"; // Username (Left Blank by Default) s += "user=&"; // Domain (To default to) s += "domain=requeny.local&"; //s += "fullBrowser=Full%20browser&"; //s += "fullScreen=Full%20screen&"; s += "playSound=0&"; // Example RemoteApp (MSPublisher) s += "command=%7C%7CMSPUB&"; // Required for RemoteApp functionality s += "startProgram=on&"; s += "mapClipboard=on&"; s += "mapPrinter=on&"; s += "clear=Clear&"; s += "clear=Delete&"; s += "save=Save&"; // Auto Connect s += "connect=Connect&"; // Scale to the size of your browser window s += "width="+width+"&"; s += "height="+height+"&"; // Color Depth s += "server_bpp=16&"; s += "audio=true"; var r = new Rdp(s, width, height, 16); r.onclose = function(){ r.hide(); //$id("login").style.display = "block"; }; r.run(); </script> |