System.Deployment.Application.InPlaceHostingManager lets you download ClickOnce applications using a programmatic API, making it perfect for managed SMS installs, or for creating a custom Windows Forms "dashboard" application that lets your customers install all available ClickOnce apps from a menu. The class is woefully underdocumented in the current docs builds - a fact I've addressed in the recent docs with a bunch of updates as well as a rich new sample.

A MAJOR caveat: InPlaceHostingManager currently throws bizarre errors when it's run under vshost.exe, which is launched when you do normal F5 debugging from VS. We don't know why. To run an app using IPHM, start it without debugging (CTRL+F5), then attach the debugger ex post facto if you need to step through your code.

Here's the sample method and supporting events that accomplish this magic. It follows a very methodical set of method calls:

  • GetManifestAsync()
  • AssertApplicationRequirements()
  • DownloadApplicationAsync()

Just pass the URL to the .application manifest to the InstallApplication() method and you ought to be golden. (If you need VB.NET code, hit me up - I'll send it to you.)


        InPlaceHostingManager iphm = null;

        private void InstallApplication(string deployManifestUriStr)
        {

            try
            {
                Uri deploymentUri = new Uri(deployManifestUriStr);
                iphm = new InPlaceHostingManager(deploymentUri, false);
                MessageBox.Show("Created the object.");
            }
            catch (UriFormatException uriEx)
            {
                MessageBox.Show("Cannot install the application: The deployment manifest URL supplied is not a valid URL." +
                    "Error: " + uriEx.Message);
                return;
            }
            catch (PlatformNotSupportedException platformEx)
            {
                MessageBox.Show("Cannot install the application: This program requires Windows XP or higher. " +
                    "Error: " + platformEx.Message);
                return;
            }
            catch (ArgumentException argumentEx)
            {
                MessageBox.Show("Cannot install the application: The deployment manifest URL supplied is not a valid URL." +
                    "Error: " + argumentEx.Message);
                return;
            }

            iphm.GetManifestCompleted += new EventHandler<GetManifestCompletedEventArgs>(iphm_GetManifestCompleted);
            iphm.GetManifestAsync();

        }


        void iphm_GetManifestCompleted(object sender, GetManifestCompletedEventArgs e)
        {
            // Check for an error.
            if (e.Error != null)
            {
                // Cancel download and install.
                MessageBox.Show("Could not download manifest. Error: " + e.Error.Message);
                return;
            }

            // Dig inside of the manifest and see if this application requests full trust.
            // You can determine this by searching for a PermissionSet tag
            // that has the Unrestricted attribute set to true.
            bool isFullTrust = CheckForFullTrust(e.ApplicationManifest);

            // Verify this application can be installed.
            try
            {
                iphm.AssertApplicationRequirements();
            }
            catch (InvalidDeploymentException assertEx)
            {
                // Security exception. Report the error to the user.
                MessageBox.Show("Cannot install the application due to a security error. " +
                    "Error text: " + assertEx.Message);
                return;
            }
            catch (Exception ex)
            {
                MessageBox.Show("An error occurred while verifying the application. " +
                    "Error text: " + ex.Message);
                return;
            }

            // Use the information from GetManifestCompleted() to confirm
            // that the user wants to proceed.
            string appInfo = "Application Name: " + e.ProductName;
            appInfo += "\nVersion: " + e.Version;
            appInfo += "\nDownload from: " + e.ApplicationManifest.BaseURI;
            appInfo += "\nSupport/Help Requests: " + (e.SupportUri != null ?
                e.SupportUri.ToString() : "N/A");
            appInfo += "\n\nConfirmed that this application can run with its requested permissions.";
            appInfo += "\n\nProceed with installation?";

            DialogResult dr = MessageBox.Show(appInfo, "Confirm Application Install",
                MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
            if (dr != System.Windows.Forms.DialogResult.OK)
            {
                return;
            }

            // Download the deployment manifest.
            // We've added error handling here simply to be robust. Usually,
            // this shouldn't throw an exception unless
            // AssertApplicationRequirements() failed, or you did not call that method
            // before calling this one.
            iphm.DownloadApplicationCompleted += new EventHandler<DownloadApplicationCompletedEventArgs>(iphm_DownloadApplicationCompleted);
            try
            {
                iphm.DownloadApplicationAsync();
            }
            catch (Exception downloadEx)
            {
                MessageBox.Show("Cannot initiate download of application. Error: " +
                    downloadEx.Message);
                return;
            }
        }


        private bool CheckForFullTrust(XmlReader appManifest)
        {
            bool isFullTrust = false;

            if (appManifest == null)
            {
                throw (new ArgumentNullException("appManifest cannot be null."));
            }

            while (appManifest.Read())
            {
                // Find the minimum required permission set.
                if (appManifest.NodeType == XmlNodeType.Element)
                {
                    if (appManifest.Name.Equals("applicationRequestMinimum"))
                    {
                        // Get the next two nodes, which are PermissionSet and
                        // defaultAssemblyRequest.
                        // TODO: Will there ALWAYS be just one PermissionSet here? If so,
                        // I can stick with the simple logic of just examining the
                        // PermissionSet node. Otherwise, I'll need to get
                        // defaultAssemblyRequest, and check the appropriate
                        // PermissionSet.  
                        while (appManifest.Read())
                        {
                            if (appManifest.Name.Equals("PermissionSet"))
                            {
                                // This is a required attribute - no need to sanity-check
                                // its existence.
                                if (appManifest.GetAttribute("Unrestricted").Equals("true"))
                                {
                                    isFullTrust = true;
                                }

                                break;
                            }
                        }

                        break;
                    }
                }
            }
           
            return isFullTrust;
        }

        void iphm_DownloadApplicationCompleted(object sender, DownloadApplicationCompletedEventArgs e)
        {
            // Check for an error.
            if (e.Error != null)
            {
                // Cancel download and install.
                MessageBox.Show("Could not download and install application. Error: " + e.Error.Message);
                return;
            }

            // Inform the user that their application is ready for use.
            MessageBox.Show("Application installed! You may now run it from the Start menu.");
        }