Unattended installs again: Understanding some more Sitecore MSI parameters

Ages back I wrote up some work on automating the Sitecore installer. I noted back then that I wasn’t sure what the purpose of the “SC_IISSITE_ID” parameter to the MSI was. Having done some more work on the topic of install automation recently, I’ve got some more detail on what it’s for now, as well as another parameter that’s more important than I had realised…

The reason for me going back to this was that the installation scripting I’d used was working fine for me during testing, but when I came to try and install more than one automated instance on the same server I started to see the script failing.

So what does SC_IISSITE_ID do?

The first problem I hit was the installation failing with errors in the MSI log file around its call to StartIIS7ConfigTransaction. A bit of internet digging lead me to this post: http://wixtoolset.org/issues/2755/. While it’s not referring to Sitecore installs, it is referring to the idea that when the installation framework is installing stuff into IIS, it can make use of either site names or site IDs to find whether a particular site exists.

During the install, the Sitecore MSI is using the ID you pass in the SC_IISSITE_ID field for creating the IIS site for your instance. Hence if that ID is unique the installer succeeds, but it will fail if you try to install two different instances with the same ID.

The MSI engine documentation that the link above refers to says that the SC_IISSITE_ID property is optional. If you know it, pass it. But if you don’t know it you can leave it out and the MSI engine will work out the next available ID to use.

So to make the scripted installation succeed for more than one instance, this needs to be left as an empty string.

There’s more than one “unique ID” number involved here…

Having fixed that, I then discovered a second problem. Installing a second version of Sitecore on the same machine would still fail, but now with a more obscure error. The log file included something like this:

MSI (s) (08:88) [12:31:21:291]: Machine policy value 'TransformsSecure' is 1
MSI (s) (08:88) [12:31:21:291]: Machine policy value 'DisableUserInstalls' is 0
MSI (s) (08:88) [12:31:21:291]: Specified instance {60C35F8E-334E-513E-B901-858D8CA9E17D} via transform :ComponentGUIDTransform5.mst is already installed. MSINEWINSTANCE requires a new instance that is not installed.
MSI (s) (08:88) [12:31:21:291]: MainEngineThread is returning 1639
MSI (s) (08:74) [12:31:21:300]: User policy value 'DisableRollback' is 0
MSI (s) (08:74) [12:31:21:300]: Machine policy value 'DisableRollback' is 0
MSI (s) (08:74) [12:31:21:300]: Incrementing counter to disable shutdown. Counter after increment: 0
MSI (s) (08:74) [12:31:21:300]: Note: 1: 1402 2: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Installer\Rollback\Scripts 3: 2 
MSI (s) (08:74) [12:31:21:300]: Note: 1: 1402 2: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Installer\Rollback\Scripts 3: 2 
MSI (s) (08:74) [12:31:21:300]: Note: 1: 1402 2: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Installer\InProgress 3: 2 
MSI (s) (08:74) [12:31:21:300]: Note: 1: 1402 2: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Installer\InProgress 3: 2 
MSI (s) (08:74) [12:31:21:300]: Decrementing counter to disable shutdown. If counter >= 0, shutdown will be denied.  Counter after decrement: -1
MSI (s) (08:74) [12:31:21:301]: Restoring environment variables
MSI (c) (98:CC) [12:31:21:311]: Decrementing counter to disable shutdown. If counter >= 0, shutdown will be denied.  Counter after decrement: -1
MSI (c) (98:CC) [12:31:21:311]: MainEngineThread is returning 1639

This one took a bit more head scratching to resolve, but the clue is in the phrase “:ComponentGUIDTransform5.mst is already installed. MSINEWINSTANCE requires a new instance that is not installed“.

The .mst file referenced here is mentioned in the install script as part of the TRANSFORMS parameter. Reading up on this, it turns out to be used to specify small updates to the overall MSI package, which can be used when you need to install the same package on a machine more than once. It takes a string which includes the name of a small update package, along with any parameters that package needs. So clearly it’s another “you need a unique ID” issue – but this time you need to specify the ID you want, and can’t leave it blank.

Initially I tried counting the number of IIS sites to work out a number to use for this, however this did not work reliably. Sometimes the install would work, and sometimes not. After a bit more research I hit on the idea that the Sitecore installer writes to the registry – so I looked at that to see what data it included.

On a 64bit machine, this data lives at HKLM\Software\Wow6432Node\Sitecore CMS and it looks a bit like this:

Sitecore Install Registry

The registry data includes the “InstanceID” property. This looks more helpful – we can look at this data to work out what the next instance ID should be. A PowerShell function can work this out for us:

function Generate-InstanceID([string]$siteName){
    $registryPath =  "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\"

    if(-not (Test-path $registryPath)){
        throw "The instance-testing path in the registry could not be found - not a 64bit machine?"
    }

    $scRegistryPath = "$registryPath\Sitecore CMS\"

    # default to 1 in case we've never had an install before
    $instanceNumber = "1"

    if( Test-path $scRegistryPath ){
        Push-Location $scRegistryPath

        $lastID = dir | Get-ItemProperty -Name InstanceID | Sort-Object -property InstanceID -Descending | Select-Object -ExpandProperty InstanceID -First 1

        if( -not [string]::IsNullOrWhiteSpace($lastID)){
            Write-Host "Previous instance found: $lastID "

            $instanceString = $lastID.Remove(0,10)
            $instanceNumber = [int]::Parse($instanceString)
            $instanceNumber = $instanceNumber + 1
        } else {
            Write-Host "No previous instance found"
        }

        Pop-Location
    } else {
        Write-Host "No previous instance found"
    }

    Write-Host "Using instance number: $instanceNumber"

    return $instanceNumber
}

The code checks if the registry entries that we want to process exist. It defaults to instance number one, and then looks at all the data under the Sitecore CMS bit of the registry. It finds the highest InstanceID value that has been stored here, extracts the number from it and then adds one to it.

So far, this has reliably found a valid ID to use.

So where does this leave the install script?

After making the changes above, the PowerShell code required to install an instance of Sitecore looks a bit like this:

function Perform-Install([string]$license, [string]$installer, [string]$site, [string]$sqlServer, [string]$sqlUser, [string]$sqlPassword) {
    $msi = "msiexec.exe"  
    $instanceID = (Generate-InstanceID $site)

    if( -Not (Test-Path $license) )
    {
        throw "Cannot find license file at $license"
    }

    if( -Not (Test-Path $installer) )
    {
        throw "Cannot find the sitecore installer at $installer"
    }

    write-host "Extracting Sitecore cabinet file..."
    &$installer /q /ExtractCab | out-null
    write-host "Extracted..."

    write-host "Installing Sitecore..."
    &$msi /qn /i "$($pwd)\SupportFiles\exe\Sitecore.msi" "TRANSFORMS=:InstanceId$($instanceID);:ComponentGUIDTransform5.mst" "MSINEWINSTANCE=1" "LOGVERBOSE=1" "SC_LANG=en-US" "SC_FULL=1" "SC_INSTANCENAME=$($site)" "SC_LICENSE_PATH=$($license)" "SC_SQL_SERVER_USER=$($sqlUser)" "SC_SQL_SERVER=$($sqlServer)" "SC_SQL_SERVER_PASSWORD=$($sqlPassword)" "SC_DBPREFIX=$($site)_" "SC_PREFIX_PHYSICAL_FILES=1" "SC_SQL_SERVER_CONFIG_USER=$($sqlUser)" "SC_SQL_SERVER_CONFIG_PASSWORD=$($sqlPassword)" "SC_DBTYPE=MSSQL" "INSTALLLOCATION=C:\Inetpub\wwwroot\$($site)" "SC_DATA_FOLDER=C:\Inetpub\wwwroot\$($site)\Data" "SC_DB_FOLDER=C:\Inetpub\wwwroot\$($site)\Database" "SC_MDF_FOLDER=C:\Inetpub\wwwroot\$($site)\Database\MDF" "SC_LDF_FOLDER=C:\Inetpub\wwwroot\$($site)\Database\LDF" "SC_NET_VERSION=4" "SITECORE_MVC=0" "SC_INTEGRATED_PIPELINE_MODE=1" "SC_IISSITE_NAME=$($site)" "SC_IISAPPPOOL_NAME=$($site)AppPool" "SC_IISSITE_HEADER=$($site)" "SC_IISSITE_PORT=80" "SC_IISSITE_ID=" "/l*+v" "$($pwd)\$($site)-Install.log" | out-null   
    write-host "Installed..."

    write-host "Tidying up..."
    rmdir ".\SupportFiles" -Recurse -Force

    return (Test-Path "C:\inetpub\wwwroot\$site")
}

This combines the two changes above, to make the install work for more than one Sitecore instance on a single server. It also includes a final test to see if the website folder now exists – which indicates wheth

One other thing to note

While testing these changes, I found a couple of situations where (for reasons I’ve not been able to determine) uninstalling an instance via .exe or .msi didn’t work correctly. Despite the removal of the IIS site, most of the files and the databases, attempts to reuse the same instance name failed. I was seeing errors in the MSI log file saying that the install had failed (but with no specific reason) in some cases, and in one case an error saying that the installation was rolled-back because connecting to the database did not work (When the supplied credentials were definitely valid).

In these cases, I was able to resolve the install problems by checking the registry (as described above) and removing left-over entries from the instance(s) which had not been removed by the uninstall process.

So if you see odd errors from the MSI process take a look at the registry and see if you have any leftovers.

Advertisements

7 thoughts on “Unattended installs again: Understanding some more Sitecore MSI parameters

  1. Pingback: Unattended installs of Sitecore | Jeremy Davis
  2. Pingback: Bonus chatter: PowerShell and automated installs | Jeremy Davis
  3. Pingback: Scripted DMS Installs | Jeremy Davis
  4. Pingback: Automated package installs | Jeremy Davis
  5. Pingback: Automating a development MongoDB Install | Jeremy Davis
  6. Pingback: Revisiting install scripting for newer versions of Sitecore | Jeremy Davis
  7. Pingback: Development environments with PowerShell DSC – Part 5 | Jeremy Davis

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s