[CmdletBinding()]
param
(
    [Parameter(Mandatory=$true)]
    [string]$servicemodelxml,

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

#region configuration generators
function Write-LCM-Configuration($configdata)
{
    Write-Verbose "Creating the LCM configuration to set the encryption certificate thumbprint..."
    $thumbprint=$configdata.AllNodes[0].Thumbprint
    $script="`
    LocalConfigurationManager 
    {
        CertificateId=`"$thumbprint`"
        ConfigurationMode = `"ApplyOnly`"
        RefreshMode = `"Push`"
    }"
    
    write-content $script
}

function Write-Certificate-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $certificates=$xd.SelectNodes("//ServiceModel/Certificates/Certificate")
    [string]$global:lastcertidentifier=[string]::Empty

    Write-Trace-Start "Certificate installation"
    foreach($certificate in $certificates){
        $identifier=$certificate.Name
        $rawdata=$certificate.RawData
        $pwd=$certificate.Password
        $storename=$certificate.StoreName
        $storelocation=$certificate.StoreLocation
        [string]$thumbprint=$certificate.Thumbprint

        if(-not [string]::IsNullOrEmpty($thumbprint) -and -not [string]::IsNullOrEmpty($rawdata) -and -not [string]::IsNullOrEmpty($pwd)){
            Write-Log "Creating the configuration for the certificate: '$identifier'"

            if(-not [string]::IsNullOrEmpty($global:lastcertidentifier)){
                $script="`
    DynamicsCertificate $identifier `
    { `
        Ensure=`"Present`" `
        Identifier=`"$identifier`" `
        RawData=`"$rawdata`" `
        StoreName=`"$storename`" `
        StoreLocation=`"$storelocation`" `
        Password=`"$pwd`" `
        Thumbprint=`"$thumbprint`" `
        DependsOn=@(`"$global:lastcertidentifier`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
            }else{
                $script="`
    DynamicsCertificate $identifier `
    { `
        Ensure=`"Present`" `
        Identifier=`"$identifier`" `
        RawData=`"$rawdata`" `
        StoreName=`"$storename`" `
        StoreLocation=`"$storelocation`" `
        Password=`"$pwd`" `
        Thumbprint=`"$thumbprint`" `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
            }

            Write-Content $script
            $global:lastcertidentifier="[DynamicsCertificate]$identifier"
        }else{
            Write-Log "Skipping the configuration for the certificate '$identifier' as a required value is missing in the service model."
        }
    }

    Write-Trace-End "Certificate installation"
}

function Write-Dependency-Configuration
{
    $parentdir=Split-Path -parent $PSCommandPath
    $parentdir=Split-Path -parent $parentdir
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $dependencies=$xd.SelectNodes("/ServiceModel/Dependencies/Dependency")
    $global:lastdependency=$global:lastcertidentifier
    Write-Trace-Start "Install dependencies"
    foreach($dependency in $dependencies){
      $type=$dependency.getAttribute("Type")
      switch($type){
        "WindowsFeature"
        {
            $name=$dependency.getAttribute("Name")
            $script="`
    WindowsFeature $name `
    { `
        Ensure=`"Present`"
        Name=`"$name`" `
        DependsOn=@(`"$global:lastdependency`") `
    }"
            write-content $script
            write-log "Creating the configuration for the WindowsFeature $name"
            $global:lastdependency="[WindowsFeature]$name"
        }

        "Msi" 
        {
            # not supported currently as some MSI installation requires reboot
        }

        "Exe" 
        {
            # not supported currently some exe installation requires reboot
        }

        Default {}
      }
    }

    Write-Trace-End "Install dependencies"
}

function Write-Firewall-Ports-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)

    $serviceName=$xd.SelectSingleNode("//ns:ServiceModel",$ns).getAttribute("Name")
    $inputendpoints=$xd.SelectNodes("/ServiceModel/Endpoints/InputEndpoint")
    Write-Trace-start "Open firewall ports"
    [string[]]$ports=$null
    foreach($inputendpoint in $inputendpoints){
        [string]$localport=$inputendpoint.getAttribute("LocalPort")
        if($ports -notcontains $localport){
            $ports += $localport
            Write-Log "Creating the configuration for the firewall port $name"
            $name="$serviceName-$localPort"
            $displayName="$ServiceName service on port $localPort"
            $access="Allow"
            $state="Enabled"
            [string[]]$profile=@("Any")
            $direction="Inbound"
            [string[]]$remoteport=@("Any")
            $description="$ServiceName service on port $localPort"
            $protocol="TCP"
        
            $script="`
            xFirewall OpenFirewallPort-$name `
            { `
                Ensure=`"Present`" `
                Name=`"$name`" `
                DisplayName=`"$displayName`" `
                Access=`"$access`" `
                State=`"$state`" `
                Profile=`"$profile`" `
                Direction=`"$direction`" `
                RemotePort=`"$remoteport`" `
                LocalPort=`"$localPort`" `
                Description=`"$description`" `
                Protocol=`"$protocol`" `
            }"
            Write-Content $script
        }
    }

    Write-Trace-End "Open firewall ports"
}

function Write-Perf-Counters-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)

    # retrieve parameters
    $webRoot=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='WebRoot']",$ns).getAttribute("Directory")
    $codeFolder = Join-Path $webRoot "bin"

    Write-Trace-start "Perf counter initialization"
    $script="`
    DynamicsPerfCounter InitializePerfCounters `
    { `
        Ensure=`"Present`" `
        CodeFolder=`"$codeFolder`" `
        DependsOn=@(`"[DynamicsPackage]DeployPackages`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"

    Write-Content $script
    Write-Log "Creating the configuration for the Dynamics perf counters"
    Write-Trace-End "Perf counter initialization"
}

function Write-DynamicsTools-Configuration
{
    $targetPath = Join-Path $env:SystemDrive "DynamicsTools"
    $sourcePath = Join-Path $PSScriptRoot "Redist\DynamicsTools"

    Write-Trace-Start "Copy DynamicsTools"
    $script = 
@"
    File CopyDynamicsTools
    { 
        Ensure = 'Present'
        DestinationPath = '$targetPath'
        Recurse = `$true
        SourcePath = '$sourcePath'
        Type = 'Directory'
        MatchSource = `$true
        DependsOn=@("$global:lastdependency")
    }
"@

    Write-Content $script
    Write-Log "Copying supporting tools (ex. nuget and 7za) to the target machine."
    Write-Trace-End "Copy DynamicsTools"
}

function Write-AosWebsite-Configuration
{
    $parentdir=Split-Path -parent $PSCommandPath
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)

    # retrieve parameters
    $webRoot=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='WebRoot']",$ns).getAttribute("Directory")
    $source=join-path $parentdir "..\Code"

    Write-Trace-start "Copy AOS website code"

    $script="`
    File CopyAosWebsiteCode `
    { `
        Ensure=`"Present`" `
        DestinationPath=`"$webRoot`" `
        Recurse=`$true `
        SourcePath=`"$source`" `
        Type=`"Directory`" `
        MatchSource=`$true `
        DependsOn=@(`"$global:lastdependency`") `
    }"

    Write-Content $script
    Write-Log "Creating the configuration to copy the AOS website code to the target machine"
    Write-Trace-End "Copy AOS website code"
}

function Write-Packages-Configuration([string]$deploydb)
{
    $parentdir=Split-Path -parent $PSCommandPath
    $parentdir=Split-Path -parent $parentdir
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)

    # retrieve parameters
    $packageDir=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='PackagesLocalDirectory']",$ns).getAttribute("Directory")
    $clickOncePackageDir=$xd.SelectSingleNode("//ns:InstallParameter[@Name='clickOnceInstallPath']",$ns).getAttribute("Value")
    $metadataInstallPath=$xd.SelectSingleNode("//ns:InstallParameter[@Name='metadataInstallPath']",$ns).getAttribute("Value")
    $webRoot=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='WebRoot']",$ns).getAttribute("Directory")
    $frameworkInstallPath=$xd.SelectSingleNode("//ns:InstallParameter[@Name='frameworkInstallPath']",$ns).getAttribute("Value")
    $dataset=$xd.SelectSingleNode("//ns:Setting[@Name='Deploy.Dataset']",$ns).getAttribute("Value")
    $packageName=$xd.SelectSingleNode("//ns:Setting[@Name='Aos.Packages']",$ns).getAttribute("Value")
    $packageSourceDir=join-path $parentdir "Packages"
	$dataStack=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.UseManagedDatastack']",$ns).getAttribute("Value")

    Write-Trace-start "Deploy packages"

    $script = 
@"
    DynamicsPackage DeployPackages
    {
        PackageName="$packageName"
        TargetPackageDirectory="$packageDir"
        ClickOnceInstallDirectory="$clickOncePackageDir"
        PackageSourceDirectory="$packageSourceDir"
        Ensure="Present"
        MetadataDirectory="$metadataInstallPath"
        FrameworkDirectory="$frameworkInstallPath"
        DataSet="$dataset"
        WebRoot="$webRoot"
        DependsOn=@("[File]CopyAosWebsiteCode", "[File]CopyDynamicsTools")
        MonitoringAssembly="$global:telemetrydll"
        LogDir='$global:logdir'
        DeployDatabase='$deploydb'
        UseManagedDatastack='$dataStack'
    }
"@

    Write-Content $script
    Write-Log "Creating the configuration to deploy the packages"
    Write-Trace-End "Deploy packages"
}

function Write-Storage-Emulator-Configuration
{
    $emulatorinstallpath=Get-AzureStorageEmulatorInstallPath
    if(-not [string]::IsNullOrEmpty($emulatorinstallpath))
    {
        Write-Log "Creating the configuration to start the Azure Storage Emulator"
        $storageemulator=join-path $emulatorinstallpath "AzureStorageEmulator.exe"
        $initEmulatorStdOut=join-path $global:logdir "InitEmulator-output.log"
        $initEmulatorStdErr=join-path $global:logdir "InitEmulator-error.log"
        $startEmulatorStdOut=join-path $global:logdir "StartEmulator-output.log"
        $startEmulatorStdErr=join-path $global:logdir "StartEmulator-error.log"
        $clearEmulatorStdOut=join-path $global:logdir "ClearEmulator-output.log"
        $clearEmulatorStdErr=join-path $global:logdir "ClearEmulator-error.log"
        $script="`
        WindowsProcess StopEmulator
        {
            Path=`"$storageemulator`" `
            Arguments=`"start /inprocess`" `
            Ensure=`"Absent`" `
        }

        DynamicsProcess InitializeEmulator
        {
            Ensure=`"Present`" `
            Arguments=`"init -forcecreate -server $env:COMPUTERNAME`"
            File=`"$storageemulator`" `
            StandardErrorPath=`"$initEmulatorStdErr`" `
            StandardOutputPath=`"$initEmulatorStdOut`" `
            MonitoringAssembly=`"$global:telemetrydll`" `
            WaitForExit=`$true `
            DependsOn=@(`"[WindowsProcess]StopEmulator`")
        }

        DynamicsProcess StartEmulator
        {
            Ensure=`"Present`" `
            Arguments=`"start`"
            File=`"$storageemulator`" `
            StandardErrorPath=`"$startEmulatorStdErr`" `
            StandardOutputPath=`"$startEmulatorStdOut`" `
            MonitoringAssembly=`"$global:telemetrydll`" `
            WaitForExit=`$true `
            DependsOn=@(`"[DynamicsProcess]InitializeEmulator`")
        }
        
        DynamicsProcess ClearEmulator
        {
            Ensure=`"Present`" `
            Arguments=`"clear all`"
            File=`"$storageemulator`" `
            StandardErrorPath=`"$clearEmulatorStdErr`" `
            StandardOutputPath=`"$clearEmulatorStdOut`" `
            MonitoringAssembly=`"$global:telemetrydll`" `
            WaitForExit=`$true `
            DependsOn=@(`"[DynamicsProcess]InitializeEmulator`")
        }
        "
    
        Write-Content $script

        # Generate script for windows task to start the azure storage emulator (reboot scenario)
        Write-Log "Creating the configuration to register windows task to start the azure storage emulator"
        
        $script = @"
        DynamicsScheduledTask StartAzureStorageEmulator_ScheduledTask
        {
            TaskName  = "DynamicsStartAzureStorageEmulator"
            Ensure    = "Present"
            Command   = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe"
            Arguments = "START"
        }
"@
        Write-Content $script

        Write-Trace-End "start storage emulator"
    }
}

function Write-Database-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $packageDir=$xd.SelectSingleNode("//ns:Setting[@Name='Aos.PackageDirectory']",$ns).getAttribute("Value")
    $dbserver=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.DbServer']",$ns).getAttribute("Value")
    $dbname=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.Database']",$ns).getAttribute("Value")
    $sqlpwd=(Get-KeyVaultSecret -VaultUri $xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlPwd']",$ns).getAttribute("Value"))
    $sqluser=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlUser']",$ns).getAttribute("Value")

    Write-Trace-start "Restore database"

    $script="`
    DynamicsDatabase DeployDatabase `
    { `
        Ensure=`"Present`" `
        PackageDirectory=`"$packageDir`" `
        DatabaseName=`"$dbname`" `
        DatabaseServer=`"$dbserver`" `
        UserName=`"$sqluser`" `
        Password=`"$sqlpwd`" `
        DependsOn=@(`"[DynamicsPackage]DeployPackages`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
    Write-Log "Creating the configuration to deploy the Unified Operations database"
    Write-Content $script

    Write-Trace-End "Restore database"
}

function Write-WebSite-Configuration([switch]$DeployDb)
{
    Import-Module WebAdministration
    $parentdir=Split-Path -parent $PSCommandPath
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $websiteName=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.ApplicationName']",$ns).getAttribute("Value")
    $webAppPoolName=$websiteName
    $localPort=$xd.SelectSingleNode("//ns:InputEndpoint",$ns).getAttribute("LocalPort")
    $webRoot=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='WebRoot']",$ns).getAttribute("Directory")
    $endpoints=$xd.SelectNodes("//ServiceModel/Endpoints/InputEndpoint")
    $hostnames=@()
    [bool]$createARRhealthprobebinding=$false
    Write-Trace-start "Create AOS website"

    if($DeployDb){
    # create the aos app-pool
    $script="`
    xWebAppPool CreateAosAppPool `
    { `
        Ensure=`"Present`" `
        State=`"Started`" `
        Name=`"$webAppPoolName`" `
        DependsOn=@(`"[DynamicsDatabase]DeployDatabase`") `
    }"}else{
     # create the aos app-pool
    $script="`
    xWebAppPool CreateAosAppPool `
    { `
        Ensure=`"Present`" `
        State=`"Started`" `
        Name=`"$webAppPoolName`" `
    }"    
    }
    Write-Log "Creating the configuration to create the AOS app pool"
    Write-Content $script

    # Task 3745881: use topology data to determine app pool settings
    $optionalAppPoolSettings = 
@"        
        PingMaxResponseTime=90
        RapidFailProtection=`$false
        IdleTimeout=0
"@
    
    $script=
@"
    DynamicsApppoolManager SetApppoolIdentity
    {
        Ensure="Present"
        IdentityType=2
        ApppoolName="$webAppPoolName"
        Username=""
        Password=""
        DependsOn=@("[xWebAppPool]CreateAosAppPool")
        MonitoringAssembly="$global:telemetrydll"
$optionalAppPoolSettings
    }
"@ 
    Write-Log "Creating the configuration to set the AOS app pool identity"
    Write-Content $script

    $bindingInfo=@()
    $endpointcollection=@{}

    Write-Log "Creating ssl bindings for the AOS website"
    for($i=0;$i -lt $endpoints.Count;$i++){
        $endpoint=$endpoints[$i]
        $baseurl=New-Object System.Uri($endpoint.BaseUrl)
        $baseurlsafe=$baseurl.DnsSafeHost
        $sslCertName=$endpoint.Certificate
        if(-not [string]::IsNullOrEmpty($sslCertName)){
            $sslCertThumbprint=$xd.SelectSingleNode("//ns:Certificate[@Name='$sslCertName']",$ns).getAttribute("Thumbprint")
            $storelocation=$xd.SelectSingleNode("//ns:Certificate[@Name='$sslCertName']",$ns).getAttribute("StoreLocation")
            $storename=$xd.SelectSingleNode("//ns:Certificate[@Name='$sslCertName']",$ns).getAttribute("StoreName")
            $cert=get-childitem Cert:\$storelocation\$storeName|where {$_.Thumbprint -eq $sslCertThumbprint}
            if($cert -ne $null){
                $thumbprint=$cert.Thumbprint
            }else{
                $message="Either the SSL binding certificate is not present in the machine store or could not be retrieved."
                Write-Log $message
                throw $message
            }
        }

        $protocol = "https"
        if ([System.String]::IsNullOrWhiteSpace($endpoint.Certificate)){
            $protocol = "http"
        }

        $port=$endpoint.LocalPort
        if($port -eq 80 -and $baseurlsafe -ne "127.0.0.1")
        {
            $createARRhealthprobebinding=$true
        }

        if(IsUniqueUrlAndPort -endpointcollection:$endpointcollection -baseurl:$baseurlsafe -port:$port){
            $endpointcollection[$i]=@($baseurlsafe,$port,$protocol)
            if($baseurlsafe -ne "127.0.0.1"){
                $hostnames+=$baseurlsafe
                write-verbose "Adding url '$baseurlsafe' to the hosts file" 
            }
        }
    }

    #special binding to support the ARR health probing
    if($createARRhealthprobebinding)
    {
        $protocol='http'
        $baseurl=""
        $port=80
        $ipaddress=Get-WMIObject win32_NetworkAdapterConfiguration|Where-Object{$_.IPEnabled -eq $true}|Foreach-Object {$_.IPAddress}|Foreach-Object {[IPAddress]$_ }|Where-Object {$_.AddressFamily -eq 'Internetwork'}|Foreach-Object {$_.IPAddressToString}
        $msftwebbindinginfo="@(MSFT_xWebBindingInformation { `
                    Protocol=`"$protocol`" `
                    Port=$port `
                    HostName=`"$baseurl`" `
                    IPAddress=`"$ipaddres`" `
                    });"

        $bindingInfo+=$msftwebbindinginfo
    }

    # create the binding info collection
    for($i=0;$i -lt $endpointcollection.Keys.Count;$i++)
    {
        $bindinginfotuple=$endpointcollection[$i]
        $baseurl=$bindinginfotuple[0]
        $port=$bindinginfotuple[1]
        $protocol=$bindinginfotuple[2]
        if($i -eq $endpointcollection.Keys.Count-1){
	        $msftwebbindinginfo="@(MSFT_xWebBindingInformation { `
                    Protocol=`"$protocol`" `
                    Port=$port `
                    CertificateThumbprint=`"$thumbprint`" `
                    CertificateStoreName=`"My`" `
                    HostName=`"$baseurl`" `
                    })"
        }else{
            $msftwebbindinginfo="@(MSFT_xWebBindingInformation { `
                    Protocol=`"$protocol`" `
                    Port=$port `
                    CertificateThumbprint=`"$thumbprint`" `
                    CertificateStoreName=`"My`" `
                    HostName=`"$baseurl`" `
                    });"
        }

		$bindingInfo+=$msftwebbindinginfo
    }

    $script="`
    xWebSite CreateAosWebSite `
    { `
        Ensure=`"Present`" `
        Name=`"$websiteName`" `
        PhysicalPath=`"$webRoot`" `
        State=`"Started`" `
        ApplicationPool=`"$webAppPoolName`" `
        BindingInfo=@($bindingInfo) `
        DependsOn=@(`"[DynamicsApppoolManager]SetApppoolIdentity`") `
    }"

    Write-Log "Creating the configuration to create the AOS web site"
    Write-Content $script

    $script = "`
    DynamicsIISAdministration SetIISConfiguration `
    { `
        Ensure=`"Present`" `
        ConfigurationPath=`"MACHINE/WEBROOT/APPHOST/$websiteName/Apps`" `
        Filter=`"system.webServer/security/requestFiltering/fileExtensions/add[@fileExtension='.config']`"
        SettingName=`"allowed`" `
        SettingValue=`"True`" `
        DependsOn=@(`"[xWebSite]CreateAosWebSite`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
    Write-Log "Creating the configuration to allow the config files from being served from the IIS server"
    Write-content $script

    $hostentries=@()
    for($i=0;$i -lt $hostnames.Length;$i++)
    {
        $hostname=$hostnames[$i]
        if($i -eq $hostnames.Length-1){
            $entry="`@{Ipaddress=`"127.0.0.1`";Hostname=`"$hostname`"`}"
        }else{
            $entry="@{Ipaddress=`"127.0.0.1`";Hostname=`"$hostname`"`},"
        }

        $hostentries+=$entry
    }

    $script = "`
    DynamicsHostsFileManager SetHostsFileEntries `
    { `
        Ensure=`"Present`" `
        WebsiteName=`"$websiteName`" `
        HostEntries=$hostentries `
        DependsOn=@(`"[DynamicsIISAdministration]SetIISConfiguration`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
    Write-Log "Creating the configuration to add hosts file entries"
    Write-content $script
    
    Write-Trace-End "Create AOS website" 
}

function Write-WebConfigFile-Configuration([string]$servicemodel)
{
    Write-Trace-start "Update web config file"
    $script = "`
    DynamicsWebConfigKeyValue UpdateWebConfigSetting`
    { `
        Ensure=`"Present`" `
        ServiceModel=`"$servicemodel`" `
        DependsOn=@(`"[DynamicsHostsFileManager]SetHostsFileEntries`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"

    Write-Log "Creating the configuration to update the web.config file"
    Write-Content $script

    Write-Trace-End "Update web config file"
}

function Write-WifConfigFile-Configuration([string]$servicemodel)
{
    Write-Trace-start "Update wif and wif.services config file"
    $script = "`
    DynamicsWifConfigKeyValue UpdateWifConfigSetting `
    { `
        ServiceModel=`"$servicemodel`" `
        Ensure=`"Present`" `
        DependsOn=@(`"[DynamicsWebConfigKeyValue]UpdateWebConfigSetting`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"

    Write-Log "Creating the configuration to update the wif.config and wif.services.config files"
    write-content $script

    Write-Trace-End "Update wif and wif.services config file"
}

function Write-Aos-Http-Configuration([string]$webroot,[string]$protocol)
{
    Write-Trace-start "Configure the HTTP protocol for the AOS website"
    $script = "`
    ConfigureAosProtocol AosHttpConfiguration `
    { `
        WebRoot=`"$webroot`" `
        Ensure=`"Present`" `
        DependsOn=@(`"[DynamicsWifConfigKeyValue]UpdateWifConfigSetting`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
        Protocol=`"$protocol`" `
    }"

    Write-Log "Creating the configuration to set the AOS protocol"
    write-content $script

    Write-Trace-End "Configure the HTTP protocol for the AOS website"
}

function Write-AosUser-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)

    $dbserver=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.DbServer']",$ns).getAttribute("Value")
    $dbname=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.Database']",$ns).getAttribute("Value")
    $username=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlUser']",$ns).getAttribute("Value")
    $password=(Get-KeyVaultSecret -VaultUri $xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlPwd']",$ns).getAttribute("Value"))
    $hosrurl=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.HostUrl']",$ns).getAttribute("Value")

     $script = "
     DynamicsAosAdminManager UpdateAosUser `
    { `
        Ensure=`"Present`" `
        HostUrl=`"$hosrurl`" `
        DatabaseServer=`"$dbserver`" `
        DependsOn=@(`"[ConfigureAosProtocol]AosHttpConfiguration`") `
        DatabaseName=`"$dbname`" `
        UserName=`"$username`" `
        Password=`"$password`" `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"

    Write-Log "Creating the configuration to update the AOS user"
    Write-Content $script
}

function Write-DBSYNC-Configuration
{
    $parentDir=Split-Path -parent $PSCommandPath
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $bindir=$xd.SelectSingleNode("//ns:Setting[@Name='Common.BinDir']",$ns).getAttribute("Value")
    $metadataDir=$bindir
    $sqlServer=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.DbServer']",$ns).getAttribute("Value")
    $database=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.Database']",$ns).getAttribute("Value")
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")

    $arguments="-bindir `"$binDir`" -metadatadir `"$metadataDir`" -sqlcredentialinenvironment `"$sqlPwd`" -sqlserver `"$sqlServer`" -sqldatabase `"$database`" -setupmode `"sync`" -syncmode `"fullall`""
    $codedir=Join-Path $webRoot "bin"
    $dbsyncexe= join-path "$codedir" "Microsoft.Dynamics.AX.Deployment.Setup.exe"

    $standardout=Join-Path $global:logdir "dbsync.output"
    $standarderr=Join-Path $global:logdir "dbsync.error"

    Write-Trace-start "Perform DBSync"

    $script = "`
    DynamicsProcess DbSync `
    { `
        Ensure=`"Present`" `
        Arguments='$arguments' `
        File=`"$dbsyncexe`" `
        StandardErrorPath=`"$standarderr`" `
        StandardOutputPath=`"$standardout`" `
        MonitoringAssembly=`"$global:telemetrydll`" `
        WaitForExit=`$true `
        DependsOn=@(`"[DynamicsAosAdminManager]UpdateAosUser`") `
    }"
    
    Write-Log "Creating the configuration to execute the DBSync process"
    Write-Content $script

    Write-Trace-End "Perform DBSync"
}

function Write-InterNode-Sync-Configuration([string]$primarynode)
{
    $script="WaitForAll DbSync `
    {
        ResourceName = '[DynamicsProcess]DbSync' `
        NodeName = '$primarynode' `
        RetryIntervalSec = 60 `
        RetryCount = 50 `
    }"

    Write-Log "Creating the configuration to synchronize actions between multiple AOS instances"
    Write-Content $script
}

function Write-SymbolicLinkGeneration-Configuration([switch]$primarynode)
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $vstoolscount=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.VSToolsCount']",$ns).getAttribute("Value")
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $packagedir=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='PackagesLocalDirectory']",$ns).getAttribute("Directory")
    if($vstoolscount -eq "0"){
        Write-Trace-start "Create symlink"

        if($primarynode){
        $script = "`
    DynamicsSymLink CreateSymLink `
    { `
        Ensure=`"Present`" `
        Webroot=`"$webRoot`" `
        PackageDir=`"$packageDir`" `
        DependsOn=@(`"[DynamicsProcess]DbSync`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
        }else{
         $script = "`
    DynamicsSymLink CreateSymLink `
    { `
        Ensure=`"Present`" `
        Webroot=`"$webRoot`" `
        PackageDir=`"$packageDir`" `
        DependsOn=@(`"[DynamicsWifConfigKeyValue]UpdateWifConfigSetting`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"
    }
        Write-Log "Creating the configuration to create sym links"
        Write-Content $script

        Write-Trace-End "Create symlink"
    }
}

function Write-NGen-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $vstoolscount=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.VSToolsCount']",$ns).getAttribute("Value")
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $packagedir=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='PackagesLocalDirectory']",$ns).getAttribute("Directory")
    if($vstoolscount -eq "0"){
        $script = "`
    DynamicsNgen GenerateNativeImages `
    { `
        Ensure=`"Present`" `
        Webroot=`"$webRoot`" `
        PackageDir=`"$packageDir`" `
        DependsOn=@(`"[DynamicsSymLink]CreateSymLink`") `
        UpdateProbingPath=`$true `
        UseLazyTypeLoader=`$false `
        MonitoringAssembly=`"$global:telemetrydll`" `
        PerformNgen=`$true `
    }"
    }else{
        $script = "`
    DynamicsNgen GenerateNativeImages `
    { `
        Ensure=`"Present`" `
        Webroot=`"$webRoot`" `
        PackageDir=`"$packageDir`" `
        DependsOn=@(`"[DynamicsWifConfigKeyValue]UpdateWifConfigSetting`") `
        UpdateProbingPath=`$false `
        UseLazyTypeLoader=`$true `
        MonitoringAssembly=`"$global:telemetrydll`" `
        PerformNgen=`$false `
    }"
    }

    Write-Log "Creating the configuration to generate native images of the Dynamics assemblies"
    Write-Content $script
}

function Write-Resources-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $packageDir=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='PackagesLocalDirectory']",$ns).getAttribute("Directory")

    Write-Trace-start "Deploy website resources"
    $script = "`
    DynamicsWebsiteResource AosResources `
    { `
        Ensure=`"Present`" `
        WebRoot=`"$webRoot`" `
        PackageDir=`"$packageDir`" `
        DependsOn=@(`"[DynamicsNgen]GenerateNativeImages`") `
        MonitoringAssembly=`"$global:telemetrydll`" `
    }"

    Write-Log "Creating the configuration to deploy the AOS resources"
    Write-Content $script
    
    Write-Trace-End "Deploy resources"
}

function Write-BatchService-Configuration
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $webRoot=$xd.SelectSingleNode("//ns:WorkingFolder[@Name='WebRoot']",$ns).getAttribute("Directory")
    $sqlUser=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlUser']",$ns).getAttribute("Value")
    $sqlPwd=(Get-KeyVaultSecret -VaultUri $xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.SqlPwd']",$ns).getAttribute("Value"))
    $sqlServer=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.DbServer']",$ns).getAttribute("Value")
    $database=$xd.SelectSingleNode("//ns:Setting[@Name='DataAccess.Database']",$ns).getAttribute("Value")

    $webconfig=join-path $webRoot "web.config"
    $batchexe=join-path $webroot "bin\Batch.exe"
    $binarypathname="$batchexe -service $webconfig"

    Write-Trace-start "Install batch service"

    # setting the state to Running will result in the service resource timing out as the timeout is hardcoded to 2 seconds :(
    $script = "`
    Service InstallBatchService `
    { `
        Ensure=`"Present`" `
        Name=`"DynamicsAxBatch`" `
        BuiltInAccount=`"NetworkService`" `
        State=`"Stopped`" `
        DisplayName=`"Microsoft Dynamics 365 Unified Operations: Batch Management Service`" `
        Path='$binarypathname' `
        DependsOn=@(`"[DynamicsWebsiteResource]AosResources`") `
        StartupType=`"Manual`"
    }"

    Write-Log "Creating the configuration to install the Dynamics batch service"
    Write-Content $script

    if ($env:aosserviceconfiguration_skipbatchstartup -eq $true) {
        Write-Log "Skipping batch startup as aosserviceconfiguration_skipbatchstartup flag was set"
    } else {
        $sc=Join-Path $env:windir "system32\sc.exe"
        $standardout=Join-Path $global:logdir "DynamicsAXBatch-sc-start-output.log"
        $standarderr=Join-Path $global:logdir "DynamicsAXBatch-sc-start-error.log"

        $script = "`
        DynamicsProcess StartBatchService `
        { `
            Ensure=`"Present`" `
            Arguments=`"start DynamicsAXBatch`" `
            File=`"$sc`" `
            StandardErrorPath=`"$standarderr`" `
            StandardOutputPath=`"$standardout`" `
            MonitoringAssembly=`"$global:telemetrydll`" `
            DependsOn=@(`"[Service]InstallBatchService`") `
            WaitForExit=`$true `
        }"

        Write-Log "Creating the configuration to start the Dynamics batch service"
        Write-Content $script
    }

    # Generate script for windows task to start the batch service (reboot scenario)
    Write-Log "Creating the configuration to register windows task to start the batch service"
        
    $script = @"
    DynamicsScheduledTask StartBatch_ScheduledTask
    {
        TaskName  = "DynamicsStartBatch"
        Ensure    = "Present"
        Command   = "POWERSHELL.EXE"
        Arguments = "Start-Service 'MSSQLSERVER' | %{$_.WaitForStatus('Running', '00:05:00')}; Start-Service 'DynamicsAxBatch'"
        DependsOn = @("[Service]InstallBatchService")
    }
"@
    Write-Content $script

    Write-Trace-End "Install batch service"
}

function Write-Reports-Configuration
{
    $parentDir=Split-Path -parent $PSCommandPath
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $settings=$xd.SelectNodes("/ServiceModel/Configuration/Setting")
    $configuration=@{}
    foreach($setting in $settings){
        $key=$setting.getAttribute("Name")
        $value=$setting.getAttribute("Value")
        $configuration[$key]=$value
    }

    $configjson=convertto-json $configuration
    $jsontobytes=[System.Text.Encoding]::UTF8.GetBytes($configjson)
    $encodedconfiguration=[System.Convert]::ToBase64String($jsontobytes)

    $scriptPath = Join-Path $parentDir "AXDeployReports.ps1"
    $log=join-path $global:logdir "AXDeployReports.log"
    if(!(Test-path $log)){
        New-Item $log -ItemType File -Force|out-null
    }

    $expr = "$scriptPath `"$encodedconfiguration`" `"$log`""

    $dependsOn = "[DynamicsProcess]StartBatchService"
    if ($env:aosserviceconfiguration_skipbatchstartup -eq $true) {
        $dependsOn = "[Service]InstallBatchService"
    }

    Write-Trace-start "Deploy reports"

    $script = "
    Script DeployReports `
    { `
        GetScript={@{}} `
        TestScript={return `$false} `
        SetScript={& $expr} `
        DependsOn=@(`"$dependsOn`") `
    }"

    Write-Log "Creating the configuration to deploy the reports"
    Write-Content $script

    Write-Trace-End "Deploy reports"
}

function Write-RetailPerfCounters-Configuration
{
    $parentDir=Split-Path -parent $PSCommandPath
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $instrumentedAssemblyPath=join-path $webRoot "bin\Microsoft.Dynamics.Retail.Diagnostics.dll"
    $scriptPath = Join-Path $parentDir "Register-PerfCounters.ps1"
    $log=join-path $global:logdir "RegisterRetailPerfCounters.log"
    if(!(Test-path $log)){
        New-Item $log -ItemType File -Force|out-null
    }

	$expr = "$scriptPath $instrumentedAssemblyPath $log"
	if(!(Test-Path "$log\RegisterRetailPerfCounters")){
		New-Item -ItemType directory -Path "$log\RegisterRetailPerfCounters" -Force|out-null
	}
    
    $dependsOn = "[DynamicsProcess]StartBatchService"
    if ($env:aosserviceconfiguration_skipbatchstartup -eq $true) {
        $dependsOn = "[Service]InstallBatchService"
    }

	Write-Trace-start "Create retail perf counters"
	if(Test-path $scriptPath)
	{
		$script = "`
		Script RetailPerfCounters `
		{ `
			GetScript={@{}} `
			TestScript={return `$false} `
			SetScript={& $expr} `
			DependsOn=@(`"$dependsOn`") `
		}"
	}
	else
	{
		Write-Log "Retail perf counters script not found, dummy script used for execution"
		$script = "`
		Script RetailPerfCounters `
		{ `
			GetScript={@{}} `
			TestScript={return `$false} `
			SetScript={return `$true} `
			DependsOn=@(`"$dependsOn`") `
		}"
	}

	Write-Log "Creating the configuration to execute the Retail perf counters script"
	Write-Content $script 

	Write-Trace-End "Create retail perf counters"
}

function Write-ProductConfiguration
{
    [string]$pcAppPool="ProductConfiguration"	
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $webRoot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $websiteName=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.ApplicationName']",$ns).getAttribute("Value")

    $packageNames = $xd.SelectSingleNode("//ns:Setting[@Name='Aos.Packages']",$ns).getAttribute("Value")
    $packagesSourceDir = Join-Path (Split-Path -Path $PSScriptRoot -Parent) "Packages"

    # product configuration variables
	$productconfigurationdir=join-path $webRoot "productconfiguration"
    $pcwebsitepath=join-path $webRoot "productconfiguration"

    Write-Trace-start "Create product configuration web application"

    # check of production configuration package will be installed
    $productConfigurationPackageFound = $false
    $targetPackageName = 'dynamicsax-productconfiguration'
    
    Add-Type -AssemblyName "System.IO.Compression"
    Add-Type -AssemblyName "System.IO.Compression.FileSystem"

    foreach($packageName in $packageNames.Split(','))
    {
        $packageName = $packageName.Trim()
        $filter = "$packageName.*.nupkg"
        $files = Get-ChildItem -Path $packagesSourceDir -Filter $filter
        foreach($file in $files)
        {
            [System.IO.Compression.ZipArchive] $archive = [System.IO.Compression.ZipFile]::OpenRead($file.FullName)
            [System.IO.Compression.ZipArchiveEntry]$entry = $archive.GetEntry("$($packageName).nuspec")
            if ($entry -ne $null)
            {
                [xml]$xml = New-Object System.Xml.XmlDocument
                $xml.Load($entry.Open())
                $ns = @{"ns"=$xml.DocumentElement.NamespaceURI}
                $dependency = select-xml -xml $xml -Namespace $ns -XPath "//ns:dependencies/ns:dependency[@id='$targetPackageName']"
                if ($dependency -ne $null)
                {
                    $productConfigurationPackageFound = $true
                }
            }
        }
    }

    if ($productConfigurationPackageFound)
    {        
        $script = 
@"
        xWebAppPool CreateProductConfigurationWebAppPool
        {
            Ensure="Present"
            State="Started"
            Name="$pcAppPool"
            DependsOn=@("[Script]RetailPerfCounters")
        }
    	
        # create product configuration web application
        xWebApplication CreateProductConfigurationWebApplication `
        {
            Ensure="Present"
            Name="ProductConfiguration"
            Website="$websiteName"
            WebAppPool="$pcAppPool"
            PhysicalPath="$pcwebsitepath"
            DependsOn=@("[xWebAppPool]CreateProductConfigurationWebAppPool")
        }
"@

        Write-Log "Creating the configuration to create the product configuration web application"
        Write-Content $script

        
        # Configure app pool settings
        $optionalAppPoolSettings = 
@"        
        PingMaxResponseTime=90
        RapidFailProtection=`$true
        IdleTimeout=20
"@

        $script=
@"
        DynamicsApppoolManager SetProductConfigurationApppoolIdentity
        {
            Ensure="Present"
            IdentityType=2
            ApppoolName="$pcAppPool"
            Username=""
            Password=""
            DependsOn=@("[xWebAppPool]CreateProductConfigurationWebAppPool")
            MonitoringAssembly="$global:telemetrydll"
$optionalAppPoolSettings
        }
"@ 
        Write-Log "Creating the configuration to set the product configuration app pool identity"
        Write-Content $script

    }
    else
    {
        Write-Log "Skipping product configuration web site DSC because package file not found: '$targetPackageName'."
    }

    Write-Trace-End "Create product configuration web application"
}

function Write-ConfigInstallationInfo-Configuration([string]$servicemodel)
{
    Write-Trace-start "Create config installation info"
    $script = "`
    ConfigInstallationInfoManager CreateConfigInstallationInfo `
    { `
        Ensure=`"Present`" `
        MonitoringAssembly=`"$global:telemetrydll`" `
        ServiceXml=`"$servicemodel`" `
        Log=`"$global:logdir`" `
        InstallationAssembly=`"$installationdll`" `
        InstallationInfoXml=`"$installationinfoxml`" `
        DependsOn=@(`"[Script]RetailPerfCounters`") `
    } "

    Write-Content $script
    Write-Trace-End "Create config installation info"
}

function Write-EncryptionConfiguration-Configuration([string]$webroot)
{
    Write-Trace-start "Create config to encrypt web configuration"
    $script = "`
    EncryptionManager EncryptConfiguration `
    { `
        Ensure=`"Present`" `
        MonitoringAssembly=`"$global:telemetrydll`" `
        WebRoot=`"$webroot`" `
        Log=`"$global:logdir`" `
        DependsOn=@(`"[ConfigInstallationInfoManager]CreateConfigInstallationInfo`") `
    } "

    Write-Content $script
    Write-Trace-End "Create config to encrypt web configuration"
}

#endregion

#region helper functions
function Initialize-Log([string]$logdir,[string]$logfile)
{
    if(-not (Test-Path $logdir)){
        New-Item -ItemType Directory -Path $logdir|out-null
    }
    
    if(Test-Path $logfile){
        Remove-Item $logfile -Force|out-null
    }

    New-item $logfile -ItemType File -Force|out-null
}

function Write-Log([string]$message)
{
    Add-Content $global:logfile $message
    Write-Host $message
}

function Write-Header
{
    $datetime=Get-Date
    $header="`
###
# ==++==
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Generated date: $datetime
#
# This file is auto-generated by a tool. Any changes made to this file will be lost.
#
# This file will be compiled to generate a Windows DSC configuration that will deploy 
# the Dynamics AOS service on this machine.
###

#region imports
Import-Module `"`$PSScriptRoot\DeploymentHelper.psm1`" -DisableNameChecking
#endregion

#region Instrumentation helpers
StartMonitoring -monitoringdll:`"`$PSScriptRoot\Microsoft.Dynamics.AX7Deployment.Instrumentation.dll`"
#endregion

#region service configuration
Configuration AosServiceConfiguration { `

    Import-DscResource -Module xWebAdministration `
    Import-DscResource -Module xNetworking `
    Import-DscResource -Module xDynamics `
    Import-DscResource -Module xDatabase `
    Import-DscResource –ModuleName PSDesiredStateConfiguration `
    
    Node `"localhost`" { 
    "
    Write-Content $header
}

function Write-Footer([string]$outputpath)
{
    $footer="}`
    }`

`$configData=@{
    AllNodes=@(
        @{ NodeName = `"localhost`";CertificateFile=`"$global:encryptioncertpublickeyfile`";Thumbprint=`"$global:encryptioncertthumbprint`";PSDscAllowDomainUser=`$true; }
    )
}

#endregion

# generate the MOF file `    
AosServiceConfiguration -OutputPath:$outputpath -ConfigurationData:`$configData"

    Write-Content $footer
}

function Write-Content([string]$content)
{
   Add-Content $global:dscconfigfile -Value $content
}

function Write-Trace-Start([string]$message)
{
    $tracestart="Start-Trace `"$message`""
    Add-Content $global:dscconfigfile -value ([string]::Empty)
    Add-Content $global:dscconfigfile -value $tracestart
}

function Write-Trace-End([string]$message)
{
    $traceend="End-Trace `"$message`""
    Add-Content $global:dscconfigfile -value $traceend
}

function Get-MSIProperties([string]$msiFile)
{
    [hashtable]$msiprops=@{}
 
    [System.IO.FileInfo]$file=Get-Item $msiFile
    $windowsInstaller=New-Object -com WindowsInstaller.Installer
    $database=$windowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod",$null,$windowsInstaller,@($file.FullName,0))
    # product code
    $q = "SELECT Value FROM Property WHERE Property = 'ProductCode'"
    $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $database, ($q))
    $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)
    $record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)
    [string]$productCode = $record.GetType().InvokeMember("StringData", "GetProperty", $Null, $record, 1)
    $productCode=$productCode.TrimStart("{")
    $productCode=$productCode.TrimEnd("}")

    # product name
    $q = "SELECT Value FROM Property WHERE Property = 'ProductName'"
    $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $database, ($q))
    $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)
    $record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)
    [string]$productname=$record.GetType().InvokeMember("StringData", "GetProperty", $Null, $record, 1)
    $View.GetType().InvokeMember("Close", "InvokeMethod", $Null, $View, $Null)

    # product version
    $q = "SELECT Value FROM Property WHERE Property = 'ProductVersion'"
    $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $database, ($q))
    $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)
    $record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)
    [string]$productversion=$record.GetType().InvokeMember("StringData", "GetProperty", $Null, $record, 1)
    $View.GetType().InvokeMember("Close", "InvokeMethod", $Null, $View, $Null)

    $msiprops.productcode=$productCode
    $msiprops.productname=$productname
    $msiprops.productversion=$productversion
    return $msiprops
}

function IsUniqueUrlAndPort($endpointcollection,[string]$baseurl,[string]$port){
    for($i=0;$i -lt $endpointcollection.Keys.Count;$i++){
        $baseurlandport=$endpointcollection[$i]
        if($baseurlandport[0] -eq $baseurl -and $baseurlandport[1] -eq $port){
            return $false
        }
    }

    return $true
}

function Get-RegistryKeyValue([string]$registryKey,[string]$registryKeyValueName,[string]$defaultValue=[string]::Empty)
{
    $item=Get-ItemProperty "$registryKey" -ErrorAction SilentlyContinue
    $value=$item.$registryKeyValueName
    if([string]::IsNullOrEmpty($value)){
        return $defaultValue
    }

    return $value
}

function Get-AzureStorageEmulatorInstallPath
{
    $registrykey="HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows Azure Storage Emulator\"
    $registrykeyvaluename="InstallPath"
    $emulatorInstallPath=Get-RegistryKeyValue -registryKey:$registrykey -registryKeyValueName:$registrykeyvaluename
    Write-Verbose "Emulator installation path: '$emulatorInstallPath'"
    return $emulatorInstallPath
}

Function Get-ProductEntry
{
    param
    (
        [string] $Name,
        [string] $IdentifyingNumber
    )
    
    $uninstallKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    $uninstallKeyWow64 = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
    
    if($IdentifyingNumber)
    {
        $keyLocation = "$uninstallKey\$identifyingNumber"
        $item = Get-Item $keyLocation -EA SilentlyContinue
        if(-not $item)
        {
            $keyLocation = "$uninstallKeyWow64\$identifyingNumber"
            $item = Get-Item $keyLocation -EA SilentlyContinue
        }

        return $item
    }
    
    foreach($item in (Get-ChildItem -EA Ignore $uninstallKey, $uninstallKeyWow64))
    {
        if($Name -eq (Get-LocalizableRegKeyValue $item "DisplayName"))
        {
            return $item
        }
    }
    
    return $null
}

function Get-LocalizableRegKeyValue
{
    param(
        [object] $RegKey,
        [string] $ValueName
    )
    
    $res = $RegKey.GetValue("{0}_Localized" -f $ValueName)
    if(-not $res)
    {
        $res = $RegKey.GetValue($ValueName)
    }
    
    return $res
}

function Copy-Files([string]$targetdir)
{
    Copy-Item "$PSScriptRoot\DeploymentHelper.psm1" $targetdir
    Copy-Item "$PSScriptRoot\..\ETWManifest\Microsoft.Dynamics.AX7Deployment.Instrumentation.dll" $targetdir
}

function CreateConfiguration($configData)
{
    [System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
    $xd.LoadXml($global:decodedservicemodelxml)
    $ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
    $ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
    $deploydb=$xd.SelectSingleNode("//ns:Setting[@Name='Deploy.DeployDatabase']",$ns).getAttribute("Value")
    $webroot=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.WebRoot']",$ns).getAttribute("Value")
    $startStorageEmulatorNode=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.StartStorageEmulator']",$ns)
    $protocol=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.HttpProtocol']",$ns).getAttribute("Value")
    $deployReportsNode=$xd.SelectSingleNode("//ns:Setting[@Name='Deploy.DeployReports']",$ns)
    
    $startStorageEmulator="false"
    if($startStorageEmulatorNode -ne $null)
    {
        $startStorageEmulator=$startStorageEmulatorNode.getAttribute("Value")
    }
    
    $deployReports="true"
    if($deployReportsNode -ne $null)
    {
        $deployReports=$deployReportsNode.getAttribute("Value")
    }

    # get the name of the primary node
    $nodes=$ConfigData.AllNodes
    foreach($node in $nodes){
        if($node.Role -eq "Primary"){
            [string]$primarynodename=$node.NodeName
            Write-Log "The primary AOS node is $primarynodename"
            break;
        }
    }

    # create the configuration file
    Write-Header 
    Write-LCM-Configuration -configdata:$ConfigData
    Write-Certificate-Configuration
    Write-Dependency-Configuration
    Write-Firewall-Ports-Configuration
    Write-Perf-Counters-Configuration
    Write-DynamicsTools-Configuration
    Write-AosWebsite-Configuration
    Write-Packages-Configuration -deploydb:$deploydb
    if($startStorageEmulator -eq "true")
    {
        Write-Storage-Emulator-Configuration
    }

    Write-WebSite-Configuration
    Write-WebConfigFile-Configuration -servicemodel:$servicemodelxml
    Write-WifConfigFile-Configuration -servicemodel:$servicemodelxml
    Write-Aos-Http-Configuration -webroot:$webroot -protocol:$protocol

    $fqdn=[System.Net.Dns]::GetHostByName(($env:computerName))

    # on a work group machine the GetHostByName() returns the netbios name
    $netbiosname=$primarynodename.Split(".")[0]
   if($primarynodename -eq $fqdn.HostName -or $netbiosname -eq $fqdn.HostName -or $primarynodename -eq $env:computerName){
        Write-AosUser-Configuration

        #TODO: refactor this code so that it uses a separate flag to determine if the DBSync should be run or not
        if($startStorageEmulator -eq "false") # if emulator is not started, we are on a non onebox deployment and run DBSync
        {
            Write-DBSYNC-Configuration
        }
               
        Write-SymbolicLinkGeneration-Configuration -primarynode
    }else{
        Write-InterNode-Sync-Configuration -primarynode:$primarynodename
        Write-SymbolicLinkGeneration-Configuration
    }
    
    Write-NGen-Configuration
    Write-Resources-Configuration
    Write-BatchService-Configuration
    if($deployReports -ne "false")
    {
        Write-Reports-Configuration
    }
    Write-RetailPerfCounters-Configuration
    Write-ProductConfiguration
    Write-ConfigInstallationInfo-Configuration -servicemodel:$servicemodelxml
    Write-EncryptionConfiguration-Configuration -webroot:$webroot
    Write-Footer -outputpath:$outputpath
}

function Copy-CustomDSCResources([string]$grandparentDir)
{
    if(Test-Path "$grandparentDir\Scripts\xDynamics"){
        Write-Log "Copying the custom DSC resources"
        Copy-Item -Path "$grandparentDir\Scripts\xDynamics" -Destination "$env:ProgramFiles\WindowsPowerShell\Modules" -Force -Recurse -Verbose
        Get-ChildItem -Path "$env:ProgramFiles\WindowsPowerShell\Modules" -Recurse | Unblock-File -Verbose  
    }else{
        Write-Log "No custom DSC resources to copy"
    }
}

function Save-EncryptionCertificate-PublicKey($certificate)
{
    Write-Log "Saving the encryption cert public key to file..."
    $global:encryptioncertpublickeyfile=join-path $global:logdir "EncryptCert.cer"
    Export-Certificate -cert:$certificate -FilePath $global:encryptioncertpublickeyfile -Force -Type CERT | out-null
}

function Get-EncryptionCertificate-Thumbprint
{
    $subject="MicrosoftDynamicsAXDSCEncryptionCert"

    #get or create a new self-signed encryption certificate to secure sensitive info in the MOF files
    $cert=Get-Make-Encryption-Cert -subject:$subject
    Save-EncryptionCertificate-PublicKey -certificate:$cert
    $cert.Thumbprint
}

function Get-Make-Encryption-Cert([string]$subject)
{
    Write-Log "Checking if a self-signed encryption cert with subject '$subject' exists..."
    $formattersubject="CN=$subject"
    $cert=Get-ChildItem Cert:\LocalMachine\My|where {$_.Subject -eq $formattersubject}
    if($cert -ne $null) # if cert existed make sure it is valid
    {
        if(!(Is-ValidCert -certificate:$cert))
        {
            Write-Log "Dynamics DSC self-signed encryption cert is expired. Generating a new self-signed encryption certificate..."
            Write-Log "Deleting the invalid self-signed encryption certificate with subject '$cert.subject'... "
            $thumbprint=$cert.Thumbprint
            Remove-Item -Path Cert:\LocalMachine\My\$thumbprint -Force -DeleteKey |out-null
            $cert=Make-Certificate -subject:$subject
        }
    }
    else
    {
        $cert=Make-Certificate -subject:$subject 
    }

    $cert
}

function Is-ValidCert($certificate)
{
    $subject=$certificate.Subject
    Write-Log "Checking if the certificate '$subject' is valid..."
    $thumbprint=$certificate.Thumbprint
    $cert=Get-ChildItem -Path Cert:\LocalMachine\My\$thumbprint
    if($cert -ne $null)
    {
        if($cert.NotAfter -lt (Get-Date)) #expired
        {
            return $false
        }
        else
        {
            return $true
        }
    }

    #if cert is not found, return false
    return $false
}

function Make-Certificate([string]$subject)
{
     Write-Log "Creating a new DSC self-signed encryption certificate with subject '$subject'..."
     return New-SelfSignedCertificate -DnsName $subject -CertStoreLocation cert:\LocalMachine\My
}

#endregion

#region Main...
$parentdir=Split-Path -parent $PSCommandPath
$grandparentdir=Split-Path -parent $parentdir

$global:decodedservicemodelxml=[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($servicemodelxml))
$global:logfile=$log
$global:logdir=[System.IO.Path]::GetDirectoryName($log)
$global:dscconfigfile=join-path $global:logdir "Install.ps1"
$outputpath=join-path $global:logdir "Install"

$etwpath=join-path $grandparentdir "ETWManifest"
$global:telemetrydll = join-path $etwpath "Microsoft.Dynamics.AX7Deployment.Instrumentation.dll"
$installationdll=Join-Path $parentdir "Microsoft.Dynamics.AX.AXInstallationInfo.dll"
$installationinfoxml=join-path $parentdir "InstallationInfo.xml"
$keyVaultModule=Join-Path -Path $PSScriptRoot -ChildPath "KeyVault.psm1"

[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$xd.LoadXml($global:decodedservicemodelxml)
$ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
$ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
$keyVaultName=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.AzureKeyVaultName']",$ns).getAttribute("Value")
$appId=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.AzureKeyVaultAppId']",$ns).getAttribute("Value")
$thumprint=$xd.SelectSingleNode("//ns:Setting[@Name='Infrastructure.AzureKeyVaultCertThumbprint']",$ns).getAttribute("Value")

Import-Module $keyVaultModule -ArgumentList ($keyVaultName, $appId, $thumprint)

if(-not (Test-Path $global:telemetrydll)){
    throw "The deployment telemetry assembly does not exist"
}

if(Test-Path $global:dscconfigfile){
    Remove-Item $global:dscconfigfile -Force
}

Initialize-Log -logdir:$global:logdir -logfile:$log
Copy-Files -targetdir:$global:logdir
Copy-CustomDSCResources -grandparentDir:$grandparentdir

Write-Log "Enabling WinRM remote management"
WinRM quickconfig -q

# construct the config data
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$xd.LoadXml($global:decodedservicemodelxml)
$ns=New-Object System.Xml.XmlNamespaceManager($xd.NameTable)
$ns.AddNamespace("ns",$xd.DocumentElement.NamespaceURI)
[string]$nodes=$xd.SelectSingleNode("//ns:Setting[@Name='Deploy.AosNodes']",$ns).getAttribute("Value")

if($env:USERDOMAIN -eq $env:COMPUTERNAME){
    $global:domain="builtin"
}else{
    $global:domain=$env:USERDOMAIN
}

$global:username=$env:USERNAME

[string[]]$aosvms=$nodes.Split(",")

if($aosvms.Count -eq 0){
    throw "Atleast one AOS node should be defined in the servicemodel.xml"
}

$global:encryptioncertthumbprint=Get-EncryptionCertificate-Thumbprint

$configData=@{
    AllNodes=@(
        @{ NodeName="*";CertificateFile=$global:encryptioncertpublickeyfile;Thumbprint=$global:encryptioncertthumbprint;PSDscAllowDomainUser=$true;}
        @{ NodeName=$aosvms[0];Role="Primary" }
    )
}

for($i=1;$i -lt $aosvms.Count;$i++){
    $configData.AllNodes += @{NodeName=$aosvms[$i];Role="Secondary" }
}

CreateConfiguration -configData:$ConfigData
#endregion

#region generate MOF and execute the configuration
try
{
    Write-Log "Generating the MOF..."
    & $global:dscconfigfile

    Set-Location $outputpath

    Write-Log "Setting up LCM to decrypt credentials..."
    Set-DscLocalConfigurationManager -path "$outputpath" -Verbose *>&1 | Tee-Object $log

    Write-Log "Applying the configuration..."
    $errorsBeforeDSCConfig=$error.Count
    Start-DscConfiguration -wait -Verbose -path "$outputpath" -Force *>&1 | Tee-Object $log
    $errorsAfterDSCConfig=$error.Count

    $configstatuslog=join-path $global:logdir "aosservice-installation-status.log"
    $ConfigStatus = Get-DscConfigurationStatus
    $ConfigStatus | Format-List -Property * | Out-File -FilePath $configstatuslog -Force
    Write-Log "Error baseline: $errorsBeforeDSCConfig, Errors after DSCConfiguration: $errorsAfterDSCConfig"

    if($ConfigStatus.Status -ieq 'Success' -and $errorsBeforeDSCConfig -eq $errorsAfterDSCConfig)
    {
        return 0
    }
    else
    {
        throw "AOS service configuration did not complete, Message: $($ConfigStatus.StatusMessage), see log for details"
    }
}
catch
{
    throw $_
}

#endregion

# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC2mWX7SMVaZFe/
# 3/Li6s8tBeCcweptVrXdMwbGPseA/aCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHU1ZNoZQtmv0cCFlmkNehBr
# bqEL11o8PE0eVJU+VEGaMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAqeUJgmclgp0AcX/LkdBzagycr/Hv7OEFVQ/xGzrqpQKqwtPbd3q+SrXT
# Z3JIhDO8bDDcaTg5IJSc7QmbRZ81P/f36uIH5atj7ho5omyWEXnwC2uZP8UHcA1y
# 2SmByKzd04/zOvYuqg+AA+IJhwptV5Js4wlQPOlOH0mrPSa4VSME04UQsm1Mjjqb
# 6/qPWc5Vyjjo/fELcKwKBotiSJWBw1o2C6HRXKQhD0oe2eqg90uIts7kJ0OjQwNr
# LJ64/s6sCCiZBpgSDh3crW4uyDpOvoejvoNkO35PtA+ZrqJAHaVDhHPE1AdLRVpS
# JFrJ3OSshL6G0ICnBcVwFy0QKgtesaGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB5TdIzKEae3AHB9BhEvStlpiHLdzqBtKMsITnZYWrSTQIGaBpnT8CY
# GBMyMDI1MDUxMjE5MjYzOS42NDlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAgpHshTZ7rKzDwABAAACCjANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy
# NTdaFw0yNjA0MjIxOTQyNTdaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCy7NzwEpb7BpwAk9LJ00Xq30TcTjcwNZ80TxAtAbhS
# aJ2kwnJA1Au/Do9/fEBjAHv6Mmtt3fmPDeIJnQ7VBeIq8RcfjcjrbPIg3wA5v5MQ
# flPNSBNOvcXRP+fZnAy0ELDzfnJHnCkZNsQUZ7GF7LxULTKOYY2YJw4TrmcHohkY
# 6DjCZyxhqmGQwwdbjoPWRbYu/ozFem/yfJPyjVBql1068bcVh58A8c5CD6TWN/L3
# u+Ny+7O8+Dver6qBT44Ey7pfPZMZ1Hi7yvCLv5LGzSB6o2OD5GIZy7z4kh8UYHdz
# jn9Wx+QZ2233SJQKtZhpI7uHf3oMTg0zanQfz7mgudefmGBrQEg1ox3n+3Tizh0D
# 9zVmNQP9sFjsPQtNGZ9ID9H8A+kFInx4mrSxA2SyGMOQcxlGM30ktIKM3iqCuFEU
# 9CHVMpN94/1fl4T6PonJ+/oWJqFlatYuMKv2Z8uiprnFcAxCpOsDIVBO9K1vHeAM
# iQQUlcE9CD536I1YLnmO2qHagPPmXhdOGrHUnCUtop21elukHh75q/5zH+OnNekp
# 5udpjQNZCviYAZdHsLnkU0NfUAr6r1UqDcSq1yf5RiwimB8SjsdmHll4gPjmqVi0
# /rmnM1oAEQm3PyWcTQQibYLiuKN7Y4io5bJTVwm+vRRbpJ5UL/D33C//7qnHbeoW
# BQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFAKvF0EEj4AyPfY8W/qrsAvftZwkMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCwk3PW0CyjOaqXCMOusTde7ep2CwP/xV1J
# 3o9KAiKSdq8a2UR5RCHYhnJseemweMUH2kNefpnAh2Bn8H2opDztDJkj8OYRd/KQ
# ysE12NwaY3KOwAW8Rg8OdXv5fUZIsOWgprkCQM0VoFHdXYExkJN3EzBbUCUw3yb4
# gAFPK56T+6cPpI8MJLJCQXHNMgti2QZhX9KkfRAffFYMFcpsbI+oziC5Brrk3361
# cJFHhgEJR0J42nqZTGSgUpDGHSZARGqNcAV5h+OQDLeF2p3URx/P6McUg1nJ2gMP
# YBsD+bwd9B0c/XIZ9Mt3ujlELPpkijjCdSZxhzu2M3SZWJr57uY+FC+LspvIOH1O
# pofanh3JGDosNcAEu9yUMWKsEBMngD6VWQSQYZ6X9F80zCoeZwTq0i9AujnYzzx5
# W2fEgZejRu6K1GCASmztNlYJlACjqafWRofTqkJhV/J2v97X3ruDvfpuOuQoUtVA
# wXrDsG2NOBuvVso5KdW54hBSsz/4+ORB4qLnq4/GNtajUHorKRKHGOgFo8DKaXG+
# UNANwhGNxHbILSa59PxExMgCjBRP3828yGKsquSEzzLNWnz5af9ZmeH4809fwItt
# I41JkuiY9X6hmMmLYv8OY34vvOK+zyxkS+9BULVAP6gt+yaHaBlrln8Gi4/dBr2y
# 6Srr/56g0DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjM3MDMtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDR
# AMVJlA6bKq93Vnu3UkJgm5HlYaCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA68wlozAiGA8yMDI1MDUxMjA3NDUz
# OVoYDzIwMjUwNTEzMDc0NTM5WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDrzCWj
# AgEAMAoCAQACAgqgAgH/MAcCAQACAhK/MAoCBQDrzXcjAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAFl493bQIYt574YIIbRQHCZhPVXwvP/czZsOVqjMpBD1
# PP1RJALLvB5kleZpzoY7E7jk+Moi21yt2BRksBUXCjXWbC7VTY3WJiqAu5T4yaLK
# 8ru4mlHLdPpMoh2bBeDOeAOhlB0zEui7dmXJWaA4JmmWxMgI8nCblZPAcpYQygt2
# 5c1FHyQqSxhVoJhxDgymVS1mtdAXesFPdl7Mob0pOVhyjTCDNIe4LKvMCkhyZLog
# YObeiHTs68L8UnKbK99idPFdoOeDxszACr9a6DFTdfSsbqFrR2MjgzA3Qrn+VDQV
# fyTzrIPKW/NmnHZPAgmC3RXwT4nf5W3t0sUZu/zYbQQxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgpHshTZ7rKzDwABAAAC
# CjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCDvxdzt2vcWtgbNxuF7/c43DalhRo9dnl9wKMwbitE/
# eDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIE2ay/y0epK/X3Z03KTcloqE
# 8u9IXRtdO7Mex0hw9+SaMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAIKR7IU2e6ysw8AAQAAAgowIgQgohf7WTZ4TlBcIIvzUIaRb6JV
# LK1hrDD/4Qn/74ECnacwDQYJKoZIhvcNAQELBQAEggIATKjlx0CIoMtw3D7JXpM0
# +/JctbdsRgLIVS4683BOdOPwJ0vqWRR14TACgwjV0YhNeuLSxly9UOX5TrALnVW9
# s1v59R/Q6XjjS8wEJ4nmN3hkRZcvstQzgSgG5QgMqqaxWB3fwJv2XFOuX4k4dsaK
# NvvZ6KiRH5VagIs52OyxeFJ5grChlK4EoPTctVeoShzuzb/lW2rEb3xN0GrjkoNc
# /xco7PIfrpZVusKACZrjoQg7rmKUMqvH2+bxf7OlUEwaB6uSIQY4YZ+2u248bW3D
# BlWMN0+arB7kQl0ODvCzIMx/5UX+MDkjoQ1Hy1xnULj4dIEhlhSh8kauYshkV5mc
# UkZf8YcD3m3L9KTaMM8p/s9rr9Tulr489YuLbhHfWfo2drgPwzSmFRaxuJQAbTQn
# Z8+YCcW3zyUTHNbwUfLdjTxKVDl/vy7J9dun8igN+BgdhS8KNuB8Q/7u7Z/iojWx
# ije4RDv7Qa3UXu/C6WEYS0c/2bTl2w8VcGsi+qYihg8miOAIPunl2fzpBhkZKTbR
# H3wfdotIj3w69goaGPYE/snPGiuAYd0HZCS/53/hoP5rmZZIqmL3yvFJPGoMJcr+
# CbomcS0OsX1pnfjAmoozH6JOmUlb3ardfy4byERKashn9DtcNDqftmZyoSFQmrpA
# CmUBsu0DhR67abNDlaveftQ=
# SIG # End signature block
