Web PI APIs: Download Latest Web Stack Products for Any Platform

Suppose you have a central Internet faced server and multiple backend servers not connected to the Internet. To insure that your Internet faced server is always up-to-date with the latest Microsoft Web Stack products and updates, you can use Web Platform Installer. You can either use Web PI UI to set up your main server or you can automate the setup process by interfacing with Web PI APIs through Microsoft.Web.PlatformInstaller.dll. It was already shown in my previous blog post how to load and install specific products for the current machine through the APIs. In this blog, I will show how to download products for any Web PI supported platform using APIs. This solution might help you in setting up your load balancer that can download and cache products for its backend servers regardless of their OS and architecture. This solution is by no means perfect and final and is given as a sample.

Overview

From this post you will learn how to use WebPI APIs to:

- load the main product and application feeds

- filter products by any OS and architecture

- pull products with their dependencies

- manage installers locally in the installer cache

- avoid a dependency check for downloaded products

Details

When dealing with Web PI APIs, you will deal mostly with ProductManager and InstallManager classes. ProductManager is responsible for loading and filtering product information from the xml feeds, where all product information is stored. And InstallManager is responsible for actually downloading and installing the products (and applications, and updates – basically everything that is available through the Web PI feeds).

ProductManager has several overloads for Load method that loads the main feed with all its enclosures (other product feeds like application feed). The overload that will be used in the sample accepts more parameters than his other overloaded counterparts. With this overload you can make Web PI filter products by any OS and architecture. If you use the overload that does not accept parameters for the OS and architecture, it means that Web PI will filter products by version, type and architecture of your current platform. Thus, for the overload that we will be using, you will need to specify the architecture (x86 or x64), the major and minor OS versions, major and minor SP versions and OS type. Here is a small table that shows the major and minor versions of some OSes.

OS Major Version Minor Version
Windows 7/ Windows Server 2008 R2 6 1
Vista/Windows Server 2008 6 0
Windows Server 2003 5 2
Windows XP 5 1



Finally, ProductManager can find you a Product based on its ProductId (can be looked up directly in the xml feed), which will contain all information available for this product including information about its installers. From a product object you just need to pick the right installer you want to use to install this product. The right installer might be based not only on the specific platform you want to install this product on, but also on the language of the installers, if the product is available in that language.

InstallManager can cache products without installing them. The only catch in this case is that while loading products (ProductManager.Load), Web PI will perform a dependency check for the products you are trying to load against your current platform and will throw an exception if you try to load something incompatible with your current platform. That’s fine. You can pragmatically download the installers yourself using the links that each installer object carries along (Installer.InstallerFile.InstallerUrl). The disadvantage of downloading the files yourself is that you don’t use the following goodies that Web PI provides you with in the background when downloading the installers itself:

  • installer management in central location, aka Installer Cache (%LocalAppData%\Microsoft\Web Platform Installer\installers) with different paths for different installers of the same product (based on the installer signature)
  • order of installers – Web PI installs products in a smart way, because some products have to be installed after their dependencies are installed
  • retry logic for download failures
  • figuring out the installer’s extension (can be msi or exe or msu (for updates), etc)

On the other hand, the advantage of using Web PI APIs in this case is still great. You are guaranteed to get "the latest and greatest" products and updates in one central location and Web PI provides you with powerful APIs to leverage this feature in the easy-to-use way that can also be automated!
Take a look yourself…

Code

You can grab the full project (with only this file in it) here.

 
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Microsoft.Web.PlatformInstaller;

namespace ProductCacher
{
    class Program
    {
        protected readonly static string MainXml = "https://go.microsoft.com/fwlink/?LinkId=158722";
        
        static void Main(string[] args)
        {
            ProductManager productManager = new ProductManager();

            // 1. Load main product feeds
            // overload documentation: http://msdn.microsoft.com/en-us/library/ee255981.aspx
            productManager.Load
                (
                    new Uri(MainXml),                    // location of the main feed 
                    false,                               // do not filter products by the current architecture and OS
                    true,                                // load enclosures (eg WebApplicationList.xml)
                    false,                               // no cache yet, so do not use cached directory
                    @"d:\MyCache",                       // where to cache XML feeds (the folder should exist)

                    // get only products available for 
                    //      a. x86 architecture (and those available for both x86 AND x64)
                    //      b. the current OS (major and minor OS versions)
                    //      c. up to service pack 2 (major and minor sevice pack versions)
                    //      d. any product types (e.g. Home, Professional, Ultimate, Web Server, etc)
                    Architecture.x86,                    // arch
                    Environment.OSVersion.Version.Major, // current major version
                    Environment.OSVersion.Version.Minor, // current minor version
                    2,                                   // current major SP
                    0,                                   // current minor SP
                    0  // OsType as defined in this list: http://msdn.microsoft.com/en-us/library/ms724358(VS.85).aspx
                       // 0 if don't want to filter products by OSType
                );

            // 2. Find all the products to cache: .NET Fx 3.5 SP1, SQL Expess, PHP, MySQL, UrlRewrite and SEOToolkit
            Product netFramework35Sp1 = productManager.GetProduct("NETFramework35");
            Product sqlExpress = productManager.GetProduct("SQLExpress");
            Product php = productManager.GetProduct("PHP");
            Product mySql = productManager.GetProduct("MySQL");
            Product SEOtoolkit = productManager.GetProduct("SEOToolkit");
            Product urlRewrite = productManager.GetProduct("UrlRewrite");
            Product webDeploy = productManager.GetProduct("WDeploy");

            // 3. Add all products with the dependencies to the install list
            List<Product> productsToInstall = new List<Product>();
            AddProductWithDependencies(netFramework35Sp1, productsToInstall);
            AddProductWithDependencies(sqlExpress, productsToInstall);
            AddProductWithDependencies(php, productsToInstall);
            AddProductWithDependencies(mySql, productsToInstall);
            AddProductWithDependencies(SEOtoolkit, productsToInstall);
            AddProductWithDependencies(urlRewrite, productsToInstall);
            AddProductWithDependencies(webDeploy, productsToInstall);
            
            // 4. For each product pick the installers (can choose a language of each installer if localized installer is available)
            List<Installer> installersToUse = new List<Installer>();
            Language english = productManager.GetLanguage("en");
            foreach (Product productToInstall in productsToInstall)
            {
                Installer currentInstaller = productToInstall.GetInstaller(english);
                
                // if current product has installer to download (it might be an installer-less Windows component)
                if (currentInstaller != null && currentInstaller.InstallerFile != null)
                {
                    installersToUse.Add(currentInstaller);
                }
            }

            // ***********************************************************************//
            // NOTE: if you use this code, to make WebPI download the installers,
            // WebPI will perform a dependency check against your CURRENT platform.
            // For example, if you are trying to download something incompatible with your
            // current OS, WebPI will throw
            // ***********************************************************************//
            /*
             
            // 5. Prepare installers
            InstallManager installManager = new InstallManager();
            installManager.Load(installersToUse);

            // 6. Download products without installing them
            foreach (InstallerContext installerContext in installManager.InstallerContexts)
            {
                // each installer will be cached under %LocalAppData%\Microsoft\Web Platform Installer\installers\<product Id>\<installer's SHA1>\
                string failureReason;
                installManager.DownloadInstallerFile(installerContext, out failureReason);
                if (!string.IsNullOrEmpty(failureReason))
                {
                    Console.Error.WriteLine("Web PI download failure: " + failureReason);
                }
                    
            }
            // ***********************************************************************/

            // 5. Download the installers in the same folder Web PI does, aka Installer Cache
            string WebPIinstallersFolder = Environment.ExpandEnvironmentVariables(@"%LocalAppData%\Microsoft\Web Platform Installer\installers");
            if (!Directory.Exists(WebPIinstallersFolder)) 
            {
                Directory.CreateDirectory(WebPIinstallersFolder);
            }
            foreach (Installer installer in  installersToUse) 
            {
                // re-create WebPI behavior: download each installer 
                // under %LocalAppData%\Microsoft\Web Platform Installer\installers\<product Id>\<installer's SHA1>\
                string currentInstallerDirectory = 
                    Path.Combine(WebPIinstallersFolder, installer.Product.ProductId) + 
                    "\\" + installer.InstallerFile.SHA1Hash;
                
                // if the directory <productId>\<installer's hash> doesn't exist, create it
                if (!Directory.Exists(currentInstallerDirectory)) 
                {
                    Directory.CreateDirectory(currentInstallerDirectory);
                }    
            
                WebClient downloadClient = new WebClient();
                downloadClient.DownloadFile
                    (
                        installer.InstallerFile.InstallerUrl,
                        Path.Combine(currentInstallerDirectory, installer.Product.ProductId + 
                        // NOTE: by far not the best method to get a file extension, 
                        // consider the case where your absolute URL does not contain file extension
                        Path.GetExtension(installer.InstallerFile.InstallerUrl.AbsolutePath))
                    );
            }
        }

        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);
                }
            }
        }
    }
}


Acknowledgements

Thanks to Bilal Alam for the original idea.

6 Comments

  • Kateryna,

    I should be able to do the same thing using powershell right? I'd like to be able to use a call to Microsoft.Web.PlatformInstaller.dll to install SQLexpress for instance and I'd like to do that via Powershell? This should be possible, no?

    Jess

  • Jess,

    Yes, you can write a powershell script that can do the same thing. Let me play with it and get back to you with some code snippets.

  • Hello, Kateryna.

    I am writing my custom feed with custom products. Is the Web PI supports product upgrades (e.g. MyProduct v.1.1 -> MyProduct v.1.2) out of box? What is the manner?

    Thank you!

  • Web PI does not have a supported update story. You will have to define two different product entries in your feed with different product ids like MyProduct_v11 and MyProduct_v12. Then, you need to make their discovery hints more specific by specifying the exact version of the product. For example, currently SMO requires a specific version in the registry:



    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\SharedManagementObjects\CurrentVersion
    Version
    10.50.1600.1



    This way, if the older version is installed and newer version is not, you will be able to install the latest version on top of the oldest version. For this, your installer should support the in-place upgrade.

  • Can you do an offline install using the Web PI ?
    For e.g. I want to cache all the dependencies using the feed and then do an offline install on a machine?

  • Once you have downloaded these installers to the temp directory how do you install them using API?

Comments have been disabled for this content.