ArcGISUtility.psm1

function ConvertTo-HttpBody($props)
{
    [string]$str = ''
    foreach($prop in $props.Keys){                
        $key = [System.Web.HttpUtility]::UrlEncode($prop)
        $value = [System.Web.HttpUtility]::UrlEncode($props[$prop])
        $str += "$key=$value&"
    }
    if($str.Length -gt 0) {
        $str = $str.Substring(0, $str.Length - 1)
    }
    $str
}


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

        [Parameter(Mandatory=$false)]
        [System.String]
        $ServerSiteName = 'arcgis', 
        
        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $Credential,

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

        [System.Int32]
        $Expiration=1000,

        [System.Int32]
        $MaxAttempts = 10
    )
    $url = ($ServerEndPoint.TrimEnd('/') + "/$ServerSiteName/admin/generateToken")
    $ServicePoint = [System.Net.ServicePointManager]::FindServicePoint($url)
    $ServicePoint.CloseConnectionGroup("")
    $token = $null
    $Done = $false
    $NumAttempts = 0
    while(-not($Done) -and ($NumAttempts -lt $MaxAttempts)) {
        try {
            $token = Invoke-ArcGISWebRequest -Url $url -HttpFormParameters @{ username = $Credential.GetNetworkCredential().UserName; password = $Credential.GetNetworkCredential().Password; client = 'referer'; referer = $Referer; expiration = $Expiration; f = 'json' } -Referer $Referer -TimeOutSec 45 
        }
        catch {
            Write-Verbose "[WARNING]:- Server at $url did not return a token on attempt $($NumAttempts + 1). Retry after 15 seconds"
        }
        if($token) {
            Write-Verbose "Retrieved server token successfully"
            $Done = $true
        }else {
            Start-Sleep -Seconds 15
            $NumAttempts = $NumAttempts + 1
        }
    }
    $token
}


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

        [Parameter(Mandatory=$false)]
        [System.String]
        $SiteName = 'arcgis', 

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

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

        [Parameter(Mandatory=$false)]
        [System.Int32]
        $Port = 7443,

        [System.Int32]
        $MaxAttempts = 10
    )
    $url = ("https://$($PortalHostName):$($Port)/$SiteName/sharing/rest/generateToken")
    $ServicePoint = [System.Net.ServicePointManager]::FindServicePoint($url)
    $ServicePoint.CloseConnectionGroup("")
    $token = $null
    $Done = $false
    $NumAttempts = 0
    while(-not($Done) -and ($NumAttempts -lt $MaxAttempts)) {
        try {
            $token = Invoke-ArcGISWebRequest -Url $url -HttpFormParameters @{ username = $Credential.UserName; password = $Credential.GetNetworkCredential().Password; referer = $Referer; f = 'json' } -Referer $Referer
            if($token.error){
                Write-Verbose "Error Response - $($token.error)"
                throw [string]::Format("ERROR: Unable to get Portal Token - {0}" , $token.error.message)
            }
        } catch {
            $token = $null
            Write-Verbose "[WARNING]:- Portal at $url did not return a token on attempt $($NumAttempts + 1). Retry after 15 seconds"
        }
        if($token) {
            Write-Verbose "Retrieved portal token successfully"
            $Done = $true
        }else {
            Start-Sleep -Seconds 15
            $NumAttempts = $NumAttempts + 1
        }
    }
    $token
}

function Confirm-ResponseStatus($Response, $Url)
{
  $parentFunc = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name

  if (!$Response) { 
    throw [string]::Format("ERROR: {0} response is NULL.URL:- {1}", $parentFunc, $Url)
  }
  if ($Response.status -and ($Response.status -ieq "error")) { 
    throw [string]::Format("ERROR: {0} failed. {1}" , $parentFunc,($Response.messages -join " "))
  }
  if ($Response.error) { 
    throw [string]::Format("ERROR: {0} failed. {1}" , $parentFunc,$Response.error.messages)
  }
}  

function Get-LastModifiedDateForRemoteFile
{
    [CmdletBinding()]
    [OutputType([System.DateTime])]
    param(
        [System.String]$Url
    )
    $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -UseDefaultCredentials -TimeoutSec 15 -Method Head -ErrorAction Ignore
    if($response) {
        return [DateTime]$response.Headers['Last-Modified']
    }else {
        return [DateTime]::MaxValue
    }
}

function Wait-ForServiceToReachDesiredState
{
 [CmdletBinding()]
 param(
    [Parameter(Mandatory=$true)]
    [System.String]
    $ServiceName,

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

    [System.Int32]
    $SleepTimeInSeconds=10,

    [System.Int32]
    $MaxSeconds=300,

    [System.Int32]
    $MaxAttempts=-1
  )
    
  $Attempts  = 0
  $Done      = $false
  $startTime = Get-Date

  while ($true)
  {
    if ($Attempts++ -gt 0) {  # to skip the message for first attempt
      Write-Verbose "Checking state of Service '$ServiceName'. Attempt # $Attempts"        
    }    
    
    $Service = Get-Service -Name $ServiceName -ErrorAction Ignore

    $msg = "Service '$ServiceName' not ready."
    if ($Service) {
      $msg  = "Service '$ServiceName' is in '$($Service.Status)' state."
      # exit if done
      if ($Service.Status -ieq $DesiredState) {
        Write-Verbose $msg
        return
      }
    } 

    Write-Verbose $msg       # not there yet, report current state

    # exit on timeout
    if (($MaxSeconds -gt 0) -and ($(Get-Date) - $startTime).TotalSeconds -ge $MaxSeconds) {
      return
    }  

    # exit on number of attempts
    if (($MaxAttempts -gt 0) -and ($Attempts -ge $MaxAttempts)) {
      return
    }

    Write-Verbose "Waiting $SleepTimeInSeconds seconds."
    Start-Sleep -Seconds $SleepTimeInSeconds
  }
}

function Wait-ForUrl
{
    [CmdletBinding()]
    param
    (
        [Parameter(Position = 0, Mandatory=$true)]
        [System.String]
        $Url, 

        [System.Int32]
        $MaxWaitTimeInSeconds = 150, 

        [System.Int32]
        $SleepTimeInSeconds = 5,

        [switch]
        $ThrowErrors,

        [System.String]
        $HttpMethod = 'GET',

        [System.Int32]
        $MaximumRedirection=5,

        [System.Int32]
        $RequestTimeoutInSeconds=15,
        
        [switch]
        $IsWebAdaptor
    )

    [bool]$Done = $false
    [int]$TotalElapsedTimeInSeconds = 0
    $WaitForError = $null
    Write-Verbose "Waiting for Url $Url"
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
    [System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls
    while((-not($Done)) -and ($TotalElapsedTimeInSeconds -lt $MaxWaitTimeInSeconds)) {
        try {
            if($HttpMethod -ieq 'GET') {
                [System.Net.HttpWebRequest]$webRequest = [System.Net.WebRequest]::Create($Url)
                $webRequest.Timeout  = ($RequestTimeoutInSeconds * 1000)
                $webRequest.AllowAutoRedirect = $MaximumRedirection -gt -1
                $webRequest.MaximumAutomaticRedirections = [System.Math]::Max(1, $MaximumRedirection)
                if($IsWebAdaptor){
                    $webRequest.Headers.Add('accept-language','en-US') 
                }
                $resp = $webRequest.GetResponse()
                Write-Verbose "Url is $($resp.StatusCode)"
                $Done = $true
            }
            else {
                $resp = Invoke-WebRequest -Uri $Url -UseBasicParsing -UseDefaultCredentials -ErrorAction Ignore -TimeoutSec $RequestTimeoutInSeconds -Method $HttpMethod -DisableKeepAlive -MaximumRedirection $MaximumRedirection
                if($resp) {
                    if(($resp.StatusCode -eq 200) -and $resp.Content) { 
                        $Done = $true
                        Write-Verbose "Url is ready : $Url"
                    }else{
                        $WaitForError = "[Warning]:- Response:- $($resp.Content)"
                        Write-Verbose $WaitForError
                    }
                }else {
                    $WaitForError = "[Warning]:- Response from $Url was NULL"
                    Write-Verbose $WaitForError
                }
            }
        }
        catch {
            $WaitForError = "[Warning]:- $($_)"
            Write-Verbose $WaitForError
        }
        if(-not($Done)) {
            Start-Sleep -Seconds $SleepTimeInSeconds
            $TotalElapsedTimeInSeconds += $SleepTimeInSeconds
        }
    }
    if($ThrowErrors -and -not($Done)){
        throw $WaitForError
    }
}

function Invoke-UploadFile
{   
    [CmdletBinding()]
    param
    (
        [System.String]
        $url, 
        
        [System.String]
        $filePath, 

        [System.String]
        $fileContentType, 
        
        $formParams,

        $httpHeaders,

        [System.String]
        $Referer,

        [System.String]
        $fileParameterName = 'file',

        [System.String]
        $fileName
    )


    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # Allow self-signed certificates
    [System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls
    [System.Net.WebRequest]$webRequest = [System.Net.WebRequest]::Create($url)
    $webRequest.ServicePoint.Expect100Continue = $false
    $webRequest.Method = "POST"
    $webRequest.Referer = $Referer
    $webRequest.Timeout = 5400000;

    if(-not($fileName) -or $fileName.Length -lt 1){
        $fileName = (Get-Item -Path $filePath).Name
    }

    if($httpHeaders){
        foreach($httpHeader in $httpHeaders.GetEnumerator())
        {
            if('Referer' -ine $httpHeader.Name) {
                $webRequest.Headers.Add($httpHeader.Name, $httpHeader.Value)
            }
        }
    }

    $boundary = [System.Guid]::NewGuid().ToString()
    $header = "--{0}" -f $boundary
    $footer = "--{0}--" -f $boundary
    $webRequest.ContentType = "multipart/form-data; boundary={0}" -f $boundary

    [System.IO.Stream]$reqStream = $webRequest.GetRequestStream()   

    $enc = [System.Text.Encoding]::GetEncoding("UTF-8")
    $headerPlusNewLine = $header + [System.Environment]::NewLine
    [byte[]]$headerBytes = $enc.GetBytes($headerPlusNewLine)

    
    #### Use StreamWriter to write form parameters ####
    [System.IO.StreamWriter]$streamWriter = New-Object 'System.IO.StreamWriter' -ArgumentList $reqStream
    foreach($formParam in $formParams.GetEnumerator()) {
        [void]$streamWriter.WriteLine($header)
        [void]$streamWriter.WriteLine(("Content-Disposition: form-data; name=""{0}""" -f $formParam.Name))
        [void]$streamWriter.WriteLine("")
        [void]$streamWriter.WriteLine($formParam.Value)
    }
    $streamWriter.Flush()     

    [void]$reqStream.Write($headerBytes,0, $headerBytes.Length)

    [System.IO.FileInfo]$fileInfo = New-Object "System.IO.FileInfo" -ArgumentList $filePath   

    #### File Header ####
    $fileHeader = "Content-Disposition: form-data; name=""{0}""; filename=""{1}""" -f $fileParameterName, $fileName
    $fileHeader = $fileHeader + [System.Environment]::NewLine    
    [byte[]]$fileHeaderBytes = $enc.GetBytes($fileHeader)
    [void]$reqStream.Write($fileHeaderBytes,0, $fileHeaderBytes.Length)
    
    #### File Content Type ####
    [string]$fileContentTypeStr = "Content-Type: {0}" -f $fileContentType
    $fileContentTypeStr = $fileContentTypeStr + [System.Environment]::NewLine + [System.Environment]::NewLine
    [byte[]]$fileContentTypeBytes = $enc.GetBytes($fileContentTypeStr)
    [void]$reqStream.Write($fileContentTypeBytes,0, $fileContentTypeBytes.Length)    
    
    #### File #####
    [System.IO.FileStream]$fileStream = New-Object 'System.IO.FileStream' -ArgumentList @($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $fileStream.CopyTo($reqStream)
    $fileStream.Flush()
    $fileStream.Close()

    [void]$streamWriter.WriteLine("")        
    [void]$streamWriter.WriteLine($footer)
    $streamWriter.Flush()
    
    $resp = $null
    try {
        $resp =  $webRequest.GetResponse()    
    }catch {
        Write-Verbose "[WARNING] $url returned an error $_"
    }
    if($resp) {
        $rs = $resp.GetResponseStream()
        [System.IO.StreamReader]$sr = New-Object System.IO.StreamReader -argumentList $rs
        $sr.ReadToEnd()
    }else {
        $null
    }
}

function Invoke-LicenseSoftware
{
    [CmdletBinding()]
    param
    (
        [string]
        $Product, 

        [string]
        $ServerRole, 
        
        [string]
        $LicenseFilePath, 
        
        [System.Management.Automation.PSCredential]
        $LicensePassword, 
        
        [string]
        $Version, 

        [string]
        $StdOutputLogFilePath, 

        [string]
        $StdErrLogFilePath,
        
        [System.Boolean]
        $IsSingleUse
    )

    $SoftwareAuthExePath = "$env:SystemDrive\Program Files\Common Files\ArcGIS\bin\SoftwareAuthorization.exe"
    $LMReloadUtilityPath = ""
    if(@('Desktop','Pro','LicenseManager') -icontains $Product) {
        $SoftwareAuthExePath = "$env:SystemDrive\Program Files (x86)\Common Files\ArcGIS\bin\SoftwareAuthorization.exe"
        if($IsSingleUse -or ($Product -ne 'LicenseManager')){
            if($Product -ieq 'Desktop'){
                $SoftwareAuthExePath = "$env:SystemDrive\Program Files (x86)\Common Files\ArcGIS\bin\softwareauthorization.exe"
            }elseif($Product -ieq 'Pro'){
                $InstallLocation = (Get-ArcGISProductDetails -ProductName "Pro").InstallLocation
                $SoftwareAuthExePath = "$($InstallLocation)bin\SoftwareAuthorizationPro.exe"
            }
        }else{
            $LMInstallLocation = (Get-ArcGISProductDetails -ProductName "License Manager").InstallLocation
            if($LMInstallLocation){
                $SoftwareAuthExePath = "$($LMInstallLocation)bin\SoftwareAuthorizationLS.exe"
                $LMReloadUtilityPath = "$($LMInstallLocation)bin\lmutil.exe"
            }
        }
    }else{
        if($Product -ieq "Server" -and ($ServerRole -ieq "NotebookServer" -or $ServerRole -ieq "MissionServer" ) -and ($Version.Split('.')[1] -ge 8)){
            $ServerTypeName = "ArcGIS Server"
            if($ServerRole -ieq "NotebookServer"){ 
                $ServerTypeName = "Notebook Server" 
            }elseif($ServerRole -ieq "MissionServer"){ 
                $ServerTypeName = "Mission Server"
            }
            $InstallLocation = (Get-ArcGISProductDetails -ProductName $ServerTypeName).InstallLocation
            if($ServerRole -ieq "MissionServer"){
                $SoftwareAuthExePath = "$($InstallLocation)bin\SoftwareAuthorization.exe"
            }else{
                $SoftwareAuthExePath = "$($InstallLocation)framework\bin\SoftwareAuthorization.exe"
            }
        }
    }
    Write-Verbose "Licensing Product [$Product] using Software Authorization Utility at $SoftwareAuthExePath" -Verbose
    
    $Params = '-s -ver {0} -lif "{1}"' -f $Version,$licenseFilePath
    if($null -ne $LicensePassword){
        $Params = '-s -ver {0} -lif "{1}" -password {2}' -f $Version,$licenseFilePath,$LicensePassword.GetNetworkCredential().Password
    }
    Write-Verbose "[Running Command] $SoftwareAuthExePath $Params" -Verbose
    
    if($StdOutputLogFilePath) {
        [bool]$Done = $false
        [int]$AttemptNumber = 1
        $err = $null
        while(-not($Done) -and ($AttemptNumber -le 10)) {
            Start-Process -FilePath $SoftwareAuthExePath -ArgumentList $Params -Wait -RedirectStandardOutput $StdOutputLogFilePath -RedirectStandardError $StdErrLogFilePath
            [string]$LicenseFileOutput = Get-Content $StdOutputLogFilePath
            if($LicenseFileOutput -and (($LicenseFileOutput.IndexOf('Error') -gt -1) -or ($LicenseFileOutput.IndexOf('(null)') -gt -1))) {
                $err = "[ERROR] - Attempt $AttemptNumber - Licensing for Product [$Product] failed. Software Authorization Utility returned $LicenseFileOutput"
                Write-Verbose $err
                Start-Sleep -Seconds (Get-Random -Maximum 61 -Minimum 30)
            }else{
                $Done = $True
                $err = $null
            }
            $AttemptNumber += 1
        }
        if($null -ne $err){
            throw $err
        }
    }
    else {
        Start-Process -FilePath $SoftwareAuthExePath -ArgumentList $Params
    }
    if($Product -ieq 'Desktop' -or $Product -ieq 'Pro') {
        Write-Verbose "Sleeping for 2 Minutes to finish Licensing"
        Start-Sleep -Seconds 120
    }
    if($Product -ieq 'LicenseManager'){
        Write-Verbose "Re-readings Licenses"
        Start-Process -FilePath $LMReloadUtilityPath -ArgumentList 'lmreread -c @localhost'
    }
    Write-Verbose "Finished Licensing Product [$Product]" -Verbose
}

function Get-EsriRegistryKeyForService([string]$ServiceName)
{
    $RegKey = $ServiceName
    if($ServiceName -ieq 'ArcGIS Server')
    {
        $RegKey = 'ArcGIS_SXS_Server'
    }
    "HKLM:\SOFTWARE\ESRI\$RegKey"
}

function Invoke-ArcGISWebRequest
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [System.String]
        $Url, 

        [Parameter(Mandatory=$true)]
        $HttpFormParameters,
        
        [Parameter(Mandatory=$false)]
        [System.String]
        $Referer = 'http://localhost',

        [Parameter(Mandatory=$false)]
        [System.Int32]
        $TimeOutSec = 30,

        [Parameter(Mandatory=$false)]
        [System.String]
        $HttpMethod = 'Post'
    )

    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} # Allow self-signed certificates
    [System.Net.ServicePointManager]::DefaultConnectionLimit = 1024
    [System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls
    $HttpBody = ConvertTo-HttpBody $HttpFormParameters
    $Headers = @{'Content-type'='application/x-www-form-urlencoded'
                    'Content-Length' = $HttpBody.Length
                    'Accept' = 'text/plain'     
                    'Referer' = $Referer             
                }
    if($HttpMethod -ieq 'GET') {
        $UrlWithQueryString = $Url
        if($UrlWithQueryString.IndexOf('?') -lt 0) {
            $UrlWithQueryString += '?'
        }else {
            $UrlWithQueryString += '&'
        }
        $UrlWithQueryString += $HttpBody
        $wc = New-Object System.Net.WebClient
        if($Referer) {
            $wc.Headers.Add('Referer', $Referer)
        }
        $res = $wc.DownloadString($UrlWithQueryString)
        Write-Verbose "Response:- $res"
        if($res) {
            $response = $res | ConvertFrom-Json
            $response
        }else {
            Write-Verbose "Response from $Url is NULL"
        }
     }else {
        $Headers = @{'Content-type'='application/x-www-form-urlencoded'
                'Content-Length' = $HttpBody.Length
                'Accept' = 'text/plain'     
                'Referer' = $Referer             
            }        
        $res = Invoke-WebRequest -Method $HttpMethod -Uri $Url -Body $HttpBody -Headers $Headers -UseDefaultCredentials -DisableKeepAlive -UseBasicParsing -TimeoutSec $TimeOutSec   
        if($res -and $res.Content) {          
            Write-Verbose "Response:- $($res.Content)"
            $response = $res.Content | ConvertFrom-Json
            $response  
        }else { 
            throw "Request to $Url failed. Response returned NULL"
        }
    }    
}

function Get-PropertyFromPropertiesFile
{
    [CmdletBinding()]
    param(
        [string]
        $PropertiesFilePath,

        [string]
        $PropertyName
    )
    
    $PropertyValue = $null
    if(Test-Path $PropertiesFilePath) {
        Get-Content $PropertiesFilePath | ForEach-Object {
            if($_ -and $_.StartsWith($PropertyName)){
                $Splits = $_.Split('=')
                if($Splits.Length -gt 1){
                    $PropertyValue = $Splits[1].Trim()
                }
            }
        }
    }
    $PropertyValue
}

function Set-PropertyFromPropertiesFile
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [System.String]
        $PropertiesFilePath,

        [System.String]
        $PropertyName,

        [System.String]
        $PropertyValue
    )      

    $Changed = $false       
    $Lines = @()
    $Exists = $false
    $Commented = $false
    $CommentedProperty = '#' + $PropertyName
    if(Test-Path $PropertiesFilePath) {
       
        Get-Content $PropertiesFilePath | ForEach-Object {
            $Line = $_
            if($_ -and $_.StartsWith($PropertyName)){
                $Line = "$($PropertyName)=$($PropertyValue)"
                $Splits = $_.Split('=')
                if(($Splits.Length -gt 1) -and ($Splits[1].Trim() -ieq $PropertyValue)){
                    $Exists = $true
                    Write-Verbose "Property entry for '$PropertyName' already exists in $PropertiesFilePath and matches expected value '$PropertyValue'"
                }
            }
            elseif($_ -and $_.StartsWith($CommentedProperty)){
                Write-Verbose "Uncomment existing property entry for '$PropertyName'"
                $Lines += "$($PropertyName)=$($PropertyValue)"
                $Commented = $true
            }
            else {
                $Lines += $Line
            }
        }
        if(-not($Exists) -and (-not($Commented))) { 
            Write-Verbose "Adding entry $PropertyName = $PropertyValue to $PropertiesFilePath"
            $Lines += "$($PropertyName)=$($PropertyValue)" 
            $Lines += [System.Environment]::NewLine # Add a newline
        }
    }else{
        $Lines += "$($PropertyName)=$($PropertyValue)"
    }
    if(-not($Exists) -or $Commented) {        
        Write-Verbose "Updating file $PropertiesFilePath"
        Set-Content -Path $PropertiesFilePath -Value $Lines -Force 
        $Changed = $true
    }
    Write-Verbose "Changed applied:- $Changed"
    $Changed
}

function Confirm-PropertyInPropertiesFile
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [System.String]
        $PropertiesFilePath,

        [System.String]
        $PropertyName,

        [System.String]
        $PropertyValue
    )  

    $CurrentValue = Get-PropertyFromPropertiesFile -PropertiesFilePath $PropertiesFilePath -PropertyName $PropertyName
    if($CurrentValue -ne $PropertyValue)
    {
        Write-Verbose "Current Value for '$PropertyName' is '$CurrentValue'. Expected value is '$PropertyValue'. Changing it"
        Set-PropertyFromPropertiesFile -PropertiesFilePath $PropertiesFilePath -PropertyName $PropertyName -PropertyValue $PropertyValue -Verbose        
    }else {
        Write-Verbose "Current Value for '$PropertyName' is '$CurrentValue' and matches expected value. No change needed"
        $false
    }
}

function Add-HostMapping
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        $hostname, 
        $ipaddress
    )
    $returnValue = $false
    if((-not($hostname)) -or (-not($ipaddress))){ return $returnValue }

    $file = "$env:SystemRoot\System32\drivers\etc\hosts"
    $contents = Get-Content $file 
    $exists = $false
    foreach($content in $contents){
        if($content -and (-not($content.StartsWith('#'))) -and ($content.StartsWith($hostname)))
        {
            $exists = $true
        }
    }    
    if($exists){
        Write-Verbose "Entry '$hostname $ipaddress' already exists in $file"
    }else{
        Write-Verbose "Adding entry '$hostname`t`t$ipaddress' to $file"
        Add-Content -Value "" -Path $file -Force  # Add a new line
        Add-Content -Value "$hostname`t`t$ipaddress`t`t# $hostname" -Path $file -Force
    }
    $returnValue
}

function Get-ConfiguredHostName
{
    [CmdletBinding()]
    param(
        [string]$InstallDir
    )

    $File = Join-Path $InstallDir 'framework\etc\hostname.properties'
    $HostName = $null
    if(Test-Path $File) {
        Get-Content $File | ForEach-Object {
            if($_ -and $_.StartsWith('hostname')){
                $Splits = $_.Split('=')
                if($Splits.Length -gt 1){
                    $HostName = $Splits[1].Trim()
                }
            }
        }
    }
    $HostName
}

function Set-ConfiguredHostName
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [string]$InstallDir,

        [Parameter(Mandatory=$true)]
        [string]$HostName
    )

    $Changed = $false
    $File = Join-Path $InstallDir 'framework\etc\hostname.properties'    
    $Lines = @()
    $Exists = $false
    if(Test-Path $File) {
        Get-Content $File | ForEach-Object {
            $Line = $_
            if($_ -and $_.StartsWith('hostname')){
                $Line = "hostname=$($HostName)"
                $Splits = $_.Split('=')
                if(($Splits.Length -gt 1) -and ($Splits[1].Trim() -ieq $HostName)){
                    $Exists = $true
                    Write-Verbose "Host entry for $HostName already exists"
                }
            }else {
                $Lines += $Line
            }
        }
        if(-not($Exists)) { $Lines += "hostname=$($HostName)" }
    }else{
        $Lines += "hostname=$($HostName)"
    }
    if(-not($Exists)) {
        Write-Verbose "Adding entry $HostName to $File"
        $Changed = $true
        Set-Content -Path $File -Value $Lines
    }
    $Changed
}


function Get-ConfiguredHostIdentifier
{
    [CmdletBinding()]
    param(
        [string]$InstallDir
    )

    $File = Join-Path $InstallDir 'framework\etc\hostidentifier.properties'
    $HostIdentifier = $null
    if(Test-Path $File) {
        Get-Content $File | ForEach-Object {
            if($_ -and $_.StartsWith('hostidentifier')){
                $Splits = $_.Split('=')
                if($Splits.Length -gt 1){
                    $HostIdentifier = $Splits[1].Trim()
                }
            }
        }
    }
    $HostIdentifier
}

function Get-ConfiguredHostIdentifierType
{
    [CmdletBinding()]
    param(
        [string]$InstallDir
    )

    $File = Join-Path $InstallDir 'framework\etc\hostidentifier.properties'
    $HostIdentifier = $null
    if(Test-Path $File) {
        Get-Content $File | ForEach-Object {
            if($_ -and $_.StartsWith('preferredidentifier')){
                $Splits = $_.Split('=')
                if($Splits.Length -gt 1){
                    $HostIdentifier = $Splits[1].Trim()
                }
            }
        }
    }
    $HostIdentifier
}

function Set-ConfiguredHostIdentifier
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [string]$InstallDir,

        [Parameter(Mandatory=$true)]
        [string]$HostIdentifier,

        [ValidateSet('hostname','ip')]
        [string]$HostIdentifierType = 'hostname'
    )

    $Changed = $false
    $File = Join-Path $InstallDir 'framework\etc\hostidentifier.properties'    
    $Lines = @()
    $HostIdExists = $false
    $HostIdTypeExists = $false
    $HostIdChanged = $true
    $HostIdTypeChanged = $true
    if(Test-Path $File) {
        Get-Content $File | ForEach-Object {
            $Line = $_            
            if($Line -and ($Line.StartsWith('hostidentifier') -or $Line.StartsWith('#hostidentifier'))) {                
                $Line = "hostidentifier=$($HostIdentifier)"
                if(-not($_.StartsWith('#'))) {
                    $Splits = $_.Split('=')
                    if(($Splits.Length -gt 1) -and ($Splits[1].Trim() -ieq $HostIdentifier)){
                        $HostIdChanged = $false
                        Write-Verbose "Host entry for $HostIdentifier already exists"                    
                    }
                }
                $HostIdExists = $true
            }
            elseif($Line -and ($Line.StartsWith('preferredidentifier') -or $Line.StartsWith('#preferredidentifier'))) {
                $Line = "preferredidentifier=$($HostIdentifierType)"
                if(-not($_.StartsWith('#'))) {
                    $Splits = $_.Split('=')
                    if(($Splits.Length -gt 1) -and ($Splits[1].Trim() -ieq $HostIdentifierType)){
                        $HostIdTypeChanged = $false
                        Write-Verbose "Host identifier type entry for $HostIdentifierType already exists"
                    }
                }
                $HostIdTypeExists = $true
            }
            $Lines += $Line
        }
        if(-not($HostIdExists)) { $Lines += "hostidentifier=$($HostIdentifier)" }
        if(-not($HostIdTypeExists)) { $Lines += "preferredidentifier=$($HostIdentifierType)" }
    }else{
        $Lines += "hostidentifier=$($HostName)"
        $Lines += "preferredidentifier=$($HostIdentifierType)" 
    }
    if((-not($HostIdExists)) -or (-not($HostIdTypeExists)) -or $HostIdChanged -or $HostIdTypeChanged) {
        Write-Verbose "Adding/modifying entry $HostIdentifier or identifier type $HostIdentifierType to $File"
        $Changed = $true
        Set-Content -Path $File -Value $Lines
    }
    $Changed
}

function Get-ComponentCode
{
       [CmdletBinding()]
       param
       (
        [ValidateSet("Server","Portal","DataStore","GeoEvent","NotebookServer","MissionServer","Monitor","WebStyles","WebAdaptor","Desktop","Pro","LicenseManager")]
        [parameter(Mandatory = $true)]
        [System.String]
        $ComponentName,

        [ValidateSet("2.0","2.1","2.2","2.3","2.4","2.5","2.6","10.4","10.4.1","10.5","10.5.1","10.6","10.6.1","10.7","10.7.1","10.8","10.8.1","2018.0","2018.1","2019.0","2019.1","2019.2","2020.0")]
        [parameter(Mandatory = $true)]
        [System.String]
        $Version
    )
        
    $ProductCodes = @{
        Server = @{          
            '10.4' = '687897C7-4795-4B17-8AD0-CB8C364778AD'
            '10.4.1' = '88A617EF-89AC-418E-92E1-926908C4D50F'
            '10.5' = 'CD87013B-6559-4804-89F6-B6F1A7B31CBC'
            '10.5.1' = '40CC6E89-93A4-4D87-A3FB-11413C218D2C'
            '10.6' = '07606F78-D997-43AE-A9DC-0738D91E8D02'
            '10.6.1' = 'F62B418D-E9E4-41CE-9E02-167BE4276105'
            '10.7' = '98D5572E-C435-4841-A747-B4C72A8F76BB'
            '10.7.1' = '08E03E6F-95D3-4D33-A171-E0DC996E08E3'
            '10.8' = '458BF5FF-2DF8-426B-AEBC-BE4C47DB6B54'
            '10.8.1' = 'E9B85E31-4C31-4528-996B-F06E213F8BB3'
        }
        Portal = @{      
            '10.4' = 'FA6FCD2D-114C-4C04-A8DF-C2E43979560E'
            '10.4.1' = '31373E04-9B5A-4CD7-B668-0B1DE7F0D45F'
            '10.5' = '43EF63C6-957B-4DA7-A222-6904053BF222'
            '10.5.1' = 'C7E44FBE-DFA6-4A95-8779-B6C40F3947B7'
            '10.6' = 'FFE4808A-1AD2-41A6-B5AD-2BA312BE6AAA'
            '10.6.1' = 'ECC6B3B9-A875-4AE3-9C03-8664EB38EED9'
            '10.7' = '6A640642-4D74-4A2F-8350-92B6371378C5'
            '10.7.1' = '7FDEEE00-6641-4E27-A54E-82ACCCE14D00'
            '10.8' = '7D432555-69F9-4945-8EE7-FC4503A94D6A'
            '10.8.1' = '0803DE56-BAE9-49F5-A120-BA249BD924E2'
        }
        WebStyles = @{ 
            '10.7.1' = 'B2E42A8D-1EE9-4610-9932-D0FCFD06D2AF'
            '10.8' = 'EF31CB36-2EB4-4FD3-A451-AC12FD22A582'
            '10.8.1' = '7748EA55-04FF-45E2-98EC-C78095AC25AA'
        }
        DataStore = @{             
            '10.4' = 'C351BC6D-BF25-487D-99AB-C963D590A8E8'
            '10.4.1' = 'A944E0A7-D268-41CA-B96E-8434457B051B'
            '10.5' = '5EA81114-6FA7-4B4C-BD72-D1C882088AAC'
            '10.5.1' = '75276C83-E88C-43F6-B481-100DA4D64F71'
            '10.6' = '846636C1-53BB-459D-B66D-524F79E40396'
            '10.6.1' = '53160721-93D8-48F8-9EDD-038794AE756E'
            '10.7' = '2B19AB45-1A17-45CD-8001-0608E8D72447'
            '10.7.1' = '112E5FD0-9DD2-45DA-ACD5-A21AA45F67E2'
            '10.8' = '2018A7D8-CBE8-4BCF-AF0E-C9AAFB4C9B6D'
            '10.8.1' = '45E1C306-B1AB-4AE5-8435-818F0F9F8821'
        }        
        GeoEvent = @{             
            '10.4' = '188191AE-5A83-49E8-88CB-1F1DB05F030D'
            '10.4.1' = 'D71379AF-A72B-4B10-A7BA-64BC6AF6841B'
            '10.5' = '4375BD31-BD98-4166-84D9-E944D77103E8'
            '10.5.1' = 'F11BBE3B-B78F-4E5D-AE45-E3B29063335F'
            '10.6' = '723742C8-6633-4C85-87AC-503507FE222B'
            '10.6.1' = 'D0586C08-E589-4942-BC9B-E83B2E8B95C2'
            '10.7' = '7430C9C3-7D96-429E-9F47-04938A1DC37E'
            '10.7.1' = '3AE4EE62-B5ED-45CB-8917-F761B9335F33'
            '10.8' = '10F38ED5-B9A1-4F0C-B8E2-06AD1365814C'
            '10.8.1' = 'C98F8E6F-A6D0-479A-B80E-C173996DD70B'
        }
        NotebookServer = @{
            '10.7' = '3721E3C6-6302-4C74-ACA4-5F50B1E1FE3A'
            '10.7.1' = 'F6DF77B9-F35E-4877-A7B1-63E1918B4E19'
            '10.8' = 'B1DB581E-E66C-4E58-B9E3-50A4D6CB5982'
            '10.8.1' = '55DE1B3D-DDFB-4906-81F2-B573BAC25018'
        }
        MissionServer = @{
            '10.8' = 'A1A58B32-2ADF-4EAD-AC84-BE97318CA569'
            '10.8.1' = '26F574C6-C9F8-487C-977A-A906AAA60136'
        }
        Monitor = @{
            '10.7' = '0497042F-0CBB-40C0-8F62-F1922B90E12E'
            '10.7.1' = 'FF811F17-42F9-4539-A879-FC8EDB9D6C46'
            '10.8' = '98AA3E79-18C5-4D51-9477-C844C6EBC6F3'
            '10.8.1' = 'F2E2767F-B7FB-43DD-A682-31544DF29D48'
        }
        Desktop = @{
            '10.4' = '72E7DF0D-FFEE-43CE-A5FA-43DFC25DC087'
            '10.4.1' = 'CB0C9578-75CB-45E5-BD81-A600BA33B0C3'
            '10.5' = '76B58799-3448-4DE4-BA71-0FDFAA2A2E9A'
            '10.5.1' = '4740FC57-60FE-45BB-B513-3309F6B73183'
            '10.6' = 'F8206086-367E-44E4-9E24-92E9E057A63D'
            '10.6.1' = 'FA2E2CBC-0697-4C71-913E-8C65B5A611E8'
            '10.7' = 'BFB4F32E-38DF-4E8F-8180-C99FC9A14BBE'
            '10.7.1' = '69262D87-3697-492B-ABED-765DDC15118B'
            '10.8' = '3DB5C522-636F-4FC2-9C38-298DBEBFD0BC'
            '10.8.1' = '7C4FF945-CE6A-415E-8EB9-2B61B0B35DCD'
        }
        LicenseManager = @{
            '2018.0' = 'CFF43ACB-9B0C-4725-B489-7F969F5B90AB'
            '2018.1' = 'E1C26E47-C6AB-4120-A3DE-2FA0F723C876'
            '2019.0' = 'CB1E78B5-9914-45C6-8227-D55F4CD5EA6F'
            '2019.1' = 'BA3C546E-6FAC-405C-B2C9-30BC6E26A7A9'
            '2019.2' = '77F1D4EB-0225-4626-BB9E-7FCB4B0309E5'
            '2020.0' = 'EEE800C6-930D-4DA4-A61A-0B1735AF2478'
        }
        Pro = @{
            '2.0' = '28A4967F-DE0D-4076-B62D-A1A9EA62FF0A'
            '2.1' = '0368352A-8996-4E80-B9A1-B1BA43FAE6E6'
            '2.2' = 'B5E1FB35-5E9D-4B40-ABA5-20F29A186889'
            '2.3' = '9CB8A8C5-202D-4580-AF55-E09803BA1959'
            '2.4' = 'E3B1CE52-A1E6-4386-95C4-5AB450EF57BD'
            '2.5' = '0D695F82-EB12-4430-A241-20226042FD40'    
            '2.6' = '612674FE-4B64-4254-A9AD-C31568C89EA4'
        }
        WebAdaptor = @{
            '10.4' = @('B83D9E06-B57C-4B26-BF7A-004BE10AB2D5','E2C783F3-6F85-4B49-BFCD-6D6A57A2CFCE','901578F9-BC82-498D-A008-EC3F53F6C943','E3849BEC-6CAF-463F-8EFA-169116A32554','EE889E4F-85C7-4B8A-9DAA-5103C9E14FD6','89D96D88-CC2F-4E9B-84DD-5C976A4741EE','0913DB77-F27B-4FDE-9F51-01BB97BBEBB9','99B6A03C-D208-4E2E-B374-BA7972334396','A0F3D072-0CD1-43D7-AFDA-8F47B15C217C','0FE26871-21C3-4561-B52E-A8FED5C8E821','1D1F3C15-F368-44AF-9728-6CF031D478AF','CE5EC52D-B54D-4381-9F6E-2C08F7721620','E71AEC5B-25F0-47E5-B52C-847A1B779E48','5DA1F056-A3F1-46D5-8F2E-74E72F85B51B','1EB3D37A-902A-43E2-9EAA-1B43BA10C369','839FFEB7-76B5-4CBB-A05E-E2276FC3421D','594E1C33-1C6D-49B4-A83F-2A780193B75F','34330B0C-34CD-4DCF-A68D-FDE7A1834659','42A96EC7-7CA9-4F68-B946-E9BF84713605','A1A8DAE4-B6F9-446F-8F6A-487F1E07A434','3BF277C6-6A88-4F72-A08C-54F1E45F44E5')
            '10.4.1' = @('F53FEE2B-54DD-4A6F-8545-6865F4FBF6DC','475ACDE5-D140-4F10-9006-C804CA93D2EF','0547D7D8-7188-4103-9387-A99FE15215AF','25DFAFFF-07CE-42A2-B157-541D7980A3DA','771998A8-A440-4F5F-B55A-0FE2C594208B','C120DC32-DBEA-4CB1-94E4-F50A7EE09F5C','3294151B-CA4C-4A89-BBC7-DCE521D8A327','E04FB941-248D-4806-9871-04DB306EEA91','66CD667D-440D-4CF1-9ECB-C6C77A7A0520','7938463B-E744-4332-8617-39E91B10FC15','C22C2AF5-D58E-4A4D-85DF-E3A38C83F37A','9AF62D15-755B-43DE-878A-DBA23D33B28A','D4F22207-C5FA-49B0-9466-9E4D37435882','C8ADE9B2-3BC8-4417-97D0-785BA0CD86D9','C85A40C5-00B9-4CDE-9299-397BFD5A2EAF','E0BD73FB-4028-4A5D-9A24-9FA0BD614D4B','83CF76EC-F959-46B3-9067-F59B2A846D2F','F7D6BD55-8D07-4A57-8284-ADACE0F567D8','C56A0E47-D4E1-4762-9BAF-07A19A154EE6','09AC608B-7CE4-4280-9F4E-F2988A58428D','5695B2B6-A25E-4013-B5F8-30686FDDFE0D')
            '10.5' = @('87B4BD93-A5E5-469E-9224-8A289C6B2F10','604CF558-B7E1-4271-8543-75E260080DFA','9666ABD8-8485-4383-B3DD-4D1598F582A3','58264BBA-5F61-41D9-839A-00B6C2C66A63','5988C905-772F-4F62-8339-1796C38674B7','ADD5FF4F-EB57-4460-BD33-D55562AE6FA7','3294151B-CA4C-4A89-BBC7-DCE521D8A327','EF65064A-96C8-4EA1-B76D-B9BCC97EF76A','6B2FA0A8-6F2C-4359-B7A4-D2F9FD63EE97','ACF59C57-A613-44CC-A927-1D8C2B280516','2E5E4CDE-9964-4B40-A1F1-843C62AC789E','2901A5D3-C16D-4993-A306-86261B0430B1','AC910B51-6077-4055-B042-D72CA0D23D69','8F36D583-35F0-43F2-8F8F-5B696F87183A','37C2CAE2-4A81-4289-B318-93D63C63AA47','CC345B69-1E26-4C56-B640-92BCBADBDF06','F0FAE80D-0C51-4D9D-A79B-057396A2456D','5BA355D1-D9B6-4CA0-B1C6-694377084464','25118D44-AD2D-423F-85F0-5D730A2691B7','D4855344-CEE0-47A3-BD50-D7E2A674D04E','9CD66AA3-F0DA-46CC-A5DD-0BB5B23499AD')
            '10.5.1' = @('0A9DA130-E764-485F-8C1A-AD78B04AA7A4','B8A6A873-ED78-47CE-A9B4-AB3192C47604','7DEAE915-5FAC-4534-853D-B4FC968DBDEB','AC10F3CF-A5C1-44B0-8271-27F26D323D14','5F748B0C-3FB6-42FF-A82D-C1573D3C1469','428DE39D-BF23-42B5-A70E-F5DD5DD21C2C','98B1DE9B-0ECF-4CAA-A29A-B89B2E8F38F1','4876508B-31CF-4328-BE11-FFF1B07A3923','D803A89F-4762-4EFD-8219-55F4C3047EDE','4A5F404B-391F-4D13-9EE4-5B9AC434FD5A','99FFFA13-2A40-4AA4-AAC1-9891F2515DB1','2B04DE60-3E79-4B44-9A93-40CAC72DE2FB','D595C9E2-BBA0-4708-A871-1166CD0CFB61','50825C57-5040-436D-B64C-A53FFB897E9D','5D750A11-BC80-45CE-B0DD-33BA8A5D8224','60390703-9077-4DDE-8BB1-A025AB0FE75B','BF75DC6C-F1A5-4A3C-A6A6-76BCB5DB5881','96B29B2F-888A-4C2B-B8C3-97E9A7849F2F','7FDD9158-2E93-4E12-A249-CD9D5445C527','A868CBAC-D9A2-41A7-8A5B-069AB63FEC7B','83462AE4-27BB-4B63-9E3E-F435BD03BB12')
            '10.6' = @('4FB9D475-9A23-478D-B9F7-05EBA2073FC7','38DBD944-7F0E-48EB-9DCB-98A0567FB062','8214B9D8-49D9-43DB-8632-AE2BAD4B21E9','B3FD1FE3-4851-4657-9754-73876D4CB265','88CDE5E9-23B8-4077-9E69-9CD3715BE805','E7630CBC-96DE-4665-9C2A-D682CFFD5B0E','E2601F84-D2E5-4DD4-B0EC-6AED75CB77D9','75FB755F-AF36-484E-98A8-FADA56574D25','AA32D01D-27CD-4842-90CF-F22C1BD6309B','CF126207-4C89-44AA-8783-9BAA2BA5F106','9F8694BE-613F-4195-AA42-B849E84D059A','2C3BE00F-57BE-4D0B-81BC-3378D823CF0E','EAC54B65-D6BC-41DC-8C82-5E99D7FD4271','76C17CB6-106C-41F8-89BA-41C086E69238','4493EB64-CAE0-439F-8FA6-897677D5A6C8','0C59A27D-B4B6-4A23-8873-897B870F6E2B','B46B6E63-D8E1-4EA4-9A9B-D98DFAA6644D','89E6330E-6139-4F4B-BA9F-ACD59065230D','238E647E-53DF-4B8B-B436-ADA5004065DE','30EF8944-904A-45D3-96D4-7DF3B0FE01D5','06012CC0-5C12-4499-B5CC-99E9D547A2FD')
            '10.6.1' = @('1B4E7470-72F4-4169-92B9-EF1BDF8AE4AF','3FA8B44E-E0E3-4245-A662-6B81E1E75048','8D6EE2C0-A393-49CD-A048-1D9FD435D7B8','6C05375F-78A5-4DF7-A157-C2A6E0D7BAD2','1F1EEC9F-80D5-48DD-B8BC-EB3D0404D9AD','2CA5FC7F-1165-4870-9C16-004ACC848435','E23D8BB8-0FEB-4316-8E09-80772FD1B5E0','C67E73AB-8278-49C9-9CA8-A714E20B8124','E527C0BD-E026-46D8-9461-2A7EEFBDA35A','D5DF4279-E3FF-4261-AB85-93F8BDE90D8D','8D439456-493A-4B48-A542-492AABD9CF7D','D61CE1AE-2DB8-4D46-AC7F-3BEAB7C29A59','9B07A4CE-58C6-4689-B37B-EFF565712CF2','C97C2CEF-F939-496E-8CB7-8756856CBBC6','59079961-A0BA-48DD-9B07-45437FCBC42A','5372DAF1-7EB6-4822-843E-0B2F7A7B052B','D807F4E9-0F87-4B3C-8F93-456251226187','7BEB71AD-3958-41FB-8EC3-64DBE4775760','286D4CB5-777E-4AA1-B2EB-D6A3A4212107','37F3B528-915F-4528-949B-F199E4B3B4AA','6FEB4C76-14AC-4A70-BE45-6CBAED529CAF')
            '10.7' = @('F343B520-F769-4D93-86D2-663168AC6975','58A76431-E1A9-4D11-BB89-0D12C6E77C78','E7B9D4A3-4E55-49F8-B34C-CA8745FFD894','D89709DB-8B6D-431A-95D4-FFEB69C474D5','858283F5-B9E9-4688-BF3C-BD3F3FD169D8','DE2BA579-D2F0-4068-9C52-8AC24022F13D','48405A7D-CFA4-4F6F-BB8C-B36A22E99B07','BEC99E10-7AB1-4B90-9C81-D2CBFCAD4239','C0C178B9-EBC6-46C5-B009-186661E9AEA3','0D5F9D8E-B221-4C74-87C3-13050F906A94','52EC0A7A-9BBA-4F47-9C52-2E1D1D09D9B4','6CF9C794-AEC2-45EF-A11A-83938F01A1E9','F36AF2F5-2E37-409B-9E71-D2D2B1D4A22F','4F54A93E-2F0F-4515-99AA-83BF88961D5F','A04ACEF7-4E22-4F4F-8608-9FD335572C6F','0D562427-2AB5-46C6-998E-4C33D642DE10','8C15E459-D24F-46E0-B945-CD4812A040AC','24821676-BD09-49CA-95B4-591BBE86118A','3C4D06FD-8194-4062-AB04-E87003CBE908','71044157-41F9-4AEC-B6B1-834FBA256135','C4ECCD46-EC43-4C44-8147-916649A2BA1B')
            '10.7.1' = @('5ECEF84F-592C-47D1-B7C5-9F3D7E2AB7CE','5F1D01EA-296E-4226-A704-6A90E2916782','7368D3C8-F13B-4786-AE38-B51952F43867','2C838C64-DF81-4A64-8618-072AD30D51C1','D4054E1B-C75C-4F69-BBB7-DBE77250F345','C2C75F23-3E15-43E4-A902-673569F3F0DF','F633A04F-D08B-4EBF-B746-00ADA0334CE3','7D13D8C5-751F-44B1-BEAE-C9EB8E13FDF8','ACAA0479-B7C5-44A1-B5FD-34A018EA2137','E0343824-0C94-4A6C-96F7-AA5E1D8F8437','8D108926-DC71-493D-B2C9-8BAE04DD9047','A19DF635-25F0-4371-AC42-A4EECAF8BD75','78F54FC8-C530-4D7E-91CC-48B9BC364535','CEFB5C80-707B-471A-B8BF-5EC333F1A8B2','BE892BB6-842B-4A18-A0B7-E89C5AAAD1A3','A11006D2-3C1A-4D56-917D-4417D3341ADD','763E3951-E827-492B-8E86-F526F251083E','57C506AE-3BB7-4936-9154-7A2744735456','4E2BA3D3-EFD2-4FCE-91C0-1D15457F8F08','1BBB3C99-8EF5-4225-B5DD-799E50582BF7','F6E99E06-B303-4965-9566-2F21EE7FD130')
            '10.8' = @('D6059C27-7199-4A94-806B-6C40EFD02828','E77ED9CA-7DC8-45FC-A8BB-57AD2096EF8A','068CA630-558A-4C4A-983F-ECF5F8183FC9','F168EBD1-CEDA-469B-89E4-E79C85253CB6','96229607-EC9D-4805-AF94-E1AC47676586','D89375FD-6CAC-4204-8761-22F51B39E6A1','BABA485F-681E-4B5D-95EF-54CC738F4A0C','AEFE7DEE-1EEE-4F99-BCA7-2193B86C7058','FC4B3333-7AEB-46C6-AD90-E071A18D653F','119C0DB0-02B8-442C-B3F5-999D99C9D42F','89946E15-3E27-4F13-946F-30205F18D34B','5B21D9CD-DDC8-4768-88F6-3C0633E7B97E','C0752042-FAAC-4A94-B5A4-918BE46B7595','751ED05E-63BF-407C-9039-C72F33CC73D4','720EDDD5-B0FD-4E53-8F80-0F328EA8ABE0','5FBFC270-1AEE-4E41-B7A2-47F7C742EF95','A46D3ECC-39D2-459C-9BC3-1C780CA5BCF1','CAE80A6F-8046-47CE-B3F6-D2ACEDDDA99A','0B5C6775-B1D2-4D41-B233-FC2CDC913FEE','278663A9-7CA3-40D5-84EA-CA7A9CABACB6','9452D085-0F4F-4869-B8B4-D660B4DD8692')
            '10.8.1' = @('9695EF78-A2A8-4383-AFBF-627C55FE31DC','56F26E70-2C61-45BC-A624-E100175086F7','996B7BC1-3AF4-4633-AF5F-F7BE67448F51','0FB565D2-7C0E-4723-B086-C67443ADE5FD','246A7BFA-BE78-4031-A36D-F7BB95FFC5E2','7D3E447C-3DB7-488D-AB11-C84A02476FF5','1B5B7A25-F639-44F8-987B-6CD8F88967BE','4242D730-262E-45E0-8E1F-9060F03452C3','7CF4D730-F1D6-4D01-B750-D1BA7E55C3CC','179DB6A6-DFE4-4AF1-92D5-2FDDD831A783','0F67B656-1ED6-4C87-9DB3-DA51ABEE40C0','86F8D877-87EA-4296-9D48-64D7AE94330B','80FEB406-8086-42FE-B14B-C45A96B36894','5990ACD0-4A80-4115-BFAE-F8DEB498A7C0','12E78447-5AD7-41DB-82E5-BEDAAE7242C0','25A1ECD8-4FAA-4271-9586-6FD94549365D','221CCAE0-DA79-4C5E-ABCA-396E827334B1','508FBAA8-FD44-4998-B797-1666BD41D804','23EB5093-17EE-45A0-AE9F-C96B6456C15F','BEB8559D-6843-4EED-A2ED-7BA9325EF482','A12D63AE-3DE9-45A0-8799-F2BFF29A1665')
        }
    }
    $ProductCodes[$ComponentName][$Version]    
} 

Function Test-Install{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [parameter(Mandatory = $false)]
        [System.String]
        $Version,
        
        [parameter(Mandatory = $false)]
        [System.String]
        $ProductId
    )
    
    $result = $false
    $resultSetFlag = $false
    $ProdId = $null

    if(-not([string]::IsNullOrEmpty($ProductId))){
        if(-not([string]::IsNullOrEmpty($Version))){
            $ProdIdObject = Get-ComponentCode -ComponentName $Name -Version $Version
            if($Name -ieq "WebAdaptor"){
                if($ProdIdObject -icontains $ProductId){
                    $ProdId = $ProductId
                }else{
                    Write-Verbose "Given product Id doesn't match the product id for the version specified for Component $Name"
                    $result = $false
                    $resultSetFlag = $True
                }
            }else{
                if($ProdIdObject -ieq $ProductId){
                    $ProdId = $ProductId
                }else{
                    Write-Verbose "Given product Id doesn't match the product id for the version specified for Component $Name"
                    $result = $false
                    $resultSetFlag = $True
                }
            }
        }else{
            $ProdId = $ProductId
        }
    }else{
        if(-not([string]::IsNullOrEmpty($Version))){
            if($Name -ieq "WebAdaptor"){
                throw "Product Id is required for Component $Name"
            }else{
                $ProdId = Get-ComponentCode -ComponentName $Name -Version $Version
            }
        }else{
            throw "Product Id or Version is required for Component $Name"
        }
    }
    
    if(-not($resultSetFlag)){    
        if(-not($ProdId.StartsWith('{'))){
            $ProdId = '{' + $ProdId
        }
        if(-not($ProdId.EndsWith('}'))){
            $ProdId = $ProdId + '}'
        }
        $PathToCheck = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$($ProdId)"
        Write-Verbose "Testing Presence for Component '$Name' with Path $PathToCheck"
        if (Test-Path $PathToCheck -ErrorAction Ignore){
            $result = $true
        }
        if(-not($result)){
            $PathToCheck = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$($ProdId)"
            Write-Verbose "Testing Presence for Component '$Name' with Path $PathToCheck"
            if (Test-Path $PathToCheck -ErrorAction Ignore){
                $result = $true
            }
        }
    }
    $result
}



# http://www.andreasnick.com/85-reading-out-an-msp-product-code-with-powershell.html
<#
.SYNOPSIS
    Get the Patch Code from an Microsoft Installer Patch MSP
.DESCRIPTION
    Get a Patch Code from an Microsoft Installer Patch MSP (Andreas Nick 2015)
.NOTES
    $NULL for an error
.LINK
.RETURNVALUE
  [String] Product Code
.PARAMETER
  [IO.FileInfo] Path to the msp file
#>

function Get-MSPqfeID {
    param (
        [IO.FileInfo] $patchnamepath
          
    )
    try {
        $wi = New-Object -com WindowsInstaller.Installer
        $mspdb = $wi.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $wi, $($patchnamepath.FullName, 32))
        $su = $mspdb.GetType().InvokeMember("SummaryInformation", "GetProperty", $Null, $mspdb, $Null)
        #$pc = $su.GetType().InvokeMember("PropertyCount", "GetProperty", $Null, $su, $Null)

        [String] $qfeID = $su.GetType().InvokeMember("Property", "GetProperty", $Null, $su, 3)
        return $qfeID
    }
    catch {
        Write-Output -InputObject $_.Exception.Message
        return $NULL
    }
}

function Convert-PSObjectToHashtable
{
    param (
        [System.Object]
        $InputObject
    )

    process
    {
        if ($null -eq $InputObject) { return $null }

        if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
        {
            $collection = @(
                foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object }
            )

            Write-Output -InputObject $collection -NoEnumerate
        }
        elseif ($InputObject -is [psobject])
        {
            $hash = @{}

            foreach ($property in $InputObject.PSObject.Properties)
            {
                $hash[$property.Name] = Convert-PSObjectToHashtable $property.Value
            }

            $hash
        }
        else
        {
            $InputObject
        }
    }
}

function Register-WebAdaptorForPortal {
    [CmdletBinding()]
    param(
        [System.String]
        $PortalHostName = 'localhost', 

        [System.String]
        $SiteName = 'arcgis', 

        [System.Int32]
        $Port = 7443,
        
        [System.String]
        $Token, 

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

        [System.String]
        $WebAdaptorUrl, 

        [System.String]
        $MachineName, 

        [System.Int32]
        $HttpPort = 80, 

        [System.Int32]
        $HttpsPort = 443
    )
    [System.String]$RegisterWebAdaptorsUrl = ("https://$($PortalHostName):$($Port)/$($SiteName)" + "/portaladmin/system/webadaptors/register")
    Write-Verbose "Register Web Adaptor URL:- $RegisterWebAdaptorsUrl"
    $WebParams = @{ token = $Token
                    f = 'json'
                    webAdaptorURL = $WebAdaptorUrl
                    machineName = $MachineName
                    httpPort = $HttpPort.ToString()
                    httpsPort = $HttpsPort.ToString()
                  }
    try {
        Invoke-ArcGISWebRequest -Url $RegisterWebAdaptorsUrl -HttpFormParameters $WebParams -Referer $Referer -TimeoutSec 3000 -ErrorAction Ignore
    }
    catch {
        Write-Verbose "[WARNING] Register-WebAdaptorForPortal returned an error. Error:- $_"
    }
}

function Get-WebAdaptorsForPortal {
    [CmdletBinding()]
    param(
        [System.String]
        $PortalHostName = 'localhost', 

        [System.String]
        $SiteName = 'arcgis', 

        [System.Int32]
        $Port = 7443,
        
        [System.String]
        $Token, 

        [System.String]
        $Referer = 'http://localhost'
    )
    $GetWebAdaptorsUrl = "https://$($PortalHostName):$($Port)/$($SiteName)" + "/portaladmin/system/webadaptors"
    try{
        Invoke-ArcGISWebRequest -Url $GetWebAdaptorsUrl -HttpFormParameters @{ token = $Token; f = 'json' } -Referer $Referer -TimeoutSec 240 -HttpMethod 'GET'    
    }catch{
        Write-Verbose "[WARNING] Get-WebAdaptorsForPortal request to $($GetWebAdaptorsUrl) did not succeed. Error:- $_"
        $null
    }   
}

function UnRegister-WebAdaptorForPortal {
    [CmdletBinding()]
    param(
        [System.String]
        $PortalHostName = 'localhost', 

        [System.String]
        $SiteName = 'arcgis', 

        [System.Int32]
        $Port = 7443,
        
        [System.String]
        $Token, 

        [System.String]
        $Referer = 'http://localhost',
         
        [System.String]
        $WebAdaptorId
    )
    
    $UnRegisterWebAdaptorsUrl = "https://$($PortalHostName):$($Port)/$($SiteName)/portaladmin/system/webadaptors/$WebAdaptorId/unregister"
    try {
        Invoke-ArcGISWebRequest -Url $UnRegisterWebAdaptorsUrl -HttpFormParameters  @{ f = 'json'; token = $Token } -Referer $Referer -TimeoutSec 300  
    }catch{
        Write-Verbose "[WARNING] UnRegister-WebAdaptorForPortal on $UnRegisterWebAdaptorsUrl failed with error $($_)"
    }    
}

Export-ModuleMember -Function Invoke-ArcGISWebRequest,Invoke-LicenseSoftware,ConvertTo-HttpBody,Invoke-UploadFile,Wait-ForUrl,Get-LastModifiedDateForRemoteFile,Confirm-ResponseStatus `
                                ,Get-ServerToken,Get-PortalToken,Wait-ForServiceToReachDesiredState,Get-EsriRegistryKeyForService,Confirm-PropertyInPropertiesFile `
                                ,Get-PropertyFromPropertiesFile,Set-PropertyFromPropertiesFile,Add-HostMapping,Get-ConfiguredHostIdentifier,Set-ConfiguredHostIdentifier `
                                ,Get-ConfiguredHostName,Set-ConfiguredHostName,Get-ConfiguredHostIdentifierType,Get-ComponentCode,Test-Install,Get-MSPqfeID `
                                ,Convert-PSObjectToHashtable,Get-WebAdaptorsForPortal,Register-WebAdaptorForPortal,UnRegister-WebAdaptorForPortal