$ErrorActionPreference = 'Stop'
$PatchSource = $PSScriptRoot
$PhysicalPath = (Get-Item "$PatchSource\..\..").FullName
$PatchesPath = "$PhysicalPath\Patches"

$RunnerDirs = @(Get-ChildItem -Directory -Path $PhysicalPath -Filter Runner* | ForEach-Object {$_.FullName})

if (Test-Path -Path "$PhysicalPath\bin\SheetKraftWeb.dll")
{
    $HasWeb = $true
    $InstalledVersion = [Version] [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$PhysicalPath\bin\SheetKraftWeb.dll").FileVersion
}
if ($null -eq $InstalledVersion)
{
    $HasWeb = $false
    $InstalledVersion = [Version] [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$PhysicalPath\Runner\ActivityRunner.exe").FileVersion
}
$Version = [Version] [System.Diagnostics.FileVersionInfo]::GetVersionInfo("$PSScriptRoot\publish\bin\SheetKraftWeb.dll").FileVersion


function AddConfigSection
{
Param ([System.Xml.XmlDocument]$doc, [String]$sectionName, [String]$pathPrefix)

    $Configuration = $doc.DocumentElement

    $ConfigSections = $Configuration.SelectSingleNode('configSections')
    if ($null -eq $ConfigSections)
    {
	    $configSections = $doc.CreateElement('configSections')
	    [Void]$Configuration.PrependChild($configSections)
    }
    $section = $configSections.SelectSingleNode('section[@name="' + $sectionName + '"]')
    if ($null -eq $section)
    {
	    $section = $doc.CreateElement('section')
	    $section.SetAttribute('name', $sectionName)
	    [Void]$configSections.AppendChild($section)
    }
    $section.SetAttribute('type', 'System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')


    if ($null -eq $Configuration.SelectSingleNode($sectionName))
    {
	    $sectionElement = $doc.CreateElement($sectionName)
	    $sectionElement.SetAttribute('configSource', $pathPrefix + $sectionName + '.config')
	    [Void]$Configuration.InsertAfter($sectionElement, $configSections)
    }
}

function AddAWSConfigSection
{
Param ([System.Xml.XmlDocument]$doc, [String]$pathPrefix)

    $Configuration = $doc.DocumentElement

    $ConfigSections = $Configuration.SelectSingleNode('configSections')
    if ($null -eq $ConfigSections)
    {
	    $configSections = $doc.CreateElement('configSections')
	    [Void]$Configuration.PrependChild($configSections)
    }
    $section = $configSections.SelectSingleNode('section[@name="aws"]')
    if ($null -eq $section)
    {
	    $section = $doc.CreateElement('section')
	    $section.SetAttribute('name', 'aws')
        $section.SetAttribute('type', 'Amazon.AWSSection,AWSSDK.Core')
	    [Void]$configSections.AppendChild($section)
    }


    if ($null -eq $Configuration.SelectSingleNode('aws'))
    {
	    $sectionElement = $doc.CreateElement('aws')
        $sectionElement.SetAttribute('configSource', $pathPrefix + 'aws.config')
	    [Void]$Configuration.InsertAfter($sectionElement, $configSections)
    }
}

function AddNpgSqlProvider
{
Param ([System.Xml.XmlDocument]$doc)
    
    $Configuration = $doc.DocumentElement

    $ConfigSections = $Configuration.SelectSingleNode('configSections')
    $systemData = $Configuration.SelectSingleNode('system.data')
    if ($null -eq $systemData)
    {
        $systemData = $doc.CreateElement('system.data')
	    [Void]$Configuration.InsertAfter($systemData, $configSections)
    }
    $DbProviderFactories = $systemData.SelectSingleNode('DbProviderFactories')
    if ($null -eq $DbProviderFactories)
    {
        $DbProviderFactories = $doc.CreateElement('DbProviderFactories')
        [Void]$systemData.AppendChild($DbProviderFactories)
    }
    $addNpgSql = $DbProviderFactories.SelectSingleNode("add[@invariant='Npgsql']")
    if ($null -eq $addNpgSql)
    {
        $removeNpgSql = $doc.CreateElement('remove')
        $removeNpgSql.SetAttribute('invariant', 'Npgsql')
        [Void]$DbProviderFactories.AppendChild($removeNpgSql)
        $addNpgSql = $doc.CreateElement('add')
        $addNpgSql.SetAttribute('name', 'Npgsql Data Provider')
        $addNpgSql.SetAttribute('invariant', 'Npgsql')
        $addNpgSql.SetAttribute('description', 'Data Provider for PostgreSQL')
        $addNpgSql.SetAttribute('type', 'Npgsql.NpgsqlFactory, Npgsql')
        [Void]$DbProviderFactories.AppendChild($addNpgSql)
    }
}

function SetExeSupportedRuntimeSku
{
Param ([System.Xml.XmlDocument]$doc)
    
    $Configuration = $doc.DocumentElement

    $supportedRuntime = $Configuration.SelectSingleNode('startup/supportedRuntime')
    if ($null -ne $supportedRuntime)
    {
        $supportedRuntime.SetAttribute('sku', '.NETFramework,Version=v4.7.2')
    }
}

function AddConnectionManagement
{
Param ([System.Xml.XmlDocument]$doc)
    
    $Configuration = $doc.DocumentElement

    $systemDotNet = $Configuration.SelectSingleNode('system.net')
    if ($null -eq $systemDotNet)
    {
        $systemDotNet = $doc.CreateElement('system.net')
        [Void]$Configuration.InsertAfter($systemDotNet, $appSettings)
    }
    if ($null -eq $systemDotNet.SelectSingleNode('connectionManagement'))
    {
        $connectionManagement = $doc.CreateElement('connectionManagement')
        [Void]$systemDotNet.AppendChild($connectionManagement)
        $maxConnection = $doc.CreateElement('add')
	    $maxConnection.SetAttribute('address', '*')
	    $maxConnection.SetAttribute('maxconnection', '256')
        [Void]$connectionManagement.AppendChild($maxConnection)
    }
}

$asm = 'urn:schemas-microsoft-com:asm.v1'

function DeleteAssemblyRedirect
{
Param ([System.Xml.XmlDocument]$doc, [System.Xml.XmlNamespaceManager]$ns, [String]$assemblyName)

    $Configuration = $doc.DocumentElement

    $assemblyBinding = $Configuration.SelectSingleNode('runtime/asm:assemblyBinding', $ns)
    $assemblyIdentity = $assemblyBinding.SelectSingleNode('asm:dependentAssembly/asm:assemblyIdentity[@name="' + $assemblyName + '"]', $ns)
    if ($null -ne $assemblyIdentity)
    {
        $dependentAssembly = $assemblyIdentity.ParentNode
        [Void]$dependentAssembly.ParentNode.RemoveChild($dependentAssembly)
    }
}

function SetAssemblyRedirect
{
Param ([System.Xml.XmlDocument]$doc, [System.Xml.XmlNamespaceManager]$ns, [String]$assemblyName, [String]$publicKeyToken, [String]$oldVersion, [String]$newVersion)

    $Configuration = $doc.DocumentElement

    $runtime = $Configuration.SelectSingleNode('runtime', $ns)
    if ($null -eq $runtime)
    {
        $runtime = $doc.CreateElement('runtime')
        [Void]$Configuration.AppendChild($runtime)
    }
    $assemblyBinding = $runtime.SelectSingleNode('asm:assemblyBinding', $ns)
    if ($null -eq $assemblyBinding)
    {
        $assemblyBinding = $doc.CreateElement('assemblyBinding', $asm)
        [Void]$runtime.AppendChild($assemblyBinding)
    }
    $assemblyIdentity = $assemblyBinding.SelectSingleNode('asm:dependentAssembly/asm:assemblyIdentity[@name="' + $assemblyName + '"]', $ns)
    if ($null -eq $assemblyIdentity)
    {
        $dependentAssembly = $doc.CreateElement('', 'dependentAssembly', $asm)
        [Void]$assemblyBinding.AppendChild($dependentAssembly)
        $assemblyIdentity = $doc.CreateElement('', 'assemblyIdentity', $asm)
        $assemblyIdentity.SetAttribute('name', $assemblyName)
        $assemblyIdentity.SetAttribute('publicKeyToken', $publicKeyToken)
        $assemblyIdentity.SetAttribute('culture', 'neutral')
        [Void]$dependentAssembly.AppendChild($assemblyIdentity)
    }
    else
    {
        $dependentAssembly = $assemblyIdentity.ParentNode
    }
    $bindingRedirect = $dependentAssembly.SelectSingleNode('asm:bindingRedirect', $ns)
    if ($null -eq $bindingRedirect)
    {
        $bindingRedirect = $doc.CreateElement('', 'bindingRedirect', $asm)
        [Void]$dependentAssembly.AppendChild($bindingRedirect)
    }
    $bindingRedirect.SetAttribute('oldVersion', $oldVersion)
    $bindingRedirect.SetAttribute('newVersion', $newVersion)
}

function RemoveAssemblyRedirectsWithBackup
{
Param ([System.Xml.XmlDocument]$doc, [System.Xml.XmlNamespaceManager]$ns, [String]$backupPath)

    $Configuration = $doc.DocumentElement
    $assemblyBinding = $Configuration.SelectSingleNode('runtime/asm:assemblyBinding', $ns)
    if ($null -ne $assemblyBinding) {
        if (!(Test-Path -Path $backupPath)) {
            $assemblyBinding.OuterXml | Out-File -Encoding UTF8 -FilePath $backupPath
        }
        [Void]$assemblyBinding.ParentNode.RemoveChild($assemblyBinding)
    }
}

function LoadAndRemoveAssemblyRedirectsWithBackup
{
Param ([String]$configPath)
    if ((Test-Path -Path $configPath)) {
        $backupPath = "$configPath.bindingRedirects.backup"
        
        $ConfigDoc = New-Object System.Xml.XmlDocument
        $ConfigDoc.Load($configPath)
        
        $ns = New-Object System.Xml.XmlNamespaceManager($ConfigDoc.NameTable)
        $ns.AddNamespace('asm', $asm)

        RemoveAssemblyRedirectsWithBackup $ConfigDoc $ns $backupPath

        $ConfigDoc.Save($configPath)
    }
}

#Helper function for calculating file hash
$Sha256 = [System.Security.Cryptography.SHA256]::Create()
function CalculateHash
{
Param ([String]$FilePath)
    try
    {
        $stream = [System.IO.File]::OpenRead($FilePath)
        return [System.BitConverter]::ToString($Sha256.ComputeHash($stream)).Replace("-", "").ToLower()
    }
    finally
    {
        if ($null -ne $stream)
        {
            $stream.Close()
        }
    }
}

function DeleteFileIfExists
{
Param ([String]$path)
    if (Test-Path -Path $path)
    {
        Remove-Item -Path $path
    }
}

Function ProcessHashesFile
{
Param ([String]$HashesPath, [String]$src1, [String]$src2, [Boolean]$copy)
    ForEach ($line in [System.IO.File]::ReadAllLines($HashesPath))
    {
        $split = $line.Split(':');
        $filename = $split[0];
        $hash = $split[1];
        $filepath = "$src1\$filename"
        if (!(Test-Path -Path $filepath))
        {
            if ($copy) {continue;}
            $filepath = "$src2\$filename"
            if (!(Test-Path -Path $filepath))
            {$filepath = $null}
        }
        if ($null -eq $filepath)
        {
            Write-Host -ForegroundColor Yellow -BackgroundColor Black "$filename does not exist"
            return $false
        }
        if ($copy)
        {
			[System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName("$src2\$filename"))
            Copy-Item -Path $filepath -Destination "$src2\$filename"
        }
        else
        {
            $chash = CalculateHash $filepath
            if ($chash -ne $hash)
            {
                Write-Host -ForegroundColor Yellow -BackgroundColor Black "$filename does not have expected hash"
                return $false
            }
        }
    }
    return $true;
}
Write-Host "Validating that the patch can be applied properly..."

function VerifyHashes
{
Param([String]$PublishDir)

    if ($HasWeb)
    {
        if (!(ProcessHashesFile "$PublishDir\WebFiles.hashes" $PublishDir $PhysicalPath $false))
        {
            return $false
        }
    }
    Foreach ($RunnerDir in $RunnerDirs)
    {
        if (!(ProcessHashesFile "$PublishDir\Runner\RunnerFiles.hashes" "$PublishDir\Runner" $RunnerDir $false))
        {
            return $false
        }
    }
    return $true
}

$FullZipAvailable = $false
if (!(VerifyHashes "$PatchSource\publish"))
{
    $DesVerStr = "$($Version.Major).$($Version.Minor).$($Version.Build)$(if ($Version.Revision -eq 0) { '' } else { '.' + $Version.Revision })"
    $FullFile = "SheetKraft.Full.$DesVerStr.zip"
    $FullZip = "$PatchesPath\$FullFile"
    if ($PatchSource -eq "$PatchesPath\SheetKraft.Full.$DesVerStr")
    {
        Write-Host -ForegroundColor Red -BackgroundColor Black "Failed to find all required files in $PatchSource.
Download $FullZip again and retry."
        $Sha256.Dispose()
        exit -1
    }
    Write-Host -ForegroundColor Yellow -BackgroundColor Black "Some required files were not found.
Attempting to locate the full package for these files."
    $FullZipAvailable = Test-Path -Path $FullZip
    if (!$FullZipAvailable)
    {
        $FullUrl = "http://releases.sheetkraft.com/$FullFile"
        Write-Host -NoNewline 'Attempting to download '
        Write-Host -ForegroundColor Yellow -BackgroundColor Black $FullUrl
        try
        {
            Invoke-WebRequest -Uri $FullUrl -OutFile $FullZip -UserAgent "NativeHost"
            Write-Host -ForegroundColor Green -BackgroundColor Black "Full zip downloaded successfully to $FullZip."
            $FullZipAvailable = $true
        }
        catch
        {
            Write-Host -ForegroundColor Red -BackgroundColor Black "Failed to download the full zip from $FullUrl.
Please download it manually and place it in the Patches folder and run the script again. Error message is shown below
$($_.Exception.Message)"
        }
    }
    if ($FullZipAvailable)
    {
        #Delete Temp Directory
        Get-Item "$PatchesPath\SheetKraft.Full*" -Filter 'SheetKraft.Full' | Remove-Item -Recurse -Force

        Add-Type -assembly "System.IO.Compression.FileSystem"

        try
        {
            Unblock-File -Path $FullZip
            Write-Host -NoNewline 'Attempting to extract '
            Write-Host -ForegroundColor Yellow -BackgroundColor Black $FullZip
            [System.IO.Compression.ZipFile]::ExtractToDirectory($FullZip, $PatchesPath)
        }
        catch
        {
            Write-Host -ForegroundColor Red -BackgroundColor Black "Failed to extract the patch from $FullZip.
The file may be corrupt. Delete the file and try again. Error message is shown below
$($_.Exception.Message)"
            $FullZipAvailable = $false;
        }
    }
    if ($FullZipAvailable)
    {
        $PatchSource = "$PatchesPath\SheetKraft.Full"
        Write-Host "Validating that the files from the full package can be applied properly..."
        if (!(VerifyHashes "$PatchSource\publish"))
        {
            Write-Host -ForegroundColor Red -BackgroundColor Black "Failed to find all required files in $PatchSource.
Delete the file $FullZip and try again."
            $PatchSource = $null
        }
    }
    else
    {$PatchSource = $null}
}

$Sha256.Dispose()
if ($null -eq $PatchSource)
{exit -1}

if ($HasWeb)
{
    $AppPoolName = (Get-WebApplication | Where-Object {$_.PhysicalPath -eq $PhysicalPath}).applicationPool
    if ($null -eq $AppPoolName)
    {
        $AppPoolName = (Get-Website | Where-Object {$_.physicalPath -eq $PhysicalPath}).applicationPool
    }

    if ($null -eq $AppPoolName)
    {
        Write-Host -ForegroundColor Red -BackgroundColor Black "Could not find an application pool corresponding to $PhysicalPath."
        exit -1
    }

    $AppPoolState = (Get-WebAppPoolState $AppPoolName).value
    if ($AppPoolState -ne 'stopped')
    {
        Write-Host "Stopping the application pool $AppPoolName"
        Stop-WebAppPool -Name $AppPoolName
        Write-Host "Application pool stopped"
    }
}

$RunnerServices = New-Object 'object[]' $RunnerDirs.Length

for ($ri = 0; $ri -lt $RunnerDirs.Length; $ri++)
{
    $RunnerDir = $RunnerDirs[$ri]
    $RunnerService = Get-WmiObject win32_service -Filter 'Name like "%SheetKraft%"' | Where-Object {$_.Pathname -like "*$RunnerDir\ActivityRunner.exe*"}
    $RunnerServices[$ri] = $RunnerService

    if ($null -eq $RunnerService)
    {
        Write-Host -ForegroundColor Yellow -BackgroundColor Black "$RunnerDir\ActivityRunner.exe is not installed as a service."
    }
    else
    {
        Write-Host "Stopping the service $RunnerService.Name"
        Stop-Service -Name $RunnerService.Name

        $maxRepeat = 300
        do 
        {
            $count = (Get-Service $RunnerService.Name | Where-Object {$_.Status -eq 'Running'}).Count
            $maxRepeat--
            Start-Sleep -Seconds 1
        } until ($count -eq 0 -or $maxRepeat -eq 0)

        if ($count -eq 0)
        {
            Write-Host "Service stopped"
        }
        else
        {
            Write-Host -ForegroundColor Red -BackgroundColor Black "Could not stop $($RunnerService.Name). Wait for the service to stop or kill it and then run $PSScriptRoot\Patch.ps1 manually."
            exit -1
        }
    }    
}


if ($HasWeb)
{
    Write-Host "Updating the website..."

    DeleteFileIfExists "$PhysicalPath\bin\SimpleInjector.Extensions.ExecutionContextScoping.dll"

    @(@(Get-Item "$PhysicalPath\dist\*.js") + @(Get-Item "$PhysicalPath\dist\*.css") + @(Get-Item "$PhysicalPath\beta\dist\*.js") + @(Get-Item "$PhysicalPath\beta\dist\*.css")) | Remove-Item

    ProcessHashesFile "$PatchSource\publish\WebFiles.hashes" "$PatchSource\publish" "$PhysicalPath" $true > $null
    Copy-Item -Path "$PatchSource\publish\WebFiles.hashes" -Destination "$PhysicalPath\WebFiles.hashes"
    
    # Take a backup of index.html if it exists
    foreach ($dirPath in "", "\beta") {
        $fullDirPath = "$PhysicalPath$dirPath"
        if (Test-Path -Path "$fullDirPath\index.html") {
            Move-Item -Path "$fullDirPath\index.html" "$fullDirPath\index.bkp.html" -Force
        }
    }
    if (!(Test-Path -Path "$PhysicalPath\index.json")) {
        Set-Content -Encoding UTF8 -Path "$PhysicalPath\index.json" (`
'{',`
'  "title": "SheetKraft",',`
'  "logo1": "logo.png",',`
'  "logo2": null,',`
'  "versionOnLogo": 1',`
'}')
    }

    #Fix up Web.config
    $ConfigDoc = New-Object System.Xml.XmlDocument
    $ConfigDoc.Load("$PhysicalPath\Web.config")
    $Configuration = $ConfigDoc.DocumentElement
    $webAppSettings = $Configuration.SelectSingleNode('appSettings')
    $ns = New-Object System.Xml.XmlNamespaceManager($ConfigDoc.NameTable)
    $ns.AddNamespace('asm', $asm)

    AddConfigSection $ConfigDoc 'staticFolders' 'Runner\'

    AddConfigSection $ConfigDoc 'activeDirectories' 'Runner\'

    AddConfigSection $ConfigDoc 'azureBlobStorage' 'Runner\'

    AddConfigSection $ConfigDoc 'amazonS3Buckets' 'Runner\'

    AddAWSConfigSection $ConfigDoc 'Runner\'

    AddConfigSection $ConfigDoc 'httpSessions' 'Runner\'

    AddConfigSection $ConfigDoc 'saml2' 'Runner\'

    AddConfigSection $ConfigDoc 'oauth2' 'Runner\'

    AddConfigSection $ConfigDoc 'extApps' 'Runner\'

    AddNpgSqlProvider $ConfigDoc

    AddConnectionManagement $ConfigDoc

    RemoveAssemblyRedirectsWithBackup $ConfigDoc $ns "$PhysicalPath\Web.config.bindingRedirects.backup"

    $compilation = $Configuration.SelectSingleNode('system.web/compilation')
    if ($null -ne $compilation)
    {
        $compilation.SetAttribute('targetFramework', '4.7.2')
        $compilation.SetAttribute('batch', 'false')
    }

    $httpRuntime = $Configuration.SelectSingleNode('system.web/httpRuntime')
    if ($null -ne $httpRuntime)
    {
        $httpRuntime.SetAttribute('targetFramework', '4.7.2')
    }

    $hostingEnvironment = $Configuration.SelectSingleNode('system.web/hostingEnvironment')
    if ($null -eq $hostingEnvironment)
    {
        $hostingEnvironment = $ConfigDoc.CreateElement('hostingEnvironment')
        [Void]$Configuration.SelectSingleNode('system.web').AppendChild($hostingEnvironment)
    }
    $hostingEnvironment.SetAttribute('shadowCopyBinAssemblies', 'false')

    $codeDomElement = $Configuration.SelectSingleNode("system.codedom")
    if ($null -ne $codeDomElement)
    {
        [Void]$codeDomElement.ParentNode.RemoveChild($codeDomElement)
    }
    
    $cspElement = $Configuration.SelectSingleNode("system.webServer/httpProtocol/customHeaders/add[@name='Content-Security-Policy']")
    if ($null -ne $cspElement)
    {
        [Void]$cspElement.ParentNode.RemoveChild($cspElement)
    }

    $ConfigDoc.Save("$PhysicalPath\Web.config")

    Write-Host "Website update completed"
}

for ($ri = 0; $ri -lt $RunnerDirs.Length; $ri++)
{
    $RunnerDir = $RunnerDirs[$ri]
    Write-Host "Updating $RunnerDir ..."

    DeleteFileIfExists "$RunnerDir\JetBrains.Annotations.dll"
    DeleteFileIfExists "$RunnerDir\Microsoft.Threading.Tasks.dll"
    DeleteFileIfExists "$RunnerDir\Microsoft.Threading.Tasks.Extensions.dll"
    DeleteFileIfExists "$RunnerDir\Microsoft.Threading.Tasks.Extensions.Desktop.dll"
    DeleteFileIfExists "$RunnerDir\SimpleInjector.Extensions.ExecutionContextScoping.dll"
    DeleteFileIfExists "$RunnerDir\System.IO.dll"
    DeleteFileIfExists "$RunnerDir\System.Net.Http.dll"
    DeleteFileIfExists "$RunnerDir\System.Net.Http.Extensions.dll"
    DeleteFileIfExists "$RunnerDir\System.Net.Http.Primitives.dll"
    DeleteFileIfExists "$RunnerDir\System.Net.Http.WebRequest.dll"
    DeleteFileIfExists "$RunnerDir\System.Runtime.dll"
    DeleteFileIfExists "$RunnerDir\System.Threading.Tasks.dll"

    ProcessHashesFile "$PatchSource\publish\Runner\RunnerFiles.hashes" "$PatchSource\publish\Runner" $RunnerDir $true > $null
    Copy-Item -Path "$PatchSource\publish\Runner\RunnerFiles.hashes" -Destination "$RunnerDir\RunnerFiles.hashes"

    #Fix up ActivityRunner.exe.config
    $ConfigDoc = New-Object System.Xml.XmlDocument
    $ConfigDoc.Load("$RunnerDir\ActivityRunner.exe.config")
    $Configuration = $ConfigDoc.DocumentElement
    $ns = New-Object System.Xml.XmlNamespaceManager($ConfigDoc.NameTable)
    $ns.AddNamespace('asm', $asm)

    AddConfigSection $ConfigDoc 'staticFolders'
    AddConfigSection $ConfigDoc 'emailConnections'
    AddConfigSection $ConfigDoc 'databaseMaintenance'
    AddConfigSection $ConfigDoc 'ftpConnections'
    AddConfigSection $ConfigDoc 'httpSessions'
    AddConfigSection $ConfigDoc 'activeDirectories'
    AddConfigSection $ConfigDoc 'azureBlobStorage'
    AddConfigSection $ConfigDoc 'amazonS3Buckets'
    AddAWSConfigSection $ConfigDoc ''

    AddNpgSqlProvider $ConfigDoc

    SetExeSupportedRuntimeSku $ConfigDoc

    $appSettings = $Configuration.SelectSingleNode('appSettings')

    AddConnectionManagement $ConfigDoc

    RemoveAssemblyRedirectsWithBackup $ConfigDoc $ns "$RunnerDir\ActivityRunner.exe.config.bindingRedirects.backup"

    $ConfigDoc.Save("$RunnerDir\ActivityRunner.exe.config")

    #Fix up ScheduleRunner.exe.config
    $ConfigDoc = New-Object System.Xml.XmlDocument
    $ConfigDoc.Load("$RunnerDir\ScheduleRunner.exe.config")
    $Configuration = $ConfigDoc.DocumentElement
    $ns = New-Object System.Xml.XmlNamespaceManager($ConfigDoc.NameTable)
    $ns.AddNamespace('asm', $asm)

    AddNpgSqlProvider $ConfigDoc
    SetExeSupportedRuntimeSku $ConfigDoc

    RemoveAssemblyRedirectsWithBackup $ConfigDoc $ns "$RunnerDir\ScheduleRunner.exe.config.bindingRedirects.backup"

    $ConfigDoc.Save("$RunnerDir\ScheduleRunner.exe.config")

    #Fix up ConfigEditor.exe.config
    if (!(Test-Path -Path "$RunnerDir\ConfigEditor.exe.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\ConfigEditor.exe.config" (`
'<?xml version="1.0" encoding="utf-8"?>',`
'<configuration>',`
'  <startup>',`
'    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />',`
'  </startup>',`
'  <runtime>',`
'    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">',`
'    </assemblyBinding>',`
'  </runtime>',`
'</configuration>')
    }
    $ConfigDoc = New-Object System.Xml.XmlDocument
    $ConfigDoc.Load("$RunnerDir\ConfigEditor.exe.config")
    $Configuration = $ConfigDoc.DocumentElement
    $ns = New-Object System.Xml.XmlNamespaceManager($ConfigDoc.NameTable)
    $ns.AddNamespace('asm', $asm)

    AddNpgSqlProvider $ConfigDoc
    SetExeSupportedRuntimeSku $ConfigDoc

    RemoveAssemblyRedirectsWithBackup $ConfigDoc $ns "$RunnerDir\ConfigEditor.exe.config.bindingRedirects.backup"

    $ConfigDoc.Save("$RunnerDir\ConfigEditor.exe.config")

    LoadAndRemoveAssemblyRedirectsWithBackup "$RunnerDir\TestConnection.exe.config"
    LoadAndRemoveAssemblyRedirectsWithBackup "$RunnerDir\MigrateData.exe.config"
    LoadAndRemoveAssemblyRedirectsWithBackup "$RunnerDir\ExportActivityInfo.exe.config"
    LoadAndRemoveAssemblyRedirectsWithBackup "$RunnerDir\MigrateFileStorage.exe.config"

    if (!(Test-Path -Path "$RunnerDir\staticFolders.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\staticFolders.config" (`
'<?xml version="1.0"?>',`
'<staticFolders>',`
'  <!--<add key="Folder:folder_name" value="full_path_to_folder" />-->',`
'</staticFolders>')
    }

    if (!(Test-Path -Path "$RunnerDir\emailConnections.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\emailConnections.config" (`
'<?xml version="1.0"?>',`
'<emailConnections>',`
'  <!--<add key="connection_name" value="type=EWS;email=user@domain.com;password=pswd1234;version=Exchange2013_SP1" />-->',`
'</emailConnections>')
    }

    if (!(Test-Path -Path "$RunnerDir\ftpConnections.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\ftpConnections.config" (`
'<?xml version="1.0"?>',`
'<ftpConnections>',`
'  <!--<add key="connection_name" value="protocol=sftp;host=host_name;port=22;username=user_name;password=password;privateKeyPath=;privateKeyPassphrase=;fingerprint=host_key_fingerprint_in_hex" />-->',`
'</ftpConnections>')
    }

    if (!(Test-Path -Path "$RunnerDir\httpSessions.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\httpSessions.config" (`
'<?xml version="1.0"?>',`
'<httpSessions>',`
'  <!--<add key="session_name" value="AllowAutoRedirect=true;StoreLocation=LocalMachine;StoreName=My;X509FindType=FindBySubjectName;FindValue=" />-->',`
'</httpSessions>')
    }

    if (Test-Path -Path "$RunnerDir\databaseMaintenance.config")
    {
        $dbMaintDoc = New-Object System.Xml.XmlDocument
        $dbMaintDoc.Load("$RunnerDir\databaseMaintenance.config")
        $dbMaintRoot = $dbMaintDoc.DocumentElement
        $dbPrimaryBackupFolderOnDbServer = $dbMaintRoot.SelectSingleNode('add[@key="dbPrimaryBackupFolderOnDbServer"]')
        $dbMaintDocSave = $false
        if ($null -eq $dbPrimaryBackupFolderOnDbServer)
        {
            $dbPrimaryBackupFolderOnDbServer = $dbMaintDoc.CreateElement('add')
	        $dbPrimaryBackupFolderOnDbServer.SetAttribute('key', 'dbPrimaryBackupFolderOnDbServer')
            $dbPrimaryBackupFolderOnDbServer.SetAttribute('value', '')
            [Void]$dbMaintRoot.InsertAfter($dbPrimaryBackupFolderOnDbServer, $dbMaintRoot.SelectSingleNode('add[@key="dbPrimaryBackupFolder"]'))
            $dbMaintDocSave = $true    
        }
        $dbRestoreFolderOnDbServer = $dbMaintRoot.SelectSingleNode('add[@key="dbRestoreFolderOnDbServer"]')
        if ($null -eq $dbRestoreFolderOnDbServer)
        {
            $dbRestoreFolderOnDbServer = $dbMaintDoc.CreateElement('add')
	        $dbRestoreFolderOnDbServer.SetAttribute('key', 'dbRestoreFolderOnDbServer')
            $dbRestoreFolderOnDbServer.SetAttribute('value', '')
            [Void]$dbMaintRoot.InsertAfter($dbRestoreFolderOnDbServer, $dbMaintRoot.SelectSingleNode('add[@key="dbRestoreFolder"]'))
            $dbMaintDocSave = $true
        }
        if ($dbMaintDocSave)
        {
            $dbMaintDoc.Save("$RunnerDir\databaseMaintenance.config")
        }
    }
    else
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\databaseMaintenance.config" (`
'<?xml version="1.0"?>',`
'<databaseMaintenance>',`
'  <add key="dbPrimaryBackupFolder" value=""/>',`
'  <add key="dbPrimaryBackupFolderOnDbServer" value=""/>',`
'  <add key="dbSecondaryBackupFolder" value=""/>',`
'  <add key="dbFullBackupFrequencyInDays" value="7"/>',`
'  <add key="dbFullBackupRetentionCountPrimary" value="2"/>',`
'  <add key="dbFullBackupRetentionCountSecondary" value="2"/>',`
'  <add key="dbDiffBackupFrequencyInDays" value="1"/>',`
'  <add key="dbLogBackupFrequencyInMinutes" value="15"/>',`
'  <add key="dbBackupTime" value="03:00"/>',`
'  <add key="dbBackupPrefix" value="SheetKraftDb_"/>',`
'  <add key="dbRestoreFolder" value=""/>',`
'  <add key="dbRestoreFolderOnDbServer" value=""/>',`
'  <add key="dbRestorePrefix" value="SheetKraftDb_"/>',`
'  <add key="dbRestoreFrequencyInDays" value="1"/>',`
'  <add key="dbRestoreTime" value="03:00"/>',`
'</databaseMaintenance>')
    }
	
	if (!(Test-Path -Path "$RunnerDir\activeDirectories.config"))
    {
		$activeDirectoryConfig = ""
		if ($HasWeb)
		{
			foreach ($param in $webAppSettings.Childnodes)
			{
				if($param.Key)
				{ 
					if ($param.Key.StartsWith("AD:"))
					{
						$activeDirectoryConfig += $param.Key.Substring(3) + "=" + $param.Value + ";"
					}
				}
			}
		}
		else
		{
			$activeDirectoryConfig = "ContextType=Domain;ContextOptions=Negotiate;ServerName=hostname.example.com;Container=DC=example,DC=com;Domain=example;UserName=user;Password=password"
		}
        $activeDirectoryConfig = '  <!--<add key="default" value="'+$activeDirectoryConfig+'" />-->'
        Set-Content -Encoding UTF8 -Path "$RunnerDir\activeDirectories.config" (`
'<?xml version="1.0"?>',`
'<activeDirectories>',`
$activeDirectoryConfig,`
'</activeDirectories>',`
'<!--Valid values for ContextType are Machine,Domain,ApplicationDirectory-->',`
'<!--Valid values for ContextOptions are Negotiate,SimpleBind,SecureSocketLayer,Signing,Sealing,ServerBind-->')
    }

    if (!(Test-Path -Path "$RunnerDir\azureBlobStorage.config"))
    {
		$azureBlobStorageConfig = "BlobContainerUri=https://storageaccountname.blob.core.windows.net/containername;AuthType=None;BlobSasToken=;AccountName=;AccountKey=;CacheFolder=..\App_Data\azureBlobCache"
        $azureBlobStorageConfig = '  <!--<add key="SheetKraft" value="'+$azureBlobStorageConfig+'" />-->'
        Set-Content -Encoding UTF8 -Path "$RunnerDir\azureBlobStorage.config" (`
'<?xml version="1.0"?>',`
'<azureBlobStorage>',`
$azureBlobStorageConfig,`
'</azureBlobStorage>')
    }
    
	if (!(Test-Path -Path "$RunnerDir\amazonS3Buckets.config"))
    {
		$amazonS3BucketConfig = "Region=ap-south-1;BucketName=;Prefix=;AccessKeyId=;SecretAccessKey=;CacheFolder=..\App_Data\s3Cache;ServiceURL="
        $amazonS3BucketConfig = '  <!--<add key="SheetKraft" value="'+$amazonS3BucketConfig+'" />-->'
        Set-Content -Encoding UTF8 -Path "$RunnerDir\amazonS3Buckets.config" (`
'<?xml version="1.0"?>',`
'<amazonS3Buckets>',`
$amazonS3BucketConfig,`
'</amazonS3Buckets>')
    }
    
	if (!(Test-Path -Path "$RunnerDir\extApps.config"))
    {
		$extAppConfig = "Url=http://localhost:Replace_with_port;UniqueId=Replace_with_new_GUID"
        $extAppConfig = '  <!--<add key="Replace_with_key_matching_codekraft_key_configured_in_app" value="'+$extAppConfig+'" />-->'
        Set-Content -Encoding UTF8 -Path "$RunnerDir\amazonS3Buckets.config" (`
'<?xml version="1.0"?>',`
'<extApps>',`
$extAppConfig,`
'</extApps>')
    }

    if (!(Test-Path -Path "$RunnerDir\aws.config"))
    {
        Set-Content -Encoding UTF8 -Path "$RunnerDir\aws.config" (`
'<?xml version="1.0"?>',`
'<aws>',`
'  <!--',`
'  <logging',`
'    logTo="None, Log4Net, SystemDiagnostics"',`
'    logResponses="Never | OnError | Always"',`
'    logMetrics="true | false"',`
'    logMetricsFormat="Standard | JSON"',`
'    logMetricsCustomFormatter="NameSpace.Class, Assembly" />',`
'  <proxy host="ip address" port="port number" username="" password="" />',`
'  -->',`
'</aws>')
    }

    
	if (!(Test-Path -Path "$RunnerDir\saml2.config"))
    {
		Set-Content -Encoding UTF8 -Path "$RunnerDir\saml2.config" (`
'<?xml version="1.0"?>',`
'<saml2>',`
'  <!--<add key="adfs" value="IdpMetadataPath=FederationMetadata.xml; NameIdFormat=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress;UserNameAttribute=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name; LoginButtonLabel=Sign On With ADFS" />-->',`
'</saml2>')
    }

    
	if (!(Test-Path -Path "$RunnerDir\oauth2.config"))
    {
		Set-Content -Encoding UTF8 -Path "$RunnerDir\oauth2.config" (`
'<?xml version="1.0"?>',`
'<oauth2>',`
'  <!--<add key="adfs" value="Issuer=https://login.microsoftonline.com/guid/v2.0; ClientId=guid; ClientSecret=secret; FirstNameClaim=given_name; LastNameClaim=family_name; UserNameClaim=oid; EmailClaim=email; LoginButtonLabel=ADFS OAuth" />-->',`
'</oauth2>')
    }

    Write-Host "$RunnerDir update completed"
}

Copy-Item "$PatchSource\ApplyPatch.ps1" -Force -Destination "$PhysicalPath\ApplyPatch.ps1"

Write-Host "Updating Database..."
$operation = ''
if ($InstalledVersion -lt $Version)
{
    $operation = 'upgrade'
    & "$PhysicalPath\Runner\ActivityRunner.exe" --allowUpgrade
}
elseif ($InstalledVersion -gt $Version)
{
    $operation = 'downgrade'
    & "$PhysicalPath\Runner\ActivityRunner.exe" --allowDowngrade
}
else
{
    $operation = 'repair'
    & "$PhysicalPath\Runner\ActivityRunner.exe" --forceUpgrade
}

if (($operation -ne 'repair') -and ($LASTEXITCODE -ne 0))
{
    Write-Host -ForegroundColor Red -BackgroundColor Black "Could not $operation the database. Check any diagnostic output produced by ActivityRunner.exe in the $PhysicalPath\Logs\SheetKraft.log file."
    exit -1
}
for ($ri = 0; $ri -lt $RunnerDirs.Length; $ri++)
{
    $RunnerService = $RunnerServices[$ri]
    if ($null -ne $RunnerService)
    {
	    Write-Host "Starting the service $RunnerService.Name"
	    Start-Service -Name $RunnerService.Name
	    Write-Host "Service started"
    }
}

if ($HasWeb)
{
    Write-Host "Starting the application pool $AppPoolName"
    Start-WebAppPool -Name $AppPoolName
    Write-Host "Application pool started"
}

Write-Host -ForegroundColor Green -BackgroundColor Black "$operation completed successfully"
