DSCResources/ChefConf_WebBinding/ChefConf_WebBinding.psm1

function Get-TargetResource 
{
    [OutputType([System.Collections.Hashtable])]
    param (
      [parameter(Mandatory)]
      [string]
      $WebsiteName,
      [parameter(Mandatory)]
      [System.UInt16]
      $Port,
      [parameter(Mandatory)]
      [ValidateSet('http', 'https')]
      [string]
      $Protocol,
      [parameter(Mandatory)]
      [string]
      $IPAddress,
      [parameter()]
      [string[]]
      $HostName,
      [string] 
      $CertificateThumbprint,
      [ValidateSet('Personal', 'WebHosting')]
      [string] 
      $CertificateStoreName
    )

    
    $TargetResource = @{}
    $MatchingBinding = Get-MatchingWebBinding -Name $WebsiteName -port $Port -Protocol $Protocol -IPAddress $IPAddress

    if ($MatchingBinding)
    {
      $TargetResource.WebsiteName = $WebsiteName
      $TargetResource.Port = $Port
      $TargetResource.Protocol = $Protocol
      $TargetResource.IPAddress = $IPAddress
      $TargetResource.HostName = $MatchingBinding.HostName | where {$HostName -contains $_ }
      $TargetResource.CertificateThumbprint = $MatchingBinding.CertificateThumbprint
      $TargetResource.CertificateStoreName = $MatchingBinding.CertificateStoreName
    }
    
    return $TargetResource

}

function Get-MatchingWebBinding
{
  param ([string]$Name, [uint16]$Port, [string]$Protocol, [string]$IPAddress)
 
  Get-WebBinding @PSBoundParameters |
    New-WebBindingObject 
}

function Set-TargetResource
{
  param (
      [parameter(Mandatory)]
      [string]
      $WebsiteName,
      [parameter(Mandatory)]
      [System.UInt16]
      $Port,
      [parameter(Mandatory)]
      [ValidateSet('http', 'https')]
      [string]
      $Protocol,
      [parameter(Mandatory)]
      [string]
      $IPAddress,
      [parameter()]
      [string[]]
      $HostName,
      [string] 
      $CertificateThumbprint,
      [ValidateSet('Personal', 'WebHosting')]
      [string] 
      $CertificateStoreName
    )

    $BindingParameters = @{
      Name = $WebsiteName 
      Protocol = $Protocol
      Port = $Port
      IPAddress = $IPAddress
    }
    if ($PSBoundParameters.ContainsKey('HostName'))
    {
      foreach ($HostHeader in $HostName)
      {
        $BindingParameters.HostHeader = $HostName
        NewWebBinding -Properties $BindingParameters
      }
    }
    else 
    {
      NewWebBinding -Properties $BindingParameters      
    }


    if ($PSBoundParameters.ContainsKey('CertificateThumbprint'))
    {
      $MatchingBinding = Get-MatchingWebBinding -Name $WebsiteName -Port $Port -Protocol $Protocol -IPAddress $IPAddress
      foreach ($ExistingBinding in $MatchingBinding)
      {
        if (($ExistingBinding.CertificateThumbprint -notlike $CertificateThumbprint) -and
          ($ExistingBinding.CertificateStoreName -notlike $CertificateStoreName))
        {
          if ( -not [string]::IsNullOrEmpty($ExistingBinding.CertificateThumbprint))
          {
            $ExistingBinding.BindingInfo.RemoveSslCertificate()
          }
          $ExistingBinding.BindingInfo.AddSslCertificate($CertificateThumbprint, $CertificateStoreName)
        }
      }
    }

    #Wait for binding to get picked up.
    Start-Sleep -seconds 1
    #Make sure the site is running (especially for the first binding)
    if (Get-WebSite -Name $WebsiteName | where {$_.state -notlike 'Started'})
    { 
      Start-Website $WebsiteName
    } 
}

function NewWebBinding 
{
  param ($properties)
  try 
  {
    Write-Verbose "Creating binding for site $($properties.name) with "
    foreach ($Key in $properties.keys)
    {
      Write-Verbose "`t$key : $($properties[$key])"
    }
    New-WebBinding @properties -ErrorAction Stop
    Write-Verbose "Binding created."
  }
  catch [Exception]
  {
    Write-Verbose "Site $($Properties.Name) already has this binding." 
  }
}

function Test-TargetResource
{
  [OutputType([System.Boolean])]
  param (
      [parameter(Mandatory)]
      [string]
      $WebsiteName,
      [parameter(Mandatory)]
      [System.UInt16]
      $Port,
      [parameter(Mandatory)]
      [ValidateSet('http', 'https')]
      [string]
      $Protocol,
      [parameter(Mandatory)]
      [string]
      $IPAddress,
      [parameter()]
      [string[]]
      $HostName,
      [string] 
      $CertificateThumbprint,
      [ValidateSet('Personal', 'WebHosting')]
      [string] 
      $CertificateStoreName
    )


  $MatchingBinding = Get-MatchingWebBinding -Name $WebsiteName -Port $Port -IPAddress $IPAddress -Protocol $Protocol

  if ($MatchingBinding)
  { 
    return (ValidateHostName -HostName $HostName -MatchingBinding $MatchingBinding) -and
      (ValidateSslSetting -CertificateThumbprint $CertificateThumbprint -CertificateStoreName $CertificateStoreName -MatchingBinding $MatchingBinding)
  }
  elseif (get-website $WebsiteName)
  {
    Write-Verbose "Website $WebsiteName exists, but has no existing bindings."
    return $false
  }
  else 
  {
      throw "Website $WebsiteName does not exist. Please create a website before attempting to create a binding."
  }
}

function ValidateHostName
{
  [cmdletbinding()]
  param ([string[]]$HostName, [psobject[]]$MatchingBinding)
  $IsValid = $true

  $HostNameIsEmpty = $false
  if ($HostName.count -eq 0)
  {
    $HostNameIsEmpty = $true
    Write-Verbose "$HostName is empty"
  }

  [string[]]$MatchingBindingHostNames = $MatchingBinding.HostName | Where {-not [string]::IsNullOrEmpty($_)}
  $MatchingBindingHostNameIsEmpty = $false
  if ($MatchingBindingHostNames.count -eq 0)
  {
    $MatchingBindingHostNameIsEmpty = $true
    Write-Verbose "MatchingBindingHostName is empty"
  }
  
  if ($MatchingBindingHostNameIsEmpty -and $HostNameIsEmpty)
  {
    return $IsValid
  }

  $IsValid = $IsValid -and ($HostName.count -eq $MatchingBinding.HostName.count)
  if ($IsValid -and $HostName.count -gt 0)
  {
    foreach ($Binding in $MatchingBinding)
    {
      $IsValid = $IsValid -and ($HostName -contains $Binding.HostName)
    }
  }
  return $IsValid
}

function ValidateSslSetting
{
  param ([string]$CertificateThumbprint, [string]$CertificateStoreName, [psobject[]]$MatchingBinding)

  $IsValid = $true

  foreach ($Binding in $MatchingBinding){
    $IsValid = $IsValid -and 
      ($CertificateThumbprint -like $Binding.CertificateThumbprint) -and
      ($CertificateStoreName -like $Binding.CertificateStoreName)
  }

  return $IsValid
}

function New-WebBindingObject
{
    Param
    (
        [parameter(valueFromPipeline)]
        [object]
        $BindingInfo
    )

    process
    {
      #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)
      }
         
      $WebBindingObject = New-Object PSObject -Property @{
        BindingInfo = $BindingInfo
        Protocol = $BindingInfo.protocol;
        IPAddress = $IPAddress;
        Port = $Port;
        HostName = $HostName;
        CertificateThumbprint = $BindingInfo.CertificateHash;
        CertificateStoreName = $BindingInfo.CertificateStoreName
      }
      return $WebBindingObject
    }
}