Provision a Second Web Sites Controller

Windows Azure Pack: Web Sites can have a maximum of two Web Sites Controllers. For High Availability, it is strongly recommended that you exercise this maximum and provision a second Web Sites Controller. This can be done by using the following scripts, which are provided here:

OnStartSecondaryController.cmd

HostingBootstrapperBootstrapper.ps1

OnStartSecondaryController.ps1

Common.ps1

Descriptions

·      OnStartSecondaryController.cmd - Installs the WebPlatform Installer (Web PI) on the Windows 2012 server or Virtual Machine that you have prepared. It then calls the HostingBootstrapperBootstrapper and OnStartSecondaryController scripts in succession to complete the provisioning of the secondary controller. Required parameters include the SQL Server and Controller administrative credentials that will be used in your setup.

·      HostingBootstrapperBootstrapper.ps1 - Calls Program Files\Microsoft\Web Platform Installer\WebpiCmd.exe to install the second Web Sites Controller. It uses the same procedure as when installing the primary controller, but discards the configuration portal by specifying the command line switch /SuppressPostFinish.

·      OnStartSecondaryController.ps1 - Copies configuration data from the primary controller to the secondary controller, including the SystemCore and SiteRuntime keys and the hosting and resource metering connection strings. When finished, it starts the WebFarmService.

·      Common.ps1 - Provides supporting functions for the OnStartSecondaryController.ps1 script.

Important

These scripts require that Windows Remote Management (WinRM) be enabled on the primary controller.

Steps to Run the Scripts

1.   Copy the script files to a folder on the server that will be the Secondary Controller.

2.   Copy the WebPlatformInstaller.msi file to the same folder as the scripts. This is necessary because the OnStartSecondaryController.cmd script automates the installation of Web PI.

3.   Run OnStartSecondaryController.cmd with administrator rights, supplying the parameters described in the table below. Administrative privileges are required so that the appropriate products can be installed through Web PI automation and the primary controller can be accessed through WinRM.

Note

In a WORKGROUP scenario, you might need to enable WinRM on the primary controller, or run the commands in the OnStartSecondaryController.cmd script manually.

OnStartSecondaryController.cmd

Syntax

OnStartSecondaryController.cmd -feed %Feed% -webSitesInstanceName %WebSitesInstanceName% -sqlservername %DatabaseServerName% -sqlsysadmin %DatabaseSysAdminAccount% -sqlsysadminpwd %DatabaseSysAdminPassword% -controllerAdminUserName %ControllerAdminUserName% -controllerAdminPassword %ControllerAdminPassword%

Parameters

 

Parameter Name

Description

Notes

feed

Optionally specifies the Web PI feed that will be used to install the controller.

If this parameter is not specified, the default Web PI primary feed is used.

webSitesInstanceName

Used as prefix for database objects

Must match the prefix name of the database in the installation.

sqlservername

Name of the instance of SQL server

 

sqlsysadmin

SQL Server sys admin user account name

Needs to be member of sysadmin server roles.

sqlsysadminpwd

SQL Server sys admin user account password

 

controllerAdminUserName

Web Farm Framework (WFF) Administrator account name to provision

If this is a domain account, it will be added to the local administrators group.

If the server is in WORKGROUP, it will try to create the user if the user doesn’t exist and then add it to the local administrators group.

controllerAdminPassword

WFF Administrator account password to provision

 

 

OnStartSecondaryController.cmd Script

@echo off

echo Starting OnStartSecondaryController.cmd

 

rem ---------------------------------------------

rem Initialize Variables

rem ---------------------------------------------

    set POWERSHELL=%windir%\System32\WindowsPowerShell\v1.0\powershell.exe

 

    set FEED=

    set INSTANCE_NAME=

    set SQL_SERVERNAME=

    set SQL_SYSADMIN=

    set SQL_SYSADMINPWD=

    set CONTROLLER_ADMIN_USERNAME=

    set CONTROLLER_ADMIN_PASSWORD=

 

rem ---------------------------------------------

rem Parse command line parameters

rem ---------------------------------------------

:parse_param

    set PARAM_MATCHED=0

    if "%1"=="" (

        goto :parse_param_completed

    )

 

    if /I "%1"=="-feed" (

        set FEED=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-webSitesInstanceName" (

        set INSTANCE_NAME=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-sqlservername" (

        set SQL_SERVERNAME=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-sqlsysadmin" (

        set SQL_SYSADMIN=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-sqlsysadminpwd" (

        set SQL_SYSADMINPWD=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-controllerAdminUserName" (

        set CONTROLLER_ADMIN_USERNAME=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if /I "%1"=="-controllerAdminPassword" (

        set CONTROLLER_ADMIN_PASSWORD=%2

        shift

        set PARAM_MATCHED=1

    )

 

    if "%PARAM_MATCHED%"=="0" (

       echo Parameter %1 was not matched

       exit 1

    )

 

    shift

    goto :parse_param

:parse_param_completed

 

rem -----------------------------------------------------------------------

rem Provision Controller

rem ------------------------------------------------------------------------

 

    echo Installing WebPlatformInstaller.

    start /wait %windir%\system32\msiexec.exe /qn /i WebPlatformInstaller.msi /l %SystemDrive%\WebPlatformInstaller.log

    if errorlevel 1 (

        echo WebPlatform Installer installation failed. See log at %SystemDrive%\WebPlatformInstaller.log for more details.

        exit 1

    )

 

    echo WebPlatformInstaller setup completed successfully.

 

    echo Enabling remote desktop access.

    start /wait cscript %windir%\system32\scregedit.wsf /ar 0

    if errorlevel 1 (

        echo Enabling remote desktop failed.

        exit 1

    )

 

    echo Remote desktop access has been enabled successfully.

 

    if exist StrongNameHijack.msi (

 

        echo Installing StrongNameHijack...

        start /wait %windir%\system32\msiexec.exe /qn /i StrongNameHijack.msi /l %SystemDrive%\StrongNameHijack.log

        if errorlevel 1 (

            echo "StrongNameHijack installation failed. See log at %SystemDrive%\StrongNameHijack.log for more details."

            exit 1

        )

 

        echo StrongNameHijack setup completed successfully.

 

        net stop msiserver & net start msiserver

    )

 

    echo Starting HostingBootstrapperBootstrapper.ps1

 

    if "%FEED%"=="" (

        %POWERSHELL% -ExecutionPolicy Unrestricted -File HostingBootstrapperBootstrapper.ps1

    ) else (

        %POWERSHELL% -ExecutionPolicy Unrestricted -File HostingBootstrapperBootstrapper.ps1 -mainFeed "%FEED%"

    )

 

    if errorlevel 1 (

        echo HostingBootstrapperBootstrapper.ps1 failed.

        exit 1

    )

 

    echo HostingBootstrapperBootstrapper.ps1 completed successfully.

 

    echo Starting OnStartSecondaryController.ps1

 

    %POWERSHELL% -ExecutionPolicy Unrestricted -File OnStartSecondaryController.ps1 -webSitesInstanceName "%INSTANCE_NAME%" -sqlservername "%SQL_SERVERNAME%" -sqlsysadmin "%SQL_SYSADMIN%" -sqlsysadminpwd "%SQL_SYSADMINPWD%" -controllerAdminUserName "%CONTROLLER_ADMIN_USERNAME%" -controllerAdminPassword "%CONTROLLER_ADMIN_PASSWORD%"

    if errorlevel 1 (

        echo OnStartSecondaryController.ps1 failed.

        exit 1

    )

 

    echo OnStartSecondaryController.ps1 completed successfully.

 

echo OnStartSecondaryController.cmd completed successfully.

 

exit 0

HostingBootstrapperBootstrapper.ps1

# PowerShell script to setup Web Sites Controller using WebPI.

# Copyright (c) Microsoft Corporation. All rights reserved.

 

Param

(

    [string] $boostrapperProductId ="HostingPrimaryControllerBootstrapper_v2",

    [string] $mainFeed = "",

    [string] $customFeed = ""

)

 

# Change Error Action to Stop

$ErrorActionPreference="Stop"

 

Function BootstrapBootstrapper ()

{

    $WebPiCmd = [System.Environment]::ExpandEnvironmentVariables("%ProgramW6432%\Microsoft\Web Platform Installer\WebpiCmd.exe")

    $WebPiLog = [System.Environment]::ExpandEnvironmentVariables("%SystemDrive%\HostingPrimaryControllerBootstrapper.log")

 

    If ($mainFeed -eq "")

    {

        Invoke-Command -ScriptBlock { & $WebPiCmd /Install /Products:$boostrapperProductId /AcceptEula /SuppressReboot /SuppressPostFinish /Log:$WebPiLog }

    }

    Else

    {

        If ($customFeed -eq "")

        {

            Invoke-Command -ScriptBlock { & $WebPiCmd /Install /Products:$boostrapperProductId /AcceptEula /SuppressReboot /SuppressPostFinish /XML:$mainFeed /Log:$WebPiLog }

        }

        Else

        {

            Invoke-Command -ScriptBlock { & $WebPiCmd /Install /Products:$boostrapperProductId /AcceptEula /SuppressReboot /SuppressPostFinish /XML:$mainFeed /Feeds:$customFeed /Log:$WebPiLog }

        }

    }

 

    If ($lastexitcode -ne $Null -And $lastexitcode -ne 0)

    {

        Exit $lastexitcode

    }

}

 

# Entry Point

BootstrapBootstrapper

OnStartSecondaryController.ps1

# PowerShell script to setup a Web Sites secondary controller.

# Copyright (c) Microsoft Corporation. All rights reserved.

 

Param

(

    [string] $webSitesInstanceName,

    [string] $sqlservername,

    [string] $sqlsysadmin,

    [string] $sqlsysadminpwd,

    [string] $controllerAdminUserName,

    [string] $controllerAdminPassword

)

 

# Init Global Variables

$cnstr ="server=$($sqlservername);database=$($webSitesInstanceName)_hosting;uid=$($sqlsysadmin);pwd=$($sqlsysadminpwd);"

 

# Load common script file

$startDir = "."

$common = [System.IO.Path]::Combine($startDir, "Common.ps1")

. $common

 

Function Get-PrimaryController()

{

    Try

    {

        $siteManager = New-Object Microsoft.Web.Hosting.SiteManager $cnstr

        $primaryController = $siteManager.Controllers.GetPrimaryController([Microsoft.Web.Hosting.PlatformOptions]::VirtualMachineManager)

 

        Return $primaryController.MachineName;

    }

    Finally

    {

        If($siteManager -ne $Null)

        {

            $siteManager.Dispose()

        }

    }

}

 

Function Test-PrimaryController()

{

    Try

    {

        $PrimaryController = Get-PrimaryController

 

        Return $PrimaryController -ne $Null

    }

    Catch [Microsoft.Web.Hosting.WebHostingObjectNotFoundException]

    {

        Write-Host "$(Get-Date): Primary Controller NOT Ready"

 

        Return $False;

    }

}

 

Function WaitForPrimaryControllerToBeReady()

{

    $WaitIndex=0;

    $MaxWait=15

    $WaitInterval=60000

 

    Write-Host "$(Get-Date): Waiting for Primary Controller to be ready"

 

    $valid = Test-PrimaryController

    If ($valid -eq $False)

    {

        While ($WaitIndex -lt $MaxWait -and $valid -eq $False)

        {

            [System.Threading.Thread]::Sleep($WaitInterval);

            $WaitIndex = $WaitIndex + 1

            $valid = Test-PrimaryController

        }

    }

 

    If ($valid -eq $False)

    {

        Throw New-Object [System.Exception] "Primary Controller NOT ready. Timed out waiting."

    }

 

    Write-Host "$(Get-Date): Primary Controller is Ready"

}

 

Function Copy-SystemCoreKeyFromPrimaryController([string] $PrimaryController)

{

    Write-Host "$(Get-Date): Copy SystemCore key"

 

    $remoteCommand = {

        Add-PSSnapIn WebHostingSnapIn

        Get-WebSitesConfig -Type SecurityKey -SymmetricKeyName SystemCore

    }

 

    $SystemCoreKey = Invoke-Command -ComputerName $PrimaryController -ScriptBlock $remoteCommand

    Set-WebSitesConfig -Type SecurityKey -SymmetricKeyName SystemCore -SymmetricKey $SystemCoreKey -Force

}

 

Function Copy-SiteRuntimeKeyFromPrimaryController([string] $PrimaryController)

{

    Write-Host "$(Get-Date): Copy SiteRuntime key"

 

    $remoteCommand = {

        Add-PSSnapIn WebHostingSnapIn

        Get-WebSitesConfig -Type SecurityKey -SymmetricKeyName SiteRuntime

    }

 

    $SiteRuntimeKey  = Invoke-Command -ComputerName $PrimaryController -ScriptBlock $remoteCommand

    Set-WebSitesConfig -Type SecurityKey -SymmetricKeyName SiteRuntime -SymmetricKey $SiteRuntimeKey -Force

}

 

Function Copy-HostingConnectionStringFromPrimaryController([string] $PrimaryController)

{

    Write-Host "$(Get-Date): Copy hosting connection string"

 

    $remoteCommand = {

        Add-PSSnapIn WebHostingSnapIn

        [Microsoft.Web.Hosting.SiteManager]::GetDefaultConnectionString()

    }

 

    $HostingCnStr  = Invoke-Command -ComputerName $PrimaryController -ScriptBlock $remoteCommand

    Set-WebSitesConnectionString -Type Hosting -ConnectionString $HostingCnStr

}

 

Function Copy-MeteringConnectionStringFromPrimaryController([string] $PrimaryController)

{

    Write-Host "$(Get-Date): Copy metering connection string"

 

    $remoteCommand = {

        Add-PSSnapIn WebHostingSnapIn

        [Microsoft.Web.Hosting.SiteManager]::GetMeteringConnectionString()

    }

 

    $computerName = [Microsoft.Web.Hosting.Common.NetworkHelper]::GetComputerName([Microsoft.Web.Hosting.Common.ComputerNameFormat]::DnsFullyQualified)

 

    $MeteringCnStr  = Invoke-Command -ComputerName $PrimaryController -ScriptBlock $remoteCommand   

    Set-WebSitesConnectionString -Type Metering -ConnectionString $MeteringCnStr -ServerName $computerName

}

 

Function OnStartSecondaryController()

{

    Try

    {

        Write-Host "$(Get-Date): Starting OnStartSecondaryController()" -ForegroundColor Green

 

        Add-PSSnapin WebHostingSnapin

 

        ConfigureRoleAdministrator $controllerAdminUserName $controllerAdminPassword

 

        WaitForHostingDatabaseToBeReady $cnstr

        WaitForPrimaryControllerToBeReady

 

        $PrimaryController = Get-PrimaryController

        Copy-SystemCoreKeyFromPrimaryController $PrimaryController

        Copy-SiteRuntimeKeyFromPrimaryController $PrimaryController

        Copy-HostingConnectionStringFromPrimaryController $PrimaryController

        Copy-MeteringConnectionStringFromPrimaryController $PrimaryController

 

        Start-Service WebFarmService

 

        Write-Host "$(Get-Date): OnStartSecondaryController completed successfully" -ForegroundColor Green

    }

    Catch [System.Exception]

    {

        Write-Host "$(Get-Date): Exception encountered while executing OnStartSecondaryController:" -ForegroundColor Red

        Write-Host $_ -ForegroundColor Red

 

        Write-Host "$(Get-Date): Exiting OnStartSecondaryController.ps1" -ForegroundColor Red

        Exit -1

    }

}

 

# Entry Point

OnStartSecondaryController

Common.ps1

# PowerShell Web Sites common script.

# Copyright (c) Microsoft Corporation. All rights reserved.

 

Function LoadHostingFramework()

{

    $mwhc = Get-Item ".\Microsoft.Web.Hosting.Common.dll"

    [void] [System.Reflection.Assembly]::LoadFrom($mwhc.FullName)

    Write-Host "$(Get-Date): Microsoft.Web.Hosting.Common assembly was successfully loaded from: $mwhc"

 

    $mwh  = Get-Item ".\Microsoft.Web.Hosting.dll"

    [void] [System.Reflection.Assembly]::LoadFrom($mwh.FullName)

    Write-Host "$(Get-Date): Microsoft.Web.Hosting assembly was successfully loaded from: $mwh"

}

 

Function IsHostingDatabaseReady([string] $cnstr)

{

    Try

    {

        $siteManager = New-Object Microsoft.Web.Hosting.SiteManager $cnstr

        $siteManager.TestConnection([Microsoft.Web.Hosting.PlatformOptions]::VirtualMachineManager)

 

        Return $true;

    }

    Catch [Exception]

    {

        Write-Host "$(Get-Date): Hosting Database NOT Ready"

 

        Return $false;

    }

    Finally

    {

        If($siteManager -ne $Null)

        {

            $siteManager.Dispose()

        }

    }

}

 

Function WaitForHostingDatabaseToBeReady ([string] $cnstr)

{

    $WaitIndex=0;

    $MaxWait=120

    $WaitInterval=60000

 

    Write-Host "$(Get-Date): Waiting for Hosting Database to be ready"

 

    $ready = IsHostingDatabaseReady $cnstr

    If($ready -ne $true)

    {

    While ($WaitIndex -lt $MaxWait -and $ready -ne $true)

        {

            [System.Threading.Thread]::Sleep($WaitInterval);

            $WaitIndex = $WaitIndex + 1

            $ready = IsHostingDatabaseReady $cnstr

        }

    }

 

    If ($ready -ne $true)

    {

        Throw New-Object [System.Exception] "Hosting Database NOT ready. Timed out waiting."

    }

 

    Write-Host "$(Get-Date): Hosting Database is Ready"

}

 

Function ConfigureRoleAdministrator([string] $roleadminusr, [string] $roleadminpwd)

{

    $isDomain = $false

 

    # Identify if user is a domain user account

    $values = $roleadminusr.Split('\');

    If ($values.Length -eq 1)

    {

        $domain = $env:COMPUTERNAME

        $username = $values[0]

    }

    ElseIf ($values.Length -eq 2)

    {

        If ([String]::Equals($values[0], ".") -Or

            [String]::Equals($values[0], [Environment]::MachineName, [StringComparison]::OrdinalIgnoreCase))

        {

            $isDomain = $false

            $domain = $env:COMPUTERNAME

            $username = $values[1]

        }

        Else

        {

            $isDomain = $true

            $domain = $values[0]

            $username = $values[1]

        }

    }

    Else

    {

        Throw New-Object ArgumentException "Invalid user name" "roleadminusr"

    }

 

    # Create user if specified user is not a domain account

    if ($isDomain -eq $false)

    {

        Try

        {

            $computer = [ADSI]"WinNT://$env:COMPUTERNAME"

            $user = $computer.Create("User", $username)

            $user.setpassword($roleadminpwd)

            $user.SetInfo()

        }

        Catch [System.Runtime.InteropServices.COMException]

        {

            # User already exists

            If ($_.Exception.ErrorCode -eq -2147022672)

            {

                Write-Host "$(Get-Date): User $domain\$username already exits."

                Write-Host "$(Get-Date): Updating password for User $domain\$username."

                $user = [ADSI]("WinNT://$env:COMPUTERNAME/$username,user")

                $user.setpassword($roleadminpwd)

                $user.SetInfo()

            }

            Else

            {

                Write-Host "$(Get-Date): Error creating user $domain\$username." -ForegroundColor Red

 

                Throw

            }

        }

    }

 

    # Add user to local administrators group

 

    #first translate the "Administrators" name to the name based on locale (make well known SID -> Name translation)

 

    $administratorsSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")

    $administratorsGroupName = $administratorsSID.Translate([System.Security.Principal.NTAccount]).Value

 

    # retrieve the short name eg in german translate VORDEFINIERT\Administratoren ->Administratoren

    # the translation should always return fully qualified name equivalent to BUILTIN\Administrators

 

    $localizedAdministratorsGroupName=$administratorsGroupName.Split("\\")[1]

 

    $adminGroup = [ADSI]("WinNT://$env:COMPUTERNAME/$localizedAdministratorsGroupName,group")

 

    Try

    {

        If ($isDomain -eq $true)

        {

            $adminGroup.add("WinNT://$domain/$username,user")

        }

        Else

        {

            $adminGroup.add("WinNT://$env:COMPUTERNAME/$username,user")

        }

    }

    Catch [System.Runtime.InteropServices.COMException]

    {

        If ($_.Exception.ErrorCode -eq -2147023518) # ERROR_MEMBER_IN_ALIAS 1378 (0x562)

        {

            Write-Host "$(Get-Date): User $domain\$username is already member of Administrators group."

        }

        Else

        {

            Write-Host "$(Get-Date): Error adding user $domain\$username to Administrators group." -ForegroundColor Red

 

            Throw

        }

    }

}

See Also

Deploy Windows Azure Pack: Web Sites