2015-04-24 08:48:02 +00:00
#!powershell
2018-01-02 00:30:18 +00:00
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
# Copyright: (c) 2015, Henrik Wallström <henrik@wallstroms.nu>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
# Requires -Module Ansible.ModuleUtils.Legacy
$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name " _ansible_check_mode " -type " bool " -default $false
$name = Get-AnsibleParam $params -name " name " -type str -failifempty $true -aliases 'website'
$state = Get-AnsibleParam $params " state " -default " present " -validateSet " present " , " absent "
$host_header = Get-AnsibleParam $params -name " host_header " -type str
$protocol = Get-AnsibleParam $params -name " protocol " -type str -default 'http'
$port = Get-AnsibleParam $params -name " port " -default '80'
$ip = Get-AnsibleParam $params -name " ip " -default '*'
$certificateHash = Get-AnsibleParam $params -name " certificate_hash " -type str -default ( [ string ] :: Empty )
$certificateStoreName = Get-AnsibleParam $params -name " certificate_store_name " -type str -default ( [ string ] :: Empty )
$sslFlags = Get-AnsibleParam $params -name " ssl_flags " -default '0' -ValidateSet '0' , '1' , '2' , '3'
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
$result = @ {
changed = $false
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
#################
### Functions ###
#################
function Create-BindingInfo {
$ht = @ {
'bindingInformation' = $args [ 0 ] . bindingInformation
'ip' = $args [ 0 ] . bindingInformation . split ( ':' ) [ 0 ]
'port' = [ int ] $args [ 0 ] . bindingInformation . split ( ':' ) [ 1 ]
'hostheader' = $args [ 0 ] . bindingInformation . split ( ':' ) [ 2 ]
#'isDsMapperEnabled' = $args[0].isDsMapperEnabled
'protocol' = $args [ 0 ] . protocol
'certificateStoreName' = $args [ 0 ] . certificateStoreName
'certificateHash' = $args [ 0 ] . certificateHash
}
#handle sslflag support
If ( [ version][System.Environment ] :: OSVersion . Version -lt [ version ] '6.2' )
{
$ht . sslFlags = 'not supported'
}
Else
{
$ht . sslFlags = [ int ] $args [ 0 ] . sslFlags
}
Return $ht
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
# Used instead of get-webbinding to ensure we always return a single binding
2018-01-19 00:00:35 +00:00
# We can't filter properly with get-webbinding...ex get-webbinding ip * returns all bindings
2018-01-02 00:30:18 +00:00
# pass it $binding_parameters hashtable
function Get-SingleWebBinding {
2018-01-19 00:00:35 +00:00
Try {
$site_bindings = get-webbinding -name $args [ 0 ] . name
2018-01-02 00:30:18 +00:00
}
2018-01-19 00:00:35 +00:00
Catch {
# 2k8r2 throws this error when you run get-webbinding with no bindings in iis
If ( -not $_ . Exception . Message . CompareTo ( 'Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value' ) )
{
Throw $_ . Exception . Message
2018-01-02 00:30:18 +00:00
}
2018-01-19 00:00:35 +00:00
Else { return }
2018-01-02 00:30:18 +00:00
}
2018-01-19 00:00:35 +00:00
Foreach ( $binding in $site_bindings )
2018-01-02 00:30:18 +00:00
{
2018-01-19 00:00:35 +00:00
$splits = $binding . bindingInformation -split ':'
if (
$args [ 0 ] . protocol -eq $binding . protocol -and
$args [ 0 ] . ipaddress -eq $splits [ 0 ] -and
$args [ 0 ] . port -eq $splits [ 1 ] -and
$args [ 0 ] . hostheader -eq $splits [ 2 ]
)
2018-01-02 00:30:18 +00:00
{
2018-01-19 00:00:35 +00:00
Return $binding
2018-01-02 00:30:18 +00:00
}
}
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
#############################
### Pre-Action Validation ###
#############################
$os_version = [ version][System.Environment ] :: OSVersion . Version
2015-04-24 08:48:02 +00:00
# Ensure WebAdministration module is loaded
2018-01-02 00:30:18 +00:00
If ( $os_version -lt [ version ] '6.1' )
{
Try {
Add-PSSnapin WebAdministration
}
Catch {
Fail-Json -obj $result -message " The WebAdministration snap-in is not present. Please make sure it is installed. "
}
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
Else
{
Try {
Import-Module WebAdministration
}
Catch {
Fail-Json -obj $result -message " Failed to load WebAdministration module. Is IIS installed? $( $_ . Exception . Message ) "
}
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
# ensure website targetted exists. -Name filter doesn't work on 2k8r2 so do where-object instead
$website_check = get-website | Where-Object { $_ . name -eq $name }
If ( -not $website_check )
{
Fail-Json -obj $result -message " Unable to retrieve website with name $Name . Make sure the website name is valid and exists. "
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
# if OS older than 2012 (6.2) and ssl flags are set, fail. Otherwise toggle sni_support
If ( $os_version -lt [ version ] '6.2' )
{
If ( $sslFlags -ne 0 )
{
Fail-Json -obj $result -message " SNI and Certificate Store support is not available for systems older than 2012 (6.2) "
}
$sni_support = $false #will cause the sslflags check later to skip
}
Else
{
$sni_support = $true
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
# make sure ssl flags only specified with https protocol
If ( $protocol -ne 'https' -and $sslFlags -gt 0 )
{
Fail-Json -obj $result -message " SSLFlags can only be set for HTTPS protocol "
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
# validate certificate details if provided
# we don't do anything with cert on state: absent, so only validate present
If ( $certificateHash -and $state -eq 'present' )
{
If ( $protocol -ne 'https' )
{
Fail-Json -obj $result -message " You can only provide a certificate thumbprint when protocol is set to https "
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
#apply default for cert store name
If ( -Not $certificateStoreName )
{
$certificateStoreName = 'my'
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
#validate cert path
$cert_path = " cert:\LocalMachine\ $certificateStoreName \ $certificateHash "
If ( -Not ( Test-Path $cert_path ) )
{
Fail-Json -obj $result -message " Unable to locate certificate at $cert_path "
}
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
# make sure binding info is valid for central cert store if sslflags -gt 1
If ( $sslFlags -gt 1 -and ( $certificateHash -ne [ string ] :: Empty -or $certificateStoreName -ne [ string ] :: Empty ) )
{
Fail-Json -obj $result -message " You set sslFlags to $sslFlags . This indicates you wish to use the Central Certificate Store feature.
This cannot be used in combination with certficiate_hash and certificate_store_name . When using the Central Certificate Store feature ,
the certificate is automatically retrieved from the store rather than manually assigned to the binding . "
}
2018-01-19 00:00:35 +00:00
# disallow host_header: '*'
If ( $host_header -eq '*' )
2018-01-02 00:30:18 +00:00
{
2018-01-19 00:00:35 +00:00
Fail-Json -obj $result -message " To make or remove a catch-all binding, please omit the host_header parameter entirely rather than specify host_header * "
2018-01-02 00:30:18 +00:00
}
##########################
### start action items ###
##########################
# create binding search splat
$binding_parameters = @ {
Name = $name
Protocol = $protocol
Port = $port
IPAddress = $ip
}
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
# insert host header to search if specified, otherwise it will return * (all bindings matching protocol/ip)
If ( $host_header )
{
$binding_parameters . HostHeader = $host_header
}
2018-01-19 00:00:35 +00:00
Else
{
$binding_parameters . HostHeader = [ string ] :: Empty
}
2018-01-02 00:30:18 +00:00
# Get bindings matching parameters
Try {
$current_bindings = Get-SingleWebBinding $binding_parameters
}
Catch {
Fail-Json -obj $result -message " Failed to retrieve bindings with Get-SingleWebBinding - $( $_ . Exception . Message ) "
}
################################################
### Remove binding or exit if already absent ###
################################################
If ( $current_bindings -and $state -eq 'absent' )
{
Try {
2018-01-19 00:00:35 +00:00
#there is a bug in this method that will result in all bindings being removed if the IP in $current_bindings is a *
#$current_bindings | Remove-WebBinding -verbose -WhatIf:$check_mode
#another method that did not work. It kept failing to match on element and removed everything.
#$element = @{protocol="$protocol";bindingInformation="$ip`:$port`:$host_header"}
#Remove-WebconfigurationProperty -filter $current_bindings.ItemXPath -Name Bindings.collection -AtElement $element -WhatIf #:$check_mode
#this method works
[ array ] $bindings = Get-WebconfigurationProperty -filter $current_bindings . ItemXPath -Name Bindings . collection
$index = Foreach ( $item in $bindings ) {
If ( $protocol -eq $item . protocol -and $current_bindings . bindingInformation -eq $item . bindingInformation ) {
$bindings . indexof ( $item )
break
}
}
Remove-WebconfigurationProperty -filter $current_bindings . ItemXPath -Name Bindings . collection -AtIndex $index -WhatIf: $check_mode
2018-01-02 00:30:18 +00:00
$result . changed = $true
}
2018-01-19 00:00:35 +00:00
2018-01-02 00:30:18 +00:00
Catch {
Fail-Json -obj $result -message " Failed to remove the binding from IIS - $( $_ . Exception . Message ) "
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
# removing bindings from iis may not also remove them from iis:\sslbindings
2015-04-24 08:48:02 +00:00
2018-01-02 00:30:18 +00:00
$result . operation_type = 'removed'
$result . binding_info = $current_bindings | ForEach-Object { Create-BindingInfo $_ }
Exit-Json -obj $result
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
ElseIf ( -Not $current_bindings -and $state -eq 'absent' )
{
# exit changed: false since it's already gone
Exit-Json -obj $result
}
################################
### Modify existing bindings ###
################################
<#
2018-01-19 00:00:35 +00:00
since we have already have the parameters available to get-webbinding ,
2018-01-02 00:30:18 +00:00
we just need to check here for the ones that are not available which are the
ssl settings ( hash , store , sslflags ) . If they aren ' t set we update here , or
exit with changed : false
#>
ElseIf ( $current_bindings )
{
#ran into a strange edge case in testing where I was able to retrieve bindings but not expand all the properties
#when adding a self-signed wildcard cert to a binding. it seemed to permanently break the binding. only removing it
#would cause the error to stop.
Try {
$null = $current_bindings | Select-Object *
}
Catch {
Fail-Json -obj $result -message " Found a matching binding, but failed to expand it's properties (get-binding | FL *). In testing, this was caused by using a self-signed wildcard certificate. $( $_ . Exception . Message ) "
}
# check if there is a match on the ssl parameters
If ( ( $current_bindings . sslFlags -ne $sslFlags -and $sni_support ) -or
$current_bindings . certificateHash -ne $certificateHash -or
$current_bindings . certificateStoreName -ne $certificateStoreName )
{
# match/update SNI
If ( $current_bindings . sslFlags -ne $sslFlags -and $sni_support )
{
Try {
Set-WebBinding -Name $name -IPAddress $ip -Port $port -HostHeader $host_header -PropertyName sslFlags -value $sslFlags -whatif: $check_mode
$result . changed = $true
}
Catch {
Fail-Json -obj $result -message " Failed to update sslFlags on binding - $( $_ . Exception . Message ) "
}
# Refresh the binding object since it has been changed
Try {
$current_bindings = Get-SingleWebBinding $binding_parameters
}
Catch {
Fail-Json -obj $result -message " Failed to refresh bindings after setting sslFlags - $( $_ . Exception . Message ) "
}
}
# match/update certificate
If ( $current_bindings . certificateHash -ne $certificateHash -or $current_bindings . certificateStoreName -ne $certificateStoreName )
{
If ( -Not $check_mode )
{
Try {
$current_bindings . AddSslCertificate ( $certificateHash , $certificateStoreName )
}
Catch {
Fail-Json -obj $result -message " Failed to set new SSL certificate - $( $_ . Exception . Message ) "
}
}
}
$result . changed = $true
$result . operation_type = 'updated'
$result . website_state = ( Get-Website | Where-Object { $_ . Name -eq $Name } ) . State
$result . binding_info = Create-BindingInfo ( Get-SingleWebBinding $binding_parameters )
Exit-Json -obj $result #exit changed true
}
Else
{
$result . operation_type = 'matched'
$result . website_state = ( Get-Website | Where-Object { $_ . Name -eq $Name } ) . State
$result . binding_info = Create-BindingInfo ( Get-SingleWebBinding $binding_parameters )
Exit-Json -obj $result #exit changed false
}
2015-04-24 08:48:02 +00:00
}
2018-01-02 00:30:18 +00:00
########################
### Add new bindings ###
########################
ElseIf ( -not $current_bindings -and $state -eq 'present' )
{
# add binding. this creates the binding, but does not apply a certificate to it.
Try
{
If ( -not $check_mode )
{
If ( $sni_support )
{
New-WebBinding @binding_parameters -SslFlags $sslFlags -Force
}
Else
{
New-WebBinding @binding_parameters -Force
}
}
$result . changed = $true
}
Catch
{
$result . website_state = ( Get-Website | Where-Object { $_ . Name -eq $Name } ) . State
Fail-Json -obj $result -message " Failed at creating new binding (note: creating binding and adding ssl are separate steps) - $( $_ . Exception . Message ) "
}
# add certificate to binding
If ( $certificateHash -and -not $check_mode )
{
Try {
2018-01-19 00:00:35 +00:00
#$new_binding = get-webbinding -Name $name -IPAddress $ip -port $port -Protocol $protocol -hostheader $host_header
$new_binding = Get-SingleWebBinding $binding_parameters
2018-01-02 00:30:18 +00:00
$new_binding . addsslcertificate ( $certificateHash , $certificateStoreName )
}
Catch {
$result . website_state = ( Get-Website | Where-Object { $_ . Name -eq $Name } ) . State
Fail-Json -obj $result -message " Failed to set new SSL certificate - $( $_ . Exception . Message ) "
}
}
$result . changed = $true
$result . operation_type = 'added'
$result . website_state = ( Get-Website | Where-Object { $_ . Name -eq $Name } ) . State
$result . binding_info = Create-BindingInfo ( Get-SingleWebBinding $binding_parameters )
Exit-Json $result
}