Modules/ArcGIS.Client/ArcGIS.Client.Portal.psm1

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

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

function Get-PortalAdminUrlForPath{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Path
    )
    return $URL.TrimEnd('/') + "/portaladmin" + $Path
}

function Get-PortalSharingApiUrlForPath{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Path,

        [switch]
        $NoRest
    )

    $PrimaryRelativePath = "/sharing/rest"
    if($NoRest){
        $PrimaryRelativePath = "/sharing"
    }

    return $URL.TrimEnd('/') + $PrimaryRelativePath + $Path
}


function Get-PortalToken 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]        
        [System.String]
        $URL, 

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

        [Parameter(Mandatory=$true)]
        [System.String]
        $Referer,

        [System.Int32]
        $MaxAttempts = 10,

        [System.String]
        $Client,

        [System.Int32]
        $Expiration = -1
    )
    $url = Get-PortalSharingApiUrlForPath -URL $URL -Path ("/generateToken")
    $token = $null
    $Done = $false
    $NumAttempts = 0
    $Params = @{ 
                username = $Credential.UserName
                password = $Credential.GetNetworkCredential().Password
                referer = $Referer
                f = 'json' 
            }
    if($Client){
        $Params["client"] = $Client
    }
    if($Expiration -gt 0){
        $Params["expiration"] = $Expiration
    }
    
    while(-not($Done) -and ($NumAttempts -lt $MaxAttempts)) {
        $NumAttempts = $NumAttempts + 1
        try {
            $token = Invoke-ArcGISWebRequest -Url $url -HttpFormParameters $Params -Referer $Referer
            if($null -eq $token){
                 throw "Unable to get token. Response is null."
            }
            if($token.error){
                throw "Unable to get token - $($token.error.message). (Error response: $($token.error))"
            }
            if($token.token){
                Write-Verbose "Token retrieved successfully."
                $Done = $true
            }
        } catch {
            $token = $null
            Write-Verbose "[WARNING]:- $($url) failed to return a token on attempt $($NumAttempts). $($_)."
            if($NumAttempts -lt $MaxAttempts){
                Write-Verbose "Retrying to get token after 15 seconds."
                Start-Sleep -Seconds 15
            }
        }
    }
    $token
}

function Invoke-FederateServer
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 

        [System.String]
        $ServerServiceUrl, 

        [System.String]
        $ServerAdminUrl, 

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

        [System.String]
        $PortalToken, 

        [System.String]
        $Referer
    )

    $FederationUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/federation/servers/federate" 
    Write-Verbose "Federation EndPoint:- $FederationUrl"
    Write-Verbose "Referer:- $Referer"
    Write-Verbose "Federation Parameters:- url:- $ServerServiceUrl adminUrl = $ServerAdminUrl"
    $RequestParams = @{ 
                        f='json'
                        url = $ServerServiceUrl
                        adminUrl = $ServerAdminUrl
                        username = $ServerAdminCredential.UserName
                        password = $ServerAdminCredential.GetNetworkCredential().Password 
                        token = $PortalToken 
                    }

    Invoke-ArcGISWebRequest -Url $FederationUrl -Verbose -HttpFormParameters $RequestParams -Referer $Referer -TimeOutSec 300
}


function Invoke-UnFederateServer
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 

        [System.String]
        $ServerID, 

        [System.String]
        $Token, 

        [System.String]
        $Referer
    )

    $UnFederationUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/federation/servers/$($ServerID)/unfederate"
    Write-Verbose "UnFederate the server with ID $($ServerID) using admin URL $UnFederationUrl"
    Invoke-ArcGISWebRequest -Url $UnFederationUrl -HttpFormParameters @{ f='json'; token = $Token } -Referer $Referer -Verbose -TimeOutSec 90
}

function Get-FederatedServers
{
    [CmdletBinding()]
    param(        
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost'
    )
    
    $GetFederatedServerPortalAdminURL = Get-PortalAdminUrlForPath -URL $URL -Path '/federation/servers/'
    Invoke-ArcGISWebRequest -Url $GetFederatedServerPortalAdminURL -HttpMethod 'GET' -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer 
}

function Get-RegisteredServersForPortal 
{
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer
    )
    
    $GetServersUrl = Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/self/servers/" 
    Invoke-ArcGISWebRequest -Url $GetServersUrl -HttpFormParameters @{ token = $Token; f = 'json' } -Referer $Referer       
}

function Update-ServerAdminUrlForPortal
{
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer,
        
        [System.String]
        $ServerAdminUrl,

        $FederatedServer
    )

    $UpdateURL = (Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/0123456789ABCDEF/servers/$($FederatedServer.id)/update")
    Invoke-ArcGISWebRequest -Url $UpdateURL -HttpMethod 'POST' -HttpFormParameters @{ f = 'json'; token = $Token; name =  $ServerAdminUrl; url = $FederatedServer.url; adminUrl = $ServerAdminUrl; isHosted = $FederatedServer.isHosted; serverType = $FederatedServer.serverType; } -Referer $Referer -Verbose
} 

function Get-OAuthApplication
{
    [CmdletBinding()]
    param(        
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        [Parameter(Mandatory=$false)]
        [System.String]
        $AppId = 'arcgisonline'
    )

    $GetOAuthAppsUrl = Get-PortalSharingApiUrlForPath -URL $URL -Path "/oauth2/apps/$($AppId)" -NoRest
    Invoke-ArcGISWebRequest -Url $GetOAuthAppsUrl -HttpMethod 'GET' -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer 
}



function Update-OAuthApplication
{
    [CmdletBinding()]
    param(        
        [System.String]
        $URL,  

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        [Parameter(Mandatory=$false)]
        [System.String]
        $AppId = 'arcgisonline',

        [Parameter(Mandatory=$true)]
        $AppObject 
    )
    $OAuthAppsUpdate = Get-PortalSharingApiUrlForPath -URL $URL -Path "/oauth2/apps/$($AppId)/update" -NoRest
    $redirect_uris = ConvertTo-Json $AppObject.redirect_uris -Depth 1    
    Invoke-ArcGISWebRequest -Url $OAuthAppsUpdate -HttpMethod 'POST' -HttpFormParameters @{ f = 'json'; token = $Token; redirect_uris = $redirect_uris } -Referer $Referer -Verbose
}

function Update-FederatedServer
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(      
        [System.String]
        $URL,

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        [Parameter(Mandatory=$true)]
        [System.String]
        $ServerId, 

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerRole, 

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerFunction
    )
    
    try{
        $UpdateUrl = Get-PortalAdminUrlForPath -URL $URL -Path ("/federation/servers/"+$ServerId+"/update")
        Invoke-ArcGISWebRequest -Url $UpdateUrl -HttpMethod 'POST' -HttpFormParameters @{ f = 'json'; token = $Token; serverRole = $ServerRole; serverFunction = $ServerFunction } -Referer $Referer -TimeOutSec 300
    }catch{
        Write-Verbose "[WARNING] Error - $($_)"
        @{ error = $_ }
    }
}

function Get-LogSettings
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )   
    $GetLogSettingURL = Get-PortalAdminUrlForPath -URL $URL -Path "/logs/settings"
    Invoke-ArcGISWebRequest -Url $GetLogSettingURL -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'
}

function Update-PortalLogSettings {
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost',

        $LogSettings
    )   
    
    $EditLogSettingURL = Get-PortalAdminUrlForPath -URL $URL -Path "/logs/settings/edit"
    $FormParameters = @{ f = 'json'; token = $Token; logDir = $LogSettings.logDir; logLevel = $LogSettings.logLevel; maxErrorReportsCount = $LogSettings.maxErrorReportsCount; maxLogFileAge = $LogSettings.maxLogFileAge; usageMeteringEnabled = $LogSettings.usageMeteringEnabled }
    Invoke-ArcGISWebRequest -Url $EditLogSettingURL -HttpFormParameters $FormParameters -Referer $Referer -HttpMethod 'POST'
}

function Test-LicensePopulated
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )
    
    $LicenseCheckURL = Get-PortalAdminUrlForPath -URL $URL
    $populateLicenseCheck = Invoke-ArcGISWebRequest -Url $LicenseCheckURL -HttpMethod "GET" -HttpFormParameters @{f = 'json'; token = $Token} -Referer $Referer -Verbose 
    return $populateLicenseCheck.isLicensePopulated
}

function Invoke-PopulateLicense
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )

    Write-Verbose 'Populating Licenses'
    [string]$populateLicenseUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/license/populateLicense"
    $populateLicenseResponse = Invoke-ArcGISWebRequest -Url $populateLicenseUrl -HttpMethod "POST" -HttpFormParameters @{f = 'json'; token = $Token} -Referer $Referer -TimeOutSec 3000 -Verbose 
    if ($populateLicenseResponse.error -and $populateLicenseResponse.error.message) {
        Write-Verbose "Error from Populate Licenses:- $($populateLicenseResponse.error.message)"
        throw $populateLicenseResponse.error.message
    }
}

function Test-PortalSiteCreated
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Referer = 'https://localhost'
    )

    $result = $false
    try{
        Test-ArcGISComponentHealth -BaseURL $URL -ComponentName "PortalSharing" -Verbose
        $PortalAdminURL = Get-PortalAdminUrlForPath -URL $URL
        $SiteCreatedCheckResponse = Invoke-ArcGISWebRequest -Url $PortalAdminURL -HttpFormParameters @{ referer = $Referer; f = 'json' } -Referer $Referer -HttpMethod "GET"
        if($SiteCreatedCheckResponse.error.code -eq 499){
            Write-Verbose "Portal Site is already created."
            $result = $true
        }else{
            # Get
            if($SiteCreatedCheckResponse.status -ieq "error"){
                if((Get-UICulture).DisplayName -imatch "English"){
                    if($SiteCreatedCheckResponse.messages -icontains "The portal site has not been initialized. Please create a new site and try again."){
                        $result = $false
                    }else{
                        throw "$(ConvertTo-JSON $SiteCreatedCheckResponse -Compress)"
                    }
                }else{
                    # Skip match for non-english languages
                    $result = $false
                }
            }else{
                throw "Unknown response - $(ConvertTo-JSON $SiteCreatedCheckResponse -Compress)"
            }
        }
    }catch{
        $errMsg = "[ERROR] Unable to detect portal site. $_"
        Write-Verbose  $errMsg
        throw $errMsg
    }

    return $result
}

function Invoke-EnsurePortalSiteHealthy
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

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

        [System.String]
        $Referer = 'https://localhost'
    )

    $Attempts = 0
    $PortalReady = $False
    $SharingAPIURL = Get-PortalSharingApiUrlForPath -URL $URL -Path "/info" 
    while(-not($PortalReady) -and ($Attempts -lt 2)) {
        
        Write-Verbose "Making request to Sharing API Url - $SharingAPIURL" 
        try {
            Invoke-ArcGISWebRequest -Url $SharingAPIURL -HttpFormParameters @{ referer = $Referer; f = 'json' } -Referer $Referer -Verbose -HttpMethod "GET"
            Write-Verbose "Sharing API rest endpoint is available."
            $PortalReady = $true
        }catch {
            Write-Verbose "Sharing API rest endpoint is not available. Error:- $_. Restarting Portal."
            Restart-ArcGISService -ComponentName 'Portal' -Verbose
            Test-ArcGISComponentHealth -BaseURL $URL -ComponentName "Portal" -MaxWaitTimeInSeconds 600 -Verbose
            $Attempts = $Attempts + 1
        }        
    }
    if(-not($PortalReady)){
        throw "Portal Site is not healthy. Please check your portal site deployment."
    }
}


<#
    .SYNOPSIS
        Resource to Configure a Portal site.
    .PARAMETER Ensure
        Ensure makes sure that a Portal site is configured and joined to site if specified. Take the values Present or Absent.
        - "Present" ensures that portal is configured, if not.
        - "Absent" ensures that existing portal site is deleted(Not Implemented).
    .PARAMETER PortalHostName
        Host Name of the Machine on which the Portal has been installed and is to be configured.
    .PARAMETER PortalAdministrator
         A MSFT_Credential Object - Initial Administrator Account
    .PARAMETER AdminEmail
        Additional User Details - Email of the Administrator.
    .PARAMETER AdminFullName
        Additional User Details - Full Name of the Administrator.
    .PARAMETER AdminDescription
        Additional User Details - Description for the Administrator.
    .PARAMETER AdminSecurityQuestionCredential.Username
        Additional User Details - Security Questions Index
        0 - What city were you born in?
        1 - What was your high school mascot?
        2 - What is your mother's maiden name?
        3 - What was the make of your first car?
        4 - What high school did you go to?
        5 - What is the last name of your best friend?
        6 - What is the middle name of your youngest sibling?
        7 - What is the name of the street on which you grew up?
        8 - What is the name of your favorite fictional character?
        9 - What is the name of your favorite pet?
        10 - What is the name of your favorite restaurant?
        11 - What is the title of your favorite book?
        12 - What is your dream job?
        13 - Where did you go on your first date?
    .PARAMETER AdminSecurityQuestionCredential.Password
        Additional User Details - Answer to the Security Question
    .PARAMETER Join
        Boolean to indicate if the machine being installed is a Secondary portal and is being joined with an existing portal
    .PARAMETER EnableDebugLogging
        Enables Debug Mode
    .PARAMETER LogLevel
        Decides what level of Logging has to take place at Tomcat level
    .PARAMETER IsHAPortal
        Boolean to Indicate if the Portal install is a High Availability setup - i.e two portals are joined.
    .PARAMETER PeerMachineHostName
        HostName of the Primary Portal Machine
    .PARAMETER ContentDirectoryLocation
        Content Directory Location for the Portal - Can be a location file path or a Network File Share
    .PARAMETER ADServiceUser
        Service User to connect the Portal-UserStore to an Active Directory
    .PARAMETER EnableAutomaticAccountCreation
        Enables the automaticAccountCreation on Portal
    .PARAMETER EnableEmailSettings
        Enable Email Settings on Portal
    .PARAMETER EmailSettingsSMTPServerAddress
        Email Settings SMTP server host on Portal
    .PARAMETER EmailSettingsFrom
        Email Settings SMTP Email From on Portal
    .PARAMETER EmailSettingsLabel
        Email Settings SMTP Email From Label on Portal
    .PARAMETER EmailSettingsAuthenticationRequired
        Email Settings SMTP Server Authentication Requirement Flag on Portal
    .PARAMETER EmailSettingsCredential
        Email Settings SMTP Server Host Authentication Credentials on Portal
    .PARAMETER EmailSettingsSMTPPort
        Email Settings SMTP Server Host Port on Portal
    .PARAMETER EmailSettingsEncryptionMethod
        Email Settings SMTP Server Encryption Method on Portal
    .PARAMETER EnableCreateSiteDebug
        Enable debug during create site operation
#>

function Invoke-CreatePortalSite{
    [CmdletBinding()]
    param(
        [System.String]
        $Version,

        [System.String]
        $URL, 

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

        [System.String]
        $FullName, 

        [System.String]
        $Email,

        [System.String]
        $Description,

        [System.String]
        $ContentDirectoryLocation,

        [System.String]
        $LicenseFilePath = $null,
        
        [System.String]
        $UserLicenseTypeId = $null,
        
        [System.Management.Automation.PSCredential]
        $AdminSecurityQuestionCredential,

        [System.String]
        [ValidateSet("None","Azure","AWS")]
        $CloudProvider = "None",

        [Parameter(Mandatory=$False)]
        [System.String]
        [ValidateSet("AccessKey","IAMRole", "None")]
        $AWSAuthenticationType = "None",

        [Parameter(Mandatory=$False)]
        [System.String]
        $AWSRegion,

        [Parameter(Mandatory=$False)]
        [System.String]
        $AWSS3ContentBucketName,

        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]
        $AWSAccessKeyCredential,

        [Parameter(Mandatory=$False)]
        [System.String]
        [ValidateSet("AccessKey","ServicePrincipal","UserAssignedIdentity", "SASToken", "None")]
        $AzureAuthenticationType = "None",

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

        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]
        $AzureServicePrincipalCredential,

        [Parameter(Mandatory=$False)]
        [System.String]
        $AzureServicePrincipalTenantId,

        [Parameter(Mandatory=$False)]
        [System.String]
        $AzureServicePrincipalAuthorityHost,

        [Parameter(Mandatory=$False)]
        [System.String]
        $AzureUserAssignedIdentityClientId,
        
        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]
        $AzureStorageAccountCredential,

        [System.Boolean]
        $EnableCreateSiteDebug = $false
    )

    $contentStore = @{}
    if($CloudProvider -ine "None"){
        if($CloudProvider -ieq "Azure"){
            $AccountName = $AzureStorageAccountCredential.UserName
            $EndpointSuffix = ''
            $Pos = $AzureStorageAccountCredential.UserName.IndexOf('.blob.')
            if($Pos -gt -1) {
                $AccountName = $AzureStorageAccountCredential.UserName.Substring(0, $Pos)
                $EndpointSuffix = $AzureStorageAccountCredential.UserName.Substring($Pos + 6) # Remove the hostname and .blob. suffix to get the storage endpoint suffix
            }
            
            $ConnectionString = @{
                accountName = $AccountName
                accountEndpoint = "blob.$($EndpointSuffix)"
            }

            if($AzureAuthenticationType -ieq "AccessKey"){
                $ConnectionString["accountKey"] = $AzureStorageAccountCredential.GetNetworkCredential().Password
                $ConnectionString["credentialType"] = "accessKey"
            }elseif($AzureAuthenticationType -ieq "UserAssignedIdentity"){
                $ConnectionString["managedIdentityClientId"] = $AzureUserAssignedIdentityClientId
                $ConnectionString["credentialType"] = "userAssignedIdentity"
            }elseif($AzureAuthenticationType -ieq "SASToken"){
                $ConnectionString["sasToken"] = $AzureStorageAccountCredential.GetNetworkCredential().Password
                $ConnectionString["credentialType"] = "sasToken"
            }elseif($AzureAuthenticationType -ieq "ServicePrincipal"){
                $ConnectionString["credentialType"] = "servicePrincipal"
                $ConnectionString["tenantId"] = $AzureServicePrincipalTenantId
                $ConnectionString["clientId"] = $AzureServicePrincipalCredential.UserName
                $ConnectionString["clientSecret"] = $AzureServicePrincipalCredential.GetNetworkCredential().Password
                if(-not([string]::IsNullOrEmpty($AzureServicePrincipalAuthorityHost))){
                    $ConnectionString["authorityHost"] = $AzureServicePrincipalAuthorityHost
                }
            }

            Write-Verbose "Using Content Store on Azure Cloud Storage"
            $contentStore = @{ 
                type = 'cloudStore'
                provider = 'Azure'
                connectionString = $ConnectionString
                objectStore = "https://$($AccountName).blob.$($EndpointSuffix)/$($AzureContentBlobContainerName)"
            }
        }elseif($CloudProvider -ieq "AWS"){

            Write-Verbose "Using Content Store in AWS S3 Storage $($AWSS3ContentBucketName)"
            $AWSConnectionString = @{
                region = $AWSRegion
            }

            if($AWSAuthenticationType -ieq "AccessKey"){
                $AWSConnectionString["credentialType"] = "accessKey"
                $AWSConnectionString["accessKeyId"] = $AWSAccessKeyCredential.UserName
                $AWSConnectionString["secretAccessKey"] = $AWSAccessKeyCredential.GetNetworkCredential().Password
            }else{
                $AWSConnectionString["credentialType"] = "IAMRole"
            }
            
            $contentStore = @{ 
                type = 'cloudStore'
                provider = 'Amazon'
                connectionString = $AWSConnectionString
                objectStore = $AWSS3ContentBucketName
            }

        }
    }else{
        Write-Verbose "Using Content Store on File System at location $ContentDirectoryLocation"
        $contentStore = @{
            type = 'fileStore'
            provider = 'FileSystem'
            connectionString = $ContentDirectoryLocation
        }
    }
    
    $CreateNewSiteUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/createNewSite"
    $WebParams = @{ 
                    username = $Credential.UserName
                    password = $Credential.GetNetworkCredential().Password
                    fullname = $FullName
                    email = $Email
                    description = $Description
                    securityQuestionIdx = $AdminSecurityQuestionCredential.UserName
                    securityQuestionAns = $AdminSecurityQuestionCredential.GetNetworkCredential().Password
                    contentStore = ConvertTo-Json -Depth 5 $contentStore
                    f = 'json'
                }
    
    if(([version]$Version -ge "11.3") -and $EnableCreateSiteDebug){
        Write-Verbose "Enable Debug during create site operation"
        $WebParams["enableDebug"] = $EnableCreateSiteDebug
    }
    
    Write-Verbose "Making request to $CreateNewSiteUrl to create the site"
    $Response = $null
    if($LicenseFilePath){
        try{
            if($UserLicenseTypeId){
                $WebParams["userLicenseTypeId"] = $UserLicenseTypeId
            }
        
            $Response = Invoke-UploadFile -url $CreateNewSiteUrl -filePath $LicenseFilePath -fileContentType 'application/json' -fileParameterName 'file' `
                                 -Referer 'https://localhost' -formParams $WebParams -Verbose
            $Response = $Response | ConvertFrom-Json
        }catch{
            throw "Create portal site request failed. $_"
        }
    }else{
        $Response = Invoke-ArcGISWebRequest -Url $CreateNewSiteUrl -HttpFormParameters $WebParams -Referer 'https://localhost' -TimeOutSec 5400 -Verbose 
    }

    Write-Verbose "Response received from create site $( $Response | ConvertTo-Json -Depth 10 )"  
    if ($Response.error -and $Response.error.message) {
        throw $Response.error.message
    }
    if ($null -ne $Response.recheckAfterSeconds) {
        Wait-RecheckAfterSeconds -Seconds $Response.recheckAfterSeconds -Multiplier 2 -Verbose
    }
    
    Write-Verbose "Waiting for portal to start."
    try {
        $token = Get-PortalToken -URL $URL -Credential $Credential -Referer $URL -MaxAttempts 40
        if($token.token){
            Write-Verbose "Portal Site create successful. Was able to retrieve token from Portal."
        }
    } catch {
        Write-Verbose $_
    }
}

function Join-PortalSite {    
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 

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

        [System.String]
        $PrimaryMachineHostName
    )
    
    $PrimaryPortalBaseURL = Get-ArcGISComponentBaseUrl -FQDN $PrimaryMachineHostName -ComponentName "Portal"
    Test-ArcGISComponentHealth -BaseURL $PrimaryPortalBaseURL -ComponentName "Portal" -MaxWaitTimeInSeconds 600 -SleepTimeInSeconds 30 -RequestTimeoutInSeconds 90 -Verbose

    [string]$JoinSiteUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/joinSite"
    $WebParams = @{
                    username = $Credential.UserName
                    password = $Credential.GetNetworkCredential().Password
                    machineAdminUrl = $PrimaryPortalBaseURL.Replace('/arcgis', '')
                    f = 'json'
                  }

    Write-Verbose "Making request to $JoinSiteUrl"
    $Response = Invoke-ArcGISWebRequest -Url $JoinSiteUrl -HttpFormParameters $WebParams -TimeOutSec 1000 -Verbose 
    
    if ($Response) {
        Write-Verbose "Response received:- $(ConvertTo-Json -Depth 5 -Compress -InputObject $Response)"  
    }
    if ($Response.error -and $Response.error.message) {
        Write-Verbose "Error from Join Site:- $($Response.error.message)"

        Restart-ArcGISService -ComponentName 'Portal' -Verbose

        Write-Verbose "Wait for portal sharing endpoint for 10 minutes"
        Test-ArcGISComponentHealth -BaseURL $URL -ComponentName "PortalSharing" `
                                    -MaxWaitTimeInSeconds 600 -Verbose
        Write-Verbose "Finished waiting for portal sharing endpoint."

        Write-Verbose "Check primary with second round of health checks"
        Test-ArcGISComponentHealth -BaseURL $PrimaryPortalBaseURL -ComponentName "Portal" `
                                    -MaxWaitTimeInSeconds 600 -SleepTimeInSeconds 30 `
                                    -RequestTimeoutInSeconds 90 -Verbose

        Write-Verbose "Waiting 5 mins before retrying to join site."
        Start-Sleep -Seconds 300
        Write-Verbose "Making second attempt request to $JoinSiteUrl"
        $Response = Invoke-ArcGISWebRequest -Url $JoinSiteUrl -HttpFormParameters $WebParams -Referer 'https://localhost' -TimeOutSec 1000 -Verbose 
        if ($Response) {
            Write-Verbose "Response received on second attempt:- $(ConvertTo-Json -Depth 5 -Compress -InputObject $Response)"  
        } else {
            Write-Verbose "Response from Join Site was null"
        }

        if ($Response.error -and $Response.error.message) {
            Write-Verbose "Error from Join Site second attempt:- $($Response.error.message)"
            throw $Response.error.message
        }
    }

    if ($null -ne $Response.recheckAfterSeconds) {
        Wait-RecheckAfterSeconds -Seconds $Response.recheckAfterSeconds -Multiplier 6 -Verbose
    }

    Write-Verbose "Waiting for portal to start."
    try {
        $token = Get-PortalToken -URL $URL -Credential $Credential -Referer $URL -MaxAttempts 40
        if($token.token){
            Write-Verbose "Portal Site create successful. Was able to retrieve token from Portal."
        }
    } catch {
        Write-Verbose $_
    }
}


function Get-SSLCertificatesForPortal
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $WebServerCertificateAlias,

        [System.String]
        $Token,

        [System.String]
        $Referer,

        [System.String]
        $MachineName
    )

    try {
        $GetCertURL = Get-PortalAdminUrlForPath -URL $URL -Path "/machines/$MachineName/sslCertificates"
        if($WebServerCertificateAlias){ $GetCertURL = $GetCertURL + "/$($WebServerCertificateAlias)" }
        Invoke-ArcGISWebRequest -Url $GetCertURL -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer -HttpMethod 'GET' -TimeOutSec 120
    }
    catch {
        Write-Verbose "[WARNING]:- Error running Get-SSLCertificatesForPortal:- $_"
    }
}

function Invoke-DeletePortalCertificate{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $WebServerCertificateAlias,

        [System.String]
        $Token,

        [System.String]
        $Referer,

        [System.String]
        $MachineName
    )
    try {
        $URL = Get-PortalAdminUrlForPath -URL $URL -Path "/machines/$MachineName/sslCertificates/$($WebServerCertificateAlias)/delete" 
        Invoke-ArcGISWebRequest -Url $URL -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer -HttpMethod 'POST' -TimeOutSec 120
    }catch{
        Write-Verbose "[WARNING]:- Error running Invoke-DeletePortalCertificate. Error:- $_"
    }
}

function Import-ExistingCertificate
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $CertAlias, 

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

        [System.String]
        $CertificateFilePath,

        [System.String]
        $MachineName,

        [System.String]
        $Version,

        [System.Boolean]
        $ImportCertificateChain = $true
    )
    $ImportCertUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/machines/$MachineName/sslCertificates/importExistingServerCertificate" 
    $props = @{ f= 'json'; token = $Token; alias = $CertAlias; password = $CertificatePassword.GetNetworkCredential().Password  }
    # Version greater than equal to 11.3
    if  ([Version]$Version -ge 11.3) {
        $props["importCertificateChain"] = $ImportCertificateChain
    }
    $res = Invoke-UploadFile -url $ImportCertUrl -filePath $CertificateFilePath -fileContentType 'application/x-pkcs12' -formParams $props -Referer $Referer -fileParameterName 'file'    
    if($res) {
        $response = $res | ConvertFrom-Json
        Confirm-ResponseStatus $response -Url $ImportCertUrl
    } else {
        Write-Verbose "[WARNING] Response from $ImportCertUrl was null"
    }
}

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

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $CertAlias, 

        [System.String]
        $CertificateFilePath,

        [System.String]
        $MachineName,

        [System.Boolean]
        $ImportCertificateChain = $true # TODO fix
    )

    $ImportCertUrl =  Get-PortalAdminUrlForPath -URL $URL -Path "/machines/$MachineName/sslCertificates/importRootOrIntermediate" 
    $props = @{ f= 'json'; token = $Token; alias = $CertAlias; norestart = $true }
    try{
        $res = Invoke-UploadFile -url $ImportCertUrl -filePath $CertificateFilePath -fileContentType 'application/x-pkcs12' -formParams $props -Referer $Referer -fileParameterName 'file'
        if($res) {
            $response = $res | ConvertFrom-Json
            Confirm-ResponseStatus $response -Url $ImportCertUrl
        } else {
            throw "[WARNING] Response from $ImportCertUrl was null"
        }
    }catch{
        Write-Verbose "Error in Import-RootOrIntermediateCertificate :- $_"
    }
}

function Update-PortalSSLCertAliasOrHSTSSetting
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $CertAlias,

        [System.String]
        $MachineName,

        [System.Boolean]
        $HSTSEnabled
    )

    $SSLCertsObject = Get-SSLCertificatesForPortal -URL $URL -Token $Token -Referer $Referer -MachineName $MachineName

    $sslProtocols = if($null -eq $SSLCertsObject.sslProtocols) {"TLSv1.2,TLSv1.1,TLSv1"}else{$SSLCertsObject.sslProtocols}
    $cipherSuites = if($null -eq $SSLCertsObject.cipherSuites){ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA" }else{ $SSLCertsObject.cipherSuites }
    $WebParams = @{ 
        f = 'json'
        token = $Token
        sslProtocols = $sslProtocols
        cipherSuites = $cipherSuites
    }

    if(-not([string]::IsNullOrEmpty($CertAlias))){
        $WebParams["HSTSEnabled"] = "$($SSLCertsObject.HSTSEnabled)"
        $WebParams["webServerCertificateAlias"] = $CertAlias
    }else{
        $WebParams["webServerCertificateAlias"] = $SSLCertsObject.webServerCertificateAlias
        $WebParams["HSTSEnabled"] = "$($HSTSEnabled)".ToLower();
    }
    $UpdateCertURL = Get-PortalAdminUrlForPath -URL $URL -Path "/machines/$MachineName/sslCertificates/update" 
    Invoke-ArcGISWebRequest -Url $UpdateCertURL -HttpFormParameters $WebParams -Referer $Referer -Verbose
}

function Get-PortalRootAndIntermdiateCertificatesToUpdate
{
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $True)]
        [System.String]
        $URL,

        [parameter(Mandatory = $True)]
        [System.String]
        $Token,

        [System.String]
        $Referer,

        [System.String]
        $MachineName, 

        [System.String]
        $SslRootOrIntermediate
    )

    $ExpectedCerts = ($SslRootOrIntermediate | ConvertFrom-Json)
    $Certs = Get-SSLCertificatesForPortal -URL $URL -Token $Token -Referer $Referer -MachineName $MachineName -ErrorAction SilentlyContinue
    $MissingCerts = @()
    foreach ($Cert in $ExpectedCerts){
        if ($Certs.sslCertificates -icontains $Cert.Alias){
            Write-Verbose "Test RootOrIntermediate $($Cert.Alias) is in List of SSL-Certificates. Validating if thumbprint matches the existing certificate"
            $RootOrIntermediateCertForMachine = Get-SSLCertificatesForPortal -URL $URL -Token $Token -Referer $Referer -WebServerCertificateAlias $Cert.Alias -MachineName $MachineName
            Write-Verbose "Existing Cert Issuer $($RootOrIntermediateCertForMachine.Issuer) and Thumbprint $($RootOrIntermediateCertForMachine.sha1Fingerprint)"
            $NewCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $Cert.Path
            Write-Verbose "Issuer and Thumprint for the supplied certificate is $($NewCert.Issuer) and $($NewCert.Thumbprint) respectively."
            if($RootOrIntermediateCertForMachine.sha1Fingerprint -ine $NewCert.Thumbprint){
                Write-Verbose "Thumbprints for Certificate with Alias $($Cert.Alias) doesn't match that of existing cetificate."
                $Cert | Add-Member -NotePropertyName "Present" -NotePropertyValue $true
                $MissingCerts += ($Cert)
            }else{
                Write-Verbose "Thumbprints for Certificate with Alias $($Cert.Alias) match that of existing cetificate."
            }
        }else{
            Write-Verbose "Test RootOrIntermediate $($Cert.Alias) is NOT in List of SSL-Certificates"
            ($Cert | Add-Member -NotePropertyName "Present" -NotePropertyValue $False)
            $MissingCerts += ($Cert)
        }
    }

    return $MissingCerts
}

function Set-PortalRootAndIntermdiateCertificates{
    [CmdletBinding()]
    param (
        [parameter(Mandatory = $True)]
        [System.String]
        $URL,

        [parameter(Mandatory = $True)]
        [System.String]
        $Token,

        [System.String]
        $Referer,

        [System.String]
        $MachineName, 

        [System.String]
        $SslRootOrIntermediate
    )

    $RestartRequired = $False
    $MissingCerts = Get-PortalRootAndIntermdiateCertificatesToUpdate -URL $URL `
                                                -Token $Token -Referer $Referer -MachineName $MachineName `
                                                -SslRootOrIntermediate $SslRootOrIntermediate -Verbose

    if($MissingCerts.Count -gt 0){
        $RestartRequired = $True
        foreach ($Cert in $MissingCerts){
            if($Cert.Present){
                Write-Verbose "Thumbprints for certificate with alias $($Cert.Alias) doesn't match that of existing cetificate. Deleting existing certificate"
                $res = Invoke-DeletePortalCertificate -URL $URL -Token $Token -Referer $Referer -MachineName $MachineName -SSLCertName $Cert.Alias.ToLower()
                Write-Verbose "Existing certificate delete successful - $($res | ConvertTo-Json)"
            }

            try{
                Import-RootOrIntermediateCertificate -URL $URL -Token $Token -Referer $Referer -MachineName $MachineName -CertAlias $Cert.Alias.ToLower() -CertificateFilePath $Cert.Path
            }catch{
                Write-Verbose "Error in Import-RootOrIntermediateCertificate :- $_"
            }
        }
    }
    
    if($RestartRequired){
        Write-Verbose "Portal root and intermediate certificates were updated. Restart required."
    }

    return $RestartRequired
}

function Unregister-PortalSiteMachine{
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $MachineFQDN
    )

    $MachineUnregisterURL = Get-PortalAdminUrlForPath -URL $URL -Path "/machines/unregister"
    $MachineInSiteFlag = $False
    $FormParameters = @{ f = 'json'; token = $Token; machineName = $MachineFQDN }
    try{
        $Response = Invoke-ArcGISWebRequest -Url $MachineUnregisterURL -HttpFormParameters $FormParameters -Referer $Referer -TimeOutSec 120
    }catch{
        $MachineInSiteFlag = Test-MachineInPortalSite -URL $URL -Token $Token -Referer $Referer -MachineFQDN $MachineFQDN
    }
    if($null -ne $Response){
        Write-Verbose (ConvertTo-Json -Depth 5 $Response)
    }

    if(($Response.status -ieq "success") -or -not($MachineInSiteFlag)){
        Write-Verbose "Sleeping for 3 minutes. Portal will restart!"
        Start-Sleep -Seconds 180
    }else{
        throw "Unable to Unregister Portal! Please run the configuration again!"
    }
}

function Test-MachineInPortalSite {
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer, 

        [System.String]
        $MachineFQDN
    )

    $Machines = Get-MachinesInPortalSite -URL $URL -Token $Token -Referer $Referer
    return (($Machines | Where-Object { $_.machineName -ieq $MachineFQDN } | Measure-Object).Count -gt 0 )
}

function Get-MachinesInPortalSite
{
    param(
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer
    )

    $GetMachinesURL = Get-PortalAdminUrlForPath -URL $URL -Path "/machines" 
    $machinesResponse = Invoke-ArcGISWebRequest -Url $GetMachinesURL -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'
    return $machinesResponse.machines
}

function Test-PortalAdminHealth{
    param(
        [System.String]
        $URL, 

        [System.String]
        $Referer,

        [System.Int32]
        $MaxAttempts = 1
    )

    $result = $false
    $PortalAdminHealthCheckURL = Get-PortalAdminUrlForPath -URL $URL -Path "/healthCheck"
    while(-not($result) -and ($Attempts -lt $MaxAttempts)) {
        Write-Verbose "Making request to health check URL '$PortalAdminHealthCheckURL'" 
        try {
            $Response = Invoke-ArcGISWebRequest -Url $PortalAdminHealthCheckURL -TimeOutSec 90 -HttpFormParameters @{ f = 'json' } -Referer $Referer -Verbose -HttpMethod 'GET'
            if ($Response.status){
                if($Response.status -ieq "success"){
                    Write-Verbose "Health check succeeded"
                    $result = $true
                }elseif ($Response.status -ieq "error") { 
                    throw [string]::Format("ERROR: {0}",($Response.messages -join " "))
                }else{
                    throw "Unknow Error"
                }
            }else{
                $jsresponse = ConvertTo-Json $Response -Compress -Depth 5
                Write-Verbose "[WARNING] Portal health check response - $jsresponse "
                if ($Response.error) { 
                    throw "ERROR: $($Response.error.messages)"
                }else{
                    throw "Unknow Error"
                }
            }
        } catch {
            Write-Verbose "Health check did not succeed. Error:- $_"
            Start-Sleep -Seconds 30
            $Attempts = $Attempts + 1
        }
    }
    return $result
}


function Get-PortalSystemProperties {
    [CmdletBinding()]
    param(        
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost'
    )
    
    $GetSystemPropertiesURL = (Get-PortalAdminUrlForPath -URL $URL -Path "/system/properties/")
    Invoke-ArcGISWebRequest -Url $GetSystemPropertiesURL -HttpMethod 'GET' -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer 
}

function Set-PortalSystemProperties {
    [CmdletBinding()]
    param(
        
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        $Properties
    )
    
    $UpdateSystemPropertiesURL = (Get-PortalAdminUrlForPath -URL $URL -Path "/system/properties/update/")
    try {
        Invoke-ArcGISWebRequest -Url $UpdateSystemPropertiesURL `
                            -HttpFormParameters @{ f = 'json'; token = $Token; properties = (ConvertTo-Json $Properties -Depth 5) } `
                            -Referer $Referer -TimeOutSec 360
    }
    catch {
        Write-Verbose "[WARNING] Request to Set-PortalSystemProperties returned error:- $_"
    }
}

function Get-PortalSecurityConfig {
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )   

    $GetSecurityPropertiesURL = (Get-PortalAdminUrlForPath -URL $URL -Path "/security/config")
    Invoke-ArcGISWebRequest -Url $GetSecurityPropertiesURL `
                        -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'
}

function Set-PortalSecurityConfig {
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost',

        [System.String]
        $SecurityParameters
    )   

    $UpdateSecurityPropertiesURL = (Get-PortalAdminUrlForPath -URL $URL -Path "/security/config/update")
    $params = @{ f = 'json'; token = $Token; securityConfig = $SecurityParameters;}
    $resp = Invoke-ArcGISWebRequest -Url $UpdateSecurityPropertiesURL `
                        -HttpFormParameters $params -Referer $Referer -TimeOutSec 100 -Verbose
    if($resp.error -and $resp.error.message){
        throw "[Error] - Set-PortalSecurityConfig Response:- $($resp.error.message)"
    }
}

function Set-PortalUserStoreConfig {
    [CmdletBinding()]
    param(
        [System.String]
        $URL,
        
        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        [System.Management.Automation.PSCredential]
        $ADServiceUser
    )

    $userStoreConfig = '{
        "type": "WINDOWS",
        "properties": {
            "userPassword": "'
 + $($ADServiceUser.GetNetworkCredential().Password) +'",
            "isPasswordEncrypted": "false",
            "user": "'
 + $($ADServiceUser.UserName.Replace("\","\\")) +'",
            "userFullnameAttribute": "cn",
            "userEmailAttribute": "mail",
            "userGivenNameAttribute": "givenName",
            "userSurnameAttribute": "sn",
            "caseSensitive": "false"
        }
    }'


    $UpdateIdentityStoreURL = Get-PortalAdminUrlForPath -URL $URL -Path "/security/config/updateIdentityStore"
    $response = Invoke-ArcGISWebRequest -Url $UpdateIdentityStoreURL `
                                -HttpFormParameters @{ f = 'json'; token = $Token; userStoreConfig = $userStoreConfig; } `
                                -Referer $Referer -TimeOutSec 300 -Verbose
    if ($response.error) {
        throw "Error in Set-PortalUserStoreConfig:- $($response.error)"
    } else {
        Write-Verbose "Response received from Portal Set UserStoreconfig:- $response"
    }
}

function Get-PortalUserDefaults{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,
        
        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost'
    )
    
    Invoke-ArcGISWebRequest -Url (Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/self/userDefaultSettings") `
                        -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'
}

function Set-PortalUserDefaults{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost',

        $UserDefaultsParameters
    )

    $params = @{ 
                f = 'json'; 
                token = $Token;
                role = $UserDefaultsParameters.role;
                userLicenseType = $UserDefaultsParameters.userLicenseType;
                groups = $UserDefaultsParameters.groups;
                userType = $UserDefaultsParameters.userType;
                apps = $UserDefaultsParameters.apps;
                appBundles = $UserDefaultsParameters.appBundles;
            }
    
    $resp = Invoke-ArcGISWebRequest -Url (Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/self/setUserDefaultSettings") -HttpFormParameters $params -Referer $Referer -Verbose
    if($resp.error -and $resp.error.message){
        throw "[Error] - Set-PortalUserDefaults Response:- $($resp.error.message)"
    }
}

function Get-PortalSelfDescription {
    [CmdletBinding()]
    param(        
        [System.String]
        $URL, 

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost'
    )
    
    Invoke-ArcGISWebRequest -Url (Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/self/") `
                        -HttpMethod 'GET' -HttpFormParameters @{ f = 'json'; token = $Token } -Referer $Referer 
}

function Set-PortalSelfDescription 
{
    [CmdletBinding()]
    param(
        
        [System.String]
        $URL,

        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost',

        $Properties
    )
    
    try {
        $Properties += @{ token = $Token; f = 'json' }
        Invoke-ArcGISWebRequest -Url (Get-PortalSharingApiUrlForPath -URL $URL -Path "/portals/self/update/") `
                            -HttpFormParameters $Properties -Referer $Referer -TimeOutSec 360
    }
    catch {
        Write-Verbose "[WARNING] Request to Set-PortalSelfDescription returned error:- $_"
    }
}

function Get-PortalEmailSettings
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    ) 
    
    $GetEmailSettingsURL = Get-PortalAdminUrlForPath -URL $URL -Path "/system/emailSettings"
    $resp = Invoke-ArcGISWebRequest -Url $GetEmailSettingsURL `
                        -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -HttpMethod 'GET'
                        
    if($resp.status -and $resp.status -ieq "error"){
        throw "[Error] - Get-PortalEmailSettings Response:- $($resp.messages)"
    }
    
    $resp 
}

function Update-PortalEmailSettings
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost',

        [System.String]
        $SMTPServerAddress,

        [System.String]
        $From,

        [System.String]
        $Label,

        [System.Boolean]
        $AuthenticationRequired = $False,

        [System.Management.Automation.PSCredential]
        $Credential,
        
        [System.Int32]
        $SMTPPort = 25,
         
        [System.String]
        $EncryptionMethod
    )

    $emailSettingObject = @{
        smtpServer = $SMTPServerAddress;
        fromEmailAddress = $From;
        fromEmailAddressLabel = $Label;
        authRequired = if($AuthenticationRequired){ "yes" }else{ "no" };
        smtpPort = $SMTPPort;
        encryptionMethod = $EncryptionMethod;
        f = 'json'; 
        token = $Token;
    }

    if($AuthenticationRequired){
        $emailSettingObject.Add("username",$Credential.UserName)
        $emailSettingObject.Add("password",$Credential.GetNetworkCredential().Password)
    }

    $UpdateEmailSettingsURL = Get-PortalAdminUrlForPath -URL $URL -Path "/system/emailSettings/update"
    $resp = Invoke-ArcGISWebRequest -Url $UpdateEmailSettingsURL `
                        -HttpFormParameters $emailSettingObject -Referer $Referer -Verbose
    if($resp.error -and $resp.error.message){
        throw "[Error] - Update-PortalEmailSettings Response:- $($resp.error.message)"
    }else{
        if($resp.status -and $resp.status -ieq "success"){
            if ($null -ne $resp.recheckAfterSeconds) {
                Wait-RecheckAfterSeconds -Seconds $resp.recheckAfterSeconds -Multiplier 2 -Verbose
            }
            Write-Verbose "Update-PortalEmailSettings successful."
        }
    }
}

function Remove-PortalEmailSettings
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )  
    
    $RemoveEmailSettingsURL = Get-PortalAdminUrlForPath -URL $URL -Path "/system/emailSettings/delete"
    $resp = Invoke-ArcGISWebRequest -Url $RemoveEmailSettingsURL `
                        -HttpFormParameters @{ f = 'json'; token = $Token; } -Referer $Referer -Verbose
    if($resp.error -and $resp.error.message){
        throw "[Error] - Remove-PortalEmailSettings Response:- $($resp.error.message)"
    }
}

function Invoke-UpgradeReindex
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL, 
        
        [System.String]
        $Token, 

        [System.String]
        $Referer = 'https://localhost'
        
    )

    [string]$ReindexSiteUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/system/indexer/reindex"

    $WebParams = @{ 
                    mode = 'FULL_MODE'
                    f = 'json'
                    token = $Token
                  }

    Write-Verbose "Making request to $ReindexSiteUrl to create the site"
    $Response = Invoke-ArcGISWebRequest -Url $ReindexSiteUrl -HttpFormParameters $WebParams -Referer $Referer -TimeOutSec 3000 -Verbose 
    $ResponseJSON = (ConvertTo-JSON $Response -Depth 5 -Compress )
    Write-Verbose "Response received from Reindex site $ResponseJSON"  
    if($Response.error -and $Response.error.message) {
        throw $Response.error.message
    }
    if($Response.status -ieq 'success') {
        Write-Verbose "Reindexing Successful"
    }
}

function Get-LivingAtlasStatus
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [System.String]
        $URL,
        
        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )
    
    $LAStatusURL =  Get-PortalSharingApiUrlForPath -URL $URL -Path "/search"
    $resp = Invoke-ArcGISWebRequest -Url $LAStatusURL -HttpFormParameters @{ f = 'json'; token = $Token; q = "owner:esri_livingatlas" } -Referer $Referer
    if($resp.total -gt 0){
        Write-Verbose "Living Atlas content found."
        $true
    }else{
        Write-Verbose "Living Atlas content not found."
        $false
    }
}

function Get-LivingAtlasGroupIds
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [System.String]
        $URL,
        
        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )
    $result = @()
    $LAGroupIdsURL = Get-PortalSharingApiUrlForPath -URL $URL -Path "/community/groups"
    $resp = Invoke-ArcGISWebRequest -Url $LAGroupIdsURL -HttpFormParameters @{ f = 'json'; token = $Token; q = "owner:esri_livingatlas" } -Referer $Referer
    if($resp.total -gt 0){
        foreach($group in $resp.results){
            $result += $group.id
        }
    }
    $result
}

function Invoke-UpgradeLivingAtlas
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )

    $LivingAtlasGroupIds = Get-LivingAtlasGroupIds -URL $URL -Referer $Referer -Token $Token
    foreach($groupId in $LivingAtlasGroupIds){
        $done = $False
        $attempts = 0
        while(-not($done)){
            $LAUpgradeURL =  Get-PortalAdminUrlForPath -URL $URL -Path "/system/content/livingatlas/upgrade"
            try{
                $resp = Invoke-ArcGISWebRequest -Url $LAUpgradeURL -HttpFormParameters @{ f = 'json'; token = $Token; groupId = $groupId } -Referer $Referer -Verbose
                if($resp.status -eq "success"){
                    Write-Verbose "Upgraded Living Atlas Content For GroupId - $groupId"
                    $done = $True
                }
            }catch{         
                if($attempts -eq 3){
                    Write-Verbose "Unable to Living Atlas Content For GroupId - $groupId"
                }
            }
            if($attempts -eq 3){
                $done = $True
            }
            $attempts++
        }
    }
}

function Test-IfLivingAtlasUpgraded
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )
    $result = $False
    $LivingAtlasGroupIds = Get-LivingAtlasGroupIds -URL $URL -Referer $Referer -Token $Token
    foreach($groupId in $LivingAtlasGroupIds){
        $done = $False
        $attempts = 0
        while(-not($done)){
            $LAUpgradeStatusCheckURL =  Get-PortalAdminUrlForPath -URL $URL -Path "/system/content/livingatlas/status"
            try{
                $resp = Invoke-ArcGISWebRequest -Url $LAUpgradeStatusCheckURL -HttpFormParameters @{ f = 'json'; token = $Token; groupId = $groupId } -Referer $Referer -Verbose
                if($resp.upgraded -eq $True -or $resp.upgraded -ieq 'true'){
                    $result = $True
                }else{
                    $result = $False
                }
                $done = $True
            }catch{
                if($attempts -eq 3){
                    Write-Verbose "Unable to Living Atlas Content For GroupId - $groupId - Please Follow manual steps specified in the documentation"
                    $done = $True
                    $result = $False
                }
            }
            if($attempts -eq 3){
                $done = $True
            }
            $attempts++
        }
        if($result -ieq $False){
            break
        }
    }
    $Result
}

function Test-PostUpgrade
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )

    $PortalAdminURL = Get-PortalAdminUrlForPath -URL $URL
    $Response = Invoke-ArcGISWebRequest -Url $PortalAdminURL -HttpFormParameters @{ token = $Token; f = 'json' } -Referer $Referer -Verbose -HttpMethod "GET"
    return -not($Response.isPostUpgrade)
}

function Invoke-PostUpgrade
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Token,

        [System.String]
        $Referer = 'https://localhost'
    )

    Write-Verbose "Invoking Post Upgrade step"
    [string]$postUpgradeUrl =  Get-PortalAdminUrlForPath -URL $URL -Path "/postUpgrade"
    $postUpgradeResponse = Invoke-ArcGISWebRequest -Url $postUpgradeUrl -HttpFormParameters @{f = 'json'; token = $Token} -Referer $Referer -TimeOutSec 3000 -Verbose
    try{
        if($postUpgradeResponse.status -ieq "success" -or $postUpgradeResponse.status -ieq "success with warnings"){
            Write-Verbose "Post Upgrade Step Successful"
            if($postUpgradeResponse.status -ieq 'success with warnings'){
                Write-Verbose "[WARNING]:- $(ConvertTo-Json $postUpgradeResponse -Compress -Depth 5)"
            }

            if($postUpgradeResponse.recheckAfterSeconds){
                Wait-RecheckAfterSeconds -Seconds $postUpgradeResponse.recheckAfterSeconds -Multiplier 3 -Verbose
            }
        }else{
            $ResponseJSON = (ConvertTo-Json $postUpgradeResponse -Compress -Depth 5)
            throw "Post upgrade step failed. Response - $($ResponseJSON)"
        }
    }catch{
        throw  "[ERROR]:- $_"
    }
}

function Test-PortalUpgrade
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Referer = 'https://localhost'
    )

    $result = $False

    $PortalAdminURL =  Get-PortalAdminUrlForPath -URL $URL
    try{
        $TestPortalResponse = Invoke-ArcGISWebRequest -Url $PortalAdminURL -HttpFormParameters @{ f = 'json' } -Referer $Referer -Verbose -HttpMethod 'GET'
        if($TestPortalResponse.status -ieq "error" -and $TestPortalResponse.isUpgrade -ieq $true -and $TestPortalResponse.messages[0] -ieq "The portal site has not been upgraded. Please upgrade the site and try again."){
            $result = $false
        }else{
            if(($null -ne $TestPortalResponse.error) -and $TestPortalResponse.error.message -ieq 'Token Required.'){
                Write-Verbose "Portal site is already upgraded."
                $result = $true
            }else{
                $jsresponse = ConvertTo-Json $TestPortalResponse -Compress -Depth 5
                throw "Unknown error. Response - $($jsresponse)"
            }
        }
    }catch{
        $result = $false
        Write-Verbose "[WARNING]:- $_"
    }
    return $result
}

function Invoke-UpgradePortal{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $Referer = 'https://localhost',

        [System.String]
        $Version,

        [System.Boolean]
        $EnableUpgradeSiteDebug,

        [System.String]
        $LicenseFilePath
    )

    Write-Verbose "Invoking portal site upgrade."
    [string]$UpgradeUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/upgrade"

    $WebParams = @{ 
        isBackupRequired = $true
        isRollbackRequired = $true
        f = 'json'
    }

    if([version]$Version -ge "11.0"){
        $WebParams["async"] = $true
        if(([version]$Version -ge "11.2") -and $EnableUpgradeSiteDebug){
            Write-Verbose "Enabling Debug for Upgrade Site"
            $WebParams["enableDebug"] = $true
        }
    } 

    $UpgradeResponse = $null
    if($LicenseFilePath){ 
        $UpgradeResponse = Invoke-UploadFile -url $UpgradeUrl -filePath $LicenseFilePath -fileContentType 'application/json' -fileParameterName 'file' `
                            -Referer $Referer -formParams $WebParams -Verbose 
        $UpgradeResponse = ConvertFrom-JSON $UpgradeResponse
    } else {
        $UpgradeResponse = Invoke-ArcGISWebRequest -Url $UpgradeUrl -HttpFormParameters $WebParams -Referer $Referer -TimeOutSec 86400 -Verbose 
    }

    if($UpgradeResponse){
        if([version]$Version -ge "11.0"){
            if($UpgradeResponse.status -ieq "in progress"){
                Write-Verbose "Upgrade in Progress"
                $PortalReady = $false
                while(-not($PortalReady)){
                    $UpgradeResponse = Invoke-ArcGISWebRequest -Url $UpgradeUrl -HttpFormParameters @{f = 'json'} -Referer $Referer -Verbose -HttpMethod 'GET'
                    if($UpgradeResponse.status -ieq "in progress"){
                        Write-Verbose "Response received:- Upgrade in progress"  
                        Start-Sleep -Seconds 20
                        $Attempts = $Attempts + 1
                    }else{
                        Write-Verbose "Response received:- $($UpgradeResponse.status)"
                        break
                    }
                }
            }
        }

        if($UpgradeResponse.status -ieq 'success' -or $UpgradeResponse.status -ieq 'success with warnings') {
            Write-Verbose "Upgrade Successful"
            if($UpgradeResponse.status -ieq 'success with warnings'){
                Write-Verbose "[WARNING]:- $(ConvertTo-Json $UpgradeResponse -Compress -Depth 5)"
            }

            if($null -ne $UpgradeResponse.recheckAfterSeconds) 
            {
                Wait-RecheckAfterSeconds -Seconds $UpgradeResponse.recheckAfterSeconds -Multiplier 2 -Verbose
            }
            
            Test-PortalAdminHealth -URL $URL -MaxAttempts 10 -Referer $Referer -Verbose
        }else{
            throw  "[ERROR]:- $(ConvertTo-Json $UpgradeResponse -Compress -Depth 5)"
        }
    }else{
        throw "[ERROR]:- Upgrade failed. Null response returned."
    }
}

function Unregister-PortalWebAdaptor
{
    [CmdletBinding()]
    param(
        [System.String]
        $URL,

        [System.String]
        $WebAdaptorURL,

        [System.String]
        $Referer = 'https://localhost',

        [System.String]
        $Token
    )

    $WASystemUrl = Get-PortalAdminUrlForPath -URL $URL -Path "/system/webadaptors"
    $WebAdaptors = Invoke-ArcGISWebRequest -HttpMethod "GET" -Url $WASystemUrl -HttpFormParameters @{ token = $Token; f = 'json' } -Referer $Referer

    $WebAdaptors.webAdaptors | ForEach-Object {
        if($_.webAdaptorURL -ieq  $WebAdaptorUrl) {
            Write-Verbose "Webadaptor with URL $($_.webAdaptorURL) exists. Unregistering the web adaptor"
            Invoke-ArcGISWebRequest -Url ("$($WASystemUrl)/$($_.id)/unregister") -HttpFormParameters  @{ f = 'json'; token = $Token } -Referer $Referer -TimeOutSec 300    
        }
    }
}

Export-ModuleMember -Function *