HTTP Publishing in App-V (Part 1)

With all the excitement around HTTP streaming in App-V 4.5, many people fail to realize HTTP Publishing has been a feature of App-V since…well…the very first release of SoftGrid. This series of articles will discuss how to build your own App-V publishing server. In this first article, I will go over how to create a very basic publishing server. In future articles, I will discuss advanced features that can be enabled using a custom HTTP publishing server.

Overview

HTTP Publishing is a surprisingly powerful feature. It provides you with complete control over which applications are delivered to each user. The criteria that you choose to make this decision are completely up to you. For example, you could base it on the ACLs associated with the package files. With this strategy, if the user has access to package files, then the applications in the package are published to the user. This simple policy provides similar entitlement functionality that is available with the App-V Management Server. It is the one that will be demonstrated in this article.

Much more interesting policies are also possible. For example, entitlement could be based on information in the corporate HR database. Or, it could be based on some other attributes on the user's Active Directory object. Another option would be to query some custom SQL database to determine which applications the user is entitled to use. Which option you choose will depend on the needs of your particular scenario.

Publishing Architecture

The publishing architecture in App-V is fairly simple. The client sends a request to the server asking for the list of applications for the user. This request is sent while impersonating the user, so that the server can access the user's token, if needed.

The server's response is a single XML file that contains the publishing information associated with each application, including its shortcuts, file type associations and DDE entries. The rest of this article describes how to generate this document in the appropriate format.

Generating the Publishing Document

Prior to App-V 4.5, creating a Publishing Server was a reasonably complicated task. It required you to generate a properly formatted XML document describing all of the applications, shortcuts, file type associations and DDE entries for each package that a user was entitled to use. App-V 4.5 introduced the concept of package manifest files, which contains all of this information in the correct format. This makes creating an HTTP publishing server a much easier task.

The following is an example publishing document that contains the information for a single application. While it contains a lot of information, most of it comes directly from the package's manifest file. So, you will not have to deal with it directly. It is included purely for illustrative purposes.  Click on the image to see a larger version.

 

As can be seen in this sample, the document element is called <DESKTOPCONFIG>. It contains two child elements, <POLICY> and <APPLIST>. The <POLICY> element has a <REFRESH> element that allows you to specify the Publishing Refresh frequency, in minutes and a boolean that determines if Publishing Refresh occurs when the user first logs in.

The <APPLIST> section is nothing but a concatenation of the <APPLIST> sections from each of the package manifests to which the user has access. The sample code that follows describes how to generate this list.

The following is the ASPX page that will be used in this sample. It contains the basic structure of the Publishing document.

 Copyright (c) Microsoft Corporation.  All rights reserved.  For personal use only.  Provided AS IS and WITH ALL FAULTS.  
 <%@                                       Page                                     Language="C#"                                        AutoEventWireup="true"                                       CodeBehind="publishing.aspx.cs"                                      Inherits="appv_publishing_service.publishing"                                        ContentType="text/xml"                                       %> 
                                
 <DESKTOPCONFIG> 
                                
 <POLICY                                     MANAGEDDESKTOP="TRUE"                                        REPORTING="FALSE"> 
                                
 <REFRESH                                        ONLOGIN="TRUE"                                       PERIOD="60"/> 
                                
 </POLICY> 
                                
 <APPLIST> 
                                
 <%                                        this.generate_app_xml(); %> 
                                
 </APPLIST> 
                                
 </DESKTOPCONFIG>

The <POLICY> section has already been described above. The <APPLIST> element is where all of the application-specific publishing information is placed. This information is taken directly from the manifest files that were generated by the Sequencer. In this sample ASPX page, the work of extracting the publishing information from the manifest files is delegated to a C# method in the CodeBehind file, named generate_app_xml(). The contents of the CodeBehind file are listed below.

 Copyright © Microsoft Corporation.  All rights reserved.  For personal use only.  Provided AS IS and WITH ALL FAULTS.  
 // CodeBehind File: publishing.aspx.cs 
                                
 using System; 
                                
 using System.Xml; 
                                
 using System.IO; 
                                
 namespace appv_publishing_service 
                                
 { 
                                
 public                                        partial                                     class                                       publishing : System.Web.UI.Page 
                                
 { 
                                
 protected                                     void generate_app_xml() 
                                
 { 
                                
 string root_phys = MapPath("."); 
                                
 string[] dirs = Directory.GetDirectories(root_phys); 
                                
 foreach (string dir in dirs) 
                                
 { 
                                
 try 
                                
 { // Best effort. 
                                
 string[] manifests = Directory.GetFiles(Path.Combine(root_phys,dir), @"*_manifest.xml", SearchOption.TopDirectoryOnly); 
                                
 foreach (string filename in manifests) 
                                
 { 
                                
 FileStream file = File.OpenRead(filename); 
                                
 XmlDocument doc = new                                      XmlDocument(); 
                                
 doc.Load(file); 
                                
 XmlNode applist = doc.SelectSingleNode(@"/PACKAGE/APPLIST"); 
                                
 Response.Write(applist.InnerXml); 
                                
 } 
                                
 } 
                                
 catch (Exception ex) 
                                
 { 
                                
 // Output to log file 
                                
 Response.AppendToLog(ex.Message); 
                                
 } 
                                
 } 
                                
 } 
                                
 } 
                                
 } 

The algorithm used by the generate_app_xml() method is pretty straightforward. It assumes that the packages are each stored in separate subdirectories under the current directory. Many other schemes are possible. This particular one was chosen for its simplicity.

The method begins by calling the MapPath() function to get the native file system path of the current directory. It then uses the GetDirectories() static method of the Directory object to get a list of all of the subdirectories under the current directory. Next, it enumerates the list of subdirectories so it can find the manifest file for each package.

My original implementation of this method attempted to just use the GetFiles() static method of the Directory object to get a list of all of the manifest files under the current directory. However, it turns out that GetFiles() has one particularly annoying characteristic (I'd call it a bug). If GetFiles() encounters a directory that it doesn't have access to during the search, it abandons the operation and throws an ACCESS_DENIED error. This severely limits the usefulness of this method.

To get around this limitation, the GetFiles() call is done in within a try…catch block. It is used to get a list of all of the manifest files under each subdirectory. Normally, we'd only expect there to be a single manifest file in each subdirectory. But, if there were more than one, this method would handle each one. If the user doesn't have access to the subdirectory, the exception thrown by GetFiles() is caught by the method, logged (for debugging purposes) and the enumeration continues.

The real work of this method is done with the inner foreach
that processes the manifest files. Each manifest file is opened and loaded into an XmlDocument object. Next, the "/PACKAGE/APPLIST" element is selected and its contents are written out to the response.

The end result of this processing is an XML document that contains all of the information for each package that the user has permission to use. Pretty simple, eh? To test your implementation, you can simply point your web browser at the publishing.aspx page and check out the results. Remember to enable Windows Integrated Authentication on the website and ACL the package directories appropriately.

Configuring the Client

Once the publishing.aspx page is working correctly, all you need to do to use it is to configure the client to point at the page. Here is an example of how to correctly configure the client:

 

In this example, the client will connect to the 'webserver.yourcompany.com' webserver over port 80 and retrieve the contents of the 'publishing.aspx' page. It will then process the XML document that is returned by the server.

Note that this example uses the standard HTTP protocol, which will not verify the identity of the webserver and will return the data unencrypted. This is only appropriate for scenarios that are inside the corporate firewall. If this were a public Internet scenario, you would use the HTTPS protocol to guard against man-in-the-middle attacks.

What's Next

This simple implementation makes an assumption about the information that is in the manifest file. It assumes that the URLs for OSDs and icons in the manifest file point to the appropriate locations. In the next installment of this series, we'll see how to remove this assumption and have the code fix-up the locations before it returns them to the client.

In future installments, we'll also look at techniques that can be used to fix up the OSDs before they are returned to the client. This can be used to enable all kinds of advanced functionality. It can be used for simple things, like fixing up the SFT URLs in the OSD files so they point to the correct HTTP URL. And, it can be used for more advanced functionality, like automatically adding the proper Dynamic Suite Composition tags to the individual OSD files for a package. This can be used to eliminate the task of manually updating all of the OSD files with DSC tags.

Another important topic that isn't addressed in this first sample is performance. The sample code is inefficient, since it loads each of the manifest files into an XmlDocument object for every request it receives. There are many possible caching optimizations that could be added that would improve the performance of this code. We may cover some of them in future releases.