modules/deploy/dsc/custom/cIIS/DscResources/OBJ_cWebsite/OBJ_cWebsite.psm1

#requires -Version 3 -Modules CimCmdlets
data LocalizedData
{
    # culture="en-US"
    ConvertFrom-StringData -StringData @'
SetTargetResourceInstallwhatIfMessage=Trying to create website "{0}".
SetTargetResourceUnInstallwhatIfMessage=Trying to remove website "{0}".
WebsiteNotFoundError=The requested website "{0}" is not found on the target machine.
WebsiteDiscoveryFailureError=Failure to get the requested website "{0}" information from the target machine.
WebsiteCreationFailureError=Failure to successfully create the website "{0}".
WebsiteRemovalFailureError=Failure to successfully remove the website "{0}".
WebsiteUpdateFailureError=Failure to successfully update the properties for website "{0}".
WebsiteBindingUpdateFailureError=Failure to successfully update the bindings for website "{0}".
WebsiteBindingInputInvalidationError=Desired website bindings not valid for website "{0}".
WebsiteCompareFailureError=Failure to successfully compare properties for website "{0}".
WebBindingCertifcateError=Failure to add certificate to web binding. Please make sure that the certificate thumbprint "{0}" is valid.
WebsiteStateFailureError=Failure to successfully set the state of the website {0}.
WebsiteBindingConflictOnStartError = Website "{0}" could not be started due to binding conflict. Ensure that the binding information for this website does not conflict with any existing website's bindings before trying to start it.
'@

}

# The Get-TargetResource cmdlet is used to fetch the status of role or Website on the target machine.
# It gives the Website info of the requested role/feature on the target machine.
function Get-TargetResource
{
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$PhysicalPath
    )

    # Check if WebAdministration module is present for IIS cmdlets
    if(!(Get-Module -ListAvailable -Name WebAdministration))
    {
        Throw 'Please ensure that WebAdministration module is installed.'
    }
    if (!(Get-Module -Name WebAdministration -ErrorAction SilentlyContinue)) {
            Import-Module -Name WebAdministration -Verbose:$false
    }

    try { 
        $Website = Get-Website | Where-Object -FilterScript {
            $_.Name -eq $Name
        }
    } catch { 
         # DO NOT REMOVE
         # This is workaround for 2008 R2 issue - see http://stevenmaglio.blogspot.com/2014/11/webadministration-not-loaded-correctly.html
         $Website = Get-Website | Where-Object -FilterScript {
            $_.Name -eq $Name
         }
    }

    if ($Website.count -eq 0) # No Website exists with this name.
    {
        $ensureResult = 'Absent'
    }
    elseif ($Website.count -eq 1) # A single Website exists with this name.
    {
        $ensureResult = 'Present'

        [PSObject[]] $Bindings
        $Bindings = (Get-ItemProperty -Path IIS:\Sites\$Name -Name Bindings).collection

        $CimBindings = foreach ($binding in $Bindings)
        {
            $BindingObject = Get-WebBindingObject -BindingInfo $binding
            New-CimInstance -ClassName OBJ_cWebBindingInformation -Namespace root/microsoft/Windows/DesiredStateConfiguration -Property @{
                Port                  = [System.UInt16]$BindingObject.Port
                Protocol              = $BindingObject.Protocol
                IPAddress             = $BindingObject.IPaddress
                HostName              = $BindingObject.Hostname
                CertificateThumbprint = $BindingObject.CertificateThumbprint
                CertificateStoreName  = $BindingObject.CertificateStoreName
                SSLFlags              = $BindingObject.SSLFlags
            } -ClientOnly
        }

        $allDefaultPage = @(Get-WebConfiguration //defaultDocument/files/*  -PSPath (Join-Path -Path 'IIS:\sites\' -ChildPath $Name) -ErrorAction SilentlyContinue | ForEach-Object -Process {
                Write-Output -InputObject $_.value
        })
    }
    else # Multiple websites with the same name exist. This is not supported and is an error
    {
        $errorId = 'WebsiteDiscoveryFailure'
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
        $errorMessage = $($LocalizedData.WebsiteDiscoveryFailureError) -f ${Name}
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }

    # Add all Website properties to the hash table
    return @{
        Name            = $Website.Name
        Ensure          = $ensureResult
        PhysicalPath    = $Website.PhysicalPath
        State           = $Website.State
        ID              = $Website.ID
        ApplicationPool = $Website.ApplicationPool
        BindingInfo     = $CimBindings
        DefaultPage     = $allDefaultPage
    }
}


# The Set-TargetResource cmdlet is used to create, delete or configure a website on the target machine.
function Set-TargetResource
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param
    (
        [ValidateSet('Present', 'Absent')]
        [string]$Ensure = 'Present',

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$PhysicalPath,

        [ValidateSet('Started', 'Stopped')]
        [string]$State = 'Started',

        [string]$ApplicationPool,

        [Microsoft.Management.Infrastructure.CimInstance[]]$BindingInfo,

        [string[]]$DefaultPage

    )

    $getTargetResourceResult = $null

    if($Ensure -eq 'Present')
    {
        #Remove Ensure from parameters as it is not needed to create new website
        $Result = $psboundparameters.Remove('Ensure')
        #Remove State parameter form website. Will start the website after configuration is complete
        $Result = $psboundparameters.Remove('State')

        #Remove bindings from parameters if they exist
        #Bindings will be added to site using separate cmdlet
        $Result = $psboundparameters.Remove('BindingInfo')

        #Remove default pages from parameters if they exist
        #Default Pages will be added to site using separate cmdlet
        $Result = $psboundparameters.Remove('DefaultPage')

        # Check if WebAdministration module is present for IIS cmdlets
        if(!(Get-Module -ListAvailable -Name WebAdministration))
        {
            Throw 'Please ensure that WebAdministration module is installed.'
        }
        if (!(Get-Module -Name WebAdministration -ErrorAction SilentlyContinue)) {
            # Without explicit Import-Module on 2008 R2, Get-Item "IIS:\..." sometimes returns "a drive with the name iis does not exist"
            Import-Module -Name WebAdministration -Verbose:$false
        }

        $Website = Get-Website | Where-Object -FilterScript {
            $_.Name -eq $Name
        }

        if($Website -ne $null)
        {
            #update parameters as required

            $UpdateNotRequired = $true

            #Update Physical Path if required
            if(Test-WebsitePath -Name $Name -PhysicalPath $PhysicalPath)
            {
                $UpdateNotRequired = $false
                Set-ItemProperty -Path "IIS:\Sites\$Name" -Name physicalPath -Value $PhysicalPath -ErrorAction Stop

                Write-Verbose -Message ("Physical path for website $Name has been updated to $PhysicalPath")
            }

            #Update Bindings if required
            if ($BindingInfo -ne $null)
            {
                if(Test-WebsiteBindings -Name $Name -BindingInfo $BindingInfo)
                {
                    $UpdateNotRequired = $false
                    #Update Bindings
                    Update-WebsiteBinding -Name $Name -BindingInfo $BindingInfo -ErrorAction Stop

                    Write-Verbose -Message ("Bindings for website $Name have been updated.")
                }
            }

            #Update Application Pool if required
            if(($Website.applicationPool -ne $ApplicationPool) -and ($ApplicationPool -ne ''))
            {
                $UpdateNotRequired = $false
                Set-ItemProperty -Path IIS:\Sites\$Name -Name applicationPool -Value $ApplicationPool -ErrorAction Stop

                Write-Verbose -Message ("Application Pool for website $Name has been updated to $ApplicationPool")
            }

            #Update Default pages if required
            if($DefaultPage -ne $null)
            {
                Update-DefaultPages $Name -DefaultPage $DefaultPage
            }

            #Update State if required
            if($Website.state -ne $State -and $State -ne '')
            {
                $UpdateNotRequired = $false
                if($State -eq 'Started')
                {
                    # Ensure that there are no other websites with binding information that will conflict with this site before starting
                    $existingSites = Get-Website | Where-Object -Property Name -NE -Value $Name

                    foreach($site in $existingSites)
                    {
                        $siteInfo = Get-TargetResource -Name $site.Name -PhysicalPath $site.PhysicalPath

                        foreach ($binding in $BindingInfo)
                        {
                            #Normalize empty IPAddress to "*"
                            if($binding.IPAddress -eq '' -or $binding.IPAddress -eq $null)
                            {
                                $NormalizedIPAddress = '*'
                            }
                            else
                            {
                                $NormalizedIPAddress = $binding.IPAddress
                            }

                            if( !(Confirm-PortIPHostisUnique -Port $binding.Port -IPAddress $NormalizedIPAddress -HostName $binding.HostName -BindingInfo $siteInfo.BindingInfo -UniqueInstances 1))
                            {
                                #return error & Do not start Website
                                $errorId = 'WebsiteBindingConflictOnStart'
                                $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
                                $errorMessage = $($LocalizedData.WebsiteBindingConflictOnStartError) -f ${Name}
                                $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                                $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

                                $PSCmdlet.ThrowTerminatingError($errorRecord)
                            }
                        }
                    }

                    try
                    {
                        Start-Website -Name $Name
                    }
                    catch
                    {
                        $errorId = 'WebsiteStateFailure'
                        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
                        $errorMessage = $($LocalizedData.WebsiteStateFailureError) -f ${Name}
                        $errorMessage += $_.Exception.Message
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

                        $PSCmdlet.ThrowTerminatingError($errorRecord)
                    }
                }
                else
                {
                    try
                    {
                        Stop-Website -Name $Name
                    }
                    catch
                    {
                        $errorId = 'WebsiteStateFailure'
                        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
                        $errorMessage = $($LocalizedData.WebsiteStateFailureError) -f ${Name}
                        $errorMessage += $_.Exception.Message
                        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

                        $PSCmdlet.ThrowTerminatingError($errorRecord)
                    }
                }

                Write-Verbose -Message ("State for website $Name has been updated to $State")
            }

            if($UpdateNotRequired)
            {
                Write-Verbose -Message ("Website $Name already exists and properties do not need to be updated.")
            }
        }
        else #Website doesn't exist so create new one
        {
            try
            {
                $Websites = Get-Website
                if ($Websites -eq $null)
                {
                    # We do not have any sites this will cause an exception in 2008R2 if we don't specify an ID
                    $Website = New-Website @psboundparameters -ID 1
                }
                else
                {
                    $Website = New-Website @psboundparameters
                }
                $Result = Stop-Website $Website.name -ErrorAction Stop

                #Clear default bindings if new bindings defined and are different
                if($BindingInfo -ne $null)
                {
                    if(Test-WebsiteBindings -Name $Name -BindingInfo $BindingInfo)
                    {
                        Update-WebsiteBinding -Name $Name -BindingInfo $BindingInfo
                    }
                }

                #Add Default pages for new created website
                if($DefaultPage -ne $null)
                {
                    Update-DefaultPages -Name $Name -DefaultPage $DefaultPage
                }

                Write-Verbose -Message ("successfully created website $Name")

                #Start site if required
                if($State -eq 'Started')
                {
                    #Wait 1 sec for bindings to take effect
                    #I have found that starting the website results in an error if it happens to quickly
                    Start-Sleep -Seconds 1
                    Start-Website -Name $Name -ErrorAction Stop
                }

                Write-Verbose -Message ("successfully started website $Name")
            }
            catch
            {
                $errorId = 'WebsiteCreationFailure'
                $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
                $errorMessage = $($LocalizedData.WebsiteCreationFailureError) -f ${Name}
                $errorMessage += $_.Exception.Message
                $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
                $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
        }
    }
    else #Ensure is set to "Absent" so remove website
    {
        try
        {
            $Website = Get-Website | Where-Object -FilterScript {
                $_.Name -eq $Name
            }
            if($Website -ne $null)
            {
                Remove-Website -name $Name
                Write-Verbose -Message ("Successfully removed Website $Name.")
            }
            else
            {
                Write-Verbose -Message ("Website $Name does not exist.")
            }
        }
        catch
        {
            $errorId = 'WebsiteRemovalFailure'
            $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
            $errorMessage = $($LocalizedData.WebsiteRemovalFailureError) -f ${Name}
            $errorMessage += $_.Exception.Message
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }
}


# The Test-TargetResource cmdlet is used to validate if the role or feature is in a state as expected in the instance document.
function Test-TargetResource
{
    [OutputType([System.Boolean])]
    param
    (
        [ValidateSet('Present', 'Absent')]
        [string]$Ensure = 'Present',

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$PhysicalPath,

        [ValidateSet('Started', 'Stopped')]
        [string]$State = 'Started',

        [string]$ApplicationPool,

        [Microsoft.Management.Infrastructure.CimInstance[]]$BindingInfo,

        [string[]]$DefaultPage
    )

    $DesiredConfigurationMatch = $true

    # Check if WebAdministration module is present for IIS cmdlets
    if(!(Get-Module -ListAvailable -Name WebAdministration))
    {
        Throw 'Please ensure that WebAdministration module is installed.'
    }

    if (!(Get-Module -Name WebAdministration -ErrorAction SilentlyContinue)) {
        Import-Module -Name WebAdministration -Verbose:$false
    }

    try { 
        $Website = Get-Website | Where-Object -FilterScript {
            $_.Name -eq $Name
        }
    } catch { 
         # DO NOT REMOVE
         # This is workaround for 2008 R2 issue - see http://stevenmaglio.blogspot.com/2014/11/webadministration-not-loaded-correctly.html
         $Website = Get-Website | Where-Object -FilterScript {
            $_.Name -eq $Name
         }
    }
    $Stop = $true

    Do
    {
        #Check Ensure
        if(($Ensure -eq 'Present' -and $Website -eq $null) -or ($Ensure -eq 'Absent' -and $Website -ne $null))
        {
            $DesiredConfigurationMatch = $false
            Write-Verbose -Message ("The Ensure state for website $Name does not match the desired state.")
            break
        }

        # Only check properties if $website exists
        if ($Website -ne $null)
        {
            #Check Physical Path property
            if(Test-WebsitePath -Name $Name -PhysicalPath $PhysicalPath)
            {
                $DesiredConfigurationMatch = $false
                Write-Verbose -Message ("Physical Path of Website $Name does not match the desired state.")
                break
            }

            #Check State
            if($Website.state -ne $State -and $State -ne $null)
            {
                $DesiredConfigurationMatch = $false
                Write-Verbose -Message ("The state of Website $Name does not match the desired state.")
                break
            }

            #Check Application Pool property
            if(($ApplicationPool -ne '') -and ($Website.applicationPool -ne $ApplicationPool))
            {
                $DesiredConfigurationMatch = $false
                Write-Verbose -Message ("Application Pool for Website $Name does not match the desired state.")
                break
            }

            #Check Binding properties
            if($BindingInfo -ne $null)
            {
                if(Test-WebsiteBindings -Name $Name -BindingInfo $BindingInfo)
                {
                    $DesiredConfigurationMatch = $false
                    Write-Verbose -Message ("Bindings for website $Name do not match the desired state.")
                    break
                }
            }
        }

        #Check Default Pages
        if($DefaultPage -ne $null)
        {
            $allDefaultPage = @(Get-WebConfiguration //defaultDocument/files/* -PSPath (Join-Path -Path 'IIS:\sites\' -ChildPath $Name) | ForEach-Object -Process {
                    Write-Output -InputObject $_.value
            })

            $allDefaultPagesPresent = $true

            foreach($page in $DefaultPage)
            {
                if(-not ($allDefaultPage -icontains $page))
                {
                    $DesiredConfigurationMatch = $false
                    Write-Verbose -Message ("Default Page for website $Name do not match the desired state.")
                    $allDefaultPagesPresent = $false
                    break
                }
            }

            if($allDefaultPagesPresent -eq $false)
            {
                # This is to break out from Test
                break
            }
        }


        $Stop = $false
    }
    While($Stop)

    $DesiredConfigurationMatch
}

#region HelperFunctions

# Helper function used to validate website path
function Test-WebsitePath
{
    param
    (
        [string] $Name,

        [string] $PhysicalPath
    )

    if((Get-ItemProperty -Path "IIS:\Sites\$Name" -Name physicalPath) -ne $PhysicalPath)
    {
        return $true
    }

    return $false
}

# Helper function used to validate website bindings
# Returns true if bindings are valid (ie. port, IPAddress & Hostname combinations are unique).

function Confirm-PortIPHostisUnique
{
    param
    (
        [parameter()]
        [System.UInt16]
        $Port,

        [parameter()]
        [string]
        $IPAddress,

        [parameter()]
        [string]
        $HostName,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $BindingInfo,

        [parameter()]
        $UniqueInstances = 0
    )

    foreach ($binding in $BindingInfo)
    {
        if($binding.Port -eq $Port -and [string]$binding.IPAddress -eq $IPAddress -and [string]$binding.HostName -eq $HostName)
        {
            $UniqueInstances += 1
        }
    }

    if($UniqueInstances -gt 1)
    {
        return $false
    }
    else
    {
        return $true
    }
}

# Helper function used to compare website bindings of actual to desired
# Returns true if bindings need to be updated and false if not.
function Test-WebsiteBindings
{
    param
    (
        [parameter()]
        [string]
        $Name,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $BindingInfo
    )

    foreach($binding in $BindingInfo)
    {
        # First ensure that desired binding information is valid ie. No duplicate IPAddres, Port, Host name combinations.

        if (!(Confirm-PortIPHostisUnique -Port $binding.Port -IPAddress $binding.IPAddress -HostName $binding.Hostname -BindingInfo $BindingInfo) )
        {
            $errorId = 'WebsiteBindingInputInvalidation'
            $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
            $errorMessage = $($LocalizedData.WebsiteBindingInputInvalidationError) -f ${Name}
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }

    # Assume bindings do not need updating
    $BindingNeedsUpdating = $false

    Write-Verbose "Getting Web Binding: $name"
    $ActualBindings = Get-WebBinding -Name $Name
    Write-Verbose "ActualBindings $($ActualBindings.ToString())"

    # Format Binding information: Split BindingInfo into individual Properties (IPAddress:Port:HostName)
    $ActualBindingObjects = @()

    foreach ($ActualBinding in $ActualBindings)
    {
        $ActualBindingObjects += Get-WebBindingObject -BindingInfo $ActualBinding
    }

    #Compare Actual Binding info ($FormatActualBindingInfo) to Desired($BindingInfo)
    try
    {
        if($BindingInfo.Count -le $ActualBindingObjects.Count)
        {
            foreach($binding in $BindingInfo)
            {
                $ActualBinding = $ActualBindingObjects | Where-Object -FilterScript {
                    $_.Port -eq $binding.CimInstanceProperties['Port'].Value
                }
                if ($ActualBinding -ne $null)
                {
                    if([string]$ActualBinding.Protocol -ne [string]$binding.CimInstanceProperties['Protocol'].Value)
                    {
                        $BindingNeedsUpdating = $true
                        break
                    }

                    if([string]$ActualBinding.IPAddress -ne [string]$binding.CimInstanceProperties['IPAddress'].Value)
                    {
                        # Special case where blank IPAddress is saved as "*" in the binding information.
                        if([string]$ActualBinding.IPAddress -eq '*' -AND [string]$binding.CimInstanceProperties['IPAddress'].Value -eq '')
                        {
                            #Do nothing
                        }
                        else
                        {
                            $BindingNeedsUpdating = $true
                            break
                        }
                    }

                    if([string]$ActualBinding.HostName -ne [string]$binding.CimInstanceProperties['HostName'].Value)
                    {
                        $BindingNeedsUpdating = $true
                        break
                    }
                    
                    if ($Binding.CimInstanceProperties["SelfSignedCertificate"].Value) 
                    {
                        if ([string]$ActualBinding.CertificateStoreName -ine 'MY') 
                        {
                            $BindingNeedsUpdating = $true
                            break
                        }
                    } 
                    else 
                    { 

                        if([string]$ActualBinding.CertificateThumbprint -ne [string]$binding.CimInstanceProperties['CertificateThumbprint'].Value)
                        {
                            $BindingNeedsUpdating = $true
                            break
                        }

                        if(-not [string]::IsNullOrWhiteSpace([string]$ActualBinding.CertificateThumbprint) -and [string]$ActualBinding.CertificateStoreName -ne [string]$binding.CimInstanceProperties['CertificateStoreName'].Value)
                        {
                            $BindingNeedsUpdating = $true
                            break
                        }

                    }
                    if([string]$ActualBinding.SSLFlags -ne [string]$binding.CimInstanceProperties['SSLFlags'].Value)
                    {
                        $BindingNeedsUpdating = $true
                        break
                    }
                }
                else
                {
                    $BindingNeedsUpdating = $true
                    break
                }
            }
        }
        else
        {
            $BindingNeedsUpdating = $true
        }

        return $BindingNeedsUpdating
    }
    catch
    {
        $errorId = 'WebsiteCompareFailure'
        $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
        $errorMessage = $($LocalizedData.WebsiteCompareFailureError) -f ${Name}
        $errorMessage += $_.Exception.Message
        $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
        $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}

function Update-WebsiteBinding
{
    param
    (
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $BindingInfo
    )

    #Need to clear the bindings before we can create new ones
    Clear-ItemProperty -Path IIS:\Sites\$Name -Name bindings -ErrorAction Stop

    foreach($binding in $BindingInfo)
    {
        $Protocol = $binding.CimInstanceProperties['Protocol'].Value
        $IPAddress = $binding.CimInstanceProperties['IPAddress'].Value
        $Port = $binding.CimInstanceProperties['Port'].Value
        $HostHeader = $binding.CimInstanceProperties['HostName'].Value
        $CertificateThumbprint = $binding.CimInstanceProperties['CertificateThumbprint'].Value
        $CertificateStoreName = $binding.CimInstanceProperties['CertificateStoreName'].Value
        $SSLFlags = $binding.CimInstanceProperties['SSLFlags'].Value
        $SelfSignedCertificate = $binding.CimInstanceProperties["SelfSignedCertificate"].Value


        $bindingParams = @{}
        $bindingParams.Add('-Name', $Name)
        $bindingParams.Add('-Port', $Port)

        #Set IP Address parameter
        if($IPAddress -ne $null)
        {
            $bindingParams.Add('-IPAddress', $IPAddress)
        }
        else # Default to any/all IP Addresses
        {
            $bindingParams.Add('-IPAddress', '*')
        }

        #Set protocol parameter
        if($Protocol -ne $null)
        {
            $bindingParams.Add('-Protocol', $Protocol)
        }
        else #Default to Http
        {
            $bindingParams.Add('-Protocol', 'http')
        }

        #Set Host parameter if it exists
        if($HostHeader -ne $null)
        {
            $bindingParams.Add('-HostHeader', $HostHeader)
        }

        if(-not [string]::IsNullOrWhiteSpace($SSLFlags))
        {
            $bindingParams.Add('-SSLFlags', $SSLFlags)
        }

        try
        {
            New-WebBinding @bindingParams -ErrorAction Stop
        }
        catch
        {
            $errorId = 'WebsiteBindingUpdateFailure'
            $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidResult
            $errorMessage = $($LocalizedData.WebsiteBindingUpdateFailureError) -f ${Name}
            $errorMessage += $_.Exception.Message
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }

        try
        {
            if ( -not [string]::IsNullOrWhiteSpace($CertificateThumbprint) )
            {
                $NewWebbinding = Get-WebBinding -Name $Name -Port $Port
                $NewWebbinding.AddSslCertificate($CertificateThumbprint, $CertificateStoreName)
            } elseif ($SelfSignedCertificate) {
                if (!$HostHeader) {
                    $dnsName = (hostname)
                } else {
                    $dnsName = $HostHeader
                }
                $cert = Get-ChildItem -Path 'cert:\LocalMachine\My' | Where-Object { $_.Subject -imatch "CN=$dnsName" } | Sort-Object NotBefore -Descending | Select-Object -First 1
                
                if ($cert) { 
                    Write-Verbose("Using existing certificate: $($cert.Name)")
                } else {
                    Write-Verbose("Creating new self-signed certificate for '$dnsName'")
                    $cert = New-SelfSignedCertificate -DnsName $dnsName -CertStoreLocation cert:\LocalMachine\My
                }
                $newWebBinding = get-WebBinding -name $Name -Port $Port
                $newWebBinding.AddSslCertificate($cert.Thumbprint, 'My')
            }

        }
        catch
        {
            $errorId = 'WebBindingCertifcateError'
            $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
            $errorMessage = $($LocalizedData.WebBindingCertifcateError) -f ${CertificateThumbprint}
            $errorMessage += $_.Exception.Message
            $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
            $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $errorId, $errorCategory, $null

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }
}

function Get-WebBindingObject
{
    param
    (
        $BindingInfo
    )

    #First split properties by ']:'. This will get IPv6 address split from port and host name
    $Split = $BindingInfo.BindingInformation.split('[]')
    if($Split.count -gt 1)
    {
        $IPAddress = $Split.item(1)
        $Port = $Split.item(2).split(':').item(1)
        $HostName = $Split.item(2).split(':').item(2)
    }
    else
    {
        $SplitProps = $BindingInfo.BindingInformation.split(':')
        $IPAddress = $SplitProps.item(0)
        $Port = $SplitProps.item(1)
        $HostName = $SplitProps.item(2)
    }

    return New-Object -TypeName PSObject -Property @{
        Protocol              = $BindingInfo.protocol
        IPAddress             = $IPAddress
        Port                  = $Port
        HostName              = $HostName
        CertificateThumbprint = $BindingInfo.CertificateHash
        CertificateStoreName  = $BindingInfo.CertificateStoreName
        sslFlags              = $BindingInfo.sslFlags
    }
}

# Helper function used to Update default pages of website
function Update-DefaultPages
{
    param
   (
        [string] $Name,

        [string[]] $DefaultPage
    )

    $allDefaultPage = @(Get-WebConfiguration //defaultDocument/files/* -PSPath (Join-Path -Path 'IIS:\sites\' -ChildPath $Name) | ForEach-Object -Process {
        Write-Output -InputObject $_.value
    })

    foreach($page in $DefaultPage)
    {
        if(-not ($allDefaultPage -icontains $page))
        {
            Write-Verbose -Message ("Deafult page for website $Name has been updated to $page")
            Add-WebConfiguration //defaultDocument/files -PSPath (Join-Path -Path 'IIS:\sites\' -ChildPath $Name) -Value @{
                value = $page
            }
        }
    }
}

#endregion