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 Due to the Windows specific calls used in the script, it requires the Desktop version of PowerShell. It doesn't run natively in PowerShell Core To use it in PowerShell Core anyways, it's possible to run it in compatibility mode. To do so: - Install the compatibility module: Install-Module WindowsCompatibility -Scope CurrentUser - Restart the PowerShell Core console - Import the Across-Solutions module: Import-WinModule Across-Solutions Now, the module commands can be run within PowerShell Core #> #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.5 - Updated syntax help v1.1.4 - If NETWORK SERVICE is already configured, no change to the DCOM settings will be made 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 examples 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 user-name 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 setup and configuration steps 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 built-in NETWORK SERVICE user (if user wasn't configured before) NOTE: Sometimes this doesn't work correctly (ALPS won't start with a DCOM related error). In such case, manually remove NETWORK SERVICE and add it again to crossAPI DCOM object - 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 built-in NETWORK SERVICE user access to the language portal log folder - Change 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 hard-coded 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 machine 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 (hard-coded) - 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 already exists. Make sure that the database doesn't 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 (hard-coded) .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 recommend 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 German 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 somepassword 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" | "somepassword. - 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 somepassword 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" | "somepassword". - 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 ssomepassword 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" | "somepassword". - 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 "$_ doesn'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 } <# Removed and replaced with a variant that works without an external module # 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 operating system <# Debug part 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" }; ($descrLaunch.DACL | where { $_.Trustee.Name -eq "NETWORK SERVICE" }).Trustee; $descrAccess.DACL | where { $_.Trustee.Name -eq "NETWORK SERVICE" }; ($descrAccess.DACL | where { $_.Trustee.Name -eq "NETWORK SERVICE" }).Trustee; #> 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 # Only set new DCOM permissions, if "NetworkService" doesn't yet exist in the DACL $daclLaunchExisting = $descrLaunch.DACL | Where-Object { $_.Trustee.Name -eq "NETWORK SERVICE" } $daclAccessExisting = $descrAccess.DACL | Where-Object { $_.Trustee.Name -eq "NETWORK SERVICE" } if ($null -eq $daclLaunchExisting -and $null -eq $daclAccessExisting) { $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 & Remote Launch and Local & Remote Activation. Probably needs to be 31. Needs to be checked $aceLaunch.trustee = $trusteeObj $aceAccess = ([wmiclass]'Win32_ACE').psbase.CreateInstance() $aceAccess.AccessMask = 3 # Mask for Local & Remote Access. Probably needs to be 7. Needs to be checked $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 } else { Write-Host "crossAPI DCOM permissions for NetworkService already exist. Not changing them. If they are not working, please check permissions manually" -ForegroundColor Yellow } } 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." Break } 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" <# Version history --------------- v1.0.0 - Initial version. #> function Remove-LanguagePortal { <# .SYNOPSIS Removes the WebApp, WebAppPool and (optional) database specified by the WebAppPool name. To be used for WebApps created by New-LanguagePortal function .DESCRIPTION The function removes a Language Portal installed by the New-LanguagePortal functiion. The following steps are performed: - Remove the web application with the given name - Remove the web application pool with the given name - Optionally removes the database that belongs to the language portal. The name of the database is retrieved from the server.config file. This only is done when the parameters -SQLServer, -SqlUsername and -SqlPassword are given .PARAMETER InstranceName The name of the instance to be deleted .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 .PARAMETER SQLPassword The password of the SQL admin user. Note: Secure credentials are currently not supported. Password will be visible on screen .EXAMPLE PS> Remove-LanguagePortal -InstanceName ALPS This command will remove the app pool and the web application of name ALPS. The database connected to this instance will not be removed .EXAMPLE PS> Remove-LanguagePortal -InstanceName ALPS -SQLServer SQLServerMachine -SQlUsername SA -SqlPassword somepassword This command will remove the app pool, the web application of name ALPS and the database associated with the language portal #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][Alias("AppPool", "inst", "i")][string]$InstanceName = "ALPS", [Parameter()][Alias("sql")][string]$SQLServer = $env:COMPUTERNAME, [Parameter()][Alias("sqlu")][string]$SqlUsername, [Parameter()][Alias("sqlp")][string]$SqlPassword ) 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 } 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 { # Get the web application by name $WebApp = Get-WebApplication | Where-Object applicationPool -EQ $InstanceName if (!$WebApp) { Write-Error "A web application with application pool name $InstanceName could not be found." Break } $AppName = $WebApp.Name if (!$AppName) { $AppName = $InstanceName } $AppPool = $WebApp.applicationPool $Site = Get-Website | Where-Object ID -EQ 1 if (!$Site) { Write-Error "A Website with ID 1 could not be found." Break } $ServerConfig = Join-Path $WebApp.PhysicalPath "server.config" if (!(Test-Path $ServerConfig)) { Write-Error "Could not find the server.config file." Break } try { $DbName = Select-Xml -Path $ServerConfig -XPath "/ServerSettings/db/database" | Select-Object -ExpandProperty Node $DbName = $DbName.'#text' } catch { Write-Error "Could not determine database name from server.config file." Break } # Rmove the web application Write-Host "Removing the web app $AppName..." -ForegroundColor Green try { Remove-WebApplication -Site $Site.Name -Name $AppName } catch { Write-Error "Could not remove web application $($WebApp.Name): $_" } # Remove the app pool Write-Host "Removing the web app pool $AppPool..." -ForegroundColor Green try { Remove-WebAppPool -Name $AppPool } catch { Write-Error "Could not remove web application pool $($AppPool): $_" } # If Sql Server and credentials are speecified, drop the database $sqlCmd = "USE [master]; GO; DROP DATABASE [$($DbName)]; GO; " Write-Host "Removing the database $DbName from $SQLServer..." -ForegroundColor Green if ($SQLServer -and $SqlUsername -and $SqlPassword -and $DbName) { try { Invoke-SqlNonQuery -ServerInstance $SQLServer -Username $SqlUsername -Password $SqlPassword -Query $sqlCmd } catch { Write-Error "Error removing the database: $_" Break } } } } New-Alias -Name Remove-Portal -Value Remove-LanguagePortal function Set-LanguagePortalLogLevel { <# .SYNOPSIS Set the logging level of the specified ALPS instance .DESWCRIPTION The function applies the changes to the user.config to set the specified logging level. It also allows to set the attributes writeControlTree, cultureIndicator, sessionIndicator and threadIndicator if the logging level TRACE is specified. For all other logging levels the attributes are removed Important: As the default for cultureIndicator is "true" (enabled), the switch is called "NoCultureIndicator" and is disabling the logging of culture indicators is set. The function does not restart the web application. To do this, use the function Restart-WebAppPool [PoolName] .PARAMETER InstanceName The name of the instance to modify. This is the web application name .PARAMETER LogLevel The logging level to apply to teh ALPS instance. Allowed values are ERROR, WARNING, INFO and TRACE .PARAMETER writeControlTree If set, the attribute writeControlTree is set to true .PARAMETER NoCultureIndicator If set, the attribute cultureIndicator is set to false .PARAMETER sessionIndicator If set, the attribute sessionIndicator is set to true .PARAMETER threadIndicator If set, the attribute threadIndicator ios set to true .EXAMPLE PS> Set-LanguagePortalLogLevel -InstanceName ALPS -LogLevel ERROR Sets the logging level of instance ALPS to ERROR and removes the logging attributes if there are any .EXAMPLE PS> Set-LanguagePortalLogLevel -InstanceName ALPS -LogLevel TRACE Sets the logging level of instance ALPS to ERROR .EXAMPLE PS> Set-LanguagePortalLogLevel -InstanceName ALPS -LogLevel TRACE -writeControlTree -sessionIndicator Sets the logging level of instance ALPS to ERROR and sets the logging attributes writeControlTree and sessionIndicator to true #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)][Alias("Instance", "Inst", "i")][string]$InstanceName = "ALPS", [Parameter()][Alias("DebugLevel", "Level", "ll")][ValidateSet("ERROR", "WARNING", "INFO", "TRACE")][string]$LogLevel, [Parameter()][Alias("ControlTree", "ct")][switch]$writeControlTree, [Parameter()][Alias("DisableCultureIndicator", "nci")][switch]$NoCultureIndicator, [Parameter()][Alias("si")][switch]$sessionIndicator, [Parameter()][Alias("ti")][switch]$threadIndicator ) Begin { # 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 { $ALPSPath = Get-WebAppPhysicalPath -Name $InstanceName if (!$ALPSPath) { Write-Error "Error getting physical path of web application $InstanceName." Break } $UserConfig = Join-Path -Path $ALPSPath -ChildPath "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/log_level" | ForEach-Object { $_.Node.'#text' = $LogLevel} if ($LogLevel -eq "TRACE") { if ($writeControlTree) { Select-Xml -Xml $UserConfigXml -XPath "/Settings/log_level" | ForEach-Object { Add-XMLAttribute -Node $_.Node -Name "writeControlTree" -Value "true"} } if ($NoCultureIndicator) { Select-Xml -Xml $UserConfigXml -XPath "/Settings/log_level" | ForEach-Object { Add-XMLAttribute -Node $_.Node -Name "cultureIndicator" -Value "false"} } if ($sessionIndicator) { Select-Xml -Xml $UserConfigXml -XPath "/Settings/log_level" | ForEach-Object { Add-XMLAttribute -Node $_.Node -Name "sessionIndicator" -Value "true"} } if ($threadIndicator) { Select-Xml -Xml $UserConfigXml -XPath "/Settings/log_level" | ForEach-Object { Add-XMLAttribute -Node $_.Node -Name "threadIndicator" -Value "true"} } } else { Select-Xml -Xml $UserConfigXml -XPath "/Settings/log_level" | ForEach-Object { Remove-AllXMLAttribute -Node $_.Node } } $UserConfigXml.Save($UserConfig) } catch { Write-Error "Error updating user.config: $_" Break } } } #endregion #region Helper functions <# Functions in this region are internally used helpers and are not intended for users #> function Add-XMLAttribute([System.Xml.XmlNode] $Node, $Name, $Value) { $attrib = $Node.OwnerDocument.CreateAttribute($Name) $attrib.Value = $Value $node.Attributes.Append($attrib) | Out-Null } function Remove-AllXMLAttribute([System.Xml.XmlNode] $Node) { $Node.RemoveAllAttributes() } <# Version history --------------- v1.0.0 - Initial version. 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() } } <# Version history --------------- v1.0.0 - Initial version. #> function Get-WebAppPhysicalPath { <# .SYNOPSIS Retrieve the physical path of a WebApp .DESCRIPTION The function retrieves the physical path of a WebApp .PARAMETER Name The name of the WebApp #> [CmdletBinding()] Param( [Parameter()][ValidateNotNullOrEmpty()][string]$Name ) 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 } 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 { # Get the web application by name $WebApp = Get-WebApplication -Name $Name if (!($WebApp)) { return "" } return $WebApp.PhysicalPath } } #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 |