Across-Solutions.psm1

<#
    Across-Solutions is a collection of functions that can be used by our technical specialists to perform certain tasks related to our products
#>

#Requires -PSEdition Desktop

#region Language Portal related functions
<#
    Functions in this region are related to the Across Language Portal (ALPS) solution
#>


<#
    Version history
    ---------------
    v1.1.3 - Updated to work on localized OS. Using "NetworkService" instead of "NETWORK SERVICE" when setting crossAPI DCOM permissions; Using WellKnown SID of NetworkService when permitting access to Log folder
    v1.1.2 - Implemented not to use SqlServer module. Added a few exsamples to the syntax help
    v1.1.1 - Implemented support for DE and EN across language server
    v1.1.0 - Migrated version from Across-Tools module to this one. Added additional parameter defaults
    v1.0.10 - Implemented setting DCOM permission without using an external module
    v1.0.9 - Implemented setting DCOM permission. Using module DCOMPermission
    v1.0.8 - Implemented setting of right authentication methods. Removed -V63 switch
    v1.0.7 - Implemented v7.0 ALPS compatibility and backwards compatibility with parameter -V63
    v1.0.6 - Fixed broken parameter handling
    v1.0.5 - Implemented starting the new site in default browser and switch to not do it
    v1.0.4 - Implemented optionally changing the portal title
    v1.0.3 - Implemented creation of ALPS database; implemented usage of specified database name and ALPS-Instance name
    v1.0.2 - Added optional parameters for SQL server username and password. Improved error handling
    v1.0.1 - Implemented creation of new database and updating db settings of ALPS instance
    v1.0.0 - Initial version. Creates app pool and web application
#>

function New-LanguagePortal {
    <#
    .SYNOPSIS
    Setup and configure a new Across Language Portal instance
 
    .DESCRIPTION
    The function does all the necessary setups and configurations to implement a new instance of a Language Portal.
    The following steps are performed:
    - Creation and configuration of a new application pool (AppPool) in Internet Information Server (IIS)
    - Configuration of proper permissions of crossAPI DCOM object for the builtin NETWORK SERVICE user
    - Creation and configuration of a new web application under the Default Web Site in IIS
    - Creation of a new SQL Server database and configuration of the portal to use it
    - Set necessary settings in language portal server.config
    - Allow builtin NETWORK SERVICE user access to the language portal log folder
    - Set necessary settings in language portal user.config
    - Restart the crossAPI service and the AppPool
    - Open the new language portal in default browser
 
    Important:
    - A local folder with all Language Portal files must be available already before executing the function.
    - The across user used to connect to the API is currently hardcoded as user "across" with the default password
    - The portal title will be used on the login page and as a new html\HeaderToolbar.html file (if none exists yet)
    - The new language portal instance must run on the same mnachine as the Across Language Server.
    - As the crossAPI service is restarted, no productive automation should be active while running this function
    - A new database will be created for the portal instance. For that purpose, the local machine needs to have access to a Microsoft SQL Server.
    - The new language portal is configured to use the database user "across" with the default password to access it's database (hardcoded)
    - Re-using an existing database is currently not supported
 
    .PARAMETER InstanceName
    The name of the new instance. This name will be used as the name of the Web AppPool and the name of the Web App (and thus also the URL).
 
    .PARAMETER PhysicalPath
    The folder, where the language portal files are located.
 
    .PARAMETER PortalTitle
    The title of the new Language portal. This will be used as the title of the login screen and as the header toolbar by creating a .\html\HeaderToolbar.html
    Note: If a .\html\HeaderToolbar.html already exists in the ALPS folder, this will NOT be applied. Instead the existing header toolbar remains
 
    .PARAMETER AcrossLanguage
    Specifies the language of the installed Across language Server. The portal configurations will be adapted according to this selection.
    Allowed values are: German, English, DE, EN
 
    .PARAMETER DatabaseName
    The name of the new database to be created.
 
    Important:
    - The function currently doesn't handle the case if a database with that name aleady exists. Make sure that the database doesn't yet exist.
    - A database user "across" with the default password as created by the Across Server installer must already exist. The portal will be configured to
      use that user and password (hardcoded)
 
    .PARAMETER SQLServer
    The name of the SQL Server computer. The server should be the same that also hosts the Across Server databases
 
    .PARAMETER SqlUsername
    The name of an SQL admin user. We recomment to use the 'SA' user
 
    Note: If not specified, a Windows Authentication connection attempt with current user scope will be done.
    This may or may not work, depending on current windows user permissions and SQL Server configuration
 
    .PARAMETER SQLPassword
    The password of the SQL admin user. Note: Secure credentials are currently not supported. Password will be visible on screen
 
    .PARAMETER NoBrowser
    If specified, the new portal is not opened in a new browser. If not specified, the new language portal is opened in the default browser.
 
    .EXAMPLE
    PS> New-LanguagePortal -PhysicalPath 'C:\Program Files (x86)\Across\ALPS'
 
    This command will setup and configure a new language portal instance with the files stored in the provided path.
    - The instance name (used as name of the web-application and the application pool) will be "ALPS".
    - The title of the portal will be the default "Across Language Portal".
    - A Germany Across Language Server is expected
    - The SQL Server is expected to be on the local machine, and connection will be made using current user context.
    - The name of the new database will be 'across_LanguagePortal'
 
    .EXAMPLE
    PS> New-LanguagePortal -PhysicalPath 'C:\Program Files (x86)\Across\ALPS' -SQLServer "SQLServerMachine" -SqlUsername "SA" -SqlPassword "sa!123456"
 
    This command will setup and configure a new language portal instance with the files stored in the provided path.
    - The instance name (used as name of the web-application and the application pool) will be "ALPS".
    - The title of the portal will be the default "Across Language Portal".
    - A Germany Across Language Server is expected
    - The SQL Server is expected to be on "SQLServerMachine", and connection will be made using "SA" | "sa!123456".
    - The name of the new database will be 'across_LanguagePortal'
 
    .EXAMPLE
    PS> New-LanguagePortal -InstanceName TestALPS -PhysicalPath 'C:\Program Files (x86)\Across\ALPS' -PortalTitle "Test Language Portal" -AcrossLanguage EN -DatabaseName across_TestALPS -SQLServer SQLServerMachine -SqlUsername SA -SqlPassword sa!123456
 
    This command will setup and configure a new language portal instance with the files stored in the provided path.
    - The instance name (used as name of the web-application and the application pool) will be "TestALPS".
    - The title of the portal will be the default "Test Language Portal".
    - An English Across Language Server is expected
    - The SQL Server is expected to be on "SQLServerMachine", and connection will be made using "SA" | "sa!123456".
    - The name of the new database will be 'across_TestALPS'
 
    .EXAMPLE
    PS> New-LanguagePortal -i TestALPS -Path 'C:\Program Files (x86)\Across\ALPS' -pt "Test Language Portal" -al EN -dbn across_TestALPS -sql SQLServerMachine -sqlu SA -sqlp sa!123456
 
    This is the same command as in previous example, but with the shorter parameter aliases used.
    This command will setup and configure a new language portal instance with the files stored in the provided path.
    - The instance name (used as name of the web-application and the application pool) will be "TestALPS".
    - The title of the portal will be the default "Test Language Portal".
    - An English Across Language Server is expected
    - The SQL Server is expected to be on "SQLServerMachine", and connection will be made using "SA" | "sa!123456".
    - The name of the new database will be 'across_TestALPS'
    #>

    [CmdletBinding()]

    Param (
        [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][Alias("inst", "i")][string]$InstanceName = "ALPS",
        [Parameter(Mandatory=$true)][ValidateScript({
            if(!(Test-Path $_)) { Throw "Path $_ does not exist." }
            if(!(Test-Path (Join-Path $_ "ALPSOperations.asmx"))) { Throw "$_ doesrestartn't seem to be a valid Language Portal folder."}
            $true
        })][Alias("Path")][string]$PhysicalPath = $PWD,
        [Parameter()][Alias("pt")][string]$PortalTitle = "Across Language Portal",
        [Parameter()][Alias("al", "lang")][ValidateSet("DE", "German", "EN", "English")][string]$AcrossLanguage = "German",
        [Parameter()][ValidateNotNullOrEmpty()][Alias("dbn")][string]$DatabaseName = "across_LanguagePortal",
        [Parameter()][Alias("sql")][string]$SQLServer = $env:COMPUTERNAME,
        [Parameter()][Alias("sqlu")][string]$SqlUsername,
        [Parameter()][Alias("sqlp")][string]$SqlPassword,
        [Parameter()][Alias("nb")][switch]$NoBrowser
    )

    Begin {
        # Verify if running elevated
        if (!(([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole] "Administrator"))) {
            Write-Error "Error: not enough rights. Please try again in an elevated console (run as Administrator)."
            Break
        }
        <#
        # Load SqlServer module. If not existing, install it
        if (Get-Module -ListAvailable -Name "SqlServer") {
            Import-Module SqlServer
        }
        else {
            # Module doesn't exist
            Write-Host "Module 'SqlServer' not found. Installing it from PowerShell Gallery..." -ForegroundColor Yellow
            try {
                Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
                Install-Module -Name SqlServer -Scope CurrentUser
                Import-Module SqlServer
            }
            catch {
                Write-Error "Error installing module 'SqlServer': $_. Please install it manually and try again."
                Break
            }
        }
        #>


        # Load WebAdministration module. Should always exist on a server when IIS is installed
        if (Get-Module -ListAvailable -Name "WebAdministration") {
            Import-Module "WebAdministration"
        }
        else {
            # Module doesn't exist
            Write-Error "Module 'WebAdministration' not found. Please install it manually (by enabling IIS feature) and try again."
            Break
        }
    }

    Process {
$sqlCmd = @"
USE [master]
CREATE DATABASE [$($DatabaseName)]
GO;
USE [$($DatabaseName)]
CREATE USER across
ALTER USER across WITH LOGIN = across
GO;
USE [$($DatabaseName)]
EXEC sp_addrolemember N'db_owner', N'across'
GO;
USE [$($DatabaseName)]
CREATE TABLE [dbo].[Orders](
    [order_guid] [nchar](36) NOT NULL,
    [creator_guid] [nchar](36) NOT NULL,
    [order_name] [nvarchar](255) NOT NULL,
    [across_project_guid] [nchar](36) NOT NULL,
    [order_creation_date] [datetime] NOT NULL
    ) ON [PRIMARY]
"@


        # Create a new web application pool for the new instance and change settings
        $poolName = $InstanceName

        Write-Host "Creating new web application pool $poolName..." -ForegroundColor Green

        if (Get-ChildItem "IIS:\AppPool\$poolName" -ErrorAction SilentlyContinue) {
            Write-Error "The web application pool $poolName already exists."
            Break
        }

        # Create and configure application pool
        try {
            $newPool = New-WebAppPool -Name $poolName -Force
            $newPool | Select-Object -ExpandProperty PSPath | ForEach-Object { Set-ItemProperty $_ managedPipelineMode 1 } # Set manged pipeline mode to Classic (alternative values: 0 = Integrated)
            $newPool | Select-Object -ExpandProperty PSPath | ForEach-Object { Set-ItemProperty $_ enable32BitAppOnWin64 $true } # Enable 32bit mode

            Set-ItemProperty $newPool.PSPath -Name processModel -Value @{ identityType=2 } # Set process model identity type to NetworkService=2 (alternative values: LocalSystem=0, LocalService=1, NetworkService=2)
            Set-ItemProperty $newPool.PSPath -Name processModel.idleTimeout -Value ([TimeSpan]::FromMinutes(0)) # Set process model idle timeout to 0
            Set-ItemProperty $newPool.PSPath -Name Recycling.periodicRestart.time -Value 0.00:00:00 # Disable periodic recycling of the pool
        }
        catch {
            Write-Error "Error creating the web application pool $($poolName): $_"
            Break
        }

        # Set proper permissions for crossAPI DCOM-Object, so Network Service can be used
        # TODO: Verify if this also works on non-english OSes
        <#
            DEBUG hint: Get the actually set AccessMasks
            $apiDCOMObj = Get-WmiObject -Query ('SELECT * FROM Win32_DCOMApplicationSetting WHERE Caption = "crossAPI"') -EnableAllPrivileges
            $descrLaunch = $apiDCOMObj.GetLaunchSecurityDescriptor().descriptor
            $descrAccess = $apiDCOMObj.GetAccessSecurityDescriptor().descriptor
            $descrLaunch.DACL | where { $_.Trustee.Name -eq "NETWORK SERVICE" }
            $descrAccess.DACL | where { $_.Trustee.Name -eq "NETWORK SERVICE" }
        #>

        Write-Host "Setting crossAPI DCOM permissions for NetworkService..." -ForegroundColor Green
        try {
            $apiDCOMObj = Get-WmiObject -Query ('SELECT * FROM Win32_DCOMApplicationSetting WHERE Caption = "crossAPI"') -EnableAllPrivileges
            $sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::NetworkServiceSid, $null)
            $descrLaunch = $apiDCOMObj.GetLaunchSecurityDescriptor().descriptor
            $descrAccess = $apiDCOMObj.GetAccessSecurityDescriptor().descriptor
            $trusteeObj = ([wmiclass]'Win32_Trustee').psbase.CreateInstance()
            # $trusteeObj.Domain = "NT AUTHORITY"
            $trusteeObj.Name = "NetworkService"
            $aceLaunch = ([wmiclass]'Win32_ACE').psbase.CreateInstance()
            $aceLaunch.AccessMask = 11                                            # Mask for Local Launch and Local Activation
            $aceLaunch.trustee = $trusteeObj
            $aceAccess = ([wmiclass]'Win32_ACE').psbase.CreateInstance()
            $aceAccess.AccessMask = 3                                             # Mask for Local Access
            $aceAccess.trustee = $trusteeObj
            $descrLaunch.DACL += [System.Management.ManagementBaseObject]$aceLaunch
            $descrAccess.DACL += [System.Management.ManagementBaseObject]$aceAccess
            $apiDCOMObj.SetLaunchSecurityDescriptor($descrLaunch) | Out-Null
            $apiDCOMObj.SetAccessSecurityDescriptor($descrAccess) | Out-Null
        }
        catch {
            Write-Error "Error configuring crossAPI DCOM object: $_"
            Break
        }

        # Create a new web application for the new instance
        $appName = $InstanceName

        Write-Host "Creating new web application $appName..." -ForegroundColor Green

        if (Get-ChildItem "IIS:\Sites\Default Web Site\$appName" -ErrorAction SilentlyContinue) {
            Write-Error "The web application $appName already exists."
            Break
        }
        try {
            $newApp = New-WebApplication -Name $appName -Site "Default Web Site" -ApplicationPool $poolName -PhysicalPath $PhysicalPath
        }
        catch {
            Write-Error "Error creating the web application $($appName): $_"
            Break
        }
        # Set authentication methods for the web application
        Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/windowsAuthentication" -Name Enabled -Value True -PSPath "IIS:\" -Location "Default Web Site/$appName"
        Set-WebConfigurationProperty -Filter "/system.webServer/security/authentication/anonymousAuthentication" -Name Enabled -Value False -PSPath "IIS:\" -Location "Default Web Site/$appName"
        $conf = Get-WebConfiguration system.web/authentication "IIS:\Sites\Default Web Site\$appName"
        $conf.mode = "Forms"
        $conf | Set-WebConfiguration system.web/authentication

        # Create a new database on given SQL Server
        Write-Host "Creating new database $DatabaseName on $SQLServer..." -ForegroundColor Green

        try {
            if ($SqlUsername -and $SqlPassword) {
                Invoke-SqlNonQuery -ServerInstance $SQLServer -Username $SqlUsername -Password $SqlPassword -Query $sqlCmd
            }
            else {
                Invoke-SqlNonQuery -ServerInstance $SQLServer -Query $sqlCmd
            }
        }
        catch {
            Write-Error "Error creating the database: $_"
            Break
        }

        # Update new ALPS instance database settings
        Write-Host "Configuring ALPS server.config to connect to created database..." -ForegroundColor Green

        $ServerConfig = Join-Path $PhysicalPath "server.config"
        if(!(Test-Path $ServerConfig)) {
            Write-Error "Error opening server.config: $_"
            Return
        }
        try {
            $serverConfigXML = [xml] (Get-Content $ServerConfig)
            Select-Xml -Xml $serverConfigXML -XPath "/ServerSettings/db/server" | ForEach-Object { $_.Node.'#text' = $SQLServer }
            Select-Xml -Xml $serverConfigXML -XPath "/ServerSettings/db/database" | ForEach-Object { $_.Node.'#text' = $DatabaseName }
            Select-Xml -Xml $serverConfigXML -XPath "/ServerSettings/db/user" | ForEach-Object { $_.Node.'#text' = "across" }
            Select-Xml -Xml $serverConfigXML -XPath "/ServerSettings/db/password" | ForEach-Object { $_.Node.'#text' = "ma7ValheaGwaA9xTiohQNxyU7OYd+CorG8SXwmczVhI=" }
            $serverConfigXML.Save($ServerConfig)
        }
        catch {
            Write-Error "Error updating server.config: $_"
            Break
        }

        # Permit user "NETWORK SERVICE" full control to log subfolder
        Write-Host "Granting user NetworkService full access to ALPS log folder..." -ForegroundColor Green

        try {
            $acl = Get-Acl -Path $(Join-Path $PhysicalPath "log")
            $sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::NetworkServiceSid, $null)
            $n = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($sid, "FullControl", "ObjectInherit", "InheritOnly", "Allow")
            $acl.AddAccessRule($n)
            $n = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($sid, "FullControl", "ContainerInherit", "InheritOnly", "Allow")
            $acl.AddAccessRule($n)
            $n = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule($sid, "FullControl", "None", "None", "Allow")
            $acl.AddAccessRule($n)
            Set-Acl -Path $(Join-Path $PhysicalPath "log") -AclObject $acl
        }
        catch {
            Write-Error "Could not set permissions to log folder for user NetworkService: $_"
            Break
        }

        # Change portal title if name is specified
        if ($PortalTitle) {
            Write-Host "Changing portal title to $PortalTitle..." -ForegroundColor Green
            $UserConfig = Join-Path $PhysicalPath "user.config"
            if (!(Test-Path $UserConfig)) {
                Write-Error "Error opening user.config: $_"
                Break
            }
            try {
                $userConfigXML = [xml] (Get-Content $UserConfig)
                Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/loginPageCaption" | ForEach-Object { $_.Node.'#text' = $PortalTitle }
                $userConfigXML.Save($UserConfig)
            }
            catch {
                Write-Error "Error updating user.config: $_"
                Break
            }

            # Create html\HeaderToolbar.html if it doesn't exist. If it exists, it's not changed!
            if (!(Test-Path -Path (Join-Path $PhysicalPath "html\HeaderToolbar.html"))) {
                try {
                    New-Item -Path $PhysicalPath -Name "html" -ItemType Directory -Force
                    $PortalTitle | Out-File -FilePath (Join-Path $PhysicalPath "html\HeaderToolbar.html") -Force
                }
                catch {
                    Write-Error "Error creating custom header: $_"
                    Break
                }

                # Add custom header to user.config
                $UserConfig = Join-Path $PhysicalPath "user.config"
                if (!(Test-Path $UserConfig)) {
                    Write-Error "Error opening user.config: $_"
                    Return
                }
                try {
                    # TODO: If node doesn't exist, create it
                    $userConfigXML = [xml] (Get-Content $UserConfig)
                    Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/customHeaderPath" | ForEach-Object { $_.Node.'#text' = "html\HeaderToolbar.html" }
                    $userConfigXML.Save($UserConfig)
                }
                catch {
                    Write-Error "Error setting <customHeaderPath> to custom header: $_"
                    Break
                }
            }
        }

        # Change language-specific settings in user.config
        Write-Host "Changing language-specific settings..." -ForegroundColor Green
        $UserConfig = Join-Path $PhysicalPath "user.config"
        if (!(Test-Path $UserConfig)) {
            Write-Error "Error opening user.config: $_"
            Break
        }
        try {
            $userConfigXML = [xml] (Get-Content $UserConfig)
            if ($AcrossLanguage -eq "EN" -or $AcrossLanguage -eq "English") {
                # DocType settingsTemplates
                # Select-Xml -Xml $userConfigXML -XPath "/Settings/documentTypes/documentType[@settingsTemplate='Standard']" | ForEach-Object { $_.Node.SettingsTemplate = "Default" }
                # SubjectList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/subjectList[@defaultValue='Allgemein']" | ForEach-Object { $_.Node.defaultValue = "General" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/subjectList/*/value[text()='Allgemein']" | ForEach-Object { $_.Node.'#text' = "General" }
                # WorkflowList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Übersetzung']" | ForEach-Object { $_.Node.'#text' = "Translation" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Übersetzung und Korrektur']" | ForEach-Object { $_.Node.'#text' = "Translation and correction" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Übersetzung, Lektorat, Korrektur']" | ForEach-Object { $_.Node.'#text' = "Translation, review, correction" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Extern editieren']" | ForEach-Object { $_.Node.'#text' = "External editing" }
                # RelationList ???
                # ReportingTemplateList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/reportingtemplateList[@defaultValue='Standard']" | ForEach-Object { $_.Node.defaultValue = "Default" }
                # ProjectSettingsList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/projectSettingsTemplateList[@defaultValue='Standard']" | ForEach-Object { $_.Node.defaultValue = "Default" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/projectSettingsTemplateList/*/value[text()='Standard']" | ForEach-Object { $_.Node.'#text' = "Default" }
                # DisplaySettings
                Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/dateFormat" | ForEach-Object { $_.Node.'#text' = "yyyy-MM-dd"}
                Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/timeFormat" | ForEach-Object { $_.Node.'#text' = "hh:mm tt"}
                # PriceSettings
                Select-Xml -Xml $userConfigXML -XPath "/Settings/priceSettings" | ForEach-Object { $_.Node.currency = "$" }
            }
            elseif ($AcrossLanguage -eq "DE" -or $AcrossLanguage -eq "German") {
                # DocType settingsTemplates
                # Select-Xml -Xml $userConfigXML -XPath "/Settings/documentTypes/documentType[@settingsTemplate='Default']" | ForEach-Object { $_.Node.SettingsTemplate = "Standard" }
                # SubjectList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/subjectList[@defaultValue='General']" | ForEach-Object { $_.Node.defaultValue = "Allgemein" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/subjectList/*/value[text()='General']" | ForEach-Object { $_.Node.'#text' = "Allgemein" }
                # WorkflowList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Translation']" | ForEach-Object { $_.Node.'#text' = "Übersetzung" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Translation and correction']" | ForEach-Object { $_.Node.'#text' = "Übersetzung und Korrektur" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='Translation, review, correction']" | ForEach-Object { $_.Node.'#text' = "Übersetzung, Lektorat, Korrektur" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/workflowList/*/value[text()='External editing']" | ForEach-Object { $_.Node.'#text' = "Extern editieren" }
                # RelationList ???
                # ReportingTemplateList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/reportingtemplateList[@defaultValue='Default']" | ForEach-Object { $_.Node.defaultValue = "Standard" }
                # ProjectSettingsList
                Select-Xml -Xml $userConfigXML -XPath "/Settings/projectSettingsTemplateList[@defaultValue='Default']" | ForEach-Object { $_.Node.defaultValue = "Standard" }
                Select-Xml -Xml $userConfigXML -XPath "/Settings/projectSettingsTemplateList/*/value[text()='Default']" | ForEach-Object { $_.Node.'#text' = "Standard" }
                # DisplaySettings
                Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/dateFormat" | ForEach-Object { $_.Node.'#text' = "dd.MM.yyyy"}
                Select-Xml -Xml $userConfigXML -XPath "/Settings/displaySettings/timeFormat" | ForEach-Object { $_.Node.'#text' = "HH:mm"}
                # PriceSettings
                Select-Xml -Xml $userConfigXML -XPath "/Settings/priceSettings" | ForEach-Object { $_.Node.currency = "€" }
            }
            $userConfigXML.Save($UserConfig)
        }
        catch {
            Write-Error "Error updating user.config: $_"
            Break
        }

        # Restart crossAPI service and the web application pool
        Write-Host "Restarting crossAPI and web application pool $poolName..." -ForegroundColor Green
        Restart-Service -Name crossAPI
        Restart-WebAppPool -Name $appName

        # Open new ALPS in default browser
        if (!$NoBrowser) {
            Start-Process "http://$($env:computername)/$InstanceName"
        }
    }

    End {
        Write-Host "Done!" -ForegroundColor White
    }
}

New-Alias -Name New-Portal -Value New-LanguagePortal
New-ALias -Name Start-IISM -Value "$env:windir\system32\inetsrv\InetMgr.exe"
#endregion

#region Helper functions
<#
    Functions in this region are internally used helpers and are not intended for users
#>


<#
    Version history
    ---------------
    v1.0.0 - Initial version. Creates app pool and web application
 
    Like: Invoke-Sqlcmd -ServerInstance $SQLServer -Username $SqlUsername -Password $SqlPassword -Query $sqlCmd
    or: Invoke-Sqlcmd -ServerInstance $SQLServer -Query $sqlCmd
#>

function Invoke-SqlNonQuery {
    <#
    .SYNOPSIS
    Invoke a SQL command (non-query) using .NET instead of an external module
 
    .DESCRIPTION
    The function invokes a SQL query / command using .NET instead of an external module
 
    .PARAMETER ServerInstance
    The name of the database instance. This name normally should be the SQL server name and may also include a SQL instance name separated by a backslash.
    It's also possible to add a non-strandard port separated by a comma
 
    Example: MySQLServerMachineName\MyInstance,1234
 
    .PARAMETER Username
    A SQL account user name. If not specified, an integrated security connection is used
 
    .PARAMETER Password
    The password of a SQL account user. If not specified, an integrated security connection is used
 
    .PARAMETER Query
    Specifies the T-SQL query to be executed
    #>

    [CmdletBinding()]

    Param(
        [Parameter()][ValidateNotNullOrEmpty()][string]$ServerInstance,
        [Parameter()][string]$Username,
        [Parameter()][string]$Password,
        [Parameter()][ValidateNotNullOrEmpty()][string]$Query
    )

    # Build connection string
    if (!$Username -or !$Password) {
        $sqlConnectionString = "Server = $ServerInstance; Integrated Security=True;"
    }
    else {
        $sqlConnectionString = "Server = $ServerInstance; User Id = $Username; Password = $Password;"
    }

    # Run the query
    $sqlConnection = New-Object System.Data.SqlClient.SqlConnection
    $sqlConnection.ConnectionString = $sqlConnectionString
    $Query -split "GO;" | ForEach-Object {
        $sqlCommand = New-Object System.Data.SqlClient.SqlCommand
        $sqlCommand.CommandText = $_
        $sqlCommand.Connection = $sqlConnection
        $sqlCommand.Connection.Open()
        try {
            $sqlQ = $_
            $sqlCommand.ExecuteNonQuery()
        }
        catch {
            Write-Error "SQL error on query $($sqlQ): $_"
        }
        $sqlCommand.Connection.Close()
    }
}

#endregion

# SIG # Begin signature block
# MIIdEwYJKoZIhvcNAQcCoIIdBDCCHQACAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQURzPI96abcU0jIUdVJsApyrRc
# m5SgghhLMIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B
# AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV
# BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU
# cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw
# NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs
# dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G
# A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF
# UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q
# gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x
# 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ
# w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH
# d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh
# 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT
# bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE
# JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P
# AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG
# A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz
# dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG
# GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C
# L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9
# 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf
# ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt
# pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd
# ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY
# Ju6ay0SnRVqBlRUa9VEwggTAMIIDqKADAgECAhB3mUqMU4Kv4kkOE9jupa3ZMA0G
# CSqGSIb3DQEBCwUAMFExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIElu
# Yy4xKzApBgNVBAMTInRoYXd0ZSBTSEEyNTYgQ29kZSBTaWduaW5nIENBIC0gRzIw
# HhcNMTgwMjA2MDAwMDAwWhcNMjAwNDA2MjM1OTU5WjB5MQswCQYDVQQGEwJERTEb
# MBkGA1UECAwSQmFkZW4tV8O8cnR0ZW1iZXJnMREwDwYDVQQHDAhLYXJsc2JhZDEc
# MBoGA1UECgwTQWNyb3NzIFN5c3RlbXMgR21iSDEcMBoGA1UEAwwTQWNyb3NzIFN5
# c3RlbXMgR21iSDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANdLQMBj
# NlWXq5/7zamYXMHBMJ4gUwR+W9mutEMAjCGyD2DQj6zD2M9OEmh/2QYRRqbreXL1
# LZ35aY34kDDOxp1j9i4//fNt3vehtchMqJfn6rX0+lthiIrO9N34J0Gp8U1YRM5G
# 6Sqh7FMPlhCzZzJJZMv3ZmgOUQk/i21bu1+bfiW/RMJyYq39Z5TaHlrgk0KMfNIR
# PANBSEPxJ+suhi6iI1nbSplx59wJ0a9+LkLwyXlfyX3huNS7Fjz1DdV9P4oIMYmZ
# aCIxb9WEFIeatj0l+0ovtHPsbZCd7wYc6ThSr4n3bB2uE6PSlj4YyvttHmdJZHtw
# t7ybZfIk1DGhIgECAwEAAaOCAWowggFmMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAU
# cPaoczpQ8lsKcM0RwWgKdvA9pVYwHQYDVR0OBBYEFC8PKg2BDfIqjjzjodzuJilN
# 6JhgMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly90by5zeW1jYi5jb20vdG8uY3Js
# MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBuBgNVHSAEZzBl
# MGMGBmeBDAEEATBZMCYGCCsGAQUFBwIBFhpodHRwczovL3d3dy50aGF3dGUuY29t
# L2NwczAvBggrBgEFBQcCAjAjDCFodHRwczovL3d3dy50aGF3dGUuY29tL3JlcG9z
# aXRvcnkwVwYIKwYBBQUHAQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8vdG8uc3lt
# Y2QuY29tMCYGCCsGAQUFBzAChhpodHRwOi8vdG8uc3ltY2IuY29tL3RvLmNydDAN
# BgkqhkiG9w0BAQsFAAOCAQEAB3jg4ymUPJtHEaOVCmlH52uCFxzkyWXosTPRkm6I
# iZ63K2Fm/RJ50m6JXhbGaJ+CYhVSga5dt5qC7VAD6ngb0/S94EjkMk8wnF+bgIHC
# 6aSL4G4sL6TQVqqNYE+BfYCfIZnhy34xo+B9UbVUGC367NzMdhbrW3BGpy2WI6Ji
# GG8Tg1X1B5YdaPPWFQKlJWsF1G3QOuYrD3YQxL++2o2uRxQk+TokxPB+C1xIpKWz
# 6UPk5j9QH+FZ1PRcH8/Z+ZRr+yL3fkOFrzOEuyBmHokr8QrgKvMplMekufSU/3wR
# PBvIp9FY5qfk6rSDptpKwa9LY5pdhQBLJOtpdxx7Jx+rRDCCBOYwggPOoAMCAQIC
# EGJcTZCM1UL7qy6lcz/xVBkwDQYJKoZIhvcNAQEFBQAwgZUxCzAJBgNVBAYTAlVT
# MQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoT
# FVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2Vy
# dHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDAeFw0xMTA0
# MjcwMDAwMDBaFw0yMDA1MzAxMDQ4MzhaMHoxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# ExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoT
# EUNPTU9ETyBDQSBMaW1pdGVkMSAwHgYDVQQDExdDT01PRE8gVGltZSBTdGFtcGlu
# ZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKqC8YSpW9hxtdJd
# K+30EyAM+Zvp0Y90Xm7u6ylI2Mi+LOsKYWDMvZKNfN10uwqeaE6qdSRzJ6438xqC
# pW24yAlGTH6hg+niA2CkIRAnQJpZ4W2vPoKvIWlZbWPMzrH2Fpp5g5c6HQyvyX3R
# TtjDRqGlmKpgzlXUEhHzOwtsxoi6lS7voEZFOXys6eOt6FeXX/77wgmN/o6apT9Z
# RvzHLV2Eh/BvWCbD8EL8Vd5lvmc4Y7MRsaEl7ambvkjfTHfAqhkLtv1Kjyx5VbH+
# WVpabVWLHEP2sVVyKYlNQD++f0kBXTybXAj7yuJ1FQWTnQhi/7oN26r4tb8QMspy
# 6ggmzRkCAwEAAaOCAUowggFGMB8GA1UdIwQYMBaAFNrtZHQUnBQ8q92Zqb1bKE2L
# PMnYMB0GA1UdDgQWBBRkIoa2SonJBA/QBFiSK7NuPR4nbDAOBgNVHQ8BAf8EBAMC
# AQYwEgYDVR0TAQH/BAgwBgEB/wIBADATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNV
# HSAECjAIMAYGBFUdIAAwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC51c2Vy
# dHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDB0BggrBgEFBQcBAQRo
# MGYwPQYIKwYBBQUHMAKGMWh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9VVE5BZGRU
# cnVzdE9iamVjdF9DQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0
# cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBABHJPeEF6DtlrMl0MQO32oM4xpK6
# /c3422ObfR6QpJjI2VhoNLXwCyFTnllG/WOF3/5HqnDkP14IlShfFPH9Iq5w5Lfx
# sLZWn7FnuGiDXqhg25g59txJXhOnkGdL427n6/BDx9Avff+WWqcD1ptUoCPTpcKg
# jvlP0bIGIf4hXSeMoK/ZsFLu/Mjtt5zxySY41qUy7UiXlF494D01tLDJWK/HWP9i
# dBaSZEHayqjriwO9wU6uH5EyuOEkO3vtFGgJhpYoyTvJbCjCJWn1SmGt4Cf4U6d1
# FbBRMbDxQf8+WiYeYH7i42o5msTq7j/mshM/VQMETQuQctTr+7yHkFGyOBkwggT+
# MIID5qADAgECAhArc9t0YxFMWlsySvIwV3JJMA0GCSqGSIb3DQEBBQUAMHoxCzAJ
# BgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcT
# B1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSAwHgYDVQQDExdD
# T01PRE8gVGltZSBTdGFtcGluZyBDQTAeFw0xOTA1MDIwMDAwMDBaFw0yMDA1MzAx
# MDQ4MzhaMIGDMQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVz
# dGVyMRAwDgYDVQQHDAdTYWxmb3JkMRgwFgYDVQQKDA9TZWN0aWdvIExpbWl0ZWQx
# KzApBgNVBAMMIlNlY3RpZ28gU0hBLTEgVGltZSBTdGFtcGluZyBTaWduZXIwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/UjaCOtx0Nw141X8WUBlm7boa
# mdFjOJoMZrJA26eAUL9pLjYvCmc/QKFKimM1m9AZzHSqFxmRK7VVIBn7wBo6bco5
# m4LyupWhGtg0x7iJe3CIcFFmaex3/saUcnrPJYHtNIKa3wgVNzG0ba4cvxjVDc/+
# teHE+7FHcen67mOR7PHszlkEEXyuC2BT6irzvi8CD9BMXTETLx5pD4WbRZbCjRKL
# Z64fr2mrBpaBAN+RfJUc5p4ZZN92yGBEL0njj39gakU5E0Qhpbr7kfpBQO1NArRL
# f9/i4D24qvMa2EGDj38z7UEG4n2eP1OEjSja3XbGvfeOHjjNwMtgJAPeekyrAgMB
# AAGjggF0MIIBcDAfBgNVHSMEGDAWgBRkIoa2SonJBA/QBFiSK7NuPR4nbDAdBgNV
# HQ4EFgQUru7ZYLpe9SwBEv2OjbJVcjVGb/EwDgYDVR0PAQH/BAQDAgbAMAwGA1Ud
# EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwQAYDVR0gBDkwNzA1Bgwr
# BgEEAbIxAQIBAwgwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9D
# UFMwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybC5zZWN0aWdvLmNvbS9DT01P
# RE9UaW1lU3RhbXBpbmdDQV8yLmNybDByBggrBgEFBQcBAQRmMGQwPQYIKwYBBQUH
# MAKGMWh0dHA6Ly9jcnQuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0Ff
# Mi5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28uY29tMA0GCSqG
# SIb3DQEBBQUAA4IBAQB6f6lK0rCkHB0NnS1cxq5a3Y9FHfCeXJD2Xqxw/tPZzeQZ
# pApDdWBqg6TDmYQgMbrW/kzPE/gQ91QJfurc0i551wdMVLe1yZ2y8PIeJBTQnMfI
# Z6oLYre08Qbk5+QhSxkymTS5GWF3CjOQZ2zAiEqS9aFDAfOuom/Jlb2WOPeD9618
# KB/zON+OIchxaFMty66q4jAXgyIpGLXhjInrbvh+OLuQT7lfBzQSa5fV5juRvgAX
# IW7ibfxSee+BJbrPE9D73SvNgbZXiU7w3fMLSjTKhf8IuZZf6xET4OHFA61XHOFd
# kga+G8g8P6Ugn2nQacHFwsk+58Vy9+obluKUr4YuMIIFDzCCA/egAwIBAgIQC/PM
# Y88EME1Hw7WHBJ+oBzANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
# aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0g
# Rm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5
# IFJvb3QgQ0EgLSBHMzAeFw0xNDA3MjIwMDAwMDBaFw0yNDA3MjEyMzU5NTlaMFEx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xKzApBgNVBAMTInRo
# YXd0ZSBTSEEyNTYgQ29kZSBTaWduaW5nIENBIC0gRzIwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQDZVq/KrWk13jsGVgF1NZfSU3xJtv4MGPC2PENvchuc
# SDTwrMmy2Zd9DBO5zoUMLrn2lsxJ5iOgJ1gyGEFZbYe+gjVkhuixOuSPdOTONpKu
# vLV2Jer5JGazeO6DoaP+0QNVVRCuIeixlZQCtSzZxfQIIwD+CY0rFwgpjAsWDlga
# 2Qw65N6ZbSL/vRuUKjTLGXqmL+Mr0OZ5mErY60uYZLu34RDGU9mrhHVjDGm38Mkb
# pfdVkSFiWmeBPw95FlfQ7e9sGjVbbUFubZg6/UyZQ83qsSlpmTUG52z2BlTjVnnS
# 9WrLyWZK7mu/X6fpj/v37nLb1vsh9S1j4Og7jUcXEdhVAgMBAAGjggGDMIIBfzAu
# BggrBgEFBQcBAQQiMCAwHgYIKwYBBQUHMAGGEmh0dHA6Ly90LnN5bWNkLmNvbTAS
# BgNVHRMBAf8ECDAGAQH/AgEAMHMGA1UdIARsMGowaAYLYIZIAYb4RQEHMAIwWTAm
# BggrBgEFBQcCARYaaHR0cHM6Ly93d3cudGhhd3RlLmNvbS9jcHMwLwYIKwYBBQUH
# AgIwIxohaHR0cHM6Ly93d3cudGhhd3RlLmNvbS9yZXBvc2l0b3J5MDQGA1UdHwQt
# MCswKaAnoCWGI2h0dHA6Ly90LnN5bWNiLmNvbS9UaGF3dGVQQ0EtRzMuY3JsMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIBBjApBgNVHREEIjAgpB4w
# HDEaMBgGA1UEAxMRU3ltYW50ZWNQS0ktMS03MjUwHQYDVR0OBBYEFHD2qHM6UPJb
# CnDNEcFoCnbwPaVWMB8GA1UdIwQYMBaAFK1sqpRgnO3k//o+CnQrYwP3tlm/MA0G
# CSqGSIb3DQEBCwUAA4IBAQBiWGZdNi/sf/XHqwg1gwoTVLtwi+F6KIBW26wgwSob
# cBB2/QhfCaWLpmAf9OrtETan1aDhVdIVJ3nRug5pbIfF5wlaCIa/zGsciixXblLN
# 0ZhScyj1NeMlwKtrXCg1uXVDDFWN3kfLX5Q9GXFXMzTdc8VY0uuuzOXC+pCZ/QPx
# izF7UIHjK3doAXsnec8FMiGthEDy9RkDxgQKrlHVyDUiOcInfMGKVUsGIc9gYSoo
# yPaC7E7HAbyq3gsSxq4684JGJ7MYtumdnk1KAnerKZZ3FOXYwh7mS2DYuLKDJMBX
# c1sSydLAVicLk3FT6I5VUjexx1orTa1oVAqOzqxRMxyMMYIEMjCCBC4CAQEwZTBR
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSswKQYDVQQDEyJ0
# aGF3dGUgU0hBMjU2IENvZGUgU2lnbmluZyBDQSAtIEcyAhB3mUqMU4Kv4kkOE9ju
# pa3ZMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqG
# SIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3
# AgEVMCMGCSqGSIb3DQEJBDEWBBSogNvKsFAmTA82hsES4iCM+gXgHzANBgkqhkiG
# 9w0BAQEFAASCAQCsI6qnzVB5kouFr2VonDuVtHEiawF0Q129B431hDsk205b1T66
# XLEX80FrLKHotXu6s3ZSTkIAj3OndEW6vVcivRI+PeIQS257o40GRDVC2BxZpeOc
# mhsTh+P72Ro2z7SSLD8gI/08WwVwyELX8dbCQsWvS3EOO4C27BB88a1KnYxNxFqR
# sgrSAm2ZBmT4AeArlL18jLPoXkatoOYPncEKoTiZhP1au5AplT3gtrdN49ptxBIl
# ed7Xwo99a20gyBYGk1MDy4SjxuQT77RuQBp0wVTI8NcdvYkAyl3iURZscaYGoS0a
# HTlzNc6QjiksjDQm1KX44fX5Zqw3/yEuxoPpoYICKDCCAiQGCSqGSIb3DQEJBjGC
# AhUwggIRAgEBMIGOMHoxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1h
# bmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBM
# aW1pdGVkMSAwHgYDVQQDExdDT01PRE8gVGltZSBTdGFtcGluZyBDQQIQK3PbdGMR
# TFpbMkryMFdySTAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEH
# ATAcBgkqhkiG9w0BCQUxDxcNMjAwMjA0MTAwNjM4WjAjBgkqhkiG9w0BCQQxFgQU
# NQ16Y4QSHJPD1T8rxbupu/IuXNEwDQYJKoZIhvcNAQEBBQAEggEACErgTWT3Cd3b
# 20lUTcrTN39HA6Ol5cH+idRQS7BxWZ6YUK1VGXFFP3tiFvwyJbN/NsdT+6iNjFcA
# DKRwJu7aOERTtsQWC2bood3qjuhz9g4o0PGOZmVJ/BX9xwsywFbSWbeq8NbfHM/x
# P/P9zB6nJDTpe7TAyBCFHGs2iyMkJZAL+sikdN0laW33O5ATkSvcg4vecizrhCG8
# pL84ftz/sxlVPN93858JNcPRKf5XPgPHxXZa2kNay3JpT2v5ThN2i4fQOj8d6iJn
# i79ESV+3mVUmjQvBtMsNY7ZuzcCaJGU5IlCSOqm1Jrcxv3z/qc7pofwbHLOIfjEx
# zQk5ZcLlgQ==
# SIG # End signature block