DSCResources/ArcGIS_ServerSettings/ArcGIS_ServerSettings.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'))

Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'ArcGIS.Client' `
            -ChildPath 'ArcGIS.Client.Server.psm1'))

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

        [parameter(Mandatory = $true)]    
        [ValidateSet("Server","NotebookServer","MissionServer","VideoServer","DataPipelinesServer")]
        [System.String]
        $ServerType
    )
    
    return @{}
}

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

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $SiteAdministrator,

        [parameter(Mandatory = $true)]
        [ValidateSet("Server","NotebookServer","MissionServer","VideoServer","DataPipelinesServer")]
        [System.String]
        $ServerType, 

        [parameter(Mandatory = $false)]
        [System.String]
        $WebContextURL,    

        [parameter(Mandatory = $false)]
        [System.String]
        $WebSocketContextUrl,

        [parameter(Mandatory = $false)]
        [System.Boolean]
        $DisableDockerHealthCheck,
        
        [parameter(Mandatory = $false)]
        [System.Boolean]
        $DisableServiceDirectory,

        [parameter(Mandatory = $false)]
        [System.String]
        $SharedKey,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $HttpProxyHost,

        [parameter(Mandatory = $false)]
        [AllowNull()]
        [Nullable[System.UInt32]]                
        $HttpProxyPort,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]           
        $HttpProxyCredential,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $HttpsProxyHost,

        [parameter(Mandatory = $false)]
        [AllowNull()]
        [Nullable[System.UInt32]]                
        $HttpsProxyPort,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]           
        $HttpsProxyCredential,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $NonProxyHosts,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $VideoServerLivestreamGatewayHostname,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $VideoServerLiveStreamPorts
    )
    
    [System.Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null

    if($VerbosePreference -ine 'SilentlyContinue') 
    {        
        Write-Verbose ("Site Administrator UserName:- " + $SiteAdministrator.UserName) 
    }

    $FQDN = if($ServerHostName){ Get-FQDN $ServerHostName }else{ Get-FQDN $env:COMPUTERNAME }
    Write-Verbose "Server Type:- $ServerType , Fully Qualified Domain Name :- $FQDN"
    
    $ServerBaseUrl = Get-ArcGISComponentBaseUrl -ComponentName $ServerType -FQDN $FQDN
    Write-Verbose "Waiting for Server '$ServerBaseUrl'"
    Test-ArcGISComponentHealth -BaseURL $ServerBaseUrl -ComponentName $ServerType -Verbose
    $Referer = "https://localhost"
    
    $token = Get-ServerToken -URL $ServerBaseUrl -Credential $SiteAdministrator -Referer $Referer
 
    $AdminSettingsModified = $False
    $systemProperties = Get-SystemProperties -URL $ServerBaseUrl -Token $token.token -Referer $Referer
    if($WebContextURL -and (-not($systemProperties.WebContextURL) -or $systemProperties.WebContextURL -ine $WebContextURL)){
        Write-Verbose "Web Context URL '$($systemProperties.WebContextURL)' doesn't match expected value '$WebContextURL'"
        if(-not($systemProperties.WebContextURL)){
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name "WebContextURL" -Value $WebContextURL
        }else{
            $systemProperties.WebContextURL = $WebContextURL
        }
        $AdminSettingsModified = $True
    }
    
    if($ServerType -ieq "MissionServer" -and $WebSocketContextUrl -and (-not($systemProperties.WebSocketContextURL) -or $systemProperties.WebSocketContextURL -ine $WebSocketContextUrl)){
        Write-Verbose "Web Socket Context URL '$($systemProperties.WebSocketContextURL)' doesn't match expected value '$WebSocketContextUrl'"
        if(-not($systemProperties.WebSocketContextURL)){
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name "WebSocketContextURL" -Value $WebSocketContextUrl
        }else{
            $systemProperties.WebSocketContextURL = $WebSocketContextUrl
        }
        $AdminSettingsModified = $True
    }

    if($ServerType -ine "Server"){
        if($systemProperties.disableServicesDirectory -ine $DisableServiceDirectory){
            if(Get-Member -InputObject $systemProperties -name "disableServicesDirectory" -Membertype NoteProperty){
                $systemProperties.disableServicesDirectory = $DisableServiceDirectory
            }else{
                Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name "disableServicesDirectory" -Value $DisableServiceDirectory
            }
        
            $AdminSettingsModified = $True
        }
    }

    if($ServerType -ieq "NotebookServer" -and $systemProperties.disableDockerHealthCheck -ine $DisableDockerHealthCheck){
        if(Get-Member -InputObject $systemProperties -name "disableDockerHealthCheck" -Membertype NoteProperty){
            $systemProperties.disableDockerHealthCheck = $DisableDockerHealthCheck
        }else{
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name "disableDockerHealthCheck" -Value $DisableDockerHealthCheck
        }
        $AdminSettingsModified = $True
    }

    if($ServerType -ieq "VideoServer"){
        if($VideoServerLivestreamGatewayHostname -and (-not($systemProperties.LivestreamGatewayHostname) -or $systemProperties.LivestreamGatewayHostname -ine $VideoServerLivestreamGatewayHostname)){
            Write-Verbose "Video Server Live stream gateway host name '$($systemProperties.LivestreamGatewayHostname)' doesn't match expected value '$VideoServerLivestreamGatewayHostname'"
            if(-not($systemProperties.LivestreamGatewayHostname)){
                Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name "LivestreamGatewayHostname" -Value $VideoServerLivestreamGatewayHostname
            }else{
                $systemProperties.LivestreamGatewayHostname = $VideoServerLivestreamGatewayHostname
            }
            $AdminSettingsModified = $True
        }

        if($VideoServerLiveStreamPorts){
            if(Test-VideoServerLiveStreamPortsNeedsUpdates -URL $ServerBaseUrl -Token $token.token -Referer $Referer -Ports $VideoServerLiveStreamPorts -Verbose){
                Write-Verbose "Video Server live stream ports do not match expected values"
                Set-UpdateVideoServerLivestreamPorts -URL $ServerBaseUrl -Token $token.token -Referer $Referer -Ports $VideoServerLiveStreamPorts -Verbose
                Write-Verbose "Video Server live stream ports updated."
            }
        }
    }
    # checking forward proxy settings
    if ($HttpProxyHost) {
        if(-not($systemProperties.HttpProxyHost)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpProxyHost' -Value $HttpProxyHost
        }else{
            $systemProperties.HttpProxyHost = $HttpProxyHost
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpProxyHost) {
        # JSON removed it, so clear it
        $systemProperties.PSObject.Properties.Remove('httpProxyHost')
        $AdminSettingsModified = $true
    }
    
    if ($HttpProxyPort) {
        if(-not($systemProperties.HttpProxyPort)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpProxyPort' -Value $HttpProxyPort
        }else{
            $systemProperties.HttpProxyPort = $HttpProxyPort
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpProxyPort) {
        $systemProperties.PSObject.Properties.Remove('httpProxyPort')
        $AdminSettingsModified = $true
    }
    if ($HttpProxyCredential) {
        if(-not($systemProperties.HttpProxyUser)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpProxyUser' -Value $HttpProxyCredential.UserName
        }else{
            $systemProperties.HttpProxyUser = $HttpProxyCredential.UserName
        }
        if(-not($systemProperties.HttpProxyPassword)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpProxyPassword' -Value $HttpProxyCredential.GetNetworkCredential().Password
        }else{
            $systemProperties.HttpProxyPassword = $HttpProxyCredential.GetNetworkCredential().Password
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpProxyUser -or $systemProperties.HttpProxyPassword) {
        $systemProperties.PSObject.Properties.Remove('httpProxyUser')
        $systemProperties.PSObject.Properties.Remove('httpProxyPassword')
        $AdminSettingsModified = $true
    }
    # Forward proxy HTTPS Proxy: set or clear
    if ($HttpsProxyHost) {
        if(-not($systemProperties.HttpsProxyHost)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpsProxyHost' -Value $HttpsProxyHost
        }else{
            $systemProperties.HttpsProxyHost = $HttpsProxyHost
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpsProxyHost) {
        # JSON removed it, so clear it
        $systemProperties.PSObject.Properties.Remove('httpsProxyHost')
        $AdminSettingsModified = $true
    }
    if ($HttpsProxyPort) {
        if(-not($systemProperties.HttpsProxyPort)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpsProxyPort' -Value $HttpsProxyPort
        }else{
            $systemProperties.HttpsProxyPort = $HttpsProxyPort
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpsProxyPort) {
        $systemProperties.PSObject.Properties.Remove('httpsProxyPort')
        $AdminSettingsModified = $true
    }
    if ($HttpsProxyCredential) {
        if(-not($systemProperties.HttpsProxyUser)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpsProxyUser' -Value $HttpsProxyCredential.UserName
        }else{
            $systemProperties.HttpsProxyUser = $HttpsProxyCredential.UserName
        }
        if(-not($systemProperties.HttpsProxyPassword)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'httpsProxyPassword' -Value $HttpsProxyCredential.GetNetworkCredential().Password
        }else{
            $systemProperties.HttpsProxyPassword = $HttpsProxyCredential.GetNetworkCredential().Password
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.HttpsProxyUser -or $systemProperties.HttpsProxyPassword) {
        $systemProperties.PSObject.Properties.Remove('httpsProxyUser')
        $systemProperties.PSObject.Properties.Remove('httpsProxyPassword')
        $AdminSettingsModified = $true
    }

    if ($NonProxyHosts) {
        if(-not($systemProperties.NonProxyHosts)) {
            Add-Member -InputObject $systemProperties -MemberType NoteProperty -Name 'nonProxyHosts' -Value $NonProxyHosts
        }else{
            $systemProperties.NonProxyHosts = $NonProxyHosts
        }
        $AdminSettingsModified = $true
    }
    elseif ($systemProperties.NonProxyHosts) {
        $systemProperties.PSObject.Properties.Remove('nonProxyHosts')
        $AdminSettingsModified = $true
    }

    if($AdminSettingsModified){
        Set-SystemProperties -URL $ServerBaseUrl -Token $token.token -Properties $systemProperties -Referer $Referer

        Write-Verbose "Admin system settings updated."

        $MaxWaitTimeInSeconds = 120
        $SleepTimeInSeconds = 10
        Write-Verbose "Waiting for up to $($MaxWaitTimeInSeconds) seconds for server to restart"
        $ServerBaseUrl = Get-ArcGISComponentBaseUrl -ComponentName $ServerType -FQDN $FQDN
        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
        $Done = $False;
        while((-not($Done)) -and ($stopwatch.Elapsed.TotalSeconds -lt $MaxWaitTimeInSeconds)) {
            try{
                # if available sleep and try again.
                Test-ArcGISComponentHealth -BaseURL $ServerBaseUrl -ComponentName $ServerType -MaxWaitTimeInSeconds 10 -Verbose -ThrowErrors $true
                Write-Verbose "Server is still available. Trying again in $($SleepTimeInSeconds) seconds"
                Start-Sleep -Seconds $SleepTimeInSeconds
            }catch{
                # if error and most likely server has become unavailable then exit loop
                Write-Verbose "Server is likely restarting as result of update of system properties:- $($_)"
                $Done = $true
            }
        }
        $stopwatch.Stop()
        
        Write-Verbose "Waiting up to 6 minutes for Server endpoint '$($ServerBaseUrl)' to come back up"
        Test-ArcGISComponentHealth -BaseURL $ServerBaseUrl -ComponentName $ServerType -MaxWaitTimeInSeconds 360 -Verbose
        Write-Verbose "Finished waiting for server endpoint '$($ServerBaseUrl)' to come back up"
    }
    
    if($ServerType -ieq "Server"){
        Update-ServiceDirectorySettings -URL $ServerBaseUrl -Token $token.token -Referer $Referer -DisableServiceDirectory $DisableServiceDirectory -Verbose

        Update-SecurityTokenSharedKey -URL $ServerBaseUrl -Token $token.token -Referer $Referer -SharedKey $SharedKey
    }
}

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

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $SiteAdministrator,

        [parameter(Mandatory = $true)]
        [ValidateSet("Server","NotebookServer","MissionServer","VideoServer","DataPipelinesServer")]
        [System.String]
        $ServerType, 

        [parameter(Mandatory = $false)]
        [System.String]
        $WebContextURL,    

        [parameter(Mandatory = $false)]
        [System.String]
        $WebSocketContextUrl,

        [parameter(Mandatory = $false)]
        [System.Boolean]
        $DisableDockerHealthCheck,
        
        [parameter(Mandatory = $false)]
        [System.Boolean]
        $DisableServiceDirectory,

        [parameter(Mandatory = $false)]
        [System.String]
        $SharedKey,

        [parameter(Mandatory = $false)]
        [System.string]                 
        $HttpProxyHost,

        [parameter(Mandatory = $false)]
        [AllowNull()]
        [Nullable[System.UInt32]]                
        $HttpProxyPort,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]           
        $HttpProxyCredential,

        [parameter(Mandatory = $false)]
        [System.string]                 
        $HttpsProxyHost,

        [parameter(Mandatory = $false)]
        [AllowNull()]
        [Nullable[System.UInt32]]                
        $HttpsProxyPort,

        [parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]           
        $HttpsProxyCredential,

        [parameter(Mandatory = $false)]
        [System.string]                 
        $NonProxyHosts,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $VideoServerLivestreamGatewayHostname,

        [parameter(Mandatory = $false)]
        [System.String]                 
        $VideoServerLiveStreamPorts
    )

    [System.Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
    $FQDN = if($ServerHostName){ Get-FQDN $ServerHostName }else{ Get-FQDN $env:COMPUTERNAME }
    Write-Verbose "Server Type:- $ServerType , Fully Qualified Domain Name :- $FQDN"
    $ServerBaseUrl = Get-ArcGISComponentBaseUrl -ComponentName $ServerType -FQDN $FQDN

    Write-Verbose "Waiting for Server '$ServerBaseUrl' to initialize"
    Test-ArcGISComponentHealth -BaseURL $ServerBaseUrl -ComponentName $ServerType -Verbose
    $Referer = 'https://localhost'
    
    $token = Get-ServerToken -URL $ServerBaseUrl -Credential $SiteAdministrator -Referer $Referer
    $result = ($null -ne $token.token)
    if($result){
        Write-Verbose "Site Exists. Was able to retrieve token for PSA"
    }else{
        throw "Unable to detect if site exists. Unable to retrieve token for PSA"
    }
   
    $result = $true
    $SystemProperties = Get-SystemProperties -URL $ServerBaseUrl -Token $token.token -Referer $Referer
    if($result -and $WebContextURL){    
        if(-not($systemProperties.WebContextURL) -or $systemProperties.WebContextURL -ine $WebContextURL){
            Write-Verbose "Web Context URL '$($systemProperties.WebContextURL)' doesn't match expected value '$WebContextURL'"
            $result = $false
        }
    }
    
    # Only for Mission Server
    if($ServerType -ieq "MissionServer" -and $result -and $WebSocketContextUrl){
        if(-not($systemProperties.WebSocketContextURL) -or $systemProperties.WebSocketContextURL -ine $WebContextURL){
            Write-Verbose "Web Socket Context URL '$($systemProperties.WebSocketContextURL)' doesn't match expected value '$WebSocketContextUrl'"
            $result = $false
        }
    }

    if($result){
        if($ServerType -ieq "Server"){
            $ServiceDirectoryProperties = Get-ServiceDirectorySettings -URL $ServerBaseUrl -Token $token.token -Referer $Referer -Verbose
            Write-Verbose "Service Directory enabled:- $($ServiceDirectoryProperties.enabled)"
            if([System.Convert]::ToBoolean($ServiceDirectoryProperties.enabled) -ine -not($DisableServiceDirectory)) {
                $result = $false
            }
        }else{
            if($systemProperties.disableServicesDirectory -ine $DisableServiceDirectory){
                $result = $false
            }
        }

        if(-not($result)){
            Write-Verbose "DisableServicesDirectory for '$($ServerType)' doesn't match expected value '$DisableServiceDirectory'"
        }
    }

    if($result -and ($ServerType -ieq "Server") -and $SharedKey) {
        Write-Verbose "Get Token and Shared Key setting"
        $CurrentSharedKey = Get-SecurityTokenSharedKey -URL $ServerBaseUrl -Token $token.token -Referer $Referer
        if($CurrentSharedKey -ine $SharedKey){
            Write-Verbose "Shared Key is not set as expected"
            $result = $false
        }else{
            Write-Verbose "Shared Key is set as expected"
        }
    }

    # Only for Notebook Server
    if($result -and ($ServerType -ieq "NotebookServer") -and $systemProperties.DisableDockerHealthCheck -ine $DisableDockerHealthCheck){
        Write-Verbose "DisableDockerHealthCheck setting for Notebook Server doesn't match expected value '$DisableDockerHealthCheck'"
        $result = $false
    }

    if($result -and $ServerType -ieq "VideoServer"){
        if($VideoServerLivestreamGatewayHostname -and (-not($systemProperties.LivestreamGatewayHostname) -or $systemProperties.LivestreamGatewayHostname -ine $VideoServerLivestreamGatewayHostname)){
            Write-Verbose "Video Server Live stream gateway host name '$($systemProperties.LivestreamGatewayHostname)' doesn't match expected value '$VideoServerLivestreamGatewayHostname'"
            $result = $false
        }
        
        if($result -and $VideoServerLiveStreamPorts){
            if(Test-VideoServerLiveStreamPortsNeedsUpdates -URL $ServerBaseUrl -Token $token.token -Referer $Referer -Ports $VideoServerLiveStreamPorts -Verbose){
                Write-Verbose "Video Server live stream ports do not match expected values"
                $result = $false
            }
        }
    }

    if ($result) {
        #--- begin proxy test block ---
        $ProtocolSettings = @(
            [PSCustomObject]@{ Prefix = 'Http';  CredentialParam = 'HttpProxyCredential'  },
            [PSCustomObject]@{ Prefix = 'Https'; CredentialParam = 'HttpsProxyCredential' }
        )
        foreach ($Protocol in $ProtocolSettings) {
            $Prefix                 = $Protocol.Prefix
            $ProxyHostParamName     = "${Prefix}ProxyHost"
            $ProxyPortParamName     = "${Prefix}ProxyPort"
            $ProxyCredentialParam   = $Protocol.CredentialParam

            # Grab the parameter values by name
            $ProxyHostValue         = Get-Variable -Name $ProxyHostParamName       -ValueOnly
            $ProxyPortValue         = Get-Variable -Name $ProxyPortParamName       -ValueOnly
            $ProxyCredentialValue   = Get-Variable -Name $ProxyCredentialParam     -ValueOnly

            # Grab the server’s current system properties
            $ServerProxyHost        = $systemProperties."${Prefix}ProxyHost"
            $ServerProxyPort        = $systemProperties."${Prefix}ProxyPort"
            $ServerProxyUser        = $systemProperties."${Prefix}ProxyUser"
            $ServerProxyPassword    = $systemProperties."${Prefix}ProxyPassword"

            # If user supplied any proxy info, compare them
            if ($ProxyHostValue -or $ProxyPortValue -or $ProxyCredentialValue) {
                if ($ProxyHostValue -and $ServerProxyHost -ne $ProxyHostValue) {
                    Write-Verbose "$Prefix ProxyHost mismatch (`"$ServerProxyHost`" vs `"$ProxyHostValue`")"
                    $result = $false
                }
                if ($ProxyPortValue -and $ServerProxyPort -ne $ProxyPortValue) {
                    Write-Verbose "$Prefix ProxyPort mismatch (`"$ServerProxyPort`" vs `"$ProxyPortValue`")"
                    $result = $false
                }
                if ($ProxyCredentialValue) {
                    $UserName = $ProxyCredentialValue.UserName
                    $Password = $ProxyCredentialValue.GetNetworkCredential().Password

                    if ($ServerProxyUser -ne $UserName) {
                        Write-Verbose "$Prefix ProxyUser mismatch (`"$ServerProxyUser`" vs `"$UserName`")"
                        $result = $false
                    }
                    if ($ServerProxyPassword -ne $Password) {
                        Write-Verbose "$Prefix ProxyPassword mismatch"
                        $result = $false
                    }
                }
            }
            # Otherwise, if nothing in JSON but server has a value => mismatch
            elseif ($ServerProxyHost -or $ServerProxyPort -or $ServerProxyUser -or $ServerProxyPassword) {
                Write-Verbose "$Prefix proxy present on server but absent in JSON"
                $result = $false
            }

            if (-not $result) { break }
        }
    }

    # NonProxyHosts
    if ($result) {
        if ($NonProxyHosts) {
            if ($systemProperties.NonProxyHosts -ne $NonProxyHosts) {
                Write-Verbose "NonProxyHosts mismatch (`"$($systemProperties.NonProxyHosts)`" vs `"$NonProxyHosts`")"
                $result = $false
            }
        }
        elseif ($systemProperties.NonProxyHosts) {
            Write-Verbose "NonProxyHosts present on server but absent in JSON"
            $result = $false
        }
    }

    $result
}


Export-ModuleMember -Function *-TargetResource