Public/Initialize-ACMEProxyRedirect.ps1
function Initialize-ACMEProxyRedirect { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Currently using Write-Host because it supports -NoNewLine')] [CmdletBinding()] param( [Parameter (Mandatory = $true, HelpMessage = "The hostname of the primary node, to which ACME challenges will redirect" )] $PrimaryServer, [Parameter (Mandatory = $false, HelpMessage = "The name of the IIS website in which the ACME redirect will be created" )] $IISSiteName = $DEFAULT_IIS_WEBSITE, [Parameter (Mandatory = $false, HelpMessage = "The name of the URL Rewrite rule that will be created to proxy ACME challenges" )] $URLRewriteRuleName = $DEFAULT_URL_REWRITE_RULE_NAME, [Parameter (Mandatory = $false, HelpMessage = "URL Rewrite Module Installer Log file" )] $URLRewriteInstallerLog = $DEFAULT_URL_REWRITE_INSTALLER_LOG, [Parameter (Mandatory = $false, HelpMessage = "URL Rewrite Module installer" )] $URLRewriteInstaller = $DEFAULT_URL_REWRITE_INSTALLER_MSI, [Parameter (Mandatory = $false, HelpMessage = "URL Rewrite Module installer download URL" )] $URLRewriteDownloadURL = $DEFAULT_URL_REWRITE_INSTALLER_DOWNLOAD_URL, [Parameter (Mandatory = $false, HelpMessage = "URL Rewrite Module installer SHA-256 hash" )] $URLRewriteInstallerExpectedHash = $DEFAULT_URL_REWRITE_INSTALLER_EXPECTED_HASH, [Parameter(Mandatory = $false, HelpMessage = "Optionally write debug information about the function's execution to a file and/or the event log" )] [Switch] $debugEnabled, [Parameter(Mandatory = $false, HelpMessage = "Optionally specify a directory to write a debug log file to" )] [string] $debugLogDirectory = $DEFAULT_DEBUG_LOG_DIRECTORY, [Parameter(Mandatory = $false, HelpMessage = "Optionally specify whether to log to the windows event log (EVT), a file (file) or both (both)" )] [ValidateScript({if($_ -in $VALIDATE_SET_DEBUG_MODE) { $true } else { throw "Parameter '$_' is invalid -- must be one of: $($VALIDATE_SET_DEBUG_MODE -join ",")"}})] [string] $debugMode = $DEFAULT_DEBUG_MODE ) # check to see if the global debug environment variable is set if($null -ne $env:CERTIFICAT_DEBUG_ALWAYS){ $debugEnabled = $true } # Build a complete command of all parameters being used to run this function $ps5Command = "powershell.exe {import-module CertifiCat-PS -Force; $($MyInvocation.MyCommand) " $functionArgs = "" foreach($a in $PSBoundParameters.Keys){ if($PSBoundParameters[$a] -eq $true){ $functionArgs += "-$a " } else { $functionArgs += "-$a `"$($PSBoundParameters[$a])`" " } } $ps5Command += ("$functionArgs}") #begin building the function's return object $fro = [PSCustomObject]@{ FunctionName = $myinvocation.MyCommand; RunningPSVersion = $PSVersionTable.PSVersion.ToString(); PS5Command = $ps5Command; FunctionArguments = $functionArgs; FunctionSuccess = $true; Errors = @(); URLRewriteDownloadURL = $URLRewriteDownloadURL; URLRewriteExpectedHash = $URLRewriteInstallerExpectedHash; URLRewriteRuleName = $URLRewriteRuleName; URLRewriteInstallerLocation = $URLRewriteInstaller; URLRewriteInstallerLog = ""; IISSiteName = $IISSiteName; ProxyRedirectConfiguredSuccessfully = $true; debugEnabled= $debugEnabled; debugLogDirectory = $debugLogDirectory; debugMode = $debugMode; } Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Configuring ACME Proxy Redirect" if(!(Assert-AdminAccess)) { Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Session lacks administrative access. Ensure that PowerShell was run as an Administrator." $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } # Check to ensure that we aren't running from a PowerShell 7 console Write-Host "-> Checking to ensure that we are running in a PowerShell 6 or earlier console..." -NoNewLine if(!(Assert-PSVersion)){ Write-Fail Write-Host "`tDetected this function running from a modern PowerShell console. This function requests the use of PowerShell 6 or older. Check the 'PS5Command' property of the return object for a complete command to run instead." -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Function requires PowerShell 6 or earlier, but running from a modern console. See the PS5Command property for a PowerShell 5 equivalent to run." $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } else { Write-Ok } Write-Host "-> Confirming that IIS is installed..." -NoNewline if((get-windowsfeature web-server).InstallState -ne "Installed"){ Write-Fail Write-Host "`tIIS does not appear to be installed. This function is only for use when IIS is installed and used!" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "IIS role not detected -- it can be setup minimally using 'Install-WindowsFeature web-server'" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } else { Write-Ok } Write-Host "-> Confirming URL Rewrite Module installation..." -NoNewline if(Test-Path "$($env:systemroot)\system32\inetsrv\rewrite.dll"){ Write-Ok } else { Write-Pending Write-Host "`tChecking for existing IIS URL Rewrite Module installer in $URLRewriteInstaller..." -NoNewline if(Test-Path $URLRewriteInstaller){ Write-Ok } else { Write-Pending Write-Host "`tDownloading IIS URL Rewrite Module..." -NoNewLine $fro.URLRewriteDownloadURL = $URLRewriteDownloadURL Invoke-WebRequest $URLRewriteDownloadURL -OutFile $URLRewriteInstaller -ErrorAction SilentlyContinue if(Test-Path $URLRewriteInstaller){ Write-Ok } else { Write-Fail Write-Host "`tFailed to download the URL Rewrite Module!" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "URL Rewrite module not found on server, and download from Microsoft failed! See the URLRewriteDownloadURL property for the URL that was used to download." $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } Write-Host "`tVerifying hash of URL Rewrite Installer (for security purposes)..." -NoNewline $URLRewriteInstallerDLHash = (Get-FileHash $URLRewriteInstaller -Algorithm SHA256).Hash if($URLRewriteInstallerDLHash -eq $URLRewriteInstallerExpectedHash){ Write-Ok } else { Write-Fail Write-Host "`t`tURL Rewrite module installer hash did not match what we expect!" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "URL Rewrite module installer hash ($URLRewriteInstallerDLHash) does not match the expected hash ($URLRewriteInstallerExpectedHash)" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } Write-Host "`tUnblocking installer..." -NoNewline Unblock-File $URLRewriteInstaller if($?) { Write-Ok } else { Write-Fail Write-Host "`t`tURL Rewrite module installer failed to unblock!" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "URL Rewrite module installer failed to unblock!" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } Write-Host "`tInstalling URL Rewrite Module..." -NoNewline $i = start-process msiexec -ArgumentList @('/i', "`"$URLRewriteInstaller`"", "/q", "/norestart", "/l*v $URLRewriteInstallerLog") -PassThru -Wait $fro.URLRewriteInstallerLog = $URLRewriteInstallerLog; if($i.ExitCode -eq 0){ Write-Ok } else { Write-Fail Write-Host "`t`tURL Rewrite module failed to install (installer exit code = $($i.ExitCode)!" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "URL Rewrite module installer failed with exit code $($i.ExitCode) -- please review logs as indicated by the URLRewriteInstaller parameter." $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } Write-Host "-> Obtaining Web Site ($iisSiteName)..." -NoNewline $iisSite = Get-WebSite $IISSiteName if($null -eq $iisSite){ Write-Fail Write-Host "`tUnable to find IIS Site with name '$IISSiteName'" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Unable to find IIS Site with name '$IISSiteName'" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } else { Write-Ok } Write-Host "-> Checking for an existing Application or Virtual Directory called '.well-known'..." -NoNewline if((($null -eq (Get-WebApplication .well-known)) -and ($null -eq (Get-WebVirtualDirectory .well-known)))){ Write-Pending $wkPath = $iisSite.physicalpath $wkPath = $wkPath.replace("%SystemDrive%", $env:systemdrive) + "\.well-known" Write-Host "`tChecking for .well-known directory in IIS Site root ($wkPath)..." -NoNewLine if(Test-Path $wkPath){ Write-Ok } else { Write-Pending Write-Host "`t`tCreating .well-known directory in IIS Site root ($wkPath)..." -NoNewLine $wkDir = New-Item -ItemType Directory -Path $wkPath -ErrorAction SilentlyContinue if($null -ne $wkDir){ Write-Ok } else { Write-Fail Write-Host "`t`t`tFailed to create .well-known directory as '$wkDir'" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Failed to create .well-known directory as '$wkDir'" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } Write-Host "`tCreating new Virtual Directory '.well-known'..." -NoNewline $wkVD = New-WebVirtualDirectory -Site $iisSite.Name -Name .well-known -PhysicalPath $wkPath if($null -ne $wkVD){ Write-Ok } else { Write-Fail Write-Host "`t`tFailed to create new .well-known virtual directory in Site '$IISSiteName'" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Failed to create new .well-known virtual directory in Site '$IISSiteName'" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } else { Write-Ok } Write-Host "-> Checking for URL Rewrite Rule '$URLRewriteRuleName'..." -NoNewline $existingRule = Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']" -name "." if($null -ne $existingRule){ Write-Ok } else { Write-Pending Write-Host "`tCreating new rule (this will take a moment)..." -NoNewline Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$iisSiteName/.well-known" -filter "system.webServer/rewrite/rules" -name "." -value @{name=$URLRewriteRuleName;stopProcessing='True'} Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$iisSiteName/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/match" -name "url" -value "acme-challenge/.*" Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$iisSiteName/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "type" -value "Redirect" Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$iisSiteName/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "url" -value "http://$PrimaryServer/.well-known/{R:0}" Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$iisSiteName/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "redirectType" -value "Found" $rwrMatchUrl = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/match" -name "url").Value $rwrActionType = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "type") $rwrActionURL = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "url").Value $rwrActionRedirectType = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/rewrite/rules/rule[@Name='$URLRewriteRuleName']/action" -name "redirectType") if(($rwrMatchUrl -ne "acme-challenge/.*") -or ($rwrActionType -ne "Redirect") -or ($rwrActionURL -ne "http://$PrimaryServer/.well-known/{R:0}") -or ($rwrACtionRedirectType -ne "Found")){ Write-Fail Write-Host "`t`tFailed to create and/or fully configure the '$URLRewriteRuleName' URL Rewrite Rule" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Failed to create and/or fully configure the '$URLRewriteRuleName' URL Rewrite Rule. Expected match URL = '.*' (set '$rwrMatchUrl') | Expected action type = 'Redirect' (set '$rwrActionType') | Expected action url = 'http://$PrimaryServer/.well-known/{R:0}' (set '$rwrActionUrl') | Expected action redirectType = 'Found' (set '$rwrActionRedirectType')" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } else { Write-Ok } } Write-Host "-> Checking SSL Settings = None / SSL Disabled..." -NoNewline $sslFlags = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/security/access" -name "sslFlags").Value if($sslFlags -eq 0){ Write-Ok } else { Write-Pending Write-Host "`tDisabling 'Require SSL' setting..." -NoNewLine Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "$iisSiteName/.well-known" -filter "system.webServer/security/access" -name "sslFlags" -value "None" $sslFlags = (Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$($iisSiteName)/.well-known" -filter "system.webServer/security/access" -name "sslFlags").Value if($sslFlags -eq 0){ Write-Ok } else { Write-Fail Write-Host "`t`tFailed to disable 'Require SSL' setting" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Failed to disable 'Require SSL' setting" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } Write-Host "-> Checking for HTTP Binding on Port 80..." -NoNewline if($null -ne (Get-WebBinding -Port 80)){ Write-Ok } else { Write-Pending Write-Host "`tCreating new HTTP Binding on port 80..." -NoNewline New-WebBinding -Name $iisSiteName -IPAddress '*' -Port 80 if($null -ne (Get-WebBinding -Port 80)){ Write-Ok } else { Write-Fail Write-Host "`t`tFailed to create HTTP binding on port 80 for Site '$IISSiteName'" -ForegroundColor Red Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Failed to create HTTP binding on port 80 for Site '$IISSiteName'" $fro.FunctionSuccess = $false $fro.ProxyRedirectConfiguredSuccessfully = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed successfully!" "green" # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $true $debugMode $debugLogDirectory } return $fro } |