SuccessFailureA while back, I posted “A guide to App-V publishing and streaming using IIS” on the App-V blog and since then we have continued to work on this since it continues to be such a popular topic. The code on the site has been put to the test and as a result of this, we have been working together to improve the code, and we would like to share that as well.

Let us start at the beginning!

 The configuration of IIS

When it comes to the configuration of IIS, we can use the same configuration that I outlined in the earlier article.  There is one important difference though. Instead of a Virtual Directory, we should create a Virtual Application.  Otherwise, the code we will discuss later on will not function as expected.

You can either create a new Virtual Application, but you can also choose to convert your existing Virtual Directory to a Virtual Application.

image
When you do this, you should make sure that the Application Pool used for your Virtual Application in IIS is using an Application Pool configured to use .NET 4.0 as shown in the example below:
image
With this being said, we can continue to discuss the new code for the creation of the Publishing Document.

The creation of the new and improved Publishing Document

The main reason for this updated article is that the code we discussed before is not optimal.
When we would use this code, we have to make sure that each XML file has the proper subfolder configured, otherwise it would not work. Even if we set the ASR, this does not handle the subfolders in a proper way and of course we do not want all package files to be stored directly under \content for all applications, instead of decently organized in subfolders.
In order to do this we would have to do a manual job, consuming lots of time.
Because of this, we have enhanced the code, so that this manual modification is not necessary. The new code will parse the subfolders itself, making it to work out of the box.
The only important note here is that the folder you are storing the packages in is actually called ‘Content’. If the content directory is called different, it will not work as is.
As we have explained before, a working or failing environment depends on the Publishing Document (to which the client connects) and the code that is used to generate the APPLIST.
For our new, improved code to work, we need to modify the original Publishing Document a bit. The new code is below:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="publishing.aspx.cs" Inherits="HttpPublishing.Publishing" ContentType="text/xml"%>

<% this.generate_app_xml(); %>

The new CodeFile is below. As you see, this code file is much more complex than the first one:

using System.Web.UI.WebControls;
using System.Xml.Linq;
using System.Xml;
using System.IO;

namespace HttpPublishing
{
    public partial class Publishing : System.Web.UI.Page
    {
        /*
         * <DESKTOPCONFIG>
         *  <POLICY    MANAGEDDESKTOP=""TRUE"" REPORTING=""FALSE"">
         *      <REFRESH ONLOGIN=""TRUE"" PERIOD=""60""/>
         *  </POLICY>
         *  <APPLIST />
         * </DESKTOPCONFIG>";
         */

        private XElement desktop_configuration =
            new XElement("DESKTOPCONFIG",
                new XElement("POLICY",
                    new XAttribute("MANAGEDDESKTOP", "TRUE"),
                    new XAttribute("REPORTING", "FALSE"),
                    new XElement("REFRESH",
                        new XAttribute("ONLOGIN", "TRUE"),
                        new XAttribute("PERIOD", "60"))),
                    new XElement("APPLIST"));

        /*
         * Page_Load will parse manifest files generated by the App-V Sequencer under the Content directory
         * in the virtual directory associated with the web application, i.e. C:\inetpub\wwwroot\httppublishing\content
         */
        //protected void Page_Load(object sender, EventArgs e)
        protected void generate_app_xml()
        {
            Response.ContentType = "text/xml";

            string path = Request.Url.ToString();
            string root_virt = path.Substring(0, path.LastIndexOf("/")) + @"/Content";
            string root_phys = MapPath("~/Content");

            try
            { // Best effort.
                string[] manifests = Directory.GetFiles(root_phys, @"*_manifest.xml", SearchOption.AllDirectories);

                XElement applist_nodes = desktop_configuration.Element("APPLIST");

                foreach (string filename in manifests)
                {
                    try
                    {
                        //Check to see if the current user has read access to the file
                        using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
                        using (XmlReader manifest = XmlReader.Create(fs))
                        {
                            XElement manifest_root = XElement.Load(manifest);
                            XElement manifest_applist = manifest_root.Element("APPLIST");

                            System.Text.StringBuilder app_xml = new System.Text.StringBuilder(manifest_applist.ToString());

                            // handle %SFT_MIME_SOURCE% paths
                            string curr_dir = Path.GetDirectoryName(filename).Substring(root_phys.Length);
                            curr_dir = curr_dir.Replace('\\', '/');
                            string full_vpath = root_virt + curr_dir;

                            app_xml.Replace("%SFT_MIME_SOURCE%", full_vpath);

                            XElement manifest_applist_nodes = XElement.Parse(app_xml.ToString());

                            foreach (XNode node in manifest_applist_nodes.Nodes())
                            {
                                applist_nodes.Add(node);
                            }
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                    }
                }
            }
            catch (Exception ex)
            {
                // Output to log file
                Response.AppendToLog(ex.Message);
            }

            Response.Write(desktop_configuration.ToString());
            Response.End();
        }
    }
}

The configuration of the App-V Client

The client can be configured as we are used to and as we described in the previous article.  The only thing that should be verified is whether the Application Source Root (ASR) has been configured.  If this is not done, the code will not work as expected.

This can be configured here:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SoftGrid\4.5\Client\Configuration

Name: ApplicationSourceRoot
Value: http://IISSERVERNAME:80/HttpPublishing/content

Therefore, this code should indeed make our lives much easier! We hope you experience this as well en enjoy your HTTP/IIS setup for App-V even more. Below is one more quick summary of the important catches, so make sure you won’t miss them:

  • Use a Virtual Application in IIS instead of a Virtual Directory.
  • Make sure that the folder containing your application packages is called ‘Content’.
  • Make sure the Application Pool for your Virtual Application uses .NET 4.0.

Note: I want to give a special thanks to Nikolaos Dalalelis for contributing the code above.  Thanks Nick!

Madelinde Walraven | Senior Support Engineer

The App-V Team blog: http://blogs.technet.com/appv/
The WSUS Support Team blog: http://blogs.technet.com/sus/
The SCMDM Support Team blog: http://blogs.technet.com/mdm/
The ConfigMgr Support Team blog: http://blogs.technet.com/configurationmgr/
The OpsMgr Support Team blog: http://blogs.technet.com/operationsmgr/
The SCVMM Team blog: http://blogs.technet.com/scvmm/
The MED-V Team blog: http://blogs.technet.com/medv/
The DPM Team blog: http://blogs.technet.com/dpm/
The OOB Support Team blog: http://blogs.technet.com/oob/
The Opalis Team blog: http://blogs.technet.com/opalis

clip_image001 clip_image002