DSCResources/ArcGIS_Server_TLS/ArcGIS_Server_TLS.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the ArcGIS Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'ArcGIS.Common' `
            -ChildPath 'ArcGIS.Common.psm1'))

<#
    .SYNOPSIS
        Creates a SelfSigned Certificate or Installs a SSL Certificated Provided and Configures it with Server
    .PARAMETER ServerHostName
        Optional Host Name or IP of the Machine on which the Server has been installed and is to be configured.
    .PARAMETER ServerRole
        Site Name or Default Context of Server
    .PARAMETER SiteAdministrator
        A MSFT_Credential Object - Primary Site Administrator.
    .PARAMETER WebServerCertificateAlias
        WebServerCertificateAlias with which the Certificate will be associated.
    .PARAMETER CertificateFileLocation
        Certificate Path from where to fetch the certificate to be installed.
    .PARAMETER CertificatePassword
        Sercret Certificate Password or Key.
    .PARAMETER SslRootOrIntermediate
        Takes a JSON string list of all the root or intermediate certificates to import
#>


function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [parameter(Mandatory = $True)]
        [System.String]
        $ServerHostName
    )

    $null # TODO
}

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $ServerHostName,

        [System.String]
        $ServerType,

        [System.Management.Automation.PSCredential]
        $SiteAdministrator,
        
        [System.String]
        $WebServerCertificateAlias,

        [System.String]
        $CertificateFileLocation,

        [System.Management.Automation.PSCredential]
        $CertificatePassword,
        
        [System.String]
        $SslRootOrIntermediate       
    )

    if($CertificateFileLocation -and -not(Test-Path $CertificateFileLocation)){
        throw "Certificate File '$CertificateFileLocation' is not found or inaccessible"
    }
    
    $ServiceName = "ArcGIS Server"
    $SitePort = 6443
    if($ServerType -ieq "NotebookServer"){
        $ServiceName = "ArcGIS Notebook Server"
        $SitePort = 11443
    }elseif($ServerType -ieq "MissionServer"){
        $ServiceName = "ArcGIS Mission Server"
        $SitePort = 20443
    }
    
    $FQDN = if($ServerHostName){ Get-FQDN $ServerHostName }else{ Get-FQDN $env:COMPUTERNAME }
    $ServerUrl = "https://$($FQDN):$($SitePort)"
    
    Wait-ForUrl -Url "$($ServerUrl)/arcgis/admin/" 
    Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'
    $Referer = $ServerUrl
                      
    $token = Get-ServerToken -ServerEndPoint $ServerURL -Credential $SiteAdministrator -Referer $Referer
    if(-not($token.token)){
        throw "Unable to retrieve token for Site Administrator"
    }

    $MachineName = $FQDN
    $AllMachines = Get-Machines -ServerURL $ServerURL -Token $token.token -Referer $Referer
    if(-not($AllMachines.machines | Where-Object { $_.machineName -ieq $MachineName })) {
        $MachineName = $env:COMPUTERNAME
        if(-not($AllMachines.machines | Where-Object { $_.machineName -ieq $MachineName })){
            throw "Not able to find machine in site with either hostname $MachineName or fully qualified domain name $FQDN"
        }
    }

    if($CertificateFileLocation){
        if(-not(Test-Path $CertificateFileLocation)){
            throw "Certificate File '$CertificateFileLocation' is not found"
        }
        if($WebServerCertificateAlias -as [ipaddress]) {
            Write-Verbose "Adding Host mapping for $WebServerCertificateAlias"
            Add-HostMapping -hostname $WebServerCertificateAlias -ipaddress $WebServerCertificateAlias        
        }
        
        $DeleteTempCert = $False
        $ImportCert = $False
        $UpdateWebAlias = $False
        $CertForMachine = Get-SSLCertificateForMachine -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName -SSLCertName $WebServerCertificateAlias.ToLower()
        if($null -ne $CertForMachine){ # Certificate with CName Found
            $NewCertIssuer = $null
            $NewCertThumbprint = $null
            if($CertificateFileLocation -and ($null -ne $CertificatePassword)) {
                $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $cert.Import($CertificateFileLocation,$CertificatePassword.GetNetworkCredential().Password,'DefaultKeySet')
                $NewCertIssuer = $cert.Issuer
                $NewCertThumbprint = $cert.Thumbprint
                Write-Verbose "Issuer for the supplied certificate is $NewCertIssuer"
                Write-Verbose "Thumbprint for the supplied certificate is $NewCertThumbprint"
            }

            $ExistingCertIssuer = $CertForMachine.Issuer    
            $ExistingCertThumbprint = $CertForMachine.Thumbprint
            Write-Verbose "Existing Cert Issuer $ExistingCertIssuer with Thumbprint $ExistingCertThumbprint"
            $machineDetails = Get-MachineDetails -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName
            if($ExistingCertThumbprint -ine $NewCertThumbprint){ #Certificate Thumbprint doesn't match
                if($WebServerCertificateAlias -ieq $machineDetails.webServerCertificateAlias){
                    $DeleteTempCert = $True
                    #Upload Temp Cert
                    Write-Verbose "Importing Supplied Certificate with Alias $($WebServerCertificateAlias)-temp"
                    Import-ExistingCertificate -ServerUrl $ServerUrl -Token $token.token -Referer $Referer `
                        -MachineName $MachineName -CertAlias "$($WebServerCertificateAlias)-temp" -CertificatePassword $CertificatePassword `
                        -CertificateFilePath $CertificateFileLocation -ServerType $ServerType

                    #Update Web Alias to Temp Cert
                    Write-Verbose "Updating to temp SSL Certificate for machine [$MachineName]"
                    $machine = Get-MachineDetails -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName
                    $machine.webServerCertificateAlias = "$($WebServerCertificateAlias)-temp"
                    Update-SSLCertificate -ServerURL $ServerURL -Token $token.token -MachineName $MachineName -MachineProperties $machine -Referer $Referer
                    Start-Sleep -Seconds 30
                    Write-Verbose "Waiting for Url '$ServerUrl/arcgis/admin' to respond"
                    Wait-ForUrl -Url "$ServerUrl/arcgis/admin" -SleepTimeInSeconds 15 -MaxWaitTimeInSeconds 150 -HttpMethod 'GET' -Verbose
                    Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'
                }

                #Delete Certificate
                Write-Verbose "Certificate with alias $WebServerCertificateAlias already exists for machine $MachineName. Deleting it"
                try {
                    $res = Invoke-DeleteSSLCertForMachine -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName -SSLCertName $WebServerCertificateAlias.ToLower()
                    Write-Verbose "Delete Certificate Operation result - $($res | ConvertTo-Json)"
                }
                catch {
                    Write-Verbose "[WARNING] Error deleting SSL Cert with alias $WebServerCertificateAlias. Error:- $_"
                }
                $ImportCert = $True #Upload New Cert
                $UpdateWebAlias = $True #Update Web Alias
            }else{ # Thumbprint matches
                if($WebServerCertificateAlias -ine $machineDetails.webServerCertificateAlias){
                    Write-Verbose "Certificate with alias $WebServerCertificateAlias already exists for machine $MachineName, but web server certificate alias $($machineDetails.webServerCertificateAlias) doesn't match."
                    $UpdateWebAlias = $True #Update Web Alias
                } else { #Everything Matches
                    Write-Verbose "Certificate with alias $WebServerCertificateAlias already exists for machine $MachineName and matches all the requirements."
                }
            }
        }else{ #Certificate with CName/Alias not found
            $ImportCert = $True #Upload New Cert
            $UpdateWebAlias = $True #Update Web Alias
        }

        if($ImportCert){
            Wait-ForUrl -Url "$ServerUrl/arcgis/admin" -SleepTimeInSeconds 15 -MaxWaitTimeInSeconds 150 -HttpMethod 'GET' -Verbose
            Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'

            # Import the Supplied Certificate
            Write-Verbose "Importing Supplied Certificate with Alias $WebServerCertificateAlias"
            Import-ExistingCertificate -ServerUrl $ServerUrl -Token $token.token -Referer $Referer `
                    -MachineName $MachineName -CertAlias $WebServerCertificateAlias -CertificatePassword $CertificatePassword `
                    -CertificateFilePath $CertificateFileLocation -ServerType $ServerType
        }

        if($UpdateWebAlias){
            $machine = Get-MachineDetails -ServerURL $ServerURL -Token $token.token -MachineName $MachineName -Referer $Referer
            # Update the SSL Cert for machine
            Write-Verbose "Updating SSL Certificate for machine [$MachineName]"
            $machine.webServerCertificateAlias = $WebServerCertificateAlias
            Update-SSLCertificate -ServerURL $ServerURL -Token $token.token -MachineName $MachineName -MachineProperties $machine -Referer $Referer
            Start-Sleep -Seconds 30
            Write-Verbose "Waiting for Url '$ServerUrl/arcgis/admin' to respond"
            Wait-ForUrl -Url "$ServerUrl/arcgis/admin" -SleepTimeInSeconds 15 -MaxWaitTimeInSeconds 150 -HttpMethod 'GET' -Verbose
            Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'

            # Restart Geoevent
            if($ServerType -ine "NotebookServer" -or $ServerType -ine "MissionServer"){ #TODO - This will cause issues in Azure, where we have Geoevent and WFM running.
                ### If the SSL Certificate is changed. Restart the GeoEvent Service so that it will pick up the new certificate
                $GeoEventServiceName = 'ArcGISGeoEvent' 
                $GeoEventService = Get-Service -Name $GeoEventServiceName -ErrorAction Ignore
                if($GeoEventService.Status -ieq 'Running') {
                    $GeoEventServerHttpsUrl = "https://localhost:6143"
                    Restart-ArcGISService -ServiceName $GeoEventServiceName -Verbose
                    Write-Verbose "Waiting for Url '$GeoEventServerHttpsUrl/geoevent/rest' to respond"
                    Wait-ForUrl -Url "$GeoEventServerHttpsUrl/geoevent/rest" -SleepTimeInSeconds 20 -MaxWaitTimeInSeconds 150 -HttpMethod 'GET' -Verbose
                    Write-Verbose "Restarted Service $GeoEventServiceName"
                }
            }
        }
        
        if($DeleteTempCert){ #Delete Temp Cert
            try {
                Write-Verbose "Deleting Temp Certificate with alias $($WebServerCertificateAlias)-temp"
                $res = Invoke-DeleteSSLCertForMachine -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName -SSLCertName "$($WebServerCertificateAlias)-temp".ToLower()
                Write-Verbose "Delete Temp Certificate Operation result - $($res | ConvertTo-Json)"
            }
            catch {
                Write-Verbose "[WARNING] Error deleting Temp SSL Cert with alias $($WebServerCertificateAlias)-temp. Error:- $_"
            }
        }
    }else{
        Write-Verbose "CertificateFileLocation not specified. Skipping web server certificate configuration"
    }

    if($null -ne $SslRootOrIntermediate){ #RootOrIntermediateCertificate
        $RestartRequired = $false
        $Certs = Get-AllSSLCertificateForMachine -ServerUrl $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName 
        foreach ($key in ($SslRootOrIntermediate | ConvertFrom-Json)){
            if ($Certs.certificates -icontains $key.Alias){
                Write-Verbose "RootOrIntermediate $($key.Alias) is in List of SSL-Certificates no Action Required"
            }else{
                Write-Verbose "RootOrIntermediate $($key.Alias) is NOT in List of SSL-Certificates Import-RootOrIntermediate"
                try{
                    Import-RootOrIntermediateCertificate -ServerUrl $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName -CertAlias $key.Alias -CertificateFilePath $key.Path
                    if(-not($RestartRequired)){
                        $RestartRequired = $True
                    }
                }catch{
                    Write-Verbose "Error in Import-RootOrIntermediateCertificate :- $_"
                }
            }
        }

        if($RestartRequired)
        {
            Write-Verbose "Server Root and intermediate certificates were updated. Restarting Server."
            Restart-ArcGISService -ServiceName $ServiceName -Verbose
            Write-Verbose "Waiting 30 seconds before checking for intitialization"
            Start-Sleep -Seconds 30
            Write-Verbose "Waiting for Url '$ServerURL/arcgis/admin' to respond"
            Wait-ForUrl -Url "$ServerURL/arcgis/admin" -SleepTimeInSeconds 10 -MaxWaitTimeInSeconds 60 -HttpMethod 'GET'
            Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'
        }
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $True)]
        [System.String]
        $ServerHostName,
        
        [System.String]
        $ServerType,

        [System.Management.Automation.PSCredential]
        $SiteAdministrator,

        [System.String]
        $WebServerCertificateAlias,

        [System.String]
        $CertificateFileLocation,

        [System.Management.Automation.PSCredential]
        $CertificatePassword,
        
        [System.String]
        $SslRootOrIntermediate
    )

    if($CertificateFileLocation -and -not(Test-Path $CertificateFileLocation)){
        throw "Certificate File '$CertificateFileLocation' is not found or inaccessible"
    }

    [System.Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
    $result = $True
       
    $FQDN = if($ServerHostName){ Get-FQDN $ServerHostName }else{ Get-FQDN $env:COMPUTERNAME }
    $SitePort = 6443
    if($ServerType -ieq "NotebookServer"){
        $SitePort = 11443
    }elseif($ServerType -ieq "MissionServer"){
        $SitePort = 20443
    }
    $ServerUrl = "https://$($FQDN):$($SitePort)"
        
    Wait-ForUrl -Url "$($ServerUrl)/arcgis/admin/" -MaxWaitTimeInSeconds 60 -HttpMethod 'GET'
    Wait-ForUrl -Url "$($ServerUrl)/arcgis/rest/info/healthCheck?f=json" -HttpMethod 'GET'

    $Referer = $ServerUrl
    $token = Get-ServerToken -ServerEndPoint $ServerUrl -Credential $SiteAdministrator -Referer $Referer 
    if(-not($token.token)){
        throw "Unable to retrieve token for Site Administrator"
    }

    $MachineName = $FQDN
    $AllMachines = Get-Machines -ServerURL $ServerURL -Token $token.token -Referer $Referer
    if(-not($AllMachines.machines | Where-Object { $_.machineName -ieq $MachineName })) {
        $MachineName = $env:COMPUTERNAME
        if(-not($AllMachines.machines | Where-Object { $_.machineName -ieq $MachineName })){
            throw "Not able to find machine in site with either hostname $MachineName or fully qualified domain name $FQDN"
        }
    }

    if($CertificateFileLocation){
        if(-not(Test-Path $CertificateFileLocation)){
            throw "Certificate File '$CertificateFileLocation' is not found"
        }
        
        $CertForMachine = Get-SSLCertificateForMachine -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName -SSLCertName $WebServerCertificateAlias.ToLower() -Verbose
        if($null -ne $CertForMachine){ # Certificate with Alias Found
            $NewCertIssuer = $null
            $NewCertThumbprint = $null
            if($CertificateFileLocation -and ($null -ne $CertificatePassword)) {
                $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $cert.Import($CertificateFileLocation,$CertificatePassword.GetNetworkCredential().Password,'DefaultKeySet')
                $NewCertIssuer = $cert.Issuer
                $NewCertThumbprint = $cert.Thumbprint
                Write-Verbose "Issuer for the supplied certificate is $NewCertIssuer"
                Write-Verbose "Thumbprint for the supplied certificate is $NewCertThumbprint"
            }
            $ExistingCertIssuer = $CertForMachine.Issuer    
            $ExistingCertThumbprint = $CertForMachine.Thumbprint
            Write-Verbose "Existing Cert Issuer $ExistingCertIssuer and Thumbprint $ExistingCertThumbprint"
            $machineDetails = Get-MachineDetails -ServerURL $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName
            if($ExistingCertThumbprint -ine $NewCertThumbprint){ #Certificate Thumbprint doesn't match
                Write-Verbose "Thumbprints for Certificate with Alias $WebServerCertificateAlias doesn't match that of existing cetificate."
                if($WebServerCertificateAlias -ieq $machineDetails.webServerCertificateAlias){
                    Write-Verbose "Certificate with alias $WebServerCertificateAlias matches the WebServerCertificateAlias."
                }
                $result = $False

            }else{ # Thumbprint matches
                if($WebServerCertificateAlias -ine $machineDetails.webServerCertificateAlias){
                    Write-Verbose "Certificate with alias $WebServerCertificateAlias already exists for machine $MachineName, but web server certificate alias $($machineDetails.webServerCertificateAlias) doesn't match."
                    $result = $False
                } else { #Everything Matches
                    Write-Verbose "Certificate with alias $WebServerCertificateAlias already exists for machine $MachineName and matches all the requirements."
                }
            }
        }else{ #Certificate with CName/Alias not found
            Write-Verbose "Certificate with Alias $WebServerCertificateAlias not found for machine $MachineName"
            $result = $False
        }
    }

    if($result -and $null -ne $SslRootOrIntermediate){ #Check RootOrIntermediateCertificate
        $Certs = Get-AllSSLCertificateForMachine -ServerUrl $ServerUrl -Token $token.token -Referer $Referer -MachineName $MachineName 
        foreach ($key in ($SslRootOrIntermediate | ConvertFrom-Json)){
            if ($Certs.certificates -icontains $key.Alias){
                Write-Verbose "RootOrIntermediate $($key.Alias) is in List of SSL-Certificates no Action Required"
            }else{
                Write-Verbose "RootOrIntermediate $($key.Alias) is NOT in List of SSL-Certificates"
                $result = $False
            }
        }
    }

    Write-Verbose "Returning $result from Test-TargetResource"
    $result
}

function Invoke-DeleteSSLCertForMachine
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $MachineName, 

        [System.String]
        $SSLCertName
    )
     
    $DeleteSSlCertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/$SSLCertName/delete"
    Invoke-ArcGISWebRequest -Url $DeleteSSlCertUrl -HttpFormParameters @{ f= 'json'; token = $Token; } -Referer $Referer -HttpMethod 'POST' -TimeoutSec 150
}

function Invoke-GenerateSelfSignedCertificate
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL,

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $MachineName,

        [System.String]
        $CertAlias, 

        [System.String]
        $CertCommonName, 

        [System.String]
        $CertOrganization, 

        [System.String]
        $ValidityInDays = 1825

    )

    $GenerateSelfSignedCertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/generate"
    $props = @{ f= 'json'; token = $Token; alias = $CertAlias; commonName = $CertCommonName; organization = $CertOrganization; validity = $ValidityInDays } 
    Invoke-ArcGISWebRequest -Url $GenerateSelfSignedCertUrl -HttpFormParameters $props -Referer $Referer -TimeOutSec 150
}

function Import-ExistingCertificate
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerUrl, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $Referer, 
        
        [System.String]
        $MachineName, 
        
        [System.String]
        $CertAlias, 
        
        [System.Management.Automation.PSCredential]
        $CertificatePassword, 
        
        [System.String]
        $CertificateFilePath,
        
        [System.String]
        $ServerType
    )

    $ImportCACertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/importExistingServerCertificate"
    $props = @{ f= 'json';  alias = $CertAlias; certPassword = $CertificatePassword.GetNetworkCredential().Password  }    

    $Header = @{}
    if(-not($ServerType -ieq "NotebookServer" -or $ServerType -ieq "MissionServer")){
        $props["token"] = $Token;
    }else{
        $Header["X-Esri-Authorization"] = "Bearer $Token"
    }

    $res = Invoke-UploadFile -url $ImportCACertUrl -filePath $CertificateFilePath -fileContentType 'application/x-pkcs12' -formParams $props -Referer $Referer -fileParameterName 'certFile' -httpHeaders $Header -Verbose 
    if($res -and $res.Content) {
        $response = $res | ConvertFrom-Json
        Confirm-ResponseStatus $response -Url $ImportCACertUrl
    } else {
        Write-Verbose "[WARNING] Response from $ImportCACertUrl was $res"
    }
}

function Import-RootOrIntermediateCertificate
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerUrl,

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [string]
        $MachineName,

        [System.String]
        $CertAlias, 

        [System.String]
        $CertificateFilePath
    )
    
    $ImportCertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/importRootOrIntermediate"
    $props = @{ f= 'json'; token = $Token; alias = $CertAlias; } 
    $res = Invoke-UploadFile -url $ImportCertUrl -filePath $CertificateFilePath -fileContentType 'application/x-pkcs12' -formParams $props -Referer $Referer -fileParameterName 'rootCACertificate'    
    if($res -and $res.Content) {
        $response = $res | ConvertFrom-Json
        Confirm-ResponseStatus $response -Url $ImportCACertUrl
    } else {
        Write-Verbose "[WARNING] Response from $ImportCertUrl was null"
    }
}

function Update-SSLCertificate 
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $MachineName,

        [System.String]
        $Referer, 
        
        $MachineProperties,
        
        [System.Int32]
        $MaxAttempts = 5,

        [System.Int32]
        $SleepTimeInSecondsBetweenAttempts = 30
    )

    $Info = Invoke-ArcGISWebRequest -Url ($ServerUrl.TrimEnd('/') + "/arcgis/rest/info") -HttpFormParameters @{f = 'json';} -Referer $Referer -Verbose -HttpMethod 'GET'
    $VersionArray = "$($Info.fullVersion)".Split('.')
    $VersionIsLessThan107 = ($VersionArray[0] -eq 10 -and $VersionArray[1] -lt 7)


    $UpdateSSLCertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/edit"
    $MachineProperties.psobject.properties | Foreach-Object -begin {$h=@{}} -process {$h."$($_.Name)" = $_.Value} -end {$h} # convert PSCustomObject to hashtable
    if($VersionIsLessThan107){
        $h.JMXPort = $MachineProperties.ports.JMXPort
        $h.OpenEJBPort = $MachineProperties.ports.OpenEJBPort
        $h.NamingPort = $MachineProperties.ports.NamingPort
        $h.DerbyPort = $MachineProperties.ports.DerbyPort
    }
    $h.ports = $null    
    $h.f = 'json'
    $h.token = $Token
    [bool]$Done = $false
    [int]$Attempt = 1
    while(-not($Done) -and $Attempt -le $MaxAttempts) 
    {
        $AttemptStr = ''
        if($Attempt -gt 1) {
            $AttemptStr = "Attempt # $Attempt"              
        }
        Write-Verbose "Update SSLCert Name $AttemptStr"
        try {    
            $response = Invoke-ArcGISWebRequest -Url $UpdateSSLCertUrl -HttpFormParameters $h -Referer $Referer -TimeOutSec 150
            if($response.status -ieq 'success'){
                Write-Verbose "Update Web Server SSL Certificate Successful! Server will Restart now."
                $Done = $true
            }else{
                if(($response.status -ieq 'error') -and $response.messages){
                    Write-Verbose "[WARNING]:- $($response.messages -join ',')"
                    Start-Sleep -Seconds $SleepTimeInSecondsBetweenAttempts
                }
            }
        }
        catch
        {                
            if($Attempt -ge $MaxAttempts) {
                Write-Verbose "[WARNING] Update failed after $MaxAttempts. Last Response:- $($_)"
                #throw "Update failed after $MaxAttempts. Error:- $($_)"
            }
            Start-Sleep -Seconds $SleepTimeInSecondsBetweenAttempts
        }   
        $Attempt++
    }
    $response
}

function Get-Machines 
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $Referer
    )
    $GetMachinesUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/"
    Invoke-ArcGISWebRequest -Url $GetMachinesUrl -HttpFormParameters @{ f= 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET' -TimeoutSec 150
}

function Get-MachineDetails 
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $Referer, 
        
        [System.String]
        $MachineName
    )
    $GetMachineDetailsUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/"
    Invoke-ArcGISWebRequest -Url $GetMachineDetailsUrl -HttpFormParameters @{ f= 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET' -TimeoutSec 150
}

function Get-AllSSLCertificateForMachine 
{
    [CmdletBinding()]
    param(
        [System.String]
        $ServerURL, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $Referer, 
        
        [System.String]
        $MachineName
    )
    $certURL = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/"
    Invoke-ArcGISWebRequest -Url $certURL -HttpFormParameters @{ f= 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET' 
}

function Get-SSLCertificateForMachine
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(
        [System.String]
        $ServerURL, 
        
        [System.String]
        $Token, 
        
        [System.String]
        $Referer, 
        
        [System.String]
        $MachineName, 
        
        [System.String]
        $SSLCertName
    )
    $CertUrl  = $ServerURL.TrimEnd("/") + "/arcgis/admin/machines/$MachineName/sslCertificates/$SSLCertName"
    try{
        $json = Invoke-ArcGISWebRequest -Url $CertUrl -HttpFormParameters @{ f= 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'  
        if($json.error){
            $errMsgs = ($json.error.messages -join ', ')
            Write-Verbose "[WARNING] Response from $CertUrl is $errMsgs"
            $null
        }elseif($json.status -and $json.status -ieq "error"){
            $errMsgs = ($json.messages -join ', ')
            Write-Verbose "[WARNING] Response from $CertUrl is $errMsgs"
            $null
        }else{
            $issuer = $json.issuer
            $thumbprint = $json.sha1Fingerprint
            $CN = if($issuer) { ($issuer.Split(',') | ConvertFrom-StringData).CN } else { $null }
            @{
                    Issuer = $issuer
                    CName = $CN
                    Thumbprint = $thumbprint
            }
        }
    }
    catch{
        # If no cert exists, an error is returned
        Write-Verbose "[WARNING] Error checking $CertUrl Error:- $_"
        $null
    }
}

Export-ModuleMember -Function *-TargetResource