param
(
    [parameter(Position = 0, Mandatory = $false)]
    [boolean]$useServiceFabric = $false
)

if (!$useServiceFabric)
{
    Import-Module WebAdministration
}

$Global:FileLockCheckDll_Path = "$PSScriptRoot/../Microsoft.Dynamics.AX.FileLockCheck.dll"

<#
.SYNOPSIS
    Locate the file lock check dll inside deployable package
.Return
    True if the dll is found
#>
function Locate-FileLockCheckDll
{
    if (Test-Path $Global:FileLockCheckDll_Path)
    {
        return $true
    }
    #if this script is not in AOSService folder, trying to find if AOSService is available
    $dllPath = "$PSScriptRoot/../../AOSService/Microsoft.Dynamics.AX.FileLockCheck.dll"
    
    if (Test-Path $dllPath)
    {
        $Global:FileLockCheckDll_Path = $dllPath
        return $true
    }
    return $false
}

<#
.SYNOPSIS
    Call this to initialize the log for use in Write-ServicingLog.

.NOTES
    Because this module is being imported with the -Force switch from multiple scripts, it must use
    global scope variable to preserve the values across imports.

.PARAMETER LogDir
    Specify the path of the log directory to write to. Set to the empty string to disable
    writing to a log file for subsequent calls to Write-ServicingLog.

.PARAMETER LogFileName
    Specify the name of the log file to write to. If not set, the file name will be determined
    from the $MyInvocation.PSCommandPath or if null, set to Servicing_<TimeStamp>.log.
#>
function Set-ServicingLog([string]$LogDir, [string]$LogFileName)
{
    if ($PSBoundParameters["LogDir"] -and $LogDir -ne $null)
    {
        # Use specified log directory.
        $Global:ServicingLogDir = $LogDir

        if ($PSBoundParameters["LogFileName"] -and $LogFileName -ne $null)
        {
            # Use specified log file name.
            $Global:ServicingLogFileName = $LogFileName
        }
        else
        {
            if ($MyInvocation.PSCommandPath)
            {
                # Use the top level script name as the log file name.
                $ScriptFileName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.PSCommandPath)
                $Global:ServicingLogFileName = "$($ScriptFileName).log"
            }
            else
            {
                # Use default if somehow not run from a script.
                $Global:ServicingLogFileName = "Servicing_$([DateTime]::UtcNow.ToString('yyyyMMddHHmmss')).log"
            }
        }
    }

    # Set full log file path or disable logging to file if not set.
    if ($Global:ServicingLogDir -and $Global:ServicingLogFileName)
    {
        # Ensure that the log directory exists.
        if (!(Test-Path -Path $Global:ServicingLogDir -ErrorAction SilentlyContinue))
        {
            New-Item -Path $Global:ServicingLogDir -ItemType Directory -Force | Out-Null
        }

        $Global:ServicingLogFilePath = Join-Path -Path $Global:ServicingLogDir -ChildPath $Global:ServicingLogFileName
    }
    else
    {
        $Global:ServicingLogFilePath = $null
    }
}

<#
.SYNOPSIS
    Gets the full path to the log file currently set for use in Write-ServicingLog.
#>
function Get-ServicingLog()
{
    return $Global:ServicingLogFilePath
}

<#
.SYNOPSIS
    Write a message or error record to a log file and/or to output.
    Call Set-ServicingLog -LogDir "<Path to logs directory>" to initialize the log file.

.PARAMETER Message
    The message or error record to log.

.PARAMETER ExcludeTimestamp
    Exclude the timestamp prefix.

.PARAMETER Vrb
    Message is verbose and should only go to log file and not to output.
#>
function Write-ServicingLog($Message, [switch]$ExcludeTimestamp, [Alias("V", "NoConsole")][switch]$Vrb)
{
    $LogMessage = ""

    if ($Message -is [System.String])
    {
        $LogMessage += $Message
    }
    elseif ($Message -is [System.Management.Automation.ErrorRecord])
    {
        # Using "Format-List -Force" which will provide
        # detailed information regarding the exception.
        $LogMessage += $($Message | Format-List -Force | Out-String)
    }
    else
    {
        # Using ($Message | Out-String) to get object to string formatting that matches the original
        # behavior of just executing Write-Output $Message.
        # ex. when $Message is a powershell error record,
        # Out-String returns both the error message and call stack.
        $LogMessage += ($Message | Out-String)
    }

    # Get the message timestamp in UTC.
    [DateTime]$Timestamp = [DateTime]::UtcNow

    # Write to log file path if it is defined.
    $LogFilePath = Get-ServicingLog
    if ($LogFilePath)
    {
        try
        {
            # Prefix long timestamp in log file.
            if (!$ExcludeTimestamp)
            {
                Add-Content -Path $LogFilePath -Value "[$($Timestamp.ToString('u'))] $($LogMessage)" -ErrorAction SilentlyContinue
            }
            else
            {
                Add-Content -Path $LogFilePath -Value $LogMessage -ErrorAction SilentlyContinue
            }
        }
        catch
        {
            # Output error in the rare case this fails.
            Write-Debug "Write-ServicingLog error: $($_)"
        }
    }

    # Verbose messages do not go to output.
    if (!$Vrb)
    {
        # Prefix short timestamp in output.
        if (!$ExcludeTimestamp)
        {
            Write-Output "[$($Timestamp.ToString('HH:mm:ss'))] $($LogMessage)"
        }
        else
        {
            Write-Output $LogMessage
        }
    }
}

<#
.SYNOPSIS
    Write Runbook Script Traces.

.PARAMETER Message
    Write a message or error to a log file.

.PARAMETER Component
    Component name for which trace needs to log.

.PARAMETER RunbookId
    Runbook ID for which trace message needs to log.
#>
function EventWrite-RunbookScriptTrace([string]$Message, [string]$Component, [Parameter(Mandatory = $false)][string]$RunbookId)
{
    try {
          $packageRootPath = "$(split-Path -parent $PSScriptRoot)" | Split-Path -Parent
          $job = Start-Job -ScriptBlock {
              param($packageRootPath, $Message, $Component, $ScriptLineNumber, $CommandName, $RunbookId)
              
              Add-Type -path "$packageRootPath\Microsoft.Dynamics.ApplicationPlatform.Development.Instrumentation.dll"
              [Microsoft.Dynamics.ApplicationPlatform.Instrumentation.ServicingEventSource]::EventWriteRunbookScriptTrace(
              $RunbookId, $Component, $CommandName ,$PSScriptRoot, $ScriptLineNumber, "1.0.0", $Message)
                            
           } -ArgumentList $packageRootPath, $Message, $Component, $MyInvocation.ScriptLineNumber, $MyInvocation.MyCommand.Name, $RunbookId
          
          Wait-Job -Job $job | Out-Null
          Receive-Job -Job $job
          
          if($job.JobStateInfo.State -eq 'Failed')
          {
            $job | %{$_.Error} | %{Write-ServicingLog $_}
          }
    }
    catch {
       Write-Host $_
      }
}

<#
.SYNOPSIS
    Add a script progress step to the progress collection.

.PARAMETER ProgressStep
    The name of the progress step to add.
#>
function Add-ScriptProgress
{
    [CmdletBinding()]
    Param([string]$ProgressStep)
    
    $mustInitScriptProgressMsg = "ScriptProgress must be initialized with the Initialize-ScriptProgress for the appropriate scope before attempting to set progress."

    if (($null -eq $executionProgress) -or ($null -eq $executionProgressFile) -or ($null -eq $executionProgressFileLocal))
    {
        Write-Error "One or more 'executionProgress*' variables are not defined. $mustInitScriptProgressMsg"
        return
    }

    if (!(Test-Path $executionProgressFile))
    {
        Write-Error "Execution progress file at [$executionProgressFile] does not exist. $mustInitScriptProgressMsg"
        return
    }

    if (!(Test-Path $executionProgressFileLocal))
    {
        Write-Error "Local execution progress file at [$executionProgressFileLocal] does not exist. $mustInitScriptProgressMsg"
        return
    }

    if (!$executionProgress.$ProgressStep)
    {
        $executionProgress.$ProgressStep = [System.DateTime]::UtcNow.ToString("O")
    }

    $executionProgress | ConvertTo-Json | Out-File $executionProgressFile -Force
    $executionProgress | ConvertTo-Json | Out-File $executionProgressFileLocal -Force
}

<#
.SYNOPSIS
    Initializes the script progress file used for tracking progress of a script or set of related scripts.

.PARAMETER ProgressFile
    The file path where the progress is tracked.

.PARAMETER Scope
    The scope the progress collection should be available in.
#>
function Initialize-ScriptProgress
{
    [CmdletBinding()]
    Param([string]$ProgressFile, [string]$Scope = "")
    
    if ([string]::IsNullOrWhitespace($Scope))
    {
        $Scope = 1
    }

    try 
    {
        # Define the variable for the progress file in the target location
        if (!(Get-Variable -Scope $Scope -Name "executionProgressFile" -ErrorAction SilentlyContinue))
        {
            New-Variable -Scope $Scope -Name "executionProgressFile"
        }

        # Define the variable for the progress file in the local location (adjacent to the executing script)
        if (!(Get-Variable -Scope $Scope -Name "executionProgressFileLocal" -ErrorAction SilentlyContinue))
        {
            New-Variable -Scope $Scope -Name "executionProgressFileLocal"
        }

        $localProgressFile = Join-Path -Path $PSScriptRoot -ChildPath (Split-Path -Path $ProgressFile -Leaf)
        Set-Variable -Name "executionProgressFile" -Scope $Scope -Value $ProgressFile
        Set-Variable -Name "executionProgressFileLocal" -Scope $Scope -Value $localProgressFile

        if (!(Get-Variable -Name "executionProgress" -Scope $Scope -ErrorAction SilentlyContinue))
        {
            New-Variable -Scope $Scope -Name "executionProgress"
        }

        $scriptProgressTable = @{ }

        # Initialize the progress table if the progress file exists at the specified path but also was initialized previously in the
        # local script path. This works around an issue where the staging could have been created previously, the package updated, 
        # and the pre-processing re-executed. In that case, the updated package would overwrite the previous local progress file
        # forcing the progress table to be initialized as empty.
        if ((Test-Path $ProgressFile) -and (Test-Path $localProgressFile))
        {
            $scriptProgressTableTmp = Get-Content $ProgressFile | ConvertFrom-Json
            $scriptProgressTableTmp.psobject.properties | Foreach-Object { $scriptProgressTable[$_.Name] = $_.Value }
        }
        else 
        {
            if (Test-Path $ProgressFile)
            {
                Remove-Item $ProgressFile -Force
            }

            if (Test-Path $localProgressFile)
            {
                Remove-Item $localProgressFile -Force
            }

            $progressFileParent = Split-Path $ProgressFile -Parent
            $progressFileLocalParent = Split-Path $localProgressFile -Parent

            if (!(Test-Path $progressFileParent -PathType Container))
            {
                New-Item $progressFileParent -ItemType Container | Out-Null
            }
        
            if (!(Test-Path $progressFileLocalParent -PathType Container))
            {
                New-Item $progressFileLocalParent -ItemType Container | Out-Null
            }

            $scriptProgressTable | ConvertTo-Json | Out-File $ProgressFile -Force
            $scriptProgressTable | ConvertTo-Json | Out-File $localProgressFile -Force
        }

        Set-Variable -Name "executionProgress" -Scope $Scope -Value $scriptProgressTable
        
    }
    catch
    {
        # Treat any terminating error in the cmdlet scope as a non-terminating error. 
        # Let the parent handle appropriately through ErrorAction
        Write-Error "Unable to initialize the script progress file [$ProgressFile]. Details: $($_ | Format-List -Force)"
    }
}

<#
.SYNOPSIS
    Tests if progress has been made for the specified progress step.

.PARAMETER ProgressStep
    The progress step name to test.
#>
function Test-ScriptProgress
{
    [CmdletBinding()]
    Param([string]$ProgressStep)

    if (($null -eq $executionProgress) -or ($null -eq $executionProgressFile) -or ($null -eq $executionProgressFileLocal))
    {
        return $false
    }

    if (!(Test-Path $executionProgressFile) -or !(Test-Path $executionProgressFileLocal))
    {
        return $false
    }

    $progressStepValue = $executionProgress.$ProgressStep

    if (![string]::IsNullOrWhiteSpace($progressStepValue))
    {
        return $true
    }

    return $false
}

<#
.SYNOPSIS
    Gets the script progress timestamp for the specified progress step. Returns null if the step was not found.

.PARAMETER ProgressStep
    The pregress step name.
#>
function Get-ScriptProgress
{
    [CmdletBinding()]
    Param([string]$ProgressStep)

    if (Test-ScriptProgress $ProgressStep)
    {
        return $executionProgress.$ProgressStep
    }

    return $null
}

<#
.SYNOPSIS
    Copies the current script progress file to the specified location.

.PARAMETER Destination
    The destination file path.
#>
function Copy-ScriptProgressFile
{
    [CmdletBinding()]
    Param([string]$Destination)

    if (($null -eq $executionProgressFile) -or !(Test-Path $executionProgressFile))
    {
        Write-Warning "The source progress file at [$executionProgressFile] was not found. Skipping copy."
        return
    }

    Copy-Item $executionProgressFile $Destination -Force
}

<#
.SYNOPSIS
    Execute a powershell code block with retry mechanism 

.PARAMETER codeBlock
    The content of the powershell script to be executed.
.PARAMETER blockMessage
    The name of the script block
.PARAMETER maxRetry
    Maximum retry count.
.PARAMETER sleepTimeInSecond
    Time interval in second between two retries.
#>
function Invoke-WithRetry([ScriptBlock]$codeBlock, [string]$scriptName = "", [int]$maxRetry = 5, [int]$sleepTimeInSecond = 10)
{    
    Write-ServicingLog "$scriptName`: Starting execution with retry"
    for ($retry = 0; $retry -lt $maxRetry; $retry++)
    {
        try
        {
            $codeBlock.Invoke()
            break;
        }
        catch
        {
            if($retry -lt $maxRetry - 1)
            {
                Write-ServicingLog "Exception in $scriptName`: $_"              
                Write-ServicingLog "Sleeping $sleepTimeInSecond seconds before retrying"
                Start-Sleep -Seconds $sleepTimeInSecond
            }
            else
            {
                Write-ServicingLog "Exception in $scriptName`: $_" 
                throw 
            }
        }
    }
    Write-ServicingLog "$scriptName`: Completed execution in $maxRetry iterations"
}


<#
.SYNOPSIS
    Attempts to get the runbook ID for the current package. Null if the ID cannot be determined.
#>
function Get-RunbookId
{
    $packageRoot = Get-PackageRoot

    if ([string]::IsNullOrEmpty(($packageRoot)))
    {
        return $null
    }

    #First find the name of the runbook working folder in the package
    $workingFolder = Join-Path -Path $packageRoot -ChildPath "RunbookWorkingFolder"
    if (Test-Path ($workingFolder))
    {
        $firstDirectory = Get-ChildItem $workingFolder -Directory | Select-Object -First 1
        if ($firstDirectory)
        {
            return $firstDirectory.Name
        }
    }

    #If the working folder isn't found, look for the runbook in the root of the package
    Get-ChildItem $packageRoot -File -Filter "*.xml" | ForEach-Object {
        $xmlFile = [xml](Get-Content $_.FullName)
        $runbookIdNode = $xmlFile.SelectSingleNode("/RunbookData/RunbookID")
        if ($runbookIdNode -and ![string]::IsNullOrWhiteSpace($runbookIdNode."#text"))
        {
            return $runbookIdNode."#text"
        }
    }

    #If it still isn't found, return null
    return $null
}

<#
.SYNOPSIS
    Attempts to get the deployable package root. Null if the root cannot be determined.
#>
function Get-PackageRoot
{
    $maxDepth = 5
    $currentPath = $PSScriptRoot

    for ($i = 5; $i -gt 0; $i--)
    {
        if ([string]::IsNullOrWhiteSpace($currentPath))
        {
            return $null
        }
        elseif (Test-Path (Join-Path -Path $currentPath -ChildPath "AxUpdateInstaller.exe"))
        {
            return $currentPath
        }
        else
        {
            $currentPath = Split-Path $currentPath -Parent
        }
    }
}

<#
.SYNOPSIS
    Stop a service and set startup type to disabled to prevent unintentional starts.
#>
function Stop-ServiceAndDisableStartup([string]$ServiceName, [int]$MaxWaitSec = 300)
{
    $Service = Get-Service -Name $ServiceName -ErrorAction "SilentlyContinue"
    if ($Service)
    {
        # Start by disabling service.
        Set-Service -Name $ServiceName -startupType Disabled

        # Stop service if not already stopped.
        if ($Service.Status -ine "Stopped")
        {
            Write-ServicingLog "Stopping $($ServiceName) service with status $($Service.Status)..." -Vrb
            $Service | Stop-Service -ErrorAction "Stop"

            $StopWatch = [System.Diagnostics.StopWatch]::StartNew()

            # Wait for service to reach stopped status. 
            while ($Service.Status -ine "Stopped" -and $StopWatch.Elapsed.TotalSeconds -lt $MaxWaitSec)
            {
                Start-Sleep -Seconds 1
                $Service = Get-Service -Name $ServiceName
            }

            $StopWatch.Stop()

            if ($Service.Status -ine "Stopped")
            {
                throw "Unable to stop the $($ServiceName) service with status $($Service.Status) within the $($MaxWaitSec) second timeout."
            }
            else
            {
                Write-ServicingLog "Stopped the $($ServiceName) service in $([Math]::Round($StopWatch.Elapsed.TotalSeconds)) seconds." -Vrb
            }
        }
        else
        {
            Write-ServicingLog "The $($ServiceName) service is already stopped." -Vrb
        }
    }
    else
    {
        Write-ServicingLog "The $($ServiceName) service could not be found and thus not stopped or disabled." -Vrb
    }
}

<#
.SYNOPSIS
    Returns true if the current user is a member of the built-in administrators group.
#>
function Test-IsRunningAsAdministrator()
{
    [bool]$IsAdministrator = $false

    [Security.Principal.WindowsPrincipal]$Principal = [Security.Principal.WindowsIdentity]::GetCurrent()
    if ($Principal)
    {
        $IsAdministrator = $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    return $IsAdministrator
}

function Create-ZipFiles(
    [string] $sourceFolder = $(Throw 'sourceFolder parameter required'),
    [string] $destFile = $(Throw 'destFile parameter required'),
    [string] $filetypesExcluded,
    [string] $folderExcluded,
    [string] $fileFilter,
    [string] $zipLogDir)
{
    Set-Variable zipLocation -Option Constant -Value (Join-Path $env:SystemDrive "DynamicsTools\7za.exe")

    if (-Not (Test-Path $sourceFolder))
    {
        throw "Path not found: $sourceFolder"
    }

    if (Test-Path $destFile)
    {
        Remove-Item $destFile -Force
    }

    Push-Location $sourceFolder
    $argumentList = "a -mx1 -r -y"

    if (![string]::IsNullOrEmpty($filetypesExcluded))
    {
        $argumentList = $argumentList + " -x!$filetypesExcluded"
    }

    if (![string]::IsNullOrEmpty($folderExcluded))
    {
        $argumentList = $argumentList + " -xr!$folderExcluded"
    }

    $argumentList = $argumentList + " $destFile"

    if (![string]::IsNullOrEmpty($fileFilter))
    {
        $argumentList = $argumentList + " $fileFilter"
    }

    $ZipLog = Join-Path $PSScriptRoot tempZipLog.txt
    if (Test-Path $ZipLog)
    {
        Remove-Item $ZipLog
    }

    $process = Start-Process $zipLocation -ArgumentList $argumentList -NoNewWindow -Wait -PassThru -RedirectStandardOutput $ZipLog #7zip doesn't have stderr
    try { if (!($process.HasExited)) { Wait-Process $process } } catch { }

    Pop-Location
    if ($process.ExitCode -ne 0)
    {
        # If zipLogDir parameter was passed, copy 7zip failure logs to zipLogDir before exiting
        if (Test-Path $zipLogDir)
        {
            Copy-Item $ZipLog -Destination (Join-Path $zipLogDir "ZipLog_$([DateTime]::UtcNow.ToString("yyyyMMddHHmmss")).log")
        }

        throw "fail to generate zip archive: $destFile, check the log file for more detail: $ZipLog"
    }
    if (Test-Path $ZipLog)
    {
        Remove-Item $ZipLog
    }
}

function KillProcessLockingFolder(
    [string] $folder = $(Throw 'Folder parameter required'))
{
    #detect if any process is locking file
    Write-Output "Finding and terminating processes accessing files under $folder"
    $ProcessesLockingAOS = Get-Process | Where-Object { $_.Modules.FileName -like "$folder\*" }
    $ProcessesLockingAOS

    foreach ($Process in $ProcessesLockingAOS)
    {
        Stop-Process -Force -Id $Process.Id
    }
}

function KillAllOtherUserSession()
{
    $sessions = &query.exe user
    if ($sessions.count -gt 0)
    {
        $header = $sessions[0];

        foreach ($session in $sessions)
        {
            if ((! $session.StartsWith('>')) -and (! $session.StartsWith($header)))
            {
                $option = [System.StringSplitOptions]::RemoveEmptyEntries
                $name = $session.Split(' ', $option)[0]
                $SubString = $session.Substring($header.IndexOf("ID") - 3)
                $sid = $SubString.Split(' ', $option)[0]
                Write-Output "terminate session for user: $name sid: $sid"
                & { reset.exe session $sid }

            }
        }
    }
}

function Create-ZipFiles-FromFileList(
    [string[]] $fileList = $(Throw 'fileList parameter required'),
    [string] $destFile = $(Throw 'destFile parameter required'))
{
    Set-Variable zipLocation -Option Constant -Value (Join-Path $env:SystemDrive "DynamicsTools\7za.exe")

    foreach ($element in $fileList)
    {
        if (-Not (Test-Path $element))
        {
            throw "Path not found: $element"
        }
    }

    if (Test-Path $destFile)
    {
        Remove-Item $destFile -Force
    }

    $argumentList = "a" + " $destFile"

    foreach ($element in $fileList)
    {
        $argumentList = $argumentList + " $element"
    }

    $ZipLog = Join-Path $PSScriptRoot tempZipLog.txt
    if (Test-Path $ZipLog)
    {
        Remove-Item $ZipLog
    }

    $process = Start-Process $zipLocation -ArgumentList $argumentList -NoNewWindow -Wait -PassThru -RedirectStandardOutput $ZipLog #7zip doesn't have stderr
    try { if (!($process.HasExited)) { Wait-Process $process } } catch { }

    if ($process.ExitCode -ne 0)
    {
        throw "fail to generate zip archive: $destFile, check the log file for more detail: $ZipLog"
    }
    if (Test-Path $ZipLog)
    {
        Remove-Item $ZipLog
    }
}

function Unpack-ZipFiles(
    [string] $sourceFile = $(Throw 'sourceFile parameter required'),
    [string] $destFolder = $(Throw 'destFolder parameter required'))
{
    Set-Variable zipLocation -Option Constant -Value (Join-Path $env:SystemDrive "DynamicsTools\7za.exe")

    if (-Not (Test-Path $sourceFile))
    {
        throw "File not found: $sourceFile"
    }

    if (-Not (Test-Path $destFolder))
    {
        throw "Path not found: $destFolder"
    }
    Push-Location $destFolder
    $argumentList = "x -y $sourceFile"

    $process = Start-Process $zipLocation -ArgumentList $argumentList -NoNewWindow -Wait -PassThru
    try { if (!($process.HasExited)) { Wait-Process $process } } catch { }

    Pop-Location
    if ($process.ExitCode -ne 0)
    {
        $argumentList
        throw "fail to extract zip archive: $sourceFile"
    }
}

function Get-WebSitePhysicalPath([string]$Name = $(Throw 'Name parameter required'))
{
    if (Get-Service W3SVC | Where-Object status -ne 'Running')
    {
        #IIS service is not running, starting IIS Service.
        Start-Service W3SVC
    }

    $webSitePhysicalPath = (Get-Website | Where-Object { $_.Name -eq $Name }).PhysicalPath

    return $webSitePhysicalPath
}

function Get-AosWebSitePhysicalPath()
{
    $websiteName = Get-AosWebSiteName
    if ($websiteName)
    {
        $websitePath = Get-WebSitePhysicalPath -Name $websiteName
        if ([string]::IsNullOrWhiteSpace($websitePath))
        {
            throw "Failed to find the webroot of AOS Service website."
        }
        return $websitePath
    }
    else
    {
        throw "Failed to find the website name. Unable to determine the physical website path."
    }
}

function Get-AosServicePath()
{
    $websitePath = Get-AosWebSitePhysicalPath
    $aosWebServicePath = "$(Split-Path -parent $websitePath)"
    return $aosWebServicePath
}

function Get-AosServiceStagingPath()
{
    $aosWebServicePath = Get-AosServicePath
    $stagingFolder = Join-Path  "$(Split-Path -parent $aosWebServicePath)" "AosServiceStaging"
    return $stagingFolder
}

function Get-AosServiceBackupPath()
{
    $aosWebServicePath = Get-AosServicePath
    $stagingFolder = Join-Path  "$(Split-Path -parent $aosWebServicePath)" "AosServiceBackup"
    return $stagingFolder
}

function Reset-IIS
{
    try
    {
        Write-Host "Attempt to reset IIS"

        iisreset /stop
        
        if($?)
        {
            Write-Host "Successfully stopped IIS"
        }
        else
        {
            Write-Host "Failed to stop IIS"
        }

        Write-Host "Sleeping for 10 seconds before restarting IIS"
        Start-Sleep -Seconds 10
        
        iisreset /start

        if($?)
        {
            Write-Host "Successfully started IIS"
        }
        else
        {
            Write-Host "Failed to start IIS"
        }
    }
    catch
    {
        Write-Host "Failed to reset IIS"
    }
}

function Get-IsIISHealthyHelper
{
    $DefaultAppPoolName = "DefaultAppPool"
    $IsIISHealthy = $false

    try
    {
        Write-Host "Attempt to determine whether IIS is healthy"
        
        $scriptBlock = {
        param($appPool) 
        Get-WebAppPoolState -Name $appPool
        }

        $DefaultAppPoolStateJob = Start-Job -ScriptBlock $scriptBlock -ArgumentList $DefaultAppPoolName | Wait-Job -Timeout 30

        if(($DefaultAppPoolStateJob.State -ne "Completed"))
        {
            $DefaultAppPoolStateJob.StopJob()
            
            Write-Host "Failed to to determine whether IIS is healthy; IIS doesn't appear to be responsive"
            
            throw "Timeout occured while attempting to determine whether IIS is healthy"
        }

        return $true
    }
    catch
    {
        Write-Host "Failed to determine the state of the default app pool; IIS doesn't appear to be responsive"
    }
    
    return $IsIISHealthy
}

function Get-IsIISHealthy
([Parameter(Mandatory=$false)]
[boolean]$attemptIISResetIfUnhealthy = $false)
{
    $DefaultAppPoolName = "DefaultAppPool"
    $IsIISHealthy = $false

    $IsIISHealthy = Get-IsIISHealthyHelper
    
    if($attemptIISResetIfUnhealthy -and !$IsIISHealthy)
    {
        Reset-IIS

        $IsIISHealthy = Get-IsIISHealthyHelper
    }

    return $IsIISHealthy
}

function Get-AosWebSiteName()
{
    if (Test-Path "iis:\sites\AosService")
    {
        return "AosService"
    }
    elseif (Test-Path "iis:\sites\AosServiceDSC")
    {
        return "AosServiceDSC"
    }
    elseif (Test-Path "iis:\sites\AosWebApplication")
    {
        return "AosWebApplication"
    }
    else
    {
        throw "Failed to find the AOS website name."
    }
}

function Get-AosAppPoolName()
{
    $websiteName = Get-AosWebSiteName
    if ($websiteName)
    {
        if ($websiteName -eq "AosWebApplication")
        {
            #Non service-model deployments have a different app pool and site name
            return "AOSAppPool"
        }
        else
        {
            #Service model-based deployments have app pool and site use the same name
            return $websiteName
        }
    }
    else
    {
        throw "Failed to find the AOS website name. Unable to determine application pool name."
    }
}

function Update-IISWebSiteServerAutoStartProperty($iISWebSiteName, $serverAutoStart)
{
    # Determine if running in admin or non-admin mode.
    $IsAdmin = Test-IsRunningAsAdministrator
    
    if ($IsAdmin)
    {
        Set-ItemProperty -Path "IIS:\Sites\$iISWebSiteName" -Name serverAutoStart -Value $serverAutoStart
    }
}

function Update-IISAppPoolServerAutoStartProperty($iISAppPoolName, $serverAutoStart)
{
    # Determine if running in admin or non-admin mode.
    $IsAdmin = Test-IsRunningAsAdministrator
    
    if ($IsAdmin)
    {
        Set-ItemProperty -Path "IIS:\AppPools\$iISAppPoolName" -Name autoStart -Value $serverAutoStart
    }
}

function Update-EnableRestartForSiteAfterIISResetOrReboot([string]$IISWebSiteName = $(Throw 'Name parameter required'))
{
    Update-IISWebSiteServerAutoStartProperty -IISWebSiteName $IISWebSiteName -ServerAutoStart $true
}

function Update-EnableRestartForAppPoolAfterIISResetOrReboot([string]$IISAppPoolName = $(Throw 'Name parameter required'))
{
    Update-IISAppPoolServerAutoStartProperty -IISAppPoolName $IISAppPoolName -ServerAutoStart $true
}

function Update-DisableRestartForSiteAfterIISResetOrReboot([string]$IISWebSiteName = $(Throw 'Name parameter required'))
{
    Update-IISWebSiteServerAutoStartProperty -IISWebSiteName  $IISWebSiteName -ServerAutoStart $false
}

function Update-DisableRestartForAppPoolAfterIISResetOrReboot([string]$IISAppPoolName = $(Throw 'Name parameter required'))
{
    Update-IISAppPoolServerAutoStartProperty -IISAppPoolName $IISAppPoolName -ServerAutoStart $false
}

function Get-ProductConfigurationAppPoolName()
{

    if (Test-Path "iis:\apppools\ProductConfiguration")
    {
        return "ProductConfiguration"
    }
    else
    {
        return ""
    }
}

function Backup-WebSite(
    [ValidateNotNullOrEmpty()]
    [string]$Name = $(Throw 'Name parameter required'),

    [string]$BackupFolder)
{
    Write-Output "Executing backup for [$Name] website"

    $webroot = Get-WebSitePhysicalPath -Name $Name
    if ([string]::IsNullOrEmpty($webroot))
    {
        throw "Failed to locate physical path for [$Name] website."
    }

    if ([string]::IsNullOrEmpty($BackupFolder))
    {
        $BackupFolder = ("$PSScriptRoot\{0}_Backup" -f $Name)
    }

    $webrootBackupFolder = Join-Path $BackupFolder 'webroot'

    if (-not (Test-Path -Path $webrootBackupFolder ))
    {
        New-Item -ItemType Directory -Path $webrootBackupFolder -Force
    }

    Write-Output "Begin backup of [$Name] website at $webroot"
    Create-ZipFiles -sourceFolder $webroot -destFile (Join-Path $webrootBackupFolder 'webroot.zip')
    Write-Output "Finished executing backup for [$Name]"
}

function Restore-WebSite(
    [ValidateNotNullOrEmpty()]
    [string]$Name = $(Throw 'Name parameter required'),

    [string]$BackupFolder)
{
    Write-Output "Executing restore for [$Name] website"

    $webroot = Get-WebSitePhysicalPath -Name $Name
    if ([string]::IsNullOrEmpty($webroot))
    {
        throw "Failed to locate physical path for [$Name] website."
    }

    if ([string]::IsNullOrEmpty($BackupFolder))
    {
        $BackupFolder = ("$PSScriptRoot\{0}_Backup" -f $Name)
    }

    $webrootBackupFolder = Join-Path $BackupFolder 'webroot'

    if (-not (Test-Path -Path $webrootBackupFolder ))
    {
        throw "Failed to find the backup file for website [$Name], restore aborted."
    }

    Write-Output "Removing website data at $webroot"
    Remove-Item -Path "$webroot\*" -Recurse -Force

    Write-Output "Restoring website data at $webroot"
    Unpack-ZipFiles -sourceFile "$webrootBackupFolder\webroot.zip" -destFolder $webroot

    Write-Output "Finished executing restore for [$Name] website"
}

function Copy-FullFolder([string] $SourcePath, [string] $DestinationPath, [string] $LogFile)
{
    if (-not (Test-Path $SourcePath))
    {
        throw error "$SourcePath path does not exist"
    }

    if (-not (Test-Path $DestinationPath))
    {
        New-Item -ItemType Directory -Path $DestinationPath
    }
    $robocopyOptions = @("/MIR", "/MT", "/FFT", "/W:5", "/R:3", "/NDL", "/NFL")
    #Bug 3822095:Servicing - in HA env the aos backup step failed with filename or extension too long error message

    $cmdArgs = @($robocopyOptions, "$SourcePath", "$DestinationPath")
    & Robocopy.exe @cmdArgs > $LogFile
    $roboCopyExitCode = $lastExitCode

    # Any value greater than 8 or minus value indicates that there was at least one failure during the copy operation..
    # 8 Several files did not copy.
    if (($roboCopyExitCode -ge 8) -or ($roboCopyExitCode -lt 0))
    {
        throw error "Robocopy.exe exited with code $roboCopyExitCode"
    }

    return $roboCopyExitCode
}

function Copy-SymbolicLinks([string] $SourcePath, [string] $DestinationPath, [switch] $Move = $false)
{
    if (-not (Test-Path $SourcePath))
    {
        throw error "$SourcePath path does not exist"
    }

    $filesToCopy = @{ } # Hashtable for each folder and files inside that folder to copy
    $foldersToCopy = @() # List of folders to copy

    # Parse existing files into folders and files that needs to be copied.
    Get-ChildItem -Recurse $SourcePath | Where-Object { $_.LinkType -eq "SymbolicLink" } | ForEach-Object {
        $dir = Split-Path $_.FullName -Parent
        $fileName = $_.Name


        if ($_.PSIsContainer)
        {
            $foldersToCopy += $_.FullName
        }
        else
        {
            if ($filesToCopy.ContainsKey($dir))
            {
                $fileList = $filesToCopy.Get_Item($dir)
                $fileList += $fileName
                $filesToCopy.Set_Item($dir, $fileList)
            }
            else
            {
                $fileList = @()
                $fileList += $fileName
                $filesToCopy.Add($dir, $fileList)
            }
        }
    }

    # Robocopy files, with each iteration going through a new directory
    $filesToCopy.GetEnumerator() | ForEach-Object {
        $source = $_.Key
        $files = $_.Value
        $relative = Get-RelativePath -ChildPath $source -ParentPath $SourcePath
        $destination = Join-Path $DestinationPath $relative

        if (-not (Test-Path $destination))
        {
            New-Item -ItemType Directory -Path $destination
        }
        $robocopyOptions = @("/SL")
        #Bug 3822095:Servicing - in HA env the aos backup step failed with filename or extension too long error message
        foreach ($file in $files)
        {
            $cmdArgs = @($robocopyOptions, "$source", "$destination", @($file))
            & Robocopy.exe @cmdArgs >$null
        }
    }

    # Copy symbolic link folders, since robocopy does not support them
    $foldersToCopy | ForEach-Object {
        $source = $_
        $relative = Get-RelativePath -ChildPath $source -ParentPath $SourcePath
        $destination = Join-Path $DestinationPath $relative
        xcopy.exe /b /i $source $destination >$null
    }

    if ($Move)
    {
        $filesToCopy.GetEnumerator() | ForEach-Object {
            $folder = $_.Key
            $_.Value | ForEach-Object {
                $file = $_
                $fullPath = Join-Path $folder $file
                Remove-Item -Force $fullPath
            }
        }

        $foldersToCopy | ForEach-Object {
            [System.IO.Directory]::Delete($_, $true)
        }
    }
}

function Get-RelativePath([string] $ChildPath, [string] $ParentPath)
{
    # Parent path must be resolved to literal
    $parentLiteralPath = Resolve-Path $ParentPath
    $childLiteralPath = Resolve-Path $ChildPath

    $parentMatch = $parentLiteralPath -replace "\\", "\\"
    if ($childLiteralPath -match "^$parentMatch(.+)$")
    {
        return $Matches[1]
    }
    else
    {
        # ChildPath is not a child of ParentPath, return empty string
        return ''
    }
}

# function to update the connection string
function Update-AOSConnectionString ([hashtable] $AxConnectionString, [string] $webConfigPath)
{
    [System.Xml.XmlDocument] $webConfig = new-object System.Xml.XmlDocument
    $webConfig.Load($webConfigPath)
    $xpath = "/configuration/appSettings/add[@key='DataAccess.DbServer' or @key='DataAccess.Database']"
    $nodes = $webConfig.SelectNodes($xpath)

    foreach ($node in $nodes)
    {
        if ($node.Key -eq 'DataAccess.DbServer')
        {
            If ($node.Value -ne $AxConnectionString['AxServerName'])
            {
                $node.value = $AxConnectionString['AxServerName']
                Write-Output "Updated value for $($node.Key) as $($AxConnectionString.AxServerName)"
            }
            else
            {
                Write-Output "Updating value for $($node.Key) is not required. Skipping this update."
            }
        }
    
        if ($node.Key -eq 'DataAccess.Database')
        {
            If ($node.Value -ne $AxConnectionString['AxDatabaseName'])
            {
                $node.value = $AxConnectionString['AxDatabaseName']
                Write-Output "Updated value for $($node.Key) as $($AxConnectionString.AxDatabaseName)"
            }
            else
            {
                Write-Output "Updating value for $($node.Key) is not required. Skipping this update."
            }
        }
    }
  
    $webConfig.Save($webConfigPath)
}

function KillWatchdogProcess
{
    $ServiceName = $serviceName = "watchdogservice"
    if (Get-Service $ServiceName -ErrorAction SilentlyContinue)
    {
        # Ensure the service in running or stopped state before attempting to stop.
        $timeout = new-timespan -Minutes 5
        $serviceProcessStarting = $true;
        $sw = [diagnostics.stopwatch]::StartNew()
        while ($sw.elapsed -lt $timeout -and $serviceProcessStarting)
        {
            if ((Get-Service $ServiceName | where status -ne 'Running' ) -and (Get-Service $ServiceName | where status -ne 'Stopped' ))
            {
                start-sleep -seconds 60 
            }
            else
            {
                $serviceProcessStarting = $false;
            }
        }
        Write-ServicingLog "Current state of the process: $serviceProcessStarting"
        if ($serviceProcessStarting)
        {
            throw "Unable to execute the $ServiceName shutdown script because the  process is not in a state where operation can be performed."
        }

        # Stop and disable the service
        Set-Service $ServiceName -startupType Disabled
        Write-ServicingLog "Stopping the service: $ServiceName"
        Stop-Service $ServiceName -Force

        # Kill any process related to the watchdog 
        $processName = "Microsoft.Dynamics365.Watchdog.Service"
        $process = Get-Process -Name $processName -ErrorAction SilentlyContinue
        If ($process)
        {
            Write-ServicingLog "Found running processes for $processName. Killing processes forcibly"
            $process | Stop-Process -Force
        }
        else
        {
            Write-ServicingLog "No processes found running for $processName. Skipping the killing of process."
        }

        $svc = Get-Service $ServiceName
        $runningProcess = Get-Process -Name $processName -ErrorAction SilentlyContinue
        if ($svc.Status -eq "Stopped" -and !$runningProcess)
        {
            Write-ServicingLog "$ServiceName stopped. No process found running for $processName"
        } 
        else 
        {
            $status = $svc.Status
            $msg = "Unable to stop service $ServiceName. Current Status: $status"
            if ($runningProcess)
            {
                $msg = "Unable to stop service $ServiceName or process $processName. Current Status of service: $status; running processes: $processName"
            }
            throw $msg
        }       
    }
    else
    {
        Write-ServicingLog "$ServiceName not installed. Exiting."
    }
}

# Get application release from aos
# This funtion is moved from AOSEnvironmentUtilities.psm1 for consumption by retail scripts
function Get-ApplicationReleaseFromAOS([Parameter(Mandatory = $false)] $webroot)
{
    if ($webroot -eq $null)
    {
        $webroot = Get-AosWebSitePhysicalPath
    }


    #must use job or process to load the production information provider dll or it'll lock it self
    #in memory copy is not usable as this dll have some special hard coded reference dll which won't resolve when loaded in memory.
    $job = Start-Job -ScriptBlock {
        param($webrootBlock)
        $VersionDLLPath = Join-Path $webrootBlock 'bin\Microsoft.Dynamics.BusinessPlatform.ProductInformation.Provider.dll'
        Add-Type -Path $VersionDLLPath
        $provider = [Microsoft.Dynamics.BusinessPlatform.ProductInformation.Provider.ProductInfoProvider]::get_Provider();
        $version = $provider.get_ApplicationVersion();
        $version
    } -ArgumentList $webroot
    Wait-Job -Job $job | Out-Null
    $version = Receive-Job -Job $job

    if ($((![string]::IsNullOrEmpty($version)) -and ($version -ne '7.0') -and ($version -ne '7.0.0.0')))
    {
        return $version
    }
    else
    {
        return "RTW"
    }
}

<#
.SYNOPSIS
    Ensure copy staging task is not running, otherwise an exception will be thrown in this function
#>
function Disable-CopyStagingTask
{
    $taskName = "DynamicsServicingCopyStaging"
    $scheduledTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
    if (-not $scheduledTask)
    {
        Write-ServicingLog "No scheduled task '$taskName' detected"
        return
    }

    if ($scheduledTask.State -ne "Disabled")
    {
        $disableTaskScript = {
            Write-ServicingLog "Stopping scheduled task '$taskName'"
            Stop-ScheduledTask -TaskName $taskName -ErrorAction Stop

            Write-ServicingLog "Disabling scheduled task '$taskName'"
            Disable-ScheduledTask -TaskName $taskName -ErrorAction Stop

            $scheduledTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
            if ($scheduledTask -and $scheduledTask.State -ne "Disabled")
            {
                throw "$scheduledTask is $($scheduledTask.State) after disabling"
            }
        }

        Invoke-WithRetry $disableTaskScript
    }
}

<#
.SYNOPSIS
    Ensure copy staging task is not running, otherwise an exception will be thrown in this function
#>
function Assert-CopyStagingTaskNotRunning
{
    $taskName = "DynamicsServicingCopyStaging"
    $scheduledTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
    if (-not $scheduledTask)
    {
        Write-ServicingLog "No scheduled task '$taskName' detected"
        return
    }

    if ($scheduledTask.State -ne "Disabled")
    {
        throw "Task '$taskName' is in '$($scheduledTask.State)' state! It should have been disabled!"
    }

    Write-ServicingLog "Task '$taskName' is disabled"
}

function Set-CopyStagingWorkingDirectory([string]$taskWorkingDirectory, [string]$taskScriptFilePath)
{
    $deployablePackagePath = (Resolve-Path "$PSScriptRoot/../..").Path

    if (-not (Test-Path $taskWorkingDirectory))
    {
        Write-ServicingLog "Creating directory: $taskWorkingDirectory ."
        New-Item -ItemType Directory -Path $taskWorkingDirectory -ErrorAction Stop
    }

    if (-not (Test-Path $taskScriptFilePath))
    {
        $taskScriptSourcePath = Join-Path $PSScriptRoot "CopyStagingFolderTaskLog.ps1"

        if (-not (Test-Path $taskScriptSourcePath))
        {
            throw "'$taskScriptSourcePath' is not found."
        }
        else
        {
            Write-ServicingLog "Copying '$taskScriptSourcePath' to directory '$taskWorkingDirectory'"
            Copy-Item $taskScriptSourcePath -Destination $taskWorkingDirectory -ErrorAction Stop
        }

        Write-ServicingLog "'$taskScriptFilePath' created!"
    }

    $targetEventSourceDllPath = Join-Path $taskWorkingDirectory "Microsoft.Dynamics.ApplicationPlatform.Development.Instrumentation.dll"
    if (-not (Test-Path $targetEventSourceDllPath))
    {
        $sourceEventSourceDllPath = Join-Path $deployablePackagePath "Microsoft.Dynamics.ApplicationPlatform.Development.Instrumentation.dll"
        Write-ServicingLog "Copying script from '$sourceEventSourceDllPath' to directory '$targetEventSourceDllPath' ."
        Copy-Item $sourceEventSourceDllPath $targetEventSourceDllPath -ErrorAction Stop
    }
}

<#
.SYNOPSIS
    Create copy staging task with disabled state if the task doesn't exist
#>
function Set-CopyStagingTask
{
    $taskName = "DynamicsServicingCopyStaging"
    $scheduledTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
    if (-not $scheduledTask)
    {
        Write-ServicingLog "Creating scheduled task '$taskName'."
        $CopyStagingFolderTaskTemplate =@'
<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
     <Version>1.0.0</Version>
     <Description>Copying aosservice folder to aosservicestaging every day.</Description>
  </RegistrationInfo>
  <Settings>
    <Enabled>false</Enabled>
  </Settings>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2100-01-01T00:00:00</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Actions/>
</Task>
'@
        $sourceFolderPath = Get-AosServicePath
        $targetFolderPath = Get-AosServiceStagingPath

        $taskWorkingDirectory = Join-Path $env:SystemDrive "DynamicsServicing"
        $taskScriptFilePath = Join-Path $taskWorkingDirectory "CopyStagingFolderTaskLog.ps1"
        $robocopyLogFile = Join-Path $taskWorkingDirectory "robocopylog.txt"
        Set-CopyStagingWorkingDirectory $taskWorkingDirectory $taskScriptFilePath

        [xml]$taskTemplate = $CopyStagingFolderTaskTemplate
        $taskActions = @(
            @{ Command = "powershell.exe"; Arguments = "-WindowStyle Hidden -ExecutionPolicy bypass -File $taskScriptFilePath " }
            @{ Command = "robocopy.exe"; Arguments = "`"$sourceFolderPath`" `"$targetFolderPath`" /MIR /FFT /W:5 /R:5 /LOG:`"$robocopyLogFile`"" }
            @{ Command = "powershell.exe"; Arguments = "-WindowStyle Hidden -ExecutionPolicy bypass -File $taskScriptFilePath -end" }
        )

        $taskNamespace = [System.Xml.XmlNamespaceManager]::new($taskTemplate.NameTable)
        $taskNamespace.AddNamespace("t", "http://schemas.microsoft.com/windows/2004/02/mit/task")
        $actionsNode = $taskTemplate.SelectSingleNode("/t:Task/t:Actions", $taskNamespace)

        foreach($taskAction in $taskActions)
        {
            $execNode = [xml]("<Exec><Command>{0}</Command><Arguments>{1}</Arguments></Exec>" -f $taskAction.Command, $taskAction.Arguments)
            $actionsNode.AppendChild($taskTemplate.ImportNode($execNode.SelectSingleNode("/Exec"), $true))
        }
        $xmlPath = Join-Path $PSScriptRoot "copystagingtask.xml"

        $taskTemplate.OuterXml.Replace("xmlns=`"`"", "") | Out-File $xmlPath

        $scheduleTasksProc = "schtasks.exe"
        $argumentList = "/Create /TN $taskName /RU SYSTEM /XML `"$xmlPath`""

        Write-ServicingLog "Starting $scheduleTasksProc $argumentList"
    
        $proc = Start-Process -FilePath $scheduleTasksProc -ArgumentList $argumentList -NoNewWindow -PassThru -Wait
    
        if ($proc.ExitCode -ne 0)
        {
            throw "Failed to schedule copy staging task. Exit Code: $($proc.ExitCode)"
        }

        Write-ServicingLog "Finished $scheduleTasksProc $argumentList"

    }
    else
    {
        Write-ServicingLog "'$taskName' already exists!"        
    }
}

<#
.SYNOPSIS
    Log the process that locking the given file, and also stop the process if necessary to release the lock.

.PARAMETER fileName
    The full path of the file that is locked by some process

.PARAMETER safeProcessListToClose
    A set of process name that can be safely closed if any process from it is detected locking the file

#>
function Resolve-FileLock([string] $fileName, [System.Collections.Generic.HashSet[string]] $safeProcessListToClose = $null)
{
    if (-not (Locate-FileLockCheckDll))
    {
        return
    }

    Add-Type -LiteralPath $Global:FileLockCheckDll_Path
    $fileName = (Resolve-Path $fileName).Path
    $processList = [FileLockCheck.FileLockCheck]::GetProcessLockingTheFile(@($fileName))

    foreach ($processId in $processList)
    {
        try {
            $process = Get-Process -Id $processId -ErrorAction SilentlyContinue

            if ($process -ne $null)
            {
                Write-ServicingLog "$($process.Name)[$($process.Id)] is locking $fileName"

                if ($safeProcessListToClose -ne $null -and $safeProcessListToClose.Contains($process.Name))
                {
                    Write-ServicingLog "Stopping process '$($process.Name)' to release the lock on '$fileName'"
                    Stop-Process -Id $processId -Force
                }
            }
        } catch {
            Write-ServicingLog "Error in Resolve-FileLock: $_"
        }
    }
}

<#
.SYNOPSIS
    Get build number of installed AOS
#>
function Get-InstalledPlatformBuild([Parameter(Mandatory=$false)] $webroot)
{
    if (!$webroot) {
       $webroot = Get-AosWebSitePhysicalPath
    }

    #must use job or process to load the production information provider dll or it'll lock it self
    #in memory copy is not usable as this dll have some special hard coded reference dll which won't resolve when loaded in memory.
    $job = Start-Job -ScriptBlock  {
        param($webrootBlock)
        $VersionDLLPath = Join-Path $webrootBlock 'bin\Microsoft.Dynamics.BusinessPlatform.ProductInformation.Provider.dll'
        Add-Type -Path $VersionDLLPath
        $provider = [Microsoft.Dynamics.BusinessPlatform.ProductInformation.Provider.ProductInfoProvider]::get_Provider();
        $version = $provider.get_PlatformBuildVersion();
        $version
    } -ArgumentList $webroot
    Wait-Job -Job $job | Out-Null
    $version = Receive-Job -Job $job
   
    $build = [System.Version]::new()
    $releaseBuild
    if([System.Version]::TryParse($Version, [ref]$build))
    {
        $releaseBuild = $build.Build
    }
    else
    {
        #default to 0 from 7.0.0.0 
        $releaseBuild = 0
    }   

    return  $releaseBuild
}

<#
.SYNOPSIS
    Analyze the ErrorRecord object and proceed with further actions if necessary.
    Currently, it only detect file lock violation of IOException.
#>
function Resolve-ErrorRecord([System.Management.Automation.ErrorRecord] $errorRecord)
{
    try
    {
        $ex = $errorRecord.Exception
        $level = 0
        $maxLevelOfInnerException = 10

        while ($ex -ne $null -and $level -lt $maxLevelOfInnerException)
        {    
            if ($ex -is [System.IO.IOException])
            {
                $win32ErrorCode = $ex.HResult -band 0xffff
                # https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
                # ERROR_LOCK_VIOLATION is 0x20
                if ($win32ErrorCode -eq 0x20)
                {
                    # Extract the file name from the message
                    if ($ex.Message -match "The process cannot access the file '(?<file>.+)'")
                    {
                        $fileName = $Matches['file']
                        Write-ServicingLog "Trying to resolve the file lock issue for '$fileName'"
                        Resolve-FileLock $fileName
                    }
                }
                else
                {
                    $win32Error = new-object System.ComponentModel.Win32Exception($win32ErrorCode)
                    Write-ServicingLog "IOException detected: [$win32ErrorCode] $win32Error.Message"
                }
                break;
            }

            $ex = $ex.InnerException
            $level += 1
        }
    } catch {
        Write-ServicingLog "Failed to resolve errorRecord: $_"
    }
}

<#
.SYNOPSIS
    Write output if PITR required or not.

.PARAMETER Message
    PITR required flag.
#>
function Write-IsPITRRequiredDuringRollback([string]$PitrRequired)
{
  [hashtable]$returnResult = @{}
  $returnResult.AXDBPITRRequiredDuringRollback =[string]$PitrRequired
  $Object = New-Object PSObject -Property @{ RunbookScriptResult = $returnResult }
  Write-Output $Object
}

Export-ModuleMember -Function Set-ServicingLog
Export-ModuleMember -Function Get-ServicingLog
Export-ModuleMember -Function Write-ServicingLog
Export-ModuleMember -Function Invoke-WithRetry
Export-ModuleMember -Function Test-IsRunningAsAdministrator
Export-ModuleMember -Function Stop-ServiceAndDisableStartup
Export-ModuleMember -Function Backup-WebSite
Export-ModuleMember -Function Create-ZipFiles
Export-ModuleMember -Function Get-AosAppPoolName
Export-ModuleMember -Function Get-ProductConfigurationAppPoolName
Export-ModuleMember -Function Get-AosWebSiteName
Export-ModuleMember -Function Get-AosWebSitePhysicalPath
Export-ModuleMember -Function Get-WebSitePhysicalPath
Export-ModuleMember -Function Restore-WebSite
Export-ModuleMember -Function Unpack-ZipFiles
Export-ModuleMember -Function Copy-SymbolicLinks
Export-ModuleMember -Function Copy-FullFolder
Export-ModuleMember -Function Get-RelativePath
Export-ModuleMember -Function Get-AosServicePath
Export-ModuleMember -Function Get-AosServiceStagingPath
Export-ModuleMember -Function Get-AosServiceBackupPath
Export-ModuleMember -Function Create-ZipFiles-FromFileList
Export-ModuleMember -Function KillProcessLockingFolder
Export-ModuleMember -Function KillAllOtherUserSession
Export-ModuleMember -Function Update-AOSConnectionString
Export-ModuleMember -Function KillWatchdogProcess
Export-ModuleMember -Function Get-ApplicationReleaseFromAOS
Export-ModuleMember -Function Initialize-ScriptProgress
Export-ModuleMember -Function Add-ScriptProgress
Export-ModuleMember -Function Test-ScriptProgress
Export-ModuleMember -Function Copy-ScriptProgressFile
Export-ModuleMember -Function Get-ScriptProgress
Export-ModuleMember -Function Get-RunbookId
Export-ModuleMember -Function Get-PackageRoot
Export-ModuleMember -Function Assert-CopyStagingTaskNotRunning
Export-ModuleMember -Function Disable-CopyStagingTask
Export-ModuleMember -Function Set-CopyStagingTask
Export-ModuleMember -Function Resolve-FileLock
Export-ModuleMember -Function Resolve-ErrorRecord
Export-ModuleMember -Function EventWrite-RunbookScriptTrace
Export-ModuleMember -Function Write-IsPITRRequiredDuringRollback
Export-ModuleMember -Function Reset-IIS
Export-ModuleMember -Function Get-IsIISHealthy
Export-ModuleMember -Function Update-EnableRestartForSiteAfterIISResetOrReboot
Export-ModuleMember -Function Update-EnableRestartForAppPoolAfterIISResetOrReboot
Export-ModuleMember -Function Update-DisableRestartForSiteAfterIISResetOrReboot
Export-ModuleMember -Function Update-DisableRestartForAppPoolAfterIISResetOrReboot
Export-ModuleMember -Function Get-InstalledPlatformBuild


# SIG # Begin signature block
# MIIsAAYJKoZIhvcNAQcCoIIr8TCCK+0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCpbvFPoZps0iMF
# SoeN9nDxIWdL0gc7LkD4R/SlPUrms6CCEW4wggh+MIIHZqADAgECAhM2AAACAO38
# jbec3qFIAAIAAAIAMA0GCSqGSIb3DQEBCwUAMEExEzARBgoJkiaJk/IsZAEZFgNH
# QkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFNRSBDUyBDQSAwMTAe
# Fw0yNDExMDgxMjQzMjhaFw0yNTExMDgxMjQzMjhaMCQxIjAgBgNVBAMTGU1pY3Jv
# c29mdCBBenVyZSBDb2RlIFNpZ24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQC5L/UPrOpwYjxcoZC0TqqvMF1WUELvwXN+k27SrA5rohJknn7Cgbxg4hGT
# XKqpcdbtsVTN3ZY896SJ20uQ+INL5OVLzpW408nCNTPYg2LtGJbqHUjpNm0hLCJ+
# gO5Jn2T8DDzIJoUijGXj1m+hRLKb2nOIicCED2GuYBmuWXnaY7INmVEaU3peryty
# ZjDuxdyGDuiPURz8lW1SUiDzoszNp1oswVr+WjDvLDUx4HlxPsG8zUjIst0NnJ6o
# z4tNFKaUBDCetcMjQxpCETn29a1CuRddxZLjPHZHfcotr5sh1S6bNQdzVaMNsxV8
# L3wjHb7XJ6ZVm662mHEiPgpyNcLhAgMBAAGjggWKMIIFhjApBgkrBgEEAYI3FQoE
# HDAaMAwGCisGAQQBgjdbAQEwCgYIKwYBBQUHAwMwPQYJKwYBBAGCNxUHBDAwLgYm
# KwYBBAGCNxUIhpDjDYTVtHiE8Ys+hZvdFs6dEoFgg93NZoaUjDICAWQCAQ4wggJ2
# BggrBgEFBQcBAQSCAmgwggJkMGIGCCsGAQUFBzAChlZodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpaW5mcmEvQ2VydHMvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1F
# JTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBSBggrBgEFBQcwAoZGaHR0cDovL2NybDEu
# YW1lLmdibC9haWEvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUy
# MDAxKDIpLmNydDBSBggrBgEFBQcwAoZGaHR0cDovL2NybDIuYW1lLmdibC9haWEv
# QlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBS
# BggrBgEFBQcwAoZGaHR0cDovL2NybDMuYW1lLmdibC9haWEvQlkyUEtJQ1NDQTAx
# LkFNRS5HQkxfQU1FJTIwQ1MlMjBDQSUyMDAxKDIpLmNydDBSBggrBgEFBQcwAoZG
# aHR0cDovL2NybDQuYW1lLmdibC9haWEvQlkyUEtJQ1NDQTAxLkFNRS5HQkxfQU1F
# JTIwQ1MlMjBDQSUyMDAxKDIpLmNydDCBrQYIKwYBBQUHMAKGgaBsZGFwOi8vL0NO
# PUFNRSUyMENTJTIwQ0ElMjAwMSxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2Vy
# dmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1BTUUsREM9R0JM
# P2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0
# aG9yaXR5MB0GA1UdDgQWBBST/HE52ZUlmsYqZcZBdrXZ5u4ZnzAOBgNVHQ8BAf8E
# BAMCB4AwRQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEWMBQGA1UEBRMNMjM2MTY3KzUwMzE1NTCCAeYGA1UdHwSCAd0wggHZMIIB
# 1aCCAdGgggHNhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpaW5mcmEvQ1JM
# L0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwxLmFtZS5nYmwv
# Y3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwyLmFtZS5n
# YmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmwzLmFt
# ZS5nYmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGMWh0dHA6Ly9jcmw0
# LmFtZS5nYmwvY3JsL0FNRSUyMENTJTIwQ0ElMjAwMSgyKS5jcmyGgb1sZGFwOi8v
# L0NOPUFNRSUyMENTJTIwQ0ElMjAwMSgyKSxDTj1CWTJQS0lDU0NBMDEsQ049Q0RQ
# LENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZp
# Z3VyYXRpb24sREM9QU1FLERDPUdCTD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0
# P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwHwYDVR0jBBgw
# FoAUllGE4Gtve/7YBqvD8oXmKa5q+dQwHwYDVR0lBBgwFgYKKwYBBAGCN1sBAQYI
# KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAEDd8Wf5RkHsB64vgn2slxDtHzSo
# It9xN/Dm3RdFjNZ0diTUPMgSPYQlSk8nIAfudnB9FLavGlvZLlyUpfrPSuikepj3
# i3pqNEFn6fNdNFv/wHMxv7hQTIDCmuoR1v1rX+w3oeleBPMnN3QmH4ff1NsynyV4
# dZdYgN9Cw9sC/S3pWZpJrbOs7YOM3vqyU6DciHhC4D9i2zByHCF2pu9nYfiQf5A2
# iUZenRvyo1E5rC+UP2VZXa4k7g66W20+zAajIKKIqEmRtWahekMkCcOIHFBY4RDA
# ybgPRSGur4VDAiZPjTXS90wQXrX9CwU20cfiCC6e76F4H95KtQjKYpzuNVAwggjo
# MIIG0KADAgECAhMfAAAAUeqP9pxzDKg7AAAAAABRMA0GCSqGSIb3DQEBCwUAMDwx
# EzARBgoJkiaJk/IsZAEZFgNHQkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxEDAOBgNV
# BAMTB2FtZXJvb3QwHhcNMjEwNTIxMTg0NDE0WhcNMjYwNTIxMTg1NDE0WjBBMRMw
# EQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRUwEwYDVQQD
# EwxBTUUgQ1MgQ0EgMDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ
# mlIJfQGejVbXKpcyFPoFSUllalrinfEV6JMc7i+bZDoL9rNHnHDGfJgeuRIYO1LY
# /1f4oMTrhXbSaYRCS5vGc8145WcTZG908bGDCWr4GFLc411WxA+Pv2rteAcz0eHM
# H36qTQ8L0o3XOb2n+x7KJFLokXV1s6pF/WlSXsUBXGaCIIWBXyEchv+sM9eKDsUO
# LdLTITHYJQNWkiryMSEbxqdQUTVZjEz6eLRLkofDAo8pXirIYOgM770CYOiZrcKH
# K7lYOVblx22pdNawY8Te6a2dfoCaWV1QUuazg5VHiC4p/6fksgEILptOKhx9c+ia
# piNhMrHsAYx9pUtppeaFAgMBAAGjggTcMIIE2DASBgkrBgEEAYI3FQEEBQIDAgAC
# MCMGCSsGAQQBgjcVAgQWBBQSaCRCIUfL1Gu+Mc8gpMALI38/RzAdBgNVHQ4EFgQU
# llGE4Gtve/7YBqvD8oXmKa5q+dQwggEEBgNVHSUEgfwwgfkGBysGAQUCAwUGCCsG
# AQUFBwMBBggrBgEFBQcDAgYKKwYBBAGCNxQCAQYJKwYBBAGCNxUGBgorBgEEAYI3
# CgMMBgkrBgEEAYI3FQYGCCsGAQUFBwMJBggrBgEFBQgCAgYKKwYBBAGCN0ABAQYL
# KwYBBAGCNwoDBAEGCisGAQQBgjcKAwQGCSsGAQQBgjcVBQYKKwYBBAGCNxQCAgYK
# KwYBBAGCNxQCAwYIKwYBBQUHAwMGCisGAQQBgjdbAQEGCisGAQQBgjdbAgEGCisG
# AQQBgjdbAwEGCisGAQQBgjdbBQEGCisGAQQBgjdbBAEGCisGAQQBgjdbBAIwGQYJ
# KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMBIGA1UdEwEB/wQI
# MAYBAf8CAQAwHwYDVR0jBBgwFoAUKV5RXmSuNLnrrJwNp4x1AdEJCygwggFoBgNV
# HR8EggFfMIIBWzCCAVegggFToIIBT4YxaHR0cDovL2NybC5taWNyb3NvZnQuY29t
# L3BraWluZnJhL2NybC9hbWVyb290LmNybIYjaHR0cDovL2NybDIuYW1lLmdibC9j
# cmwvYW1lcm9vdC5jcmyGI2h0dHA6Ly9jcmwzLmFtZS5nYmwvY3JsL2FtZXJvb3Qu
# Y3JshiNodHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9hbWVyb290LmNybIaBqmxkYXA6
# Ly8vQ049YW1lcm9vdCxDTj1BTUVSb290LENOPUNEUCxDTj1QdWJsaWMlMjBLZXkl
# MjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPUFNRSxE
# Qz1HQkw/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNz
# PWNSTERpc3RyaWJ1dGlvblBvaW50MIIBqwYIKwYBBQUHAQEEggGdMIIBmTBHBggr
# BgEFBQcwAoY7aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJhL2NlcnRz
# L0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUHMAKGK2h0dHA6Ly9jcmwyLmFt
# ZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUHMAKGK2h0dHA6
# Ly9jcmwzLmFtZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQwNwYIKwYBBQUH
# MAKGK2h0dHA6Ly9jcmwxLmFtZS5nYmwvYWlhL0FNRVJvb3RfYW1lcm9vdC5jcnQw
# gaIGCCsGAQUFBzAChoGVbGRhcDovLy9DTj1hbWVyb290LENOPUFJQSxDTj1QdWJs
# aWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9u
# LERDPUFNRSxEQz1HQkw/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNl
# cnRpZmljYXRpb25BdXRob3JpdHkwDQYJKoZIhvcNAQELBQADggIBAFAQI7dPD+jf
# XtGt3vJp2pyzA/HUu8hjKaRpM3opya5G3ocprRd7vdTHb8BDfRN+AD0YEmeDB5HK
# QoG6xHPI5TXuIi5sm/LeADbV3C2q0HQOygS/VT+m1W7a/752hMIn+L4ZuyxVeSBp
# fwf7oQ4YSZPh6+ngZvBHgfBaVz4O9/wcfw91QDZnTgK9zAh9yRKKls2bziPEnxeO
# ZMVNaxyV0v152PY2xjqIafIkUjK6vY9LtVFjJXenVUAmn3WCPWNFC1YTIIHw/mD2
# cTfPy7QA1pT+GPARAKt0bKtq9aCd/Ym0b5tPbpgCiRtzyb7fbNS1dE740re0COE6
# 7YV2wbeo2sXixzvLftH8L7s9xv9wV+G22qyKt6lmKLjFK1yMw4Ni5fMabcgmzRvS
# jAcbqgp3tk4a8emaaH0rz8MuuIP+yrxtREPXSqL/C5bzMzsikuDW9xH10graZzSm
# PjilzpRfRdu20/9UQmC7eVPZ4j1WNa1oqPHfzET3ChIzJ6Q9G3NPCB+7KwX0OQmK
# yv7IDimj8U/GlsHD1z+EF/fYMf8YXG15LamaOAohsw/ywO6SYSreVW+5Y0mzJutn
# BC9Cm9ozj1+/4kqksrlhZgR/CSxhFH3BTweH8gP2FEISRtShDZbuYymynY1un+Ry
# fiK9+iVTLdD1h/SxyxDpZMtimb4CgJQlMYIZ6DCCGeQCAQEwWDBBMRMwEQYKCZIm
# iZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRUwEwYDVQQDEwxBTUUg
# Q1MgQ0EgMDECEzYAAAIA7fyNt5zeoUgAAgAAAgAwDQYJYIZIAWUDBAIBBQCgga4w
# GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG
# AQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIM8jF+XIABzU4Edj+2OWnkwAwXTL+Jdi
# X14ot1VlT/VcMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYA
# dKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEA
# OZYoSQ/aMnHVlZ/+LIwK3mQ937jcVcXAI5UWwKxeMhhu8C9c7+mOIcOpiiWyXt2T
# zovigLdfPil3F1dZBJ0GJ96wC2MtkVg1mX/oFxDb6l76Mm0RpBzkKUwzZ3ntb604
# y6e6GPUunlvRM7DYUzzRaU3nAwDKsjlVzxv6jKzcKpmkGoo6Tz0GftrPH6c2f7oM
# j5Fdm0R/kUFqL/sCv/zXQwdo33cdqLlc7m32KcA50wfFgc1Qb1MSsx+ygzgx450u
# UVVo0V0Kj4H/1uSsrnMXJGLvH6JAxRrj8di7mFYy/iRFT1Tf5si0/+R8WeOxyeSq
# 0JqgP8eLco1pXkE54ICkUqGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqG
# SIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0B
# CRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUA
# BCBj6+s690jxFlSfBtSGs2kR+e20U+VQeNtZ1T5A5ZWQJwIGaBLAccKxGBMyMDI1
# MDUwOTExMjEyMy43NjJaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBP
# cGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2QjA1
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCEf4wggcoMIIFEKADAgECAhMzAAAB9oMvJmpUXSLBAAEAAAH2MA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0MDcyNTE4
# MzEwNFoXDTI1MTAyMjE4MzEwNFowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlv
# bnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjZCMDUtMDVFMC1E
# OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0UJeLMR/N9WPBZhuKVFF+eWJZ68W
# ujdj4X6JR05cxO5CepNXo17rVazwWLkm5AjaVh19ZVjDChHzimxsoaXxNu8IDggK
# wpXvpAAItv4Ux50e9S2uVwfKv57p9JKG+Q7VONShujl1NCMkcgSrPdmd/8zcsmhz
# cNobLomrCAIORZ8IwhYy4siVQlf1NKhlyAzmkWJD0N+60IiogFBzg3yISsvroOx0
# x1xSi2PiRIQlTXE74MggZDIDKqH/hb9FT2kK/nV/aXjuo9LMrrRmn44oYYADe/rO
# 95F+SG3uuuhf+H4IriXr0h9ptA6SwHJPS2VmbNWCjQWq5G4YkrcqbPMax7vNXUwu
# 7T65E8fFPd1IuE9RsG4TMAV7XkXBopmPNfvL0hjxg44kpQn384V46o+zdQqy5K9d
# DlWm/J6vZtp5yA1PyD3w+HbGubS0niEQ1L6wGOrPfzIm0FdOn+xFo48ERl+Fxw/3
# OvXM5CY1EqnzEznPjzJc7OJwhJVR3VQDHjBcEFTOvS9E0diNu1eocw+ZCkz4Pu/o
# Qv+gqU+bfxL8e7PFktfRDlM6FyOzjP4zuI25gD8tO9zJg6g6fRpaZc439mAbkl3z
# CVzTLDgchv6SxQajJtvvoQaZxQf0tRiPcbr2HWfMoqqd9uiQ0hTUEhG44FBSTeUP
# ZeEenRCWadCW4G8CAwEAAaOCAUkwggFFMB0GA1UdDgQWBBRIwZsJuOcJfScPWcXZ
# uBA4B89K8jAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8E
# WDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9N
# aWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYB
# BQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEw
# KDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G
# A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA13kBirH1cHu1WYR1ysj1
# 25omGtQ0PaQkEzwGb70xtqSoI+svQihsgdTYxaPfp2IVFdgjaMaBi81wB8/nu866
# FfFKKdhdp3wnMZ91PpP4Ooe7Ncf6qICkgSuwgdIdQvqE0h8VQ5QW5sDV4Q0Jnj4f
# 7KHYx4NiM8C4jTw8SQtsuxWiTH2Hikf3QYB71a7dB9zgHOkW0hgUEeWO9mh2wWqY
# S/Q48ASjOqYw/ha54oVOff22WaoH+/Hxd9NTEU/4vlvsRIMWT0jsnNI71jVArT4Q
# 9Bt6VShWzyqraE6SKUoZrEwBpVsI0LMg2X3hOLblC1vxM3+wMyOh97aFOs7sFnue
# mtI2Mfj8qg16BZTJxXlpPurWrG+OBj4BoTDkC9AxXYB3yEtuwMs7pRWLyxIxw/wV
# 9THKUGm+x+VE0POLwkrSMgjulSXkpfELHWWiCVslJbFIIB/4Alv+jQJSKAJuo9CE
# rbm2qeDk/zjJYlYaVGMyKuYZ+uSRVKB2qkEPcEzG1dO9zIa1Mp32J+zzW3P7suJf
# jw62s3hDOLk+6lMQOR04x+2o17G3LceLkkxJm41ErdiTjAmdClen9yl6HgMpGS4o
# kjFCJX+CpOFX7gBA3PVxQWubisAQbL5HgTFBtQNEzcCdh1GYw/6nzzNNt+0GQnno
# bBddfOAiqkzvItqXjvGyK1QwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAA
# AAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBB
# dXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YB
# f2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKD
# RLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus
# 9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTj
# kY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56
# KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39
# IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHo
# vwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJo
# LhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMh
# XV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREd
# cu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEA
# AaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqn
# Uv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnp
# cjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0w
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEw
# CwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/o
# olxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNy
# b3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+
# TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2Y
# urYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4
# U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJ
# w7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb
# 30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ
# /gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGO
# WhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFE
# fnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJ
# jXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rR
# nj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUz
# WLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBP
# cGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo2QjA1
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaIjCgEBMAcGBSsOAwIaAxUAFU9eSpdxs0a06JFIuGFHIj/I+36ggYMwgYCkfjB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOvH
# ymwwIhgPMjAyNTA1MDkwMDI3MjRaGA8yMDI1MDUxMDAwMjcyNFowdzA9BgorBgEE
# AYRZCgQBMS8wLTAKAgUA68fKbAIBADAKAgEAAgIhjwIB/zAHAgEAAgISsjAKAgUA
# 68kb7AIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQCJMqbmRjFh1xfAjbwu
# cWhB78NvPLRH0VAo6gxbjJ8jV9fuWFLPiX8mBgEohUbwL1gh24dCQ2nFcbbGEkrT
# mt/tKt9xC90GK94B+WSMbXYAqjL/PluiAnQt+QJJNPXoqXOZ8OG/awtoMJigLkmW
# Fuc6f1/PRqSOEpGupaz746Ic+mo8RQ7uh7QUy7bOa1kFfNQKsetGKf6PDUnxPzUb
# 8GNv8emj3WqwH9Ngd4B26d5rlWKvjRtkyl2OGbT2Yvg0em2vG7sqU71qsoMyRBJI
# Uso4SZ+xMhH7NODwACZB0ubG8s4FggSLepN+zprOzt7ly1kqsdL8UX+3pQZqN4VN
# 7vXNMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAAH2gy8malRdIsEAAQAAAfYwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgn5cLD4A6NEui0q3a
# V3FnkNz6R6mIE6Npvyh0CpPG9wgwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9
# BCArYUzxlF6m5USLS4f8NXL/8aoNEVdsCZRmF+LlQjG2ojCBmDCBgKR+MHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB9oMvJmpUXSLBAAEAAAH2MCIE
# IL4ITDHzDKFM1XQWK1iIEuqqvQZOcw16cJBe58Fvl478MA0GCSqGSIb3DQEBCwUA
# BIICAISEjDuoVnLA7cnhDTupfW2IgzDgkJTWlm/WTtWIDvOKUMn3fePhMmr4a2je
# W7PiPWLuBP8HMc/ci8KZ6wQxij6hxObWiwb2LsiDS5c/SW4jljxrtYDgl8mv5uYE
# +ktESSOU38XK/O/Etb5t5ZtOPeGCAIi1my00igO16KPlvdWYyD1Yrj1JBz/KGLQh
# Nz8pBTEHi9Oe9cl7XYkCCdbXLcBxuuhIJoQ4uYMaq1FL99iKpy3Ym4QZPbKOunzc
# F7QNcDUxASPphKKT9kFqyYM8INFpp02PYwD0FLLSNl6iWVxG6Qq7LJE7Upz/Unaj
# tG/foYk0xQFlP18kTNoA7bDe2vKnUDVkLVOFekBvIQ1dYVlXtU3zbeNVAqsRN3HV
# fsvjTCjo/2ZmGVcUJcFn5ASD9y51qmH6QoExlUsirk/qx3UL0uCu4KHFSbXDyPiE
# EcaevjVj2lLrHQn3b8vKAne+NJgIVOcIsRQ1wpIwLwEG/kRVYybpYdYK+htUty2U
# urJmi23PjQqUlUZvZtiAd3iQQcvhqnNgeTY7wI8XaSGqkdHAmUkxMBVwXWvy6UwY
# GJPGscjSD4K4uYB+zsGDt7Cta6jkgCwX0pKQMUVcfftx1fzkMxJahgPR/7ab62Gq
# 3w6KbVVq4PpW3iKZHy0HjUiVxWLaC4yHRqAVZmpA5ZoBVcNm
# SIG # End signature block
