Stti.psm1
param( [switch] $SkipAdminCheck = $false ) #Check if the user is in the administrator group. Warns and stops if the user is not. if (-not ($SkipAdminCheck) -and -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { throw "You are not running this as local administrator. Run it again in an elevated prompt." } #Load or install SqlServer Module in Windows Powershell for compatibility $sqlServerModule = Import-Module SqlServer -UseWindowsPowerShell -ErrorAction SilentlyContinue -PassThru if ($null -eq $sqlServerModule){ Start-Job -PSVersion 5.1 { Install-Module SqlServer -Force -AllowClobber } | Receive-Job -Wait $sqlServerModule = Import-Module SqlServer -UseWindowsPowerShell -ErrorAction SilentlyContinue -PassThru if ($null -eq $sqlServerModule){ throw "Could not load SqlServer Module under Windows Powershell" } } enum SttiInstanceRoles { Worker Web } enum LogLevels { Debug = 1 Verbose = 2 Info = 3 Warn = 4 Error = 5 } class SttiPackage{ [System.Version] $Version [string] $Path [bool] $IsLocal SttiPackage([Version] $version, [string] $path, [bool] $isLocal){ $this.Version = $version $this.Path = $path $this.IsLocal = $isLocal } } class SttiInstanceConfig{ [string] $Name [string] $SqlServer [string] $Database [SttiInstanceRoles[]] $Roles [string] $Path [string] $ServiceUsername [bool] $StoreDbInInstanceDataPath [string] $WorkerSslCertificateSubject [uint] $WorkerPort [string] $WebSslCertificateSubject [uint] $WebPort [string] $WorkerHostname [string] $EncryptionCertificateThumbprint [string] $EncryptionMasterKeyName } class SttiInstanceInstallStatus{ [bool] $Installed [SttiInstanceRoles[]] $Roles [Datetime] $Timestamp [string] $Version SttiInstanceInstallStatus(){ $this.Installed = $false } [string] ToString(){ if ($this.Installed -eq $true){ return "Installed Version $($this.Version) with Roles [$($this.Roles)] at $($this.Timestamp)" } else { return "Not installed" } } } class SttiInstanceStatus{ [bool] $DatabaseExists [bool] $InstanceExists [SttiInstanceInstallStatus] $InstallStatus [bool] $WorkerRoleRunning [bool] $WebRoleRunning [bool] $DatabaseEncryptionRunning SttiInstanceStatus(){ $this.DatabaseExists = $false $this.InstanceExists = $false $this.WebRoleRunning = $false $this.WorkerRoleRunning = $false $this.InstallStatus = $null $this.DatabaseEncryptionRunning = $false } } #region Installation function Install-SttiInstance { [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiPackage] $Package, [Parameter(Mandatory)] [PSCredential] $ServiceCredential, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Write-SttiLog "Start install instance $($Instance.Name)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose Test-ADCredential $ServiceCredential -ErrorAction Stop > $null Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $true){ throw "The instance $($Instance.Name) is already installed" } $errorAction = $Force ? "Continue" : "Stop" Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Build-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction New-SttiDatabase -Instance $instance -ErrorAction $errorAction Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Start-SttiInstance -Instance $Instance -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished install instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during install of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Install-SttiInstance -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage "latest") function Update-SttiInstance { [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiPackage] $Package, [Parameter(Mandatory)] [PSCredential] $ServiceCredential, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Write-SttiLog "Start update instance $($Instance.Name)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose Test-ADCredential $ServiceCredential -ErrorAction Stop > $null Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $false -and $Force -eq $false){ throw "The instance $($Instance.Name) is not installed" } $errorAction = $Force ? "Continue" : "Stop" Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Build-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Start-SttiInstance -Instance $Instance -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished update instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during update of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Update-SttiInstance -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage "latest") function Uninstall-SttiInstance { [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Write-SttiLog "Start uninstall instance $($Instance.Name)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $false -and $Force -eq $false){ throw "The instance $($Instance.Name) is not installed" } $errorAction = $Force ? "Continue" : "Stop" Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction Remove-SttiInstanceServices -Instance $Instance -ErrorAction $errorAction Remove-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $false -Roles $null -Version $null -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished uninstall instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during uninstall of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Uninstall-SttiInstance -Instance (Get-SttiInstanceConfig dev) function Get-SttiInstanceInstallStatus{ [OutputType([SttiInstanceInstallStatus])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance")] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [X509Certificate] $Certificate ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $installStatus = [SttiInstanceInstallStatus]::new() if ($null -eq $Instance){ $Instance = Get-SttiInstanceConfig -Name $Name -ErrorAction Stop if ($null -eq $Instance){ return $installStatus } } $instanceInstallStatusFile = Get-SttiInstanceInstallStatusFileName -Name $Instance.Name if (-not (Test-Path $instanceInstallStatusFile)){ Write-SttiLog "The instance installation status $instanceInstallStatusFile does not exists" -Level Verbose return $installStatus } return [SttiInstanceInstallStatus](Get-Content $instanceInstallStatusFile -Raw | ConvertFrom-Json) } #Get-SttiInstanceInstallStatus -Name dev #Get-SttiInstanceInstallStatus -Instance (Get-SttiInstanceConfig -Name dev) function Set-SttiInstanceInstallStatus{ [OutputType([SttiInstanceInstallStatus])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance")] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [bool] $Installed, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $Version, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [SttiInstanceRoles[]] $Roles ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug if ($null -ne $Instance){ $Name = $Instance.Name } $config = Get-SttiInstanceConfig -Name $Name -ErrorAction Stop if ($null -eq $config){ throw "The instance configuration does not exist" } $installStatus = Get-SttiInstanceInstallStatus -Name $Name -ErrorAction Stop if ($PSBoundParameters.ContainsKey("Installed")){ $installStatus.Installed = $Installed } if ($PSBoundParameters.ContainsKey("Version")){ $installStatus.Version = $Version } if ($PSBoundParameters.ContainsKey("Roles")){ $installStatus.Roles = $Roles } $installStatus.Timestamp = Get-Date $instanceInstallStatusFile = Get-SttiInstanceInstallStatusFileName $Name $installStatus | ConvertTo-Json -ErrorAction Stop | Out-File $instanceInstallStatusFile -Encoding UTF8 -ErrorAction Stop $PSCmdlet.WriteObject($installStatus) } #Set-SttiInstanceInstallStatus -Name dev -Installed $true -Roles Web, Worker -Version 1.0.0.0 #Set-SttiInstanceInstallStatus -Name dev -Installed $false -Roles @() -Version $null function Clear-SttiInstanceDirectory{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $instanceDirectory = Get-Item -Path $Instance.Path -ErrorAction Stop $instanceDirectory | Get-ChildItem -Exclude "data", "config.json", "install.json" -ErrorAction Stop | Remove-Item -Recurse -Force -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Clear-SttiInstanceDirectory -Instance (Get-SttiInstanceConfig dev) function Build-SttiInstanceDirectory{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $instanceDirectory = Get-Item -Path $Instance.Path New-Item -Path $instanceDirectory -Name "bin" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null $dataDirectory = Get-Item -Path ([System.IO.Path]::Combine($instanceDirectory, "data")) -ErrorAction SilentlyContinue || New-Item -Path $instanceDirectory -Name "data" -ItemType Directory -ErrorAction Stop New-Item -Path $dataDirectory -Name "logs" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null New-Item -Path $dataDirectory -Name "clients" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null if ($Instance.StoreDbInInstanceDataPath -eq $true){ New-Item -Path $dataDirectory -Name "db" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null } # Grant permissions for service user # Read permissions for instance directory Write-SttiLog "Set instance directory permissions" -Level Verbose $instanceDirectoryAcl = Get-Acl -Path $instanceDirectory -ErrorAction Stop $instanceDirectoryAcl.SetAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($Instance.ServiceUsername, "Read", [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.PropagationFlags]::None, "Allow")) Set-Acl -Path $instanceDirectory -AclObject $instanceDirectoryAcl -ErrorAction Stop # Full control for data directory Write-SttiLog "Set data directory permissions" -Level Verbose $dataDirectoryAcl = Get-Acl -Path $dataDirectory -ErrorAction Stop $dataDirectoryAcl.SetAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($Instance.ServiceUsername, "FullControl", [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.PropagationFlags]::None, "Allow")) Set-Acl -Path $dataDirectory -AclObject $dataDirectoryAcl -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Build-SttiInstanceDirectory -Instance (Get-SttiInstanceConfig dev) function Expand-SttiPackage{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiPackage] $Package ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $tempPath = [System.IO.Path]::GetTempPath() $tempWorkingDir = New-Item -Name (Get-Random) -Path $tempPath -ItemType Directory -ErrorAction Stop Expand-Archive $Package.Path -DestinationPath $tempWorkingDir -ErrorAction Stop $tempContentPath = [System.IO.Path]::Combine($tempWorkingDir.FullName, "Content") $binPath = [System.IO.Path]::Combine($Instance.Path, "bin\") Get-ChildItem -Path $tempContentPath | Copy-Item -Destination $binPath -Recurse -Force -ErrorAction Stop $tempWorkingDir | Remove-Item -Recurse -Force -ErrorAction Continue } catch{ Write-SttiLog $_ -Level Error } } #Expand-SttiPackage -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage 0.0.0.1) function Edit-SttiInstanceConfigFiles{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $sqlConnectionString = Get-SttiDatabaseConnectionString -Instance $Instance try{ # Set database connection Get-ChildItem $Instance.Path -Exclude "data" -Include "appsettings.json" -Recurse -PipelineVariable "currentFile" -ErrorAction Stop | Copy-Item -Destination {"$($_.FullName).bak"} -Force -PassThru -PipelineVariable "backupFile" -ErrorAction Stop # create backup file | ForEach-Object { Get-Content -Path $currentFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } # Read content as string in one block and deserialize json | ForEach-Object { if ($currentFile.FullName -like "*\halvotec.stti.worker\*"){ $port = $Instance.WorkerPort $sslCertificateSubject = $Instance.WorkerSslCertificateSubject } elseif ($currentFile.FullName -like "*\halvotec.stti.web\*"){ $port = $Instance.WebPort $sslCertificateSubject = $Instance.WebSslCertificateSubject } if ($_.Stti -and $_.Stti.Connections -and $_.Stti.Connections.DBConnection) { $_.Stti.Connections.DBConnection = $sqlConnectionString } if ($_.SttiWeb -and $_.SttiWeb.SttiWorkerService ) { $_.SttiWeb.SttiWorkerService = "https://$($Instance.WorkerHostname):$($Instance.WorkerPort)" } if ($_.Kestrel){ if ($_.Kestrel.Endpoints -and $_.Kestrel.Endpoints.HttpsDefault -and $_.Kestrel.Endpoints.HttpsDefault.Url){ $_.Kestrel.Endpoints.HttpsDefault.Url = "https://*:$($port)" } if ($_.Kestrel.Certificates -and $_.Kestrel.Certificates.Default){ $_.Kestrel.Certificates.Default = [PSCustomObject]@{ Subject = $sslCertificateSubject Store = "My" Location = "CurrentUser" AllowInvalid = $true } } } $_ } # Replace connection string | ForEach-Object { ConvertTo-Json -InputObject $_ -Depth 50 -ErrorAction Stop | Out-File $currentFile -Encoding utf8 -ErrorAction Stop } # Serialize to Json and write to file | ForEach-Object { Remove-Item $backupFile -ErrorAction Continue } # Remove backup file } catch{ Write-SttiLog $_ -Level Error } } #Edit-SttiInstanceConfigFiles -Instance (Get-SttiInstanceConfig dev) function Add-SttiInstanceServices{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ Write-SttiLog "Grant LogonAsService right to service user" -Level Verbose Grant-LogonAsServiceRight $ServiceCredential.UserName -ErrorAction Stop $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) if ($Instance.Roles.Contains([SttiInstanceRoles]::Worker)){ if ($null -eq (Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue)){ $workerBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker\Halvotec.Stti.Worker.exe") Write-SttiLog "Create service for worker" -Level Verbose New-Service -Name $workerServiceName -BinaryPathName $workerBinaryPath -StartupType Automatic -Credential $ServiceCredential -Description "Halvotec STTI $($Instance.Name) Worker" -ErrorAction Stop > $null } else{ Write-SttiLog "Service for worker already exists" -Level Verbose } } else { Write-SttiLog "Remove service for worker" Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) if ($Instance.Roles.Contains([SttiInstanceRoles]::Web)){ if ($null -eq (Get-Service -Name $webServiceName -ErrorAction SilentlyContinue)){ $webBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Web\Halvotec.Stti.Web.exe") Write-SttiLog "Create service for web" -Level Verbose New-Service -Name $webServiceName -BinaryPathName $webBinaryPath -StartupType Automatic -Credential $ServiceCredential -Description "Halvotec STTI $($Instance.Name) Web" -ErrorAction Stop > $null } else{ Write-SttiLog "Service for web already exists" -Level Verbose } } else { Write-SttiLog "Remove service for web" Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } } catch{ Write-SttiLog $_ -Level Error } } #Install-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) -ServiceCredential (Get-Credential) function Remove-SttiInstanceServices{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) Write-SttiLog "Remove service for worker" -Level Verbose Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } try{ $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) Write-SttiLog "Remove service for web" -Level Verbose Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Uninstall-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) function Get-SttiServiceName{ [OutputType([string])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiInstanceRoles] $Role ) return "STTI.$($Instance.Name).$Role" } #endregion Installation #region Certificate management function New-SttiUserCertificate{ [OutputType([X509Certificate])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [string] $Subject, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(10) ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug return New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation Cert:CurrentUser\My -KeyExportPolicy Exportable -Type Custom -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -NotAfter $NotAfter -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2") -ErrorAction Stop } function New-SttiDatabaseEncryptionKey{ [OutputType([X509Certificate])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [string] $Subject, [Parameter(Mandatory)] [string] $User, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(10) ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $location = "Cert:CurrentUser\My" return New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation $location -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage KeyEncipherment -KeySpec KeyExchange -KeyLength 2048 -NotAfter $NotAfter -ErrorAction Stop } function Get-SttiDatabaseEncryptionKey { [OutputType([X509Certificate])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Thumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -eq $key){ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Thumbprint } if ($null -eq $key){ Write-SttiLog "No database encryption key was found with thumbprint $Thumbprint" -Level Error } return $key } function Get-SttiDatabaseEncryptionKeyLocation { [OutputType([string])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Thumbprint ) $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -ne $key){ Write-SttiLog "Found database encryption key in Cert:CurrentUser\My" -Level Verbose return "CurrentUser" } else{ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -ne $key){ Write-SttiLog "Found database encryption key in Cert:LocalMachine\My" -Level Verbose return "LocalMachine" } else { Write-SttiLog "No database encryption key was found with thumbprint $Thumbprint" -Level Error return $null } } } #endregion Certificate management #region Instance Management function Get-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [string] $Name ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $instanceNames = $PSBoundParameters.ContainsKey("Name") ? @($Name) : (Get-ChildItem -Path "$([System.IO.Path]::Combine((Get-SttiBasePath), "instances\*"))" | Select-Object -ExpandProperty Name) foreach ($name in $instanceNames) { $instanceConfigFile = Get-SttiInstanceConfigFileName -Name $Name if (-not (Test-Path $instanceConfigFile)){ Write-SttiLog "The instance configuration $instanceConfigFile does not exists" -Level Verbose continue } $config = [SttiInstanceConfig](Get-Content $instanceConfigFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) $config.Path = Get-SttiInstancePath -Name $Name Write-Output $config } } #Get-SttiInstanceConfig -Name "dev" function New-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter(Mandatory=$true)] [string] $SqlServer, [Parameter()] [string] $Database = "STTI_$($Name.ToUpperInvariant())", [Parameter(Mandatory=$true)] [SttiInstanceRoles[]] $Roles, [Parameter(Mandatory=$true)] [string] $ServiceUsername, [Parameter()] [switch] $StoreDbInInstanceDataPath = $false, [Parameter(Mandatory)] [string] $WorkerSslCertificateSubject, [Parameter(Mandatory)] [uint] $WorkerPort, [Parameter(Mandatory)] [string] $WebSslCertificateSubject, [Parameter(Mandatory)] [uint] $WebPort, [Parameter()] [string] $WorkerHostname = "localhost", [Parameter(Mandatory)] [string] $EncryptionCertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $Name = $Name.ToUpperInvariant() $instancePath = Get-SttiInstancePath $Name if (-not (Test-Path $instancePath)){ New-Item $instancePath -ItemType Directory -ErrorAction Stop | Out-Null } $instanceConfigFile = Get-SttiInstanceConfigFileName $Name if ((Test-Path $instanceConfigFile)){ throw "The instance configuration $instanceConfigFile already exists" } $config = [SttiInstanceConfig]::new() $config.Name = $Name $config.SqlServer = $SqlServer $config.Database = $Database $config.Roles = $Roles $config.Path = $instancePath $config.ServiceUsername = $ServiceUsername $config.StoreDbInInstanceDataPath = $StoreDbInInstanceDataPath $config.WorkerSslCertificateSubject = $WorkerSslCertificateSubject $config.WorkerPort = $WorkerPort $config.WebSslCertificateSubject = $WebSslCertificateSubject $config.WebPort = $WebPort $config.WorkerHostname = $WorkerHostname $config.EncryptionCertificateThumbprint = $EncryptionCertificateThumbprint $config.EncryptionMasterKeyName = "STTI_$EncryptionCertificateThumbprint" $config | ConvertTo-Json -ErrorAction Stop | Out-File $instanceConfigFile -Encoding utf8 -ErrorAction Stop return $config } #New-SttiInstanceConfig -Name "DEV" -SqlServer localdb -Database SttiDev -Roles Worker, Web function Set-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance", ValueFromPipeline)] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $SqlServer, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $Database, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [SttiInstanceRoles[]] $Roles, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $ServiceUsername, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [switch] $StoreDbInInstanceDataPath = $false, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WorkerSslCertificateSubject, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [uint] $WorkerPort, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WebSslCertificateSubject, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [uint] $WebPort, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WorkerHostname, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $EncryptionCertificateThumbprint ) Process{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug if ($null -ne $Instance){ $Name = $Instance.Name } $config = Get-SttiInstanceConfig ($Name) -ErrorAction Stop if ($null -eq $config){ throw "The instance configuration does not exist" } if ($PSBoundParameters.ContainsKey("SqlServer")){ $config.SqlServer = $SqlServer } if ($PSBoundParameters.ContainsKey("Database")){ $config.Database = $Database } if ($PSBoundParameters.ContainsKey("Roles")){ $config.Roles = $Roles } if ($PSBoundParameters.ContainsKey("ServiceUsername")){ $config.ServiceUsername = $ServiceUsername } if ($PSBoundParameters.ContainsKey("StoreDbInInstanceDataPath")){ $config.StoreDbInInstanceDataPath = $StoreDbInInstanceDataPath } if ($PSBoundParameters.ContainsKey("WorkerSslCertificateSubject")){ $config.WorkerSslCertificateSubject = $WorkerSslCertificateSubject } if ($PSBoundParameters.ContainsKey("WorkerPort")){ $config.WorkerPort = $WorkerPort } if ($PSBoundParameters.ContainsKey("WebSslCertificateSubject")){ $config.WebSslCertificateSubject = $WebSslCertificateSubject } if ($PSBoundParameters.ContainsKey("WebPort")){ $config.WebPort = $WebPort } if ($PSBoundParameters.ContainsKey("WorkerHostname")){ $config.WorkerHostname = $WorkerHostname } if ($PSBoundParameters.ContainsKey("EncryptionCertificateThumbprint")){ if ($null -ne $config.EncryptionCertificateThumbprint -and $EncryptionCertificateThumbprint -ne $config.EncryptionCertificateThumbprint){ Write-SttiLog "The EncryptionCertificateThumbprint has already been set and cannot be changed. Use Rotate-SttiEncryptionMasterKey" -Level Error } $config.EncryptionCertificateThumbprint = $EncryptionCertificateThumbprint $config.EncryptionMasterKeyName = "STTI_$EncryptionCertificateThumbprint" } $instanceConfigFile = Get-SttiInstanceConfigFileName $Name $config | ConvertTo-Json -ErrorAction Stop | Out-File $instanceConfigFile -Encoding utf8 -ErrorAction Stop $PSCmdlet.WriteObject($config) } } #Set-SttiInstanceConfig -InstanceName dev -Database "Test" -Roles Web, Worker #Get-SttiInstanceConfig dev | Set-SttiInstanceConfig -Database "Test" -Roles Web function Test-SttiInstanceBinding{ [OutputType([bool])] [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug foreach ($otherInstance in (Get-SttiInstanceConfig | Where-Object Name -ne $Instance.Name)) { if ($Instance.WorkerPort -eq $otherInstance.WorkerPort){ Write-SttiLog "$($otherInstance.Name) is also configured for port $($Instance.WorkerPort)" -Level ((Get-SttiInstanceInstallStatus -Instance $otherInstance).Installed -eq $true ? "Error" : "Warn") return $false } } return $true } function Get-SttiInstancePath{ Param( [Parameter(Mandatory, Position, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance")] [SttiInstanceConfig] $Instance ) if ($null -ne $Instance){ $Name = $Instance.Name } return [System.IO.Path]::Combine((Get-SttiBasePath), "Instances\", $Name) } function Get-SttiInstanceConfigFileName{ Param( [Parameter(Mandatory)] [string] $Name ) return [System.IO.Path]::Combine((Get-SttiInstancePath $Name), "config.json") } function Get-SttiInstanceInstallStatusFilename{ Param( [Parameter(Mandatory)] [string] $Name ) return [System.IO.Path]::Combine((Get-SttiInstancePath $Name), "install.json") } function Get-SttiInstallLogFilename{ Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [DateTime] $Timestamp ) if ($Timestamp -and $null -ne $Timestamp){ return [System.IO.Path]::Combine((Get-SttiBasePath), "install-$($Instance.Name)-$(("{0:s}" -f $Timestamp) -replace ":", "-").log") } else{ return [System.IO.Path]::Combine((Get-SttiBasePath), "install-$($Instance.Name).log") } } function Get-SttiBasePath{ [OutputType([string])] Param() $basePath = $env:SttiBasePath ?? "C:\STTI\" if (-not (Test-Path $basePath)){ Write-SttiLog "STTI base path $basePath does not exist" -Level Warn } return $basePath } function Set-SttiBasePath{ Param( [Parameter(Mandatory)] [string] $BasePath ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug [System.Environment]::SetEnvironmentVariable("SttiBasePath", $BasePath, [System.EnvironmentVariableTarget]::Machine) $env:SttiBasePath = $BasePath if (-not (Test-Path $BasePath)){ Write-SttiLog "STTI base path $BasePath does not exist. It will be created." -Level Warn New-Item $BasePath -ItemType Directory -ErrorAction Stop | Out-Null } $PackagePath = [System.IO.Path]::Combine($BasePath, "Packages") if (-not (Test-Path $PackagePath)){ New-Item $PackagePath -ItemType Directory -ErrorAction Stop | Out-Null } } function Get-SttiInstanceStatus{ [OutputType([SttiInstanceStatus])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance", ValueFromPipeline)] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [X509Certificate] $Certificate ) Process { Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $status = [SttiInstanceStatus]::new() if ($null -eq $Instance){ $Instance = Get-SttiInstanceConfig -Name $Name -ErrorAction Stop if ($null -eq $Instance){ Write-SttiLog "No instance config found" -Level Verbose return $status } } # Check if instance exists $status.InstanceExists = Test-Path (Get-SttiInstancePath -Name $Instance.Name) # Check install status $status.InstallStatus = Get-SttiInstanceInstallStatus -Instance $Instance # Check if database exists try { $sqlDb = Get-SqlDatabase -Name $Instance.Database -ServerInstance $Instance.SqlServer -ErrorAction Ignore } catch { Write-SttiLog "Could not access database $($Instance.Database) on $($Instance.SqlServer): $($_.Exception)." -Level Warn } $status.DatabaseExists = ($null -ne $sqlDb) # Check if worker service is running $workerService = Get-Service -Name (Get-SttiServiceName -Instance $Instance -Role Worker) -ErrorAction SilentlyContinue $status.WorkerRoleRunning = ($null -ne $workerService -and $workerService.Status -eq "Running") # Check if webui service is running $webService = Get-Service -Name (Get-SttiServiceName -Instance $Instance -Role Web) -ErrorAction SilentlyContinue $status.WebRoleRunning = ($null -ne $webService -and $webService.Status -eq "Running") $PSCmdlet.WriteObject($status) } } #Get-SttiInstanceStatus -Name dev function Start-SttiInstance { [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance", ValueFromPipeline)] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [X509Certificate] $Certificate ) Process { Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Where-Object Status -eq Stopped | Start-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } try{ $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Where-Object Status -eq Stopped | Start-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } } function Stop-SttiInstance { [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance", ValueFromPipeline)] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [X509Certificate] $Certificate ) Process { Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) $workerService = Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Where-Object Status -eq Running if ($null -ne $workerService){ $processId = Get-CimInstance Win32_Service -Verbose:$false | Where-Object { $_.Name -eq $workerService.Name } | Select-Object -ExpandProperty ProcessId Stop-Service $workerService Get-Process -Id $processId -ErrorAction SilentlyContinue | Wait-Process -TimeoutSec 120 -ErrorAction Stop } Start-Sleep -Seconds 0.5 } catch{ Write-SttiLog $_ -Level Error } try{ $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) $webService = Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Where-Object Status -eq Running if ($null -ne $webService){ $processId = Get-CimInstance Win32_Service -Verbose:$false | Where-Object { $_.Name -eq $webService.Name } | Select-Object -ExpandProperty ProcessId Stop-Service $webService Get-Process -Id $processId -ErrorAction SilentlyContinue | Wait-Process -TimeoutSec 120 -ErrorAction Stop } Start-Sleep -Seconds 0.5 } catch{ Write-SttiLog $_ -Level Error } } } #endregion Instance Management #region Database Management function New-SttiDatabase{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $dbPath = [System.IO.Path]::Combine((Get-SttiInstancePath -Name $Instance.Name), "data\db\") try{ $command = "CREATE DATABASE [$($Instance.Database)];" $command = " DECLARE @DefaultDataPath varchar(max) SET @DefaultDataPath = (SELECT CONVERT(varchar(max), SERVERPROPERTY('INSTANCEDEFAULTDATAPATH'))) DECLARE @DefaultLogPath varchar(max) SET @DefaultLogPath = (SELECT CONVERT(varchar(max), SERVERPROPERTY('INSTANCEDEFAULTLOGPATH'))) DECLARE @DataPath varchar(max) SET @DataPath = $($Instance.StoreDbInInstanceDataPath ? "'$dbPath'" : "@DefaultDataPath") DECLARE @LogPath varchar(max) SET @LogPath = $($Instance.StoreDbInInstanceDataPath ? "'$dbPath'" : "@DefaultLogPath") EXECUTE(' CREATE DATABASE [$($Instance.Database)] ON PRIMARY ( NAME = N''STTI'', FILENAME = N''' + @DataPath + 'STTI_DEV.mdf'' , SIZE = 262144KB , FILEGROWTH = 65536KB ) LOG ON ( NAME = N''STTI_log'', FILENAME = N''' + @LogPath + 'STTI_DEV_log.ldf'' , SIZE = 65536KB , FILEGROWTH = 65536KB ) COLLATE Latin1_General_100_CI_AS'); GO ALTER DATABASE [$($Instance.Database)] SET COMPATIBILITY_LEVEL = 140 ALTER DATABASE [$($Instance.Database)] SET ANSI_NULL_DEFAULT OFF ALTER DATABASE [$($Instance.Database)] SET ANSI_NULLS OFF ALTER DATABASE [$($Instance.Database)] SET ANSI_PADDING OFF ALTER DATABASE [$($Instance.Database)] SET ANSI_WARNINGS OFF ALTER DATABASE [$($Instance.Database)] SET ARITHABORT OFF ALTER DATABASE [$($Instance.Database)] SET AUTO_CLOSE OFF ALTER DATABASE [$($Instance.Database)] SET AUTO_SHRINK OFF ALTER DATABASE [$($Instance.Database)] SET AUTO_CREATE_STATISTICS ON(INCREMENTAL = OFF) ALTER DATABASE [$($Instance.Database)] SET AUTO_UPDATE_STATISTICS ON ALTER DATABASE [$($Instance.Database)] SET CURSOR_CLOSE_ON_COMMIT OFF ALTER DATABASE [$($Instance.Database)] SET CONCAT_NULL_YIELDS_NULL OFF ALTER DATABASE [$($Instance.Database)] SET NUMERIC_ROUNDABORT OFF ALTER DATABASE [$($Instance.Database)] SET QUOTED_IDENTIFIER OFF ALTER DATABASE [$($Instance.Database)] SET RECURSIVE_TRIGGERS OFF ALTER DATABASE [$($Instance.Database)] SET DISABLE_BROKER ALTER DATABASE [$($Instance.Database)] SET ALLOW_SNAPSHOT_ISOLATION ON ALTER DATABASE [$($Instance.Database)] SET READ_COMMITTED_SNAPSHOT ON ALTER DATABASE [$($Instance.Database)] SET RECOVERY FULL ALTER DATABASE [$($Instance.Database)] SET PAGE_VERIFY CHECKSUM GO IF NOT EXISTS (SELECT name FROM master.sys.server_principals WHERE name = '$($Instance.ServiceUsername)') BEGIN CREATE LOGIN [$($Instance.ServiceUsername)] FROM WINDOWS WITH DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english] END GO USE [$($Instance.Database)] GO IF NOT EXISTS (SELECT [login].name, [dbuser].name FROM sys.syslogins AS [login] INNER JOIN sys.database_principals [dbuser] ON [login].sid = [dbuser].sid WHERE [login].name = '$($Instance.ServiceUsername)') BEGIN CREATE USER [$($Instance.ServiceUsername)] FOR LOGIN [$($Instance.ServiceUsername)] ALTER ROLE [db_owner] ADD MEMBER [$($Instance.ServiceUsername)] END GO" Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database "master" -Query $command -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Get-SttiInstanceConfig dev | New-SttiDatabase -EncryptionMasterKey $null function Add-SttiDatabaseEncryption{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [PSCredential] $ServiceCredential = $null ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $scriptBlock = { param( [string] $ScriptRoot, $Instance ) $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Instance.EncryptionCertificateThumbprint if ($null -ne $key){ Write-Verbose "Found database encryption key in Cert:CurrentUser\My" $certificateStoreLocation = "CurrentUser" } else{ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Instance.EncryptionCertificateThumbprint if ($null -ne $key){ Write-Verbose "Found database encryption key in Cert:LocalMachine\My" $certificateStoreLocation = "LocalMachine" } else { throw "No database encryption key was found with thumbprint $($Instance.EncryptionCertificateThumbprint)" } } $db = Get-SqlDatabase -Name $Instance.Database -ServerInstance $Instance.SqlServer -ErrorAction Stop if ($null -eq (Get-SqlColumnMasterKey -Name $Instance.EncryptionMasterKeyName -InputObject $db -ErrorAction SilentlyContinue)){ $masterKeySettings = New-SqlCertificateStoreColumnMasterKeySettings -Thumbprint $Instance.EncryptionCertificateThumbprint -CertificateStoreLocation $certificateStoreLocation -ErrorAction Stop New-SqlColumnMasterKey -Name $Instance.EncryptionMasterKeyName -InputObject $db -ColumnMasterKeySettings $masterKeySettings -ErrorAction Stop > $null } if ($null -eq (Get-SqlColumnEncryptionKey -Name "CEK" -InputObject $db -ErrorAction SilentlyContinue)){ New-SqlColumnEncryptionKey -Name "CEK" -InputObject $db -ColumnMasterKeyName $Instance.EncryptionMasterKeyName -ErrorAction Stop > $null } } $argumentList = @($PSScriptRoot, $Instance) # Workaround, since start-job with credentials cannot be called in remoting session if ($null -eq $ServiceCredential -or ($env:useCurrentUser -and $env:useCurrentUser -eq $true)){ Start-Job -ScriptBlock $scriptBlock -ArgumentList $argumentList -PSVersion 5.1 | Receive-Job -Wait -ErrorAction Stop } else{ Start-Job -Credential $ServiceCredential -ScriptBlock $scriptBlock -ArgumentList $argumentList -PSVersion 5.1 | Receive-Job -Wait -ErrorAction Stop } } catch{ Write-SttiLog $_ -Level Error } } function Update-SttiDatabase{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ if ($Instance.Roles.Contains([SttiInstanceRoles]::Worker)){ try{ $workerPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker") $workerBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker\Halvotec.Stti.Worker.exe") $outPath = [System.IO.Path]::Combine($Instance.Path, "data\logs\migratedb-out.txt") $errorPath = [System.IO.Path]::Combine($Instance.Path, "data\logs\migratedb-err.txt") #Workaround for running in remoting session where no different credentials can be used if ($env:useCurrentUser -and $env:useCurrentUser -eq $true){ $process = Start-Process -FilePath $workerBinaryPath -ArgumentList "migratedb" -WorkingDirectory $workerPath -Wait -PassThru -RedirectStandardOutput $outPath -RedirectStandardError $errorPath -ErrorAction Stop } else{ $process = Start-Process -FilePath $workerBinaryPath -ArgumentList "migratedb" -WorkingDirectory $workerPath -Credential $ServiceCredential -Wait -PassThru -RedirectStandardOutput $outPath -RedirectStandardError $errorPath -ErrorAction Stop } $exitCode = $process.ExitCode Get-Content $outPath -ErrorAction SilentlyContinue | Write-SttiLog -Level Verbose Get-Content $errorPath -ErrorAction SilentlyContinue | Write-SttiLog -Level Warn if ($exitCode -ne 0){ Write-SttiLog "Halvotec.Stti.Worker.exe exited with code $exitCode" -Level Error } } finally{ Remove-Item $outPath -ErrorAction SilentlyContinue Remove-Item $errorPath -ErrorAction SilentlyContinue } } } catch{ Write-SttiLog $_ -Level Error } } function Test-SttiDatabaseEncryption{ [OutputType([bool])] [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance # [Parameter(Mandatory)] # [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ return $true } catch{ Write-SttiLog $_ -Level Error return $false } } function Get-SttiDatabaseConnectionString{ [OutputType([string])] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) return "Server=$($Instance.SqlServer);Database=$($Instance.Database);Integrated Security=true;Column Encryption Setting=enabled;ApplicationIntent=ReadWrite;Language=English" } #endregion Database Management #region Package Management $deploymentFilePattern = "^SttiApp.(?<version>\d+\.\d+\.\d+\.\d+)\.nupkg$" function Get-SttiPackage { [OutputType([SttiPackage])] [CmdletBinding()] Param( [ValidatePattern("^\d+\.\d+\.\d+\.\d+$|^latest$")] [string] $VersionNumber = "latest" ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $deploymentFiles = Get-ChildItem "$([System.IO.Path]::Combine((Get-SttiBasePath), "Packages", "*.nupkg"))" $packages = $deploymentFiles | Where-Object -Property Name -Match $deploymentFilePattern | ForEach-Object {([SttiPackage]::new([System.Version]::Parse($Matches.version), $_.FullName, $true))} if ($VersionNumber -eq "latest"){ return $packages | Sort-Object -Property Version -Descending | Select-Object -First 1 } else { $version = [System.Version]::Parse($VersionNumber) return $packages | Where-Object -Property Version -eq $version } } # Get-SttiPackage -VersionNumber "latest" function Get-SttiUserHomePath{ [OutputType([string])] Param() $sttiUserHomePath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "Stti") if (!(Test-Path $sttiUserHomePath)){ New-Item $sttiUserHomePath -ItemType Directory -Force > $null } return $sttiUserHomePath } function Set-SttiCustomerCredential{ [CmdletBinding()] Param( [PSCredential] $CustomerCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $customerCredentialFile = [System.IO.Path]::Combine((Get-SttiUserHomePath), "customercredentials.json") $customerCredentialData = @{ UserName = $CustomerCredential.UserName Password = ($CustomerCredential.Password | ConvertFrom-SecureString) } $customerCredentialData | ConvertTo-Json | Out-File $customerCredentialFile -Encoding utf8 } # $cred = Get-Credential # Set-SttiCustomerCredential $cred function Get-SttiCustomerCredential{ [OutputType([PSCredential])] [CmdletBinding()] Param( ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $customerCredentialFile = [System.IO.Path]::Combine((Get-SttiUserHomePath), "customercredentials.json") if (!(Test-Path $customerCredentialFile)){ Write-SttiLog "Customer credential file does not exist at $customerCredentialFile" -Level Error } $customerCredentialData = Get-Content $customerCredentialFile -Raw -Encoding utf8 | ConvertFrom-Json return [System.Management.Automation.PsCredential]::new($customerCredentialData.UserName, ($customerCredentialData.Password | ConvertTo-SecureString)) } #Get-SttiCustomerCredential function Find-SttiPackage{ [OutputType([SttiPackage[]])] [CmdletBinding()] Param( [ValidatePattern("^\d+\.\d+\.\d+\.\d+$|^latest$")] [string] $VersionNumber, [string] $PackageSource = "https://appdemo8.halvotec.de/v3/index.json" ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $customerCredential = Get-SttiCustomerCredential -ErrorAction Stop Write-SttiLog "Getting feed resources" -Level Verbose $resources = (Invoke-RestMethod -Uri $PackageSource -Credential $customerCredential -ErrorAction Stop).resources $searchEndpoint = ($resources | Where-Object "@type" -like "SearchQueryService")."@id" if ($null -eq $searchEndpoint){ $searchEndpoint = ($resources | Where-Object "@type" -like "SearchQueryService/*" | Select-Object -First 1)."@id" } if ($null -eq $searchEndpoint){ Write-SttiLog "Could not find a v3 search endpoint in the feed" -Level Error } $packageContentEndpoint = ($resources | Where-Object "@type" -like "PackageBaseAddress/3.0.0")."@id" if ($null -eq $packageContentEndpoint){ Write-SttiLog "Could not find a v3 content endpoint in the feed" -Level Error } Write-SttiLog "Searching package" -Level Verbose $packageData = (Invoke-RestMethod -Uri "$($searchEndpoint)?q=SttiApp" -Credential $customerCredential -ErrorAction Stop).data $packages = $packageData.versions | ForEach-Object {([SttiPackage]::new([System.Version]::Parse($_.version), "$packageContentEndpoint/$($packageData.id.ToLower())/$($_.version.ToLower())/$($packageData.id.ToLower()).$($_.version.ToLower()).nupkg", $false ))} if (!$PSBoundParameters.ContainsKey("VersionNumber")){ return $packages } elseif ($VersionNumber -like "latest") { return $packages | Where-Object Version -eq ([System.Version]::Parse($packageData.version)) } else{ return $packages | Where-Object Version -eq ([System.Version]::Parse($VersionNumber)) } } #Find-SttiPackage latest function Import-SttiPackage{ [CmdletBinding()] Param( [Parameter(Mandatory, ValueFromPipeline)] [SttiPackage] $Package ) Process{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug if ($Package.IsLocal){ Write-SttiLog "Package must not be local" } $customerCredential = Get-SttiCustomerCredential -ErrorAction Stop $fileName = [System.Uri]::new($Package.Path).Segments | Select-Object -Last 1 $packagePath = [System.IO.Path]::Combine((Get-SttiBasePath), "Packages", $fileName) Write-SttiLog "Downloading package" -Level Verbose Invoke-RestMethod -Uri $Package.Path -Credential $customerCredential -OutFile $packagePath -ErrorAction Stop } } #Find-SttiPackage "latest" -Verbose | Import-SttiPackage -Verbose #endregion Package Management #region Windows Service Rights management Add-Type @' using System; using System.Runtime.InteropServices; public enum LSA_AccessPolicy : long { // Other values omitted for clarity POLICY_ALL_ACCESS = 0x00001FFFL } [StructLayout(LayoutKind.Sequential)] public struct LSA_UNICODE_STRING { public UInt16 Length; public UInt16 MaximumLength; public IntPtr Buffer; } [StructLayout(LayoutKind.Sequential)] public struct LSA_OBJECT_ATTRIBUTES { public UInt32 Length; public IntPtr RootDirectory; public LSA_UNICODE_STRING ObjectName; public UInt32 Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; } public static partial class AdvAPI32 { [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaOpenPolicy( ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out IntPtr PolicyHandle); [DllImport("advapi32.dll")] public static extern Int32 LsaClose(IntPtr ObjectHandle); [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] public static extern uint LsaAddAccountRights( IntPtr PolicyHandle, byte[] AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights); } '@ function Get-LsaPolicyHandle() { $system = New-Object LSA_UNICODE_STRING $attrib = New-Object LSA_OBJECT_ATTRIBUTES -Property @{ Length = 0 RootDirectory = [System.IntPtr]::Zero Attributes = 0 SecurityDescriptor = [System.IntPtr]::Zero SecurityQualityOfService = [System.IntPtr]::Zero }; $handle = [System.IntPtr]::Zero $hr = [AdvAPI32]::LsaOpenPolicy([ref] $system, [ref]$attrib, [LSA_AccessPolicy]::POLICY_ALL_ACCESS, [ref]$handle) if (($hr -ne 0) -or ($handle -eq [System.IntPtr]::Zero)) { Write-Error "Failed to open Local Security Authority policy. Error code: $hr" } else { $handle } } function New-Right([string]$rightName){ $unicodeCharSize = 2 New-Object LSA_UNICODE_STRING -Property @{ Buffer = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($rightName) Length = $rightName.Length * $unicodeCharSize MaximumLength = ($rightName.Length + 1) * $unicodeCharSize } } function Grant-Rights([System.IntPtr]$policyHandle, [byte[]]$sid, [LSA_UNICODE_STRING[]]$rights) { $result = [AdvAPI32]::LsaAddAccountRights($policyHandle, $sid, $rights, 1) if ($result -ne 0) { Write-Error "Failed to grant right. Error code $result" } } function Grant-LogonAsServiceRight { [CmdletBinding()] Param( [string] $username ) $sid = ((New-Object System.Security.Principal.NTAccount($username)).Translate([System.Security.Principal.SecurityIdentifier])) $sidBytes = [byte[]]::new($sid.BinaryLength) $sid.GetBinaryForm($sidBytes, 0) $logonAsServiceRightName = "SeServiceLogonRight" try { $policy = Get-LsaPolicyHandle $right = New-Right $logonAsServiceRightName Grant-Rights $policy $sidBytes @($right) } finally { if($null -ne $policy){ [AdvAPI32]::LsaClose($policy) | Out-Null } } } #endregion Windows Service Rights management #region Logging function Start-SttiInstallLogFile{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [switch] $Append = $false, [LogLevels] $Level = [LogLevels]::Verbose ) $Script:logFile = Get-SttiInstallLogFilename -Instance $Instance -Timestamp (Get-Date) $Script:archiveLogFile = Get-SttiInstallLogFilename -Instance $Instance $Script:logLevel = $Level Out-File $logFile -Append:$Append Write-SttiLog "Start installation logfile $($Script:logFile)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug } function Stop-SttiInstallLogFile{ [CmdletBinding()] Param( ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug Write-SttiLog "Stop installation logfile $($Script:logFile)" -Level Info Get-Content -Path $Script:logFile -Raw | Out-File $Script:archiveLogFile -Append -ErrorAction Continue $Script:logFile = $null $Script:archiveLogFile = $null $Script:logLevel = $null } function Write-SttiInstallLogHeader{ [CmdletBinding()] Param( ) Write-SttiLog "ComputerInfo:" -Level Verbose Get-ComputerInfo | Write-SttiLog -Level Verbose Write-SttiLog "HostInfo:" -Level Verbose Get-Host | Write-SttiLog -Level Verbose Write-SttiLog "----------------------------------------------------------------" -Level Info -Extraline } function Write-SttiInstallLogFooter{ [CmdletBinding()] Param( ) Write-SttiLog "----------------------------------------------------------------" -Level Info } function Write-SttiLog{ [CmdletBinding()] Param( [switch] $Extraline, [LogLevels] $Level = [LogLevels]::Info, [Parameter(ValueFromPipeline, Position=0)] [object[]] $InputObject ) Process{ if ($null -ne $_){ $i = $_ } elseif ($null -ne $InputObject) { $i = $InputObject } else { throw "InputObject has no value" } # Write to log if existing if ($Script:logFile -and $null -ne $Script:logFile ` -and $Level -ge ($Script:logLevel ?? [LogLevels]::Verbose)) { $i | ForEach-Object{ if ($_ -is [string]) { "$(Get-Date) $($Level.ToString().ToUpperInvariant()) $_" } else { "$(Get-Date) $($Level.ToString().ToUpperInvariant()) $($_ | Format-List | Out-String)" }} | Out-File -FilePath $Script:logFile -Append } switch ($Level){ "Error" { if ($i -is [System.Management.Automation.ErrorRecord]){ Write-Error -ErrorRecord $i } elseif ($i -is [System.Exception]){ Write-Error -Exception $i } else{ Write-Error ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } } "Warn" { Write-Warning ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } "Info" { Write-Output $i } "Verbose" { Write-Verbose ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } "Debug" { Write-Debug ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } } } End{ if ($Extraline -eq $true){ if ($Script:logFile -and $null -ne $Script:logFile) { "" | Out-File -FilePath $Script:logFile -Append } switch ($Level){ "Error" { #Write-Error "" } "Warn" { Write-Warning "" } "Info" { Write-Output "" } "Verbose" { Write-Verbose "" } "Debug" { Write-Debug "" } } } } } #"Hallo" | Write-SttiLog -FilePath "Test.txt" #endregion Logging #region Helper function Test-ADCredential { [OutputType([bool])] [CmdletBinding()] Param ( [Parameter(Mandatory)] [pscredential] $credential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $username = $credential.UserName $password = $credential.GetNetworkCredential().Password if (!($UserName) -or !($Password)) { Write-SttiLog "Please specify both user name and password" -Level Error return $false } else { Add-Type -AssemblyName System.DirectoryServices.AccountManagement $isValid = $false try{ $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('domain') $isValid = $DS.ValidateCredentials($UserName, $Password) return $isValid } catch{ Write-SttiLog -Exception $_ -Message "Could not validate the credentials. An error occurred." -Level Error } if (-not ($isValid)){ Write-SttiLog "Could not validate the credentials. Username or password seems to be invalid." -Level Error } } } #endregion Helper #region Deployment function Deploy-Stti { [CmdletBinding()] [OutputType([SttiInstanceStatus])] Param( [string] $InstanceName, [PSCredential] $ServiceCredential, [string] $ServiceUserName, [string] $Version="latest", [string] $SqlServer, [string] $Database="STTI_$($InstanceName.ToUpperInvariant())", [string] $AdminKeyThumbprint, [string] $EncryptionCertificateThumbprint, [bool] $StoreDbInInstanceDataPath = $true, [string] $WorkerSslCertificateSubject, [uint] $WorkerPort, [string] $WebSslCertificateSubject, [uint] $WebPort, [string] $WorkerHostname, [switch] $Uninstall=$false, [switch] $Force=$false ) if ($ServiceCredential){ $ServiceUserName = $ServiceCredential.UserName } # Create instance stub $instance = Get-SttiInstanceConfig -Name $InstanceName if ($null -eq $instance) { $instance = New-SttiInstanceConfig -Name $InstanceName -SqlServer $SqlServer -Database $Database -Roles Worker, Web -ServiceUserName $ServiceUserName -StoreDbInInstanceDataPath:$StoreDbInInstanceDataPath -WorkerSslCertificateSubject $WorkerSslCertificateSubject -WorkerPort $WorkerPort -WebPort $WebPort -WebSslCertificateSubject $WebSslCertificateSubject -WorkerHostname $WorkerHostname -EncryptionCertificateThumbprint $EncryptionCertificateThumbprint } else { $instance = Set-SttiInstanceConfig -Instance $instance -SqlServer $SqlServer -Database $Database -Roles Worker, Web -ServiceUserName $ServiceUserName -StoreDbInInstanceDataPath:$StoreDbInInstanceDataPath -WorkerSslCertificateSubject $WorkerSslCertificateSubject -WorkerPort $WorkerPort -WebPort $WebPort -WebSslCertificateSubject $WebSslCertificateSubject -WorkerHostname $WorkerHostname -EncryptionCertificateThumbprint $EncryptionCertificateThumbprint } $adminCert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object Thumbprint -eq $AdminKeyThumbprint $status = Get-SttiInstanceStatus -Instance $instance -Certificate $adminCert if ($Uninstall -eq $false){ $package = Get-SttiPackage $Version if ($status.InstallStatus.Installed -eq $false){ # Get credentials for service user $svcCred = $ServiceCredential ?? (Get-Credential -UserName $instance.ServiceUserName -Message "Please enter the credentials for the service user") Install-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force # -Certificate $adminCert } elseif ($status.InstallStatus.Version -ne $package.Version -or $Force){ $svcCred = $ServiceCredential ?? (Get-Credential -UserName $instance.ServiceUserName -Message "Please enter the credentials for the service user") Update-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force # -Certificate $adminCert } else{ #Nothing to do Write-Information "Version $($package.Version) was already installed" } } else { if ($status.InstallStatus.Installed -eq $true -or $Force){ Uninstall-SttiInstance -Instance $instance -Force:$Force # -Certificate $adminCert } else { #Nothing to do Write-Information "The instance was already uninstalled" } } Get-SttiInstanceStatus -Instance $instance -Certificate $adminCert } #endregion Deployment #region Firewall rules function Add-SttiFirewallRules{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ if ($Instance.Roles.Contains([SttiInstanceRoles]::Worker)){ if ($null -eq (Get-NetFirewallRule -Name "Stti-Worker-$($Instance.Name)" -ErrorAction SilentlyContinue)){ Write-SttiLog "Add firewall rule for worker" -Level Verbose $workerBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker\Halvotec.Stti.Worker.exe") New-NetFirewallRule –Name "Stti-Worker-$($Instance.Name)" -DisplayName "Stti Worker ($($Instance.Name))" -Program $workerBinaryPath -Direction Inbound -Action Allow -Protocol TCP -Profile Domain -Group "Stti" -ErrorAction Stop > $null } else { Write-SttiLog "Firewall rule for worker already exists" -Level Verbose } } } catch{ Write-SttiLog $_ -Level Error } } function Remove-SttiFirewallRules{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ Write-SttiLog "Remove firewall rule for worker" -Level Verbose Remove-NetFirewallRule -Name "Stti-Worker-$($Instance.Name)" -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #endregion Firewall rules |