Sunday, 7 July 2013

Assign Certificate (Set HTTPS Binding certificate) to IIS website from Wix Installer

I'm working on this project where we have a secure website and I was tasked with creating an installer for it. After quite a few searches and not coming up with any results I went down the Custom Action route.

Not shown here is how to install the website for which we are modifying the binding.

This is very simple, it just uses IIS server manager to set the binding for the certificate, note that since this operation requires elevation of permissions, there is a check to ensure that the user is running with elevated permissions, if this is not the case then an the NotElevated custom action will be triggered, and error messaged displayed and the installation will be rolled back.

This is the Custom Action code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Web.Administration;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Security.Principal;

namespace Installer.CustomActions
{
    public class CustomActions
    {
        const string protocol = "https";
        const string bindingPattern = "*:{0}:";


        [CustomAction]
        public static ActionResult UpdateBinding(Session session)
        { 
            ActionResult result = ActionResult.Failure;
            session.Log("Start UpdateBinding.");
   if (CheckRunAsAdministrator())
   {
    bool outcome=       UpdateBinding("Portal", protocol, string.Format(bindingPattern, session["SSLPORT"]), session["CERT"], session);
     if(outcome){result = ActionResult.Success;}
                            session.Log("End UpdateBinding.");
                            return result;
   }
   else
   {
       session.Log("Not running with elevated permissions.STOP");
              session.DoAction("NotElevated");
   }
        }

        private static bool UpdateBinding(string sitename, string protocol, string port, string certSubject, Session session)
        {
            bool result=false; 
            session.Log(string.Format("Binding info (Port) {0}.", port));
            session.Log(string.Format("Certificate Subject {0}.", certSubject));

            using (ServerManager serverManager = new ServerManager())
            {
                Site site = serverManager.Sites.Where(x => x.Name == sitename).SingleOrDefault();

                X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

                var certificate = store.Certificates.OfType<X509Certificate2>().Where(x => x.Subject == certSubject).FirstOrDefault();

                if (certificate != null)
                {
                    session.Log(string.Format("Certificate - Friendly Name: {0}. Thumbprint {1}", certificate.FriendlyName, certificate.Thumbprint));

                    site.Bindings[0].CertificateHash = certificate.GetCertHash();
                    site.Bindings[0].CertificateStoreName = store.Name;
                    site.Bindings[0].BindingInformation = port;

                    serverManager.CommitChanges();
                    result=true;
                }

                session.Log(string.Format("Could not find a certificate with Subject Name:{0}.", certSubject));

                store.Close();

            }
            return result;    
        }

        /// <summary>
        /// Check that process is being run as an administrator
        /// </summary>
        /// <returns></returns>
        private static bool CheckRunAsAdministrator()
        {
            var identity = WindowsIdentity.GetCurrent();
            var principal = new WindowsPrincipal(identity);
            return principal.IsInRole(WindowsBuiltInRole.Administrator);
        }
    }
}
and here is the Wix markup that uses the above custom action :
<Product ....>
<!--All the rest of the stuff-->

    <Binary Id="CA" SourceFile="$(var.Installer.CustomActions.TargetDir)Installer.CustomActions.CA.dll"/>

    <CustomAction Id="UpdateBinding" BinaryKey="CA" DllEntry="UpdateBinding" Execute="immediate" Return="check" />

    <CustomAction Id="NotElevated" Error="Ensure that the Installer is Run with elevated permissions (i.e. Run as Administrator)" />

    <InstallExecuteSequence>
      <Custom Action="UpdateBinding" After="InstallFinalize">NOT Installed</Custom>
    </InstallExecuteSequence>
</Product>

3 comments:

  1. Hi,

    Thanks for this great post! :)

    I'm facing 1 issue. My Bindings are getting conflicted in IIS.

    Can you show how to install the website for which the bindings are being modified?

    Can you please help?

    ReplyDelete
  2. Hi,

    Good article. Can you explain please where did you define SSLPORT and CERT properties?

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete