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 |