AppCmdOnTargetMachines.ps1

Write-Verbose "Entering script AppCmdOnTargetMachines.ps1"
$AppCmdRegKey = "HKLM:\SOFTWARE\Microsoft\InetStp"

function Run-Command
{
    param(
        [string]$command,
        [bool] $failOnErr = $true
    )

    $ErrorActionPreference = 'Continue'

    if( $psversiontable.PSVersion.Major -le 4)
    {
        $result = cmd.exe /c "`"$command`""
    }
    else
    {
        $result = cmd.exe /c "$command"
    }

    $ErrorActionPreference = 'Stop'

    if($failOnErr -and $LASTEXITCODE -ne 0)
    {
        throw $result
    }

    return $result
}

function Get-AppCmdLocation
{
    param(
    [Parameter(Mandatory=$true)]
    [string]$regKeyPath
    )
    
    $appCmdNotFoundError = "Cannot find appcmd.exe location. Verify IIS is configured on $env:ComputerName and try operation again."
    $appCmdMinVersionError = "Version of IIS is less than 7.0 on machine $env:ComputerName. Minimum version of IIS required is 7.0"
    
    
    if(-not (Test-Path -Path $regKeyPath))
    {
        throw $appCmdNotFoundError
    }

    $regKey = Get-ItemProperty -Path $regKeyPath
    $path = $regKey.InstallPath
    $version = $regKey.MajorVersion
        
    if($version -le 6.0)
    {
        throw $appCmdMinVersionError
    }

    if( -not (Test-Path $path))
    {
        throw $appCmdNotFoundError
    }

    return (Join-Path $path appcmd.exe), $version
}

function Does-WebsiteExists
{
    param([string] $siteName)

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $appCmdArgs = [string]::Format(' list site /name:"{0}"',$siteName)
    $command = "`"$appCmdPath`" $appCmdArgs"
    Write-Verbose "Checking website exists. Running command : $command"

    $website = Run-Command -command $command -failOnErr $false

    if($website -ne $null)
    {
        Write-Verbose "Website (`"$siteName`") already exists"
        return $true
    }

    Write-Verbose "Website (`"$siteName`") does not exist"
    return $false
}

function Does-BindingExists
{
    param(
        [string]$siteName,
        [string]$protocol,
        [string]$ipAddress,
        [string]$port,
        [string]$hostname
    )

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $appCmdArgs = ' list sites'
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Checking binding exists for website (`"$siteName`"). Running command : $command"

    $sites = Run-Command -command $command -failOnErr $false
    $binding = [string]::Format("{0}/{1}:{2}:{3},", $protocol, $ipAddress, $port, $hostname)

    $isBindingExists = $false

    foreach($site in $sites)
    {
        $site = $site.ToLower()
        if($site.Contains($siteName.ToLower()) -and $site.Contains($binding.ToLower()))
        {
            Write-Verbose "Given binding already exists for the current website (`"$siteName`")."
            $isBindingExists = $true
        }
        elseif($site.Contains($binding.ToLower()))
        {
            throw "Given binding already exists for a different website (`"$site`"), change the port and retry the operation."
        }
    }

    Write-Verbose "Does bindings exist for website (`"$siteName`") is : $isBindingExists"
    return $isBindingExists
}

function Does-AppPoolExists
{  
    param(
        [string]$appPoolName
    )

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $appCmdArgs = [string]::Format(' list apppool /name:"{0}"',$appPoolName)
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Checking application exists. Running command : $command"

    $appPool = Run-Command -command $command -failOnErr $false

    if($appPool -ne $null)
    {
        Write-Verbose "Application Pool (`"$appPoolName`") already exists"
        return $true
    }

    Write-Verbose "Application Pool (`"$appPoolName`") does not exists"
    return $false
}

function Enable-SNI
{
    param(
        [string]$siteName,
        [string]$sni,
        [string]$ipAddress,
        [string]$port,
        [string]$hostname
    )

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey

    if( -not ($sni -eq "true" -and $iisVersion -ge 8 -and -not [string]::IsNullOrWhiteSpace($hostname)))
    {
        Write-Verbose "Not enabling SNI : sni : $sni, iisVersion : $iisVersion, hostname : $hostname. Possible Reasons: `n 1. IIS Version is less than 8 `n 2. HostName input is not provided `n 3. SNI input is set to false"
        return
    }

    if($ipAddress -eq "All Unassigned")
    {
        $ipAddress = "*"
    }

    $appCmdArgs = [string]::Format(' set site /site.name:"{0}" /bindings.[protocol=''https'',bindingInformation=''{1}:{2}:{3}''].sslFlags:"1"',$siteName, $ipAddress, $port, $hostname)
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Enabling SNI by setting SslFlags=1 for binding. Running command : $command"
    Run-Command -command $command
}

function Add-SslCert
{
    param(
        [string]$port,
        [string]$certhash,
        [string]$hostname,
        [string]$sni,
        [string]$iisVersion,
        [string]$ipAddress
    )

    if([string]::IsNullOrWhiteSpace($certhash))
    {
        Write-Verbose "CertHash is empty. Returning"
        return
    }

    if($ipAddress -eq "All Unassigned")
    {
        $ipAddress = "0.0.0.0"
    }

    $result = $null
    $isItSameBinding = $false
    $addCertCmd = [string]::Empty

    #SNI is supported IIS 8 and above. To enable SNI hostnameport option should be used
    if($sni -eq "true" -and $iisVersion -ge 8 -and -not [string]::IsNullOrWhiteSpace($hostname))
    {
        $showCertCmd = [string]::Format("netsh http show sslcert hostnameport={0}:{1}", $hostname, $port)
        Write-Verbose "Checking if SslCert binding is already present. Running command : $showCertCmd"

        $result = Run-Command -command $showCertCmd -failOnErr $false
        $isItSameBinding = $result.Get(4).Contains([string]::Format("{0}:{1}", $hostname, $port))

        $addCertCmd = [string]::Format("netsh http add sslcert hostnameport={0}:{1} certhash={2} appid={{{3}}} certstorename=MY", $hostname, $port, $certhash, [System.Guid]::NewGuid().toString())
    }
    else
    {
        $showCertCmd = [string]::Format("netsh http show sslcert ipport={0}:{1}", $ipAddress, $port)
        Write-Verbose "Checking if SslCert binding is already present. Running command : $showCertCmd"

        $result = Run-Command -command $showCertCmd -failOnErr $false
        $isItSameBinding = $result.Get(4).Contains([string]::Format("{0}:{1}", $ipAddress, $port))
        
        $addCertCmd = [string]::Format("netsh http add sslcert ipport={0}:{1} certhash={2} appid={{{3}}} certstorename=MY", $ipAddress, $port, $certhash, [System.Guid]::NewGuid().toString())
    }

    $isItSameCert = $result.Get(5).ToLower().Contains($certhash.ToLower())

    if($isItSameBinding -and $isItSameCert)
    {
        Write-Verbose "SSL cert binding is already present. Returning"
        return
    }

    Write-Verbose "Setting SslCert for website."
    Run-Command -command $addCertCmd
}

function Create-Website
{
    param(
    [string]$siteName,
    [string]$physicalPath
    )

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $appCmdArgs = [string]::Format(' add site /name:"{0}" /physicalPath:"{1}"',$siteName, $physicalPath)
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Creating website. Running command : $command"
    Run-Command -command $command
}

function Create-AppPool
{
    param(
        [string]$appPoolName
    )

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $appCmdArgs = [string]::Format(' add apppool /name:"{0}"', $appPoolName)
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Creating application Pool. Running command : $command"
    Run-Command -command $command
}

function Run-AdditionalCommands
{
    param(
        [string]$additionalCommands
    )

    $appCmdCommands = $additionalCommands.Trim('"').Split([System.Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey

    foreach($appCmdCommand in $appCmdCommands)
    {
        if(-not [string]::IsNullOrWhiteSpace($appCmdCommand.Trim(' ')))
        {
            $command = "`"$appCmdPath`" $appCmdCommand"

            Write-Verbose "Running additional command. $command"
            Run-Command -command $command
        }
    }
}

function Update-Website
{
    param(
        [string]$siteName,
        [string]$appPoolName,
        [string]$physicalPath,
        [string]$authType,
        [string]$userName,
        [string]$password,
        [string]$addBinding,
        [string]$protocol,
        [string]$ipAddress,
        [string]$port,
        [string]$hostname
    )

    $appCmdArgs = [string]::Format(' set site /site.name:"{0}"', $siteName)

    if(-not [string]::IsNullOrWhiteSpace($appPoolName))
    {
        $appCmdArgs = [string]::Format('{0} -applicationDefaults.applicationPool:"{1}"', $appCmdArgs, $appPoolName)
    }

    if(-not [string]::IsNullOrWhiteSpace($physicalPath))
    {
        $tmpPhysicalPath = $physicalPath.Replace("%SystemDrive%", "$env:SystemDrive")
        Write-Verbose "Checking website physical path exists $tmpPhysicalPath"
        if(!(Test-Path -Path $tmpPhysicalPath))
        {
            Write-Verbose "Creating website physical path $tmpPhysicalPath"
            New-Item -ItemType Directory -Path $tmpPhysicalPath
        }
        $appCmdArgs = [string]::Format("{0} -[path='/'].[path='/'].physicalPath:`"{1}`"", $appCmdArgs, $physicalPath)
    }

    if(-not [string]::IsNullOrWhiteSpace($userName) -and $authType -eq "WebsiteWindowsAuth")
    {
        $appCmdArgs = [string]::Format("{0} -[path='/'].[path='/'].userName:{1}", $appCmdArgs, $userName)
    }

    if(-not [string]::IsNullOrWhiteSpace($password) -and $authType -eq "WebsiteWindowsAuth")
    {
        $appCmdArgs = [string]::Format("{0} -[path='/'].[path='/'].password:{1}", $appCmdArgs, $password)
    }

    if($ipAddress -eq "All Unassigned")
    {
        $ipAddress = "*"
    }

    if($addBinding -eq "true")
    {
        $isBindingExists = Does-BindingExists -siteName $siteName -protocol $protocol -ipAddress $ipAddress -port $port -hostname $hostname

        if($isBindingExists -eq $false)
        {
            $appCmdArgs = [string]::Format("{0} /+bindings.[protocol='{1}',bindingInformation='{2}:{3}:{4}']", $appCmdArgs, $protocol, $ipAddress, $port, $hostname)
        }
    }

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Updating website properties. Running command : $command"
    Run-Command -command $command
}

function Update-AppPool
{
    param(
        [string]$appPoolName,
        [string]$clrVersion,
        [string]$pipeLineMode,
        [string]$identity,
        [string]$userName,
        [string]$password
    )

    $appCmdArgs = ' set config -section:system.applicationHost/applicationPools'

    if($clrVersion -ieq "No Managed Code")
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].managedRuntimeVersion:', $appCmdArgs, $appPoolName)
    }
    elseif(-not [string]::IsNullOrWhiteSpace($clrVersion))
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].managedRuntimeVersion:{2}', $appCmdArgs, $appPoolName, $clrVersion)
    }

    if(-not [string]::IsNullOrWhiteSpace($pipeLineMode))
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].managedPipelineMode:{2}', $appCmdArgs, $appPoolName, $pipeLineMode)
    }

    if($identity -eq "SpecificUser" -and -not [string]::IsNullOrWhiteSpace($userName) -and -not [string]::IsNullOrWhiteSpace($password))
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].processModel.identityType:SpecificUser /[name=''"{1}"''].processModel.userName:"{2}" /[name=''"{1}"''].processModel.password:"{3}"',`
                                $appCmdArgs, $appPoolName, $userName, $password)
    }
    elseif ($identity -eq "SpecificUser" -and -not [string]::IsNullOrWhiteSpace($userName))
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].processModel.identityType:SpecificUser /[name=''"{1}"''].processModel.userName:"{2}"',`
                                $appCmdArgs, $appPoolName, $userName)
    }
    else
    {
        $appCmdArgs = [string]::Format('{0} /[name=''"{1}"''].processModel.identityType:{2}', $appCmdArgs, $appPoolName, $identity)
    }

    $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
    $command = "`"$appCmdPath`" $appCmdArgs"

    Write-Verbose "Updating application pool properties. Running command : $command"
    Run-Command -command $command
}

function Create-And-Update-Website
{
    param(
        [string]$siteName,
        [string]$appPoolName,
        [string]$physicalPath,
        [string]$authType,
        [string]$userName,
        [string]$password,
        [string]$addBinding,
        [string]$protocol,
        [string]$ipAddress,
        [string]$port,
        [string]$hostname
    )

    $doesWebsiteExists = Does-WebsiteExists -siteName $siteName

    if( -not $doesWebsiteExists)
    {
        Create-Website -siteName $siteName -physicalPath $physicalPath
    }

    Update-Website -siteName $siteName -appPoolName $appPoolName -physicalPath $physicalPath -authType $authType -userName $userName -password $password `
    -addBinding $addBinding -protocol $protocol -ipAddress $ipAddress -port $port -hostname $hostname
}

function Create-And-Update-AppPool
{
    param(
        [string]$appPoolName,
        [string]$clrVersion,
        [string]$pipeLineMode,
        [string]$identity,
        [string]$userName,
        [string]$password
    )

    $doesAppPoolExists = Does-AppPoolExists -appPoolName $appPoolName

    if(-not $doesAppPoolExists)
    {
        Create-AppPool -appPoolName $appPoolName
    }

    Update-AppPool -appPoolName $appPoolName -clrVersion $clrVersion -pipeLineMode $pipeLineMode -identity $identity -userName $userName -password $password
}

function Execute-Main
{
    param (
        [string]$CreateWebsite,
        [string]$WebsiteName,
        [string]$WebsitePhysicalPath,
        [string]$WebsitePhysicalPathAuth,
        [string]$WebsiteAuthUserName,
        [string]$WebsiteAuthUserPassword,
        [string]$AddBinding,
        [string]$Protocol,
        [string]$IpAddress,
        [string]$Port,
        [string]$HostName,
        [string]$ServerNameIndication,
        [string]$SslCertThumbPrint,
        [string]$CreateAppPool,
        [string]$AppPoolName,
        [string]$DotNetVersion,
        [string]$PipeLineMode,
        [string]$AppPoolIdentity,
        [string]$AppPoolUsername,
        [string]$AppPoolPassword,
        [string]$AppCmdCommands
        )

    Write-Verbose "Entering Execute-Main function"
    Write-Verbose "CreateWebsite= $CreateWebsite"
    Write-Verbose "WebsiteName = $WebsiteName"
    Write-Verbose "WebsitePhysicalPath = $WebsitePhysicalPath"
    Write-Verbose "WebsitePhysicalPathAuth = $WebsitePhysicalPathAuth"
    Write-Verbose "WebsiteAuthUserName = $WebsiteAuthUserName"
    Write-Verbose "WebSiteAuthUserPassword = $WebSiteAuthUserPassword"
    Write-Verbose "AddBinding = $AddBinding"
    Write-Verbose "Protocol = $Protocol"
    Write-Verbose "IpAddress = $IpAddress"
    Write-Verbose "Port = $Port"
    Write-Verbose "HostName = $HostName"
    Write-Verbose "ServerNameIndication = $ServerNameIndication"

    Write-Verbose "CreateAppPool = $CreateAppPool"
    Write-Verbose "AppPoolName = $AppPoolName"
    Write-Verbose "DotNetVersion = $DotNetVersion"
    Write-Verbose "PipeLineMode = $PipeLineMode"
    Write-Verbose "AppPoolIdentity = $AppPoolIdentity"
    Write-Verbose "AppPoolUsername = $AppPoolUsername"
    Write-Verbose "AppPoolPassword = $AppPoolPassword"
    Write-Verbose "AppCmdCommands = $AppCmdCommands"
    
    if($CreateAppPool -ieq "true")
    {
        Create-And-Update-AppPool -appPoolName $AppPoolName -clrVersion $DotNetVersion -pipeLineMode $PipeLineMode -identity $AppPoolIdentity -userName $AppPoolUsername -password $AppPoolPassword
    }

    if($CreateWebsite -ieq "true")
    {
        Create-And-Update-Website -siteName $WebsiteName -appPoolName $AppPoolName -physicalPath $WebsitePhysicalPath -authType $WebsitePhysicalPathAuth -userName $WebsiteAuthUserName `
         -password $WebsiteAuthUserPassword -addBinding $AddBinding -protocol $Protocol -ipAddress $IpAddress -port $Port -hostname $HostName

        if($Protocol -ieq "https" -and $AddBinding -ieq "true")
        {
            $appCmdPath, $iisVersion = Get-AppCmdLocation -regKeyPath $AppCmdRegKey
            Add-SslCert -ipAddress $IpAddress -port $Port -certhash $SslCertThumbPrint -hostname $HostName -sni $ServerNameIndication -iisVersion $iisVersion
            Enable-SNI -siteName $WebsiteName -sni $ServerNameIndication -ipAddress $IpAddress -port $Port -hostname $HostName
        }
    }
    Run-AdditionalCommands -additionalCommands $AppCmdCommands
    Write-Verbose "Exiting Execute-Main function"
}