Web PI APIs: Install a product from a custom feed
In my previous blog posts, I described how to create custom feeds for Web Platform Installer to install custom products and applications. There are many uses to these custom feeds from automatically setting up your environment with custom products in addition to products already offered in WebPI to testing your applications for inclusion to Windows Web App Gallery. With the last use in mind, automating the process of testing the application can be very useful.
From this post you will learn how to use WebPI APIs to:
- load an existing custom feed
- install a product with all its dependencies
- install an application with dependencies and tweak its parameters
- select a language of your install
Resources
Download the solution with the driver below from here.
Refer to MSDN documentation for the full list and description of WebPI APIs: Microsoft.Web.PlatformInstaller Namespace.Details
The program below should be pretty self-descriptive. I’m using a custom feed used in this blog that contains one product and one application in it - http://blogs.iis.net/blogs/kateroh/SIR/SIRWebPIFeed.xml.
Reference to Microsoft.Web.PlatformInstaller.dll is required, which is GAC’ed during installation of WebPI.
First, we need to make WebPI load the main product feed and only then our custom feed. The reason for this is that our custom feed can refer to some products from the main feed as dependencies. For example, the application defines a dependency on Web Deploy tool, the tool that WebPI uses to install all Application Gallery applications. If this tool is not installed, it should be installed before that application can be deployed.
Once all feeds are loaded, we find the products we need to install by its productId (defined as <productId /> in the feeds). The method to find a product and application is the same, though the method to install them is a bit different. There are two major APIs to install products – InstallManager.StartInstallation() and InstallManager.StartApplicationInstallation(). There are two reasons why applications should be installed separately from the products:
- To successfully deploy and run applications, the environment should be setup correctly with the requirements. For example, in addition to Web Deploy that all applications require, a certain application might require a certain version of framework to be install, a database engine, UrlRewrite to name just a few.
- In order to set parameters on the application (step 9), the application has to be downloaded first. In step 8, in installManager.StartInstallation() WebPI not only installs all products added to ProductManager, but also downloads all applications added to ProductManager in the same way. Thus, after this step, the parameters can be set and application installation can start.
The driver also signs up for two major WebPI installation events – InstallerStatusUpdated and InstallCompleted and simply outputs progress to the screen.
using System; using System.Collections.Generic; using Microsoft.Web.PlatformInstaller; namespace CustomProductInstaller { class Program { protected readonly static string MainXml = "https://go.microsoft.com/fwlink/?LinkId=158722"; protected readonly static string CustomXml = "https://iisnetblogs.blob.core.windows.net/media/kateroh/SIR/SIRWebPIFeed.xml"; protected readonly static string CustomProductId = "AppGallerySIRBinaries"; protected readonly static string CustomAppId = "AppGallerySIRApp"; private static bool _installCompleted = false; static void Main(string[] args) { ProductManager productManager = new ProductManager(); // 1. Load main product feeds productManager.Load ( new Uri(MainXml), // location of the main feed false, // load products for all architectures and platforms true // load enclosures (eg WebApplicationList.xml) ); // 2. Load custom feed productManager.LoadExternalFile(new Uri(CustomXml)); // 3. Find the custom product by product Id Product customProduct = productManager.GetProduct(CustomProductId); Product customApp = productManager.GetProduct(CustomAppId); // 4. Find all products to install (custom product + its dependencies) List<Product> productsToInstall = new List<Product>(); AddProductWithDependencies(customProduct, productsToInstall); AddProductWithDependencies(customApp, productsToInstall); // 5. Pick the right installers - for the chosen language, if such is not available, fall back to English List<Installer> installersToUse = new List<Installer>(); Language languageOfInstallers = productManager.GetLanguage("fr"); Language english = productManager.GetLanguage("en"); foreach (Product productToInstall in productsToInstall) { Installer currentInstaller = productToInstall.GetInstaller(languageOfInstallers); if (currentInstaller == null) { currentInstaller = productToInstall.GetInstaller(english); ; } installersToUse.Add(currentInstaller); } // 6. Prepare installers InstallManager installManager = new InstallManager(); installManager.Load(installersToUse); // 7. Sign up for installation events installManager.InstallerStatusUpdated += new EventHandler<InstallStatusEventArgs>(InstallManager_InstallerStatusUpdated); installManager.InstallCompleted += new EventHandler<EventArgs>(InstallManager_InstallCompleted); foreach (InstallerContext installerContext in installManager.InstallerContexts) { string failureReason; installManager.DownloadInstallerFile(installerContext, out failureReason); } // 8. Install products first installManager.StartInstallation(); while (!_installCompleted) ; _installCompleted = false; // 9. At this point all products in ProductManager are downloaded, so we can set parameters on the application // NOTE: you cannot set application parameters, before it is downloaded // in this case, the language of the Web Deploy package does not matter, since it is not displayed in the UI Installer appInstaller = customApp.GetInstaller(english); foreach (DeclaredParameter declaredParameter in appInstaller.MSDeployPackage.DeclaredParameters) { // set values of parameters that do not have default values if (string.IsNullOrEmpty(declaredParameter.DefaultValue)) { appInstaller.MSDeployPackage.SetParameters[declaredParameter.Name] = "New Value"; } } // 10. Then install applications (because application might have product dependencies, but not vice versa) installManager.StartApplicationInstallation(); while (!_installCompleted) ; Console.ReadKey(); } private static void AddProductWithDependencies(Product product, List<Product> productsToInstall) { // avoid duplicates if (!productsToInstall.Contains(product)) { productsToInstall.Add(product); } // GetMissingDependencies will check whether any dependencies are not installed for the product // AND are not already added to the install list (productsToInstall) ICollection<Product> missingDependencies = product.GetMissingDependencies(productsToInstall); if (missingDependencies != null) { foreach (Product dependency in missingDependencies) { AddProductWithDependencies(dependency, productsToInstall); } } } private static void InstallManager_InstallerStatusUpdated(object sender, InstallStatusEventArgs e) { Console.WriteLine(e.InstallerContext.ProductName + ": " + e.InstallerContext.InstallationState + ". " + e.InstallerContext.ReturnCode.DetailedInformation + "Progress: " + e.ProgressValue.ToString()); } private static void InstallManager_InstallCompleted(object sender, EventArgs e) { Console.WriteLine("Installation completed"); _installCompleted = true; } } }