Public/Tools/Invoke-ServerConfigurator.ps1

function Invoke-ServerConfigurator {
    <#
    .SYNOPSIS
        Invokes the Milestone Server Configurator utility using command-line arguments
    .DESCRIPTION
        The Server Configurator is the utility responsible for managing the registration of
        Management Servers, Recording Servers and Data Collectors as well as the configuration of
        certificates for Management/Recorder communication, Client/Recorder communication and
        Mobile Server/Web Client/Mobile communication.
 
        In the 2020 R3 release, command-line parameters were introduced for the Server Configurator
        making it possible to automate registration and certificate configuration processes. Since
        PowerShell offers a more rich environment for discovering parameters and valid values as
        well as more useful object-based output, this cmdlet was written to wrap the utility with
        a PowerShell-friendly interface.
    .EXAMPLE
        PS C:\> Invoke-ServerConfigurator -ListCertificateGroups
        Lists the available Certificate Groups such as 'Server certificate',
        'Streaming media certificate' and 'Mobile streaming media certificate', and their ID's.
    .EXAMPLE
        PS C:\> Invoke-ServerConfigurator -Register -AuthAddress http://MGMT -PassThru
        Registers all local Milestone components with the authorization server at http://MGMT and
        outputs a [pscustomobject] with the exit code, and standard output/error from the invocation
        of the Server Configurator executable.
    #>

    [CmdletBinding()]
    param(
        # Enable encryption for the CertificateGroup specified
        [Parameter(ParameterSetName = 'EnableEncryption', Mandatory)]
        [switch]
        $EnableEncryption,
        
        # Disable encryption for the CertificateGroup specified
        [Parameter(ParameterSetName = 'DisableEncryption', Mandatory)]
        [switch]
        $DisableEncryption,
        
        # Specifies the CertificateGroup [guid] identifying which component for which encryption
        # should be enabled or disabled
        [Parameter(ParameterSetName = 'EnableEncryption', Mandatory)]
        [Parameter(ParameterSetName = 'DisableEncryption', Mandatory)]
        [guid]
        $CertificateGroup,
        
        # Specifies the thumbprint of the certificate to be used to encrypt communications with the
        # component designated by the CertificateGroup id.
        [Parameter(ParameterSetName = 'EnableEncryption', Mandatory)]
        [string]
        $Thumbprint,

        # List the available certificate groups on the local machine. Output will be a [hashtable]
        # where the keys are the certificate group names (which may contain spaces) and the values
        # are the associated [guid] id's.
        [Parameter(ParameterSetName = 'ListCertificateGroups')]
        [switch]
        $ListCertificateGroups,

        # Register all local components with the optionally specified AuthAddress. If no
        # AuthAddress is provided, the last-known address will be used.
        [Parameter(ParameterSetName = 'Register', Mandatory)]
        [switch]
        $Register,

        # Specifies the address of the Authorization Server which is usually the Management Server
        # address. A [uri] value is expected, but only the URI host value will be used. The scheme
        # and port will be inferred based on whether encryption is enabled/disabled and is fixed to
        # port 80/443 as this is how Server Configurator is currently designed.
        [Parameter(ParameterSetName = 'Register')]
        [uri]
        $AuthAddress,

        # Specifies that the standard output from the Server Configurator utility should be written
        # after the operation is completed. The output will include the following properties:
        # - StandardOutput
        # - StandardError
        # - ExitCode
        [Parameter(ParameterSetName = 'EnableEncryption')]
        [Parameter(ParameterSetName = 'DisableEncryption')]
        [Parameter(ParameterSetName = 'Register')]
        [switch]
        $PassThru
    )

    process {
        # Find ServerConfigurator.exe by locating either the Management Server or Recording Server installation path
        $configurationInfo = try {
            Get-ManagementServerConfig
        }
        catch {
            try {
                Get-RecorderConfig
            }
            catch {
                $null
            }
        }
        if ($null -eq $configurationInfo) {
            Write-Error "Could not find a Management Server or Recording Server installation"
            return
        }
        $fileInfo = [io.fileinfo]::new($configurationInfo.InstallationPath)
        $exePath = Join-Path $fileInfo.Directory.Parent.FullName "Server Configurator\serverconfigurator.exe"
        if (-not (Test-Path $exePath)) {
            Write-Error "Expected to find Server Configurator at '$exePath' but failed."
            return
        }


        # Ensure version is 20.3 (2020 R3) or newer
        $fileInfo = [io.fileinfo]::new($exePath)
        if ($fileInfo.VersionInfo.FileVersion -lt [version]"20.3") {
            Write-Error "Invoke-ServerConfigurator requires Milestone version 2020 R3 or newer as this is when command-line options were introduced. Found Server Configurator version $($fileInfo.VersionInfo.FileVersion)"
            return
        }
        
        $exitCode = @{
            0 = 'Success'
            -1 = 'Unknown error'
            -2 = 'Invalid arguments'
            -3 = 'Invalid argument value'
            -4 = 'Another instance is running'
        }

        # Get Certificate Group list for either display to user or verification
        $output = Get-ProcessOutput -FilePath $exePath -ArgumentList /listcertificategroups
        if ($output.ExitCode -ne 0) {
            Write-Error "Server Configurator exited with code $($output.ExitCode). $($exitCode.($output.ExitCode))."
            Write-Error $output.StandardOutput
            return
        }
        Write-Information $output.StandardOutput
        $groups = @{}
        foreach ($line in $output.StandardOutput -split ([environment]::NewLine)) {
            if ($line -match "Found '(?<groupName>.+)' group with ID = (?<groupId>.{36})") {
                $groups.$($Matches.groupName) = [guid]::Parse($Matches.groupId)
            }
        }


        switch ($PSCmdlet.ParameterSetName) {
            'EnableEncryption' {
                if ($groups.Values -notcontains $CertificateGroup) {
                    Write-Error "CertificateGroup value '$CertificateGroup' not found. Use the ListCertificateGroups switch to discover valid CertificateGroup values"
                    return
                }

                $enableArgs = @('/enableencryption', "/certificategroup=$CertificateGroup", "/thumbprint=$Thumbprint", '/quiet')
                $output = Get-ProcessOutput -FilePath $exePath -ArgumentList $enableArgs
                if ($output.ExitCode -ne 0) {
                    Write-Error "EnableEncryption failed. Server Configurator exited with code $($output.ExitCode). $($exitCode.($output.ExitCode))."
                    Write-Error $output.StandardOutput
                }
            }

            'DisableEncryption' {
                if ($groups.Values -notcontains $CertificateGroup) {
                    Write-Error "CertificateGroup value '$CertificateGroup' not found. Use the ListCertificateGroups switch to discover valid CertificateGroup values"
                    return
                }
                $disableArgs = @('/disableencryption', "/certificategroup=$CertificateGroup", '/quiet')
                $output = Get-ProcessOutput -FilePath $exePath -ArgumentList $disableArgs
                if ($output.ExitCode -ne 0) {
                    Write-Error "EnableEncryption failed. Server Configurator exited with code $($output.ExitCode). $($exitCode.($output.ExitCode))."
                    Write-Error $output.StandardOutput
                }
            }

            'ListCertificateGroups' {
                Write-Output $groups
                return
            }

            'Register' {
                $registerArgs = @('/register', '/quiet')
                if ($PSCmdlet.MyInvocation.BoundParameters -contains 'AuthAddress') {
                    $registerArgs += $AuthAddress.ToString()
                }
                $output = Get-ProcessOutput -FilePath $exePath -ArgumentList $registerArgs
                if ($output.ExitCode -ne 0) {
                    Write-Error "Registration failed. Server Configurator exited with code $($output.ExitCode). $($exitCode.($output.ExitCode))."
                    Write-Error $output.StandardOutput
                }
                
            }

            Default {
            }
        }

        Write-Information $output.StandardOutput
        if ($PassThru) {
            Write-Output $output
        }
    }
}