[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=20
"@
    
    $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
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD+SxnuTJVXQYwp
# Awy2iKmbtYpgHEaaV6UcQxe9t8qx/qCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ4wghmaAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJYgL2ykDkWs0uotJHKzKO7o
# xc52XOldSrFVG7jFXFy1MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEA2AfRQx+L+OSh4bub89PDiKqf0NPbqegBdSEXC1ek1Gq6RwdQSxEx76B9
# TNcL6HYh/E5cep2S2zC5PtLXrjGqQSyCDvrmYMYX26FotUde1U5dmzsc3XecCtPA
# UcLNZH1m10DEqRGhcWxXDtfD5CtJpV015R7Qaw5IrGdlpqYX4qDaaNuMIR6Nik8t
# bSXUGOmr0PQrTV6v+A1ItysjCGA43vjg7UEdwA/9C9gwc93u6jpo9+kjFP7RKeat
# /Avkb8CZsSuyRRm156Hf7Uz14a5N7BE3G3/3YQHhtobp7e5U/ODEXtgR5FlSDnZ3
# 85OZxT+D1aGI8/HuAhkoRZ1qwPKLOaGCFygwghckBgorBgEEAYI3AwMBMYIXFDCC
# FxAGCSqGSIb3DQEHAqCCFwEwghb9AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFYBgsq
# hkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAbA/C7RcG4BNzrLz9KB83i2ODbjl4LvD/3KDv9vjs8NwIGZS/zLiPt
# GBIyMDIzMTAxOTA3MDEwNC45NFowBIACAfSggdikgdUwgdIxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs
# YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046
# MTc5RS00QkIwLTgyNDYxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAbWtGt/XhXBtEwABAAABtTANBgkq
# hkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMjA5
# MjAyMDIyMTFaFw0yMzEyMTQyMDIyMTFaMIHSMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy
# YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjE3OUUtNEJC
# MC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlwsKuGVegsKNiYXFwU+CSHnt
# 2a7PfWw2yPwiW+YRlEJsH3ibFIiPfk/yblMp8JGantu+7Di/+3e5wWN/nbJUIMUj
# EWJnc8JMjoPmHCWsMtJOuR/1Ru4aa1RrxQtIelq098TBl4k7NsEE87l7qKFmy8iw
# GNQjkwr0bMu4BJwy7BUXiXHegOSU992rfQ4xNZoxznv42TLQsc9NmcBq5WslkqVA
# Tcc8PSfgBLEpdG1Dp2wqNw4JrJFwJNA1bfzTScYABc5smRZBgsP4JiK/8CVrloch
# eEyQonjm3rFttrojAreSUnixALu9pDrsBI4DUPGG34oIbieI1oqFl/xk7A+7uM8k
# 4o8ifMVWNTaczbPldDYtn6hBre7r25RED4uecCxP8Dxy34YPUElWllPP3LAXp5cM
# wRjx+EWzjEtILEKXuAcfxrXCTwyYhm5XNzCCZYh4/gF2U2y/bYfekKpaoFYwkoZe
# T6ZxoQbX5Kftgj+tZkFV21UvZIkJ6b34a/44dtrsK6diTmVnNTM9J6P6Ehlk2sfc
# UwbHIGL8mYqdKOiyd4RxOCmSvcFNkZEgrk548mHCbDbTyO9xSzN1EkWxbp8n/LHV
# nZ9fp5hILGntkMzaD5aXRCQyHSIhsPtR7Q/rKoHyjFqgtGO9ftnxYvxzNrbKeMCz
# wmcqwMrX6Hcxe0SeKZ8CAwEAAaOCAUkwggFFMB0GA1UdDgQWBBRsUIbZgoZVXVXV
# WQX0Ok1VO2bHUzAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV
# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny
# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI
# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAkFGOpyjKV2s2sA+w
# TqDwDdhp0mFrPtiU4rN3OonTWqb85M6WH19c/P517xujLCih/HllP5xKWmXnAIRV
# 1/NQDkJBLSdLTb/NQtcT1FWGQ7CMTnrn9tLZxqIFtKVylvQNyh31C/qkC8QmNpyz
# akO0G38uOGgOkJ9Eq4nA+7QwVfobDlggWuEpzdFnRdyXL32gOqSvrLjFKpv4KEVq
# aBTiaxCWZDlIhG3YgUza7cnG5Z2SA/feMq/IiV06AzUadZw6XgcTrqXmEmE0tMmd
# l44MMFC3wGU9AVeFCWKdD9WOnYA2zHg+XF2LQVto0VYtFLd6c6DQFcmB38GvPCKV
# YSn8r10EoXuRN+gQ7hLcim12esOnW4F4bHCmHWTVWeAGgPiSItHHRfGKLEUZmotV
# OdFPR8wiuADT/fHSXBkkdpL12tvgEGELeTznzFulZ16b/Nv6dtbgSRZreesJBNKp
# TjdYju/GqnlAkpflL6J0wxk957/UVYnmjjRY61jX90QGQmBzm9vs/+2bj02Xx/bX
# Xy8vq57jmNXQ2ufOaJm3nAcD2qOaSyXEOj9mqhMt4tdvMjHhiNPldfj0Q7Kq1Hgd
# RBrKWkzCQNi4ts8HRJBipNaVpWfU7BcRn8BeYzdLoIzwRLDtatz6aBho3oD/bXHr
# ZagxprM5MsMB/rVfb5Xn1YS7/uEwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ
# AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
# V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9
# alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv
# Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928
# jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t
# pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe
# HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o
# ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C
# vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug
# poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps
# xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C
# AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
# FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT
# NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o
# dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD
# AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW
# y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y
# My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc
# FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
# Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j
# VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3
# +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR
# sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw
# ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b
# RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx
# aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX
# HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0
# W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0
# HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu
# ZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjE3
# OUUtNEJCMC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQCNMJ9r11RZj0PWu3uk+aQHF3IsVaCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
# 6NsaUzAiGA8yMDIzMTAxOTExMDAzNVoYDzIwMjMxMDIwMTEwMDM1WjB0MDoGCisG
# AQQBhFkKBAExLDAqMAoCBQDo2xpTAgEAMAcCAQACAgNYMAcCAQACAhQ9MAoCBQDo
# 3GvTAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH
# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAjaoRvR0fJn/yN8kghEe4
# s8HnwU+xBwAKOuPpljGtObm8IlNnxbmLC0mYe7I9KV5vRgU308LOEntxI4CR6vcX
# aH0+r4ZJRTSBn8tHvoALRM+0UV7VchkL3XjV1/cD7V97Q73O46qVn4UCE6SLMEIt
# pJpccUDQGUMFmDd6aHJviT8xggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMAITMwAAAbWtGt/XhXBtEwABAAABtTANBglghkgBZQMEAgEF
# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi
# BCC5iuNQ4vRBkjqfJe4qdSN05l/p7/uqw4lTs6s3iAaoGDCB+gYLKoZIhvcNAQkQ
# Ai8xgeowgecwgeQwgb0EICfKDTUtaGcWifYc3OVnIpp7Ykn0S8JclVzrlAgF8ciD
# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG1rRrf
# 14VwbRMAAQAAAbUwIgQg9uh2lH1uaxGI0sWB0c8rN7NSQKUe5eks6fctmsD+NRww
# DQYJKoZIhvcNAQELBQAEggIAIhi6Qe7CPOxzm7c73rBrHkKYv4KpZ4hsOFbxg0gb
# 7j9q8cVfTulZgC7l6A/rQe49Nu69+Ae0ltVKZXJLni6FRRk/n0Mf6uCsRBx4pJee
# vFTK7eX8kLuoV+pMzAV/kWry22NGqN7EOsdckddmeLBuH4hnmR4P+0jukeVcXZsv
# ajvDxadj3nwWM1SOHcuj3Ype54yylYc59EYx5VI4C+TPrWIg5BbgOWC277s8oEMp
# xYuk1AZytshKGa5IFDhHrPNj4jq857CrmuKk+mzhTPLp9wX113Jmd/9jueA1G/ur
# 72swxuXA8/I3OxYSPQjPs9dbQopfUJDLwMHE7mx9svsgqFc876xYO+fofhcVpjIO
# JfXBFaAIxRpqkzloZ4KLfYCisH/R9+d73fvaTZkwFAoAC1vK+T+cP5XLG4JCfBhp
# Rf6KfbvX8m5fcEPIoihG6aD06Fp9hBfBcON+LT2+iMN1NIDiB6S0+6w1+Cu1xjKa
# d/KuwKKcN83IXfJhK0d1rsBd7BceSb0qrJaVdm5LfZrBp0VlbFS1yQt0Bw3/K+BZ
# P7vAFI663sgmTCJvHWcXPbn6WdnG/Ak/aC4CY/H/T+qPOX9SqlyF8L7LVju7QGis
# 1m4/RcYUHGtiWPrKRXvXFJkMjScBe9O73FNB8yH8lSSABZ8dRHwEOxSMKgU8Glvu
# 3f4=
# SIG # End signature block
