In our earlier blog post, we introduced RemoteApp and Desktop Connections to enable users to have their RemoteApp and Desktop icons integrated on their Windows 7 start menu. RemoteApp and Desktop Connections works with this new feature of Remote Desktop Web Access (RD Web Access)--the RemoteApp and Desktop Connection feed. This RemoteApp and Desktop Connection feed provides significant extensibility to partners and customers in presenting remote resources in various ways. The connection feed feature contains information about published remote resources (e.g. RDP files and their associated icon and image files) in a software-parsable XML format.
On Remote Desktop Web Access (RD Web Access) Server, there are two URLs available to serve the connection feed. You need to choose one depending on the extensibility scenario.
By using this connection feed URL There is no need to write a custom authentication mechanism because the webpage runs within the RD Web Access web application. It is easier to support Single Sign On for launching remote resources. In a future post, we will cover on how to enable SSO.
By using this connection feed URL
Here is a sample connection XML containing information about remote resources:
<?xml version="1.0" encoding="utf-8"?> <ResourceCollection PubDate="2009-07-09T17:57:30.323Z" SchemaVersion="1.1" xmlns="http://schemas.microsoft.com/ts/2007/05/tswf"> <Publisher LastUpdated="2009-07-09T17:57:12.588625Z" Name="Remote Desktop Services Default Connection" ID="Contoso" Description=""> <Resources> <Resource ID="60fb077b94a241a473cf982140337213e4d93177" Alias="mspaint" Title="Paint" LastUpdated="2009-07-09T17:57:12.588625Z" Type="RemoteApp" ExecutableName="mspaint.exe"> <Icons> <IconRaw FileType="Ico" FileURL="/RDWeb/Pages/rdp/mspaint032x32.ico" /> <Icon32 Dimensions="32x32" FileType="Png" FileURL="/RDWeb/Pages/rdp/mspaint032x32.png" /> </Icons> <FileExtensions /> <HostingTerminalServers> <HostingTerminalServer> <ResourceFile FileExtension=".rdp" URL="/RDWeb/Pages/rdp/Contoso-mspaint.rdp" /> <TerminalServerRef Ref="Contoso" /> </HostingTerminalServer> </HostingTerminalServers> </Resource> </Resources> <TerminalServers> <TerminalServer ID="Contoso" Name="Contoso" LastUpdated="2009-07-09T17:57:12.588625Z" /> </TerminalServers> </Publisher> </ResourceCollection>
The bolded attributes in the connection provide information about the remote resources. The schema file for the connection is located at %windir%\schemas\tsworkspace\tswf.xsd in Windows Server 2008 R2 or on a Window 7 client machine.
Following is a code sample in C# (ASP.NET) on how to use the connection to create a new web page as demonstrated at PDC 2008. This code sample uses a connection at the “/rdweb/pages/webfeed.aspx” location. Below are the steps that need to complete in creating the new web page.
Notes on this sample code:
<%@ Page Language="C#" Debug="false" Trace="false" %> <% @Import Namespace="System.IO" %> <% @Import Namespace="System.Collections.Generic" %> <% @Import Namespace="System.Web.Configuration" %> <% @Import Namespace="System.Xml" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script language="C#" runat="server">
protected void Page_PreInit(object sender, EventArgs e) { AuthenticationMode eAuthenticationMode = AuthenticationMode.None; AuthenticationSection objAuthenticationSection = ConfigurationManager.GetSection("system.web/authentication") as AuthenticationSection; if (objAuthenticationSection != null) { eAuthenticationMode = objAuthenticationSection.Mode; } if (eAuthenticationMode == AuthenticationMode.Forms) { if (HttpContext.Current.User.Identity.IsAuthenticated == false) { // User is not logged in, so redirect the request to login page. Response.Redirect("login.aspx?ReturnUrl=" + Request.FilePath); } } } protected void Page_Load(object sender, EventArgs e) { try { string connectionXml; // Execute the handler for webfeed.aspx in the context of current request // and capture output connectionXml. using (StringWriter stringWriter = new StringWriter()) { // This new web page is running under the same web application, and // with using existing authentication. // So We need run the Server.Execute to get the connection Xml. HttpContext.Current.Server.Execute("..//webfeed.aspx", stringWriter); connectionXml = stringWriter.ToString(); } // Set current reponse content type Response.ContentType = "text/HTML"; //Strip out the BOM (U+FEFF) connectionXml = connectionXml.Trim(); // Sample code to parse the connection xml. Instead of parsing the connection xml, we can also use XSL transformations directly to display the remote resources List<ConnectionResourceInfo> listConnectionResourceInfos = GetConnectionResourceInfo(connectionXml); dataListResources.DataSource = listConnectionResourceInfos; dataListResources.DataBind(); labelStatus.Text = "Connection Resources : " + listConnectionResourceInfos.Count; } catch (Exception ex) { labelStatus.Text = "Error occurred ~ " + ex.Message; } } private String GetAbsolutePath(string relativePath) { return String.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Host, relativePath); } // Parse the XML file for demo purpose. In this function we are not using all // the xml information available, just the title and resource's image file path. private List<ConnectionResourceInfo> GetConnectionResourceInfo(string connectionXml) { List<ConnectionResourceInfo> listConnectionResourceInfos = new List<ConnectionResourceInfo>(); XmlDocument xmlDocument = new XmlDocument(); // Load the connection xml string xmlDocument.LoadXml(connectionXml); XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable); xmlNamespaceManager.AddNamespace("tswf", "http://schemas.microsoft.com/ts/2007/05/tswf"); // Get the list of Resource elements XmlNodeList xmlNodeList = xmlDocument.SelectNodes("/tswf:ResourceCollection/tswf:Publisher/tswf:Resources/tswf:Resource", xmlNamespaceManager); // For each resource get the title and image file relative path for (int i = 0; i < xmlNodeList.Count; i++) { ConnectionResourceInfo connectionResourceInfo = new ConnectionResourceInfo(); connectionResourceInfo.Title = xmlNodeList[i].Attributes["Title"].Value; XmlNode xmlNodeIcon32 = xmlNodeList[i].SelectSingleNode("tswf:Icons/tswf:Icon32", xmlNamespaceManager); // Convert the image's relative path to ablosute path connectionResourceInfo.ImageFilePath = GetAbsolutePath(xmlNodeIcon32.Attributes["FileURL"].Value); listConnectionResourceInfos.Add(connectionResourceInfo); // Here you would want to retrieve any other XML information you // want for each resource from the connection } return listConnectionResourceInfos; } protected void SignOut_Click(object sender, EventArgs e) { //Redirect the request to logoff page Response.Redirect("LogOff.aspx"); } // Entity class to hold connection resource info, for now it contains only the resource title, and its image path private class ConnectionResourceInfo { private string title; private string imageFilePath; public string Title { get { return title; } set { title = value; } } public string ImageFilePath { get { return imageFilePath; } set { imageFilePath = value; } } }
</script> <html> <head id="Head1" runat="server"> <meta http-equiv="CONTENT-TYPE" content="TEXT/HTML; CHARSET=UTF-8"> <meta http-equiv="CACHE-CONTROL" content="NO-CACHE"> <meta http-equiv="PRAGMA" content="NO-CACHE" /> <title>New RD Web Access</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Button ID="btnSignOut" runat="server" Text="SignOut" OnClick="SignOut_Click" /> <br /> <asp:Label ID="labelStatus" runat="server" Text=""></asp:Label> <br /> <asp:DataList ID="dataListResources" runat="server" RepeatDirection="Horizontal" RepeatColumns="4" BorderColor="black" CellPadding="1" BorderWidth="1" ItemStyle-BorderWidth="1"> <ItemTemplate> <asp:Image ID="ResImage" ImageUrl='<%# DataBinder.Eval(Container.DataItem, "ImageFilePath") %>' runat="server" /><br /> <%# DataBinder.Eval(Container.DataItem, "Title") %> </ItemTemplate> </asp:DataList> </div> </form> </body> </html>
The sample page will appear as below.
Following is a code sample in C# on how to use the connection to create a client application. This code sample uses the connection at the “/rdweb/feed/webfeed.aspx” location.
using System.IO; using System.Net; using System.Web.Security; private void GetConnectionContents() { System.Diagnostics.ConsoleTraceListener trace = new System.Diagnostics.ConsoleTraceListener(); //RemoteApp and Desktop Connections uses HTTPS to connect to the server. //In order to connect properly, the client operating system must trust the SSL certificate of the RD Web Access server. //Also, the server name in the URL must match the one in the server’s SSL certificate // User credentials to access the connection. Fill in <username>, <password>, <domainname> with your user credentials. NetworkCredential networkCredential = new NetworkCredential("<username>", "<password>", "<domainname>"); //This URL will generally be of this form. Fill in <servername> with your server’s name string connectionUrl = "https://<servername>/rdweb/feed/webfeed.aspx"; string formsAuthenticationCookie = GetFormsAuthenticationCookie(connectionUrl, networkCredential, trace); string connectionXml = GetConnectionXml(connectionUrl, formsAuthenticationCookie, trace); //Fill in your code to parse the connection, and to download resource files and associated icon & image files. Use earlier cache cookie for authentication. } private string GetFormsAuthenticationCookie(string connectionUrl, NetworkCredential networkCredential, System.Diagnostics.ConsoleTraceListener trace) { // // Request connection page is protected by Forms Authentication Cookie. So making a request to that page will be redirected to login page // The login page is // trace.Write("Requesting : " + connectionUrl); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(connectionUrl); CredentialCache credentialCache = new CredentialCache(); credentialCache.Add(new Uri(connectionUrl), "Negotiate", networkCredential); httpWebRequest.Credentials = credentialCache; httpWebRequest.AllowAutoRedirect = true; HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); if (httpWebResponse.StatusCode == HttpStatusCode.OK) { trace.Write("Response: 200 "); trace.WriteLine(httpWebResponse.StatusCode); } else { trace.Fail("Response status is " + httpWebResponse.StatusCode + ". Expected was OK"); } trace.WriteLine("Response will be the Forms Authentication Cookie"); trace.WriteLine(""); string formsAuthenticationCookie; using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { formsAuthenticationCookie = streamReader.ReadToEnd(); streamReader.Close(); } trace.WriteLine("formsAuthenticationCookie " + formsAuthenticationCookie); return formsAuthenticationCookie; } private string GetConnectionXml(string connectionUrl, string formsAuthenticationCookie, System.Diagnostics.ConsoleTraceListener trace) { // // Request connection page // trace.Write("Requesting : " + connectionUrl); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(connectionUrl); httpWebRequest.CookieContainer = new CookieContainer(); // // Set Froms Authentication Cookie // httpWebRequest.CookieContainer.Add(new Cookie(FormsAuthentication.FormsCookieName, formsAuthenticationCookie, "/", httpWebRequest.RequestUri.Host)); // Get response HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse(); if (httpWebResponse.StatusCode == HttpStatusCode.OK) { trace.Write("Response: 200 "); trace.WriteLine(httpWebResponse.StatusCode); } else { trace.Fail("Response status is " + httpWebResponse.StatusCode + ". Expected was OK"); } trace.WriteLine("Response will be the connectionXml"); trace.WriteLine(""); string connectionXml; using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { connectionXml = streamReader.ReadToEnd(); } trace.WriteLine("connectionXml " + connectionXml); return connectionXml; }
As shown above, RemoteApp and Desktop Connection provides an easy way to customize the standard look and feel of RD Web Access or to present remote resources in new ways.