Public/Initialize-ExistingACMECertificate.ps1
function Initialize-ExistingACMECertificate{ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Currently using Write-Host because it supports -NoNewLine')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The only password used in this function is a certificate password, which is the well-known Posh-ACME default.')] param( [Parameter(Mandatory=$false, HelpMessage="When applied, this switch will attempt to update IIS HTTPS bindings" )] [switch] $UpdateBindings, [Parameter(Mandatory=$false, HelpMessage="Comma-separated list of posts on which to update bindings. When this parameter is omitted, all HTTPS-based bindings will be updated" )] [string[]] $BindingPorts, [Parameter(Mandatory=$false, HelpMessage="Specifies the Windows Certificate Store Name to import the resulting certificate into." )] [ValidateScript({if($_ -in $VALIDATE_SET_CERTIFICATE_STORE_NAME) { $true } else { throw "Parameter '$_' is invalid -- must be one of: $($VALIDATE_SET_CERTIFICATE_STORE_NAME -join ",")"}})] [string] $StoreName = $DEFAULT_CERTIFICATE_STORE_NAME, [Parameter(Mandatory = $false, HelpMessage = "Specified the Windows Certificate Store Location to import the resulting certificate into." )] [ValidateScript({if($_ -in $VALIDATE_SET_CERTIFICATE_STORE_LOCATION) { $true } else { throw "Parameter '$_' is invalid -- must be one of: $($VALIDATE_SET_CERTIFICATE_STORE_LOCATION -join ",")"}})] [string] $StoreLocation = $DEFAULT_CERTIFICATE_STORE_LOCATION, [Parameter (Mandatory = $true, HelpMessage = "Specify the full path to the PFX certificate file, including the filename itself." )] [ValidateScript({if($_ -match $VALIDATE_PATTERN_PFX_PATH) { $true } else { throw "Parameter '$_' is invalid -- must match the pattern: $VALIDATE_PATTERN_PFX_PATH"}})] $PfxPath, [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 = @(); Certificate = @(); Bindings = @(); CertificateImported = $true; BindingsUpdated = $true; StoreLocation = $StoreLocation; StoreName = $StoreName; PFXPath = $PfxPath; debugEnabled= $debugEnabled; debugLogDirectory = $debugLogDirectory; debugMode = $debugMode; } Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Attempting to import Certificate" # Check to ensure that we're running from an elevated PowerShell session 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 # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } ####################################### # Begin Parameter Pre-Fight Checks ####################################### Write-Host "-> Validating incoming parameters..." -NoNewline # Check to see if we're going to update bindings, and, if so, if we're running in a modern (but unsupported) version of PowerShell if(($UpdateBindings) -and (!(Assert-PSVersion))){ Write-Fail Write-Host "`tDetected this function running from a modern PowerShell console. This combination of parameters REQUIRES the use of PowerShell 6 or earlier. 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/parameters require 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.BindingsUpdated = $false $fro.CertificateImported = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } # Check to make sure we aren't attempting to update binding(s) with a certificate that's being imported into the CurrentUser store if(($UpdateBindings) -and $($StoreLocation -ne "LocalMachine")){ Write-Fail "`tWhen the -UpdateBindings switch is applied to this function, the -StoreLocation parameter MUST be LocalMachine!" | Write-Host -ForegroundColor Red -BackgroundColor Black Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "UpdateBindings switch applied, but -StoreLocation parameter set to $StoreLocation. To update Site bindings, StoreLocation MUST be LocalMachine" $fro.FunctionSuccess = $false $fro.BindingsUpdated = $false $fro.CertificateImported = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } # Ensure that the certificate file in question actually exists if(!(Test-Path $pfxPath)){ Write-Fail "`tUnable to find certificate file: '$pfxPath' -- cannot continue!" | Write-Host -ForegroundColor Red -BackgroundColor Black Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "PFX certificate file not found in '$PfxPath" $fro.FunctionSuccess = $false $fro.BindingsUpdated = $false $fro.CertificateImported = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } Write-Ok Write-Host "-> Importing certificate into $StoreLocation\$StoreName..." -NoNewline # here we're assuming that this is a cert generated by Posh-ACME without a custom password $certPass = ConvertTo-SecureString "poshacme" -AsPlainText -Force $importedCert = Import-PfxCertificate -FilePath $PfxPath -CertStoreLocation "cert:\$StoreLocation\$StoreName" -Password $certPass -Exportable if($null -eq $importedCert){ Write-Fail "`tAn error occurred while attempting to import the certificate. This could potentially be due to the certificate having a non-default PFX password." $fro.Errors += "PFX certificate file failed to import -- certificate file may be a non-Posh-ACME certificate, or have a non-standard password" $fro.FunctionSuccess = $false $fro.BindingsUpdated = $false $fro.CertificateImported = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } else { Write-Ok $fro.Certificate = $importedCert } # check to see if we're going to update IIS bindings Write-Host "-> Updating IIS Site Bindings..." -NoNewLine if($UpdateBindings){ Write-Pending $updatedBindings = Update-IISBindings $BindingPorts $StoreName $importedCert.Thumbprint #remove the phantom $nulls appearing in the list $updatedBindings = $updatedBindings | where-object {$_ -ne $Null } $fro.Bindings = $updatedBindings if(($updatedBindings | Where-Object {$_.UpdatedSuccessfully -eq $false}).Count -eq 0){ Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed successfully!" "green" # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $true $debugMode $debugLogDirectory } return $fro } else { Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed unsuccessfully!" "red" $fro.Errors += "Error(s) occurred updating one or more bindings. Please review the Bindings property of this return object for more details." $fro.BindingsUpdated = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $false $debugMode $debugLogDirectory } return $fro } } else { Write-Skipped Write-FunctionBlock "[$($myinvocation.MyCommand)]" "Completed successfully!" "green" $fro.BindingsUpdated = $false # write debug information if desired if($debugEnabled){ Write-ACMEDebug $myInvocation.MyCommand $fro $true $debugMode $debugLogDirectory } return $fro } } |