Public/Install-CertificateAutomation.ps1

function Install-CertificateAutomation {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position = 1)]
        [string]
        $Domain,
        [Parameter(Position = 2)]
        [string]
        $Contact,
        [Parameter(Position = 3)]
        [string]
        $DnsPlugin,
        [Parameter(Position = 4)]
        [hashtable]
        $PluginArgs,
        [Parameter(Position = 5)]
        [string]
        $ScriptDirectory = "C:\scripts"
    )
    
    begin {
        if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
            throw "This command requires elevation. Please run as administrator."
        }

        Write-Output "Setting Execution Policy to RemoteSigned for the current process"
        Set-ExecutionPolicy RemoteSigned -Scope Process

        if (!(Get-Module Posh-ACME)) {
            Install-Module Posh-ACME -Repository PSGallery -Verbose
        }
    }
    
    process {
        try {
            Write-Output "Testing certificate request against staging server"
            Set-PAServer LE_STAGE
            New-PACertificate -force $domain -AcceptTOS -Contact $contact -DnsPlugin $DnsPlugin -PluginArgs $PluginArgs -Install -ErrorAction Stop -Verbose
            Write-Output "Successfully installed certificate from staging server"

            Write-Output "Removing test certificate"
            $stagingCert = Get-PACertificate
            Get-ChildItem Cert:\LocalMachine\My | Where-Object Thumbprint -eq $stagingCert.Thumbprint | Remove-Item

            Write-Output "Installing production certificate"
            Set-PAServer LE_PROD
            New-PACertificate -force $domain -AcceptTOS -Contact $contact -DnsPlugin $DnsPlugin -PluginArgs $PluginArgs -Install -ErrorAction Stop -Verbose
            Write-Output "Certificate installed to Cert:\LocalMachine\My"

            Write-Output "Binding certificate to Mobile Server"
            Get-PACertificate | Set-MobileServerCertificate -ErrorAction Stop -Verbose
        
            Write-Output "Setting up automatic certificate renewal script in $ScriptDirectory"
            $scriptPath = Join-Path $ScriptDirectory "renew-certificate.ps1"
            $logPath = Join-Path $ScriptDirectory "log.txt"
            if (!(Test-Path $ScriptDirectory)) {
                New-Item $ScriptDirectory -ItemType Directory | Out-Null
            }
            $scriptBlock = {
                param([string]$LogPath)
                function WriteLog {
                    Param ([string]$message)
                    Add-Content -Path $LogPath -Value "$(Get-Date) - $message"
                }
        
                try {
        
                    $thumbprint = (Get-PACertificate).Thumbprint
                    $cert = Submit-Renewal -WarningAction Stop -ErrorAction Stop
                    $cert | Set-MobileServerCertificate
        
                    WriteLog "New certificate installed with thumbprint $($cert.Thumbprint)"
                    WriteLog "Removing old certificate with thumbprint $thumbprint"
        
                    Get-ChildItem Cert:\LocalMachine\My |
                        Where-Object Thumbprint -eq $thumbprint |
                        Remove-Item
        
                } catch {
                    WriteLog $_.Exception.Message
                    throw
                }
            }
            Set-Content -Path $scriptPath -Value $scriptBlock
        
            Write-Output "Registering a new scheduled task to run the renewal script daily"
            $taskName = 'Posh-ACME Certificate Renewal'
            $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -File ""$scriptPath"" $logPath"
            $trigger = New-ScheduledTaskTrigger -Daily -At 2am
            Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
            $credential = Get-Credential -Message "Enter your password to setup the Scheduled Task" -UserName ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
            $taskParams = @{
                Action = $action
                Trigger = $trigger
                TaskName = $taskName
                RunLevel = "Highest"
                User = $credential.UserName
                Password = ($credential.Password | ConvertTo-UnsecureString)
            }
            Register-ScheduledTask @taskParams | Out-Null
            $taskParams = $null
        
        
            # Edits Windows hosts file so that on the local machine, the $domain address always routes to the local machine
            Write-Output "Adding $domain to the local hosts file"
            $params = @{
                Path = "$($env:SystemRoot)\System32\drivers\etc\hosts"
                Value = "`r`n127.0.0.1 $domain"
            }
            Add-Content @params
        
        
            # Launch the default web browser to the mobile server's HTTPS page
            $mobileServer = Get-MobileServerInfo
            $url = "https://$($domain):$($mobileServer.HttpsPort)"
            Write-Output "Finished! Opening a web browser to $url"
            Start-Process $url
        
        } catch {
            throw
        }
    }

    <#
    .SYNOPSIS
        Uses Posh-ACME to request a Let's Encrypt certificate and configure Mobile Server to use it
 
    .DESCRIPTION
        Uses Posh-ACME to request a Let's Encrypt certificate and configure Mobile Server to use it, then
        creates a Scheduled Task to run daily, and execute a renewal script which will handle certificate
        renewal when the certificate becomes eligible for renewal - typically 60 days after issue.
 
        When the certificate is renewed, it will be installed into the Windows certificate store and the
        old certificate will be removed from the certificate store. The Milestone XProtect Mobile Server
        service will be restarted so that it automatically uses the renewed certificates going forward.
 
    .PARAMETER Domain
        The domain for which you will request a Let's Encrypt certificate. See Get-Help New-PACertificate for more info.
 
    .PARAMETER Contact
        The email address associated with this domain for the purpose of renewal notifications. See Get-Help New-PACertificate for more info.
 
    .PARAMETER DnsPlugin
        The DnsPlugin to use for handling DNS challenges. See Get-Help New-PACertificate for more info.
 
    .PARAMETER PluginArgs
        A hashtable with the necessary parameters for the chosen DnsPlugin. See Get-Help New-PACertificate for more info.
 
    .PARAMETER ScriptDirectory
        The path where the renew-certificate.ps1 script will be saved, and the log.txt file will be written to.
 
        A scheduled task named Posh-ACME Certificate Renewal will be created to run the renew-certificate.ps1 script daily,
        and this script will append information to log.txt in the same path.
 
    .EXAMPLE
        $InstallParams = @{
            Domain = test.example.com
            Contact = admin@example.com
            DnsPlugin = Dynu
            PluginArgs = @{DynuClientID='xxxx';DynuSecret='xxxx'}
            ScriptDirectory = "C:\scripts"
        }
        Install-CertificateAutomation @InstallParams
 
        Requests a Let's Encrypt certificate for test.example.com, uses Dynu DNS to handle the ACME-protocol DNS challenge,
        binds the certificate to the Mobile Server's HTTPS port using 'netsh http add|update sslcert', restarts the Mobile
        Server service, creates a .PS1 certificate renewal script in C:\scripts\ and a scheduled task to call this script
        daily at 2AM, logging the result to C:\scripts\log.txt.
    #>

}