Stti.psm1
#Requires -Modules SqlServer $initialUserCertificateThumbprint = "F0425D76A28A1180C5AB667B847EE9BF161B7B81" $initialUserCertificatePassword = "0#dJB87I8UsL" | ConvertTo-SecureString -AsPlainText -Force $initialUserId = "dbfd9d8b-a443-49d2-96b0-c4a15d5dba65" $defaultPackageSource = "https://raqrepository.halvotec.de/packages/stti/v3/index.json" $defaultBasePath = "C:\STTI\" enum SttiInstanceRoles { Worker Web } enum LogLevels { Debug = 1 Verbose = 2 Info = 3 Warn = 4 Error = 5 } enum SttiUserRoles{ OperationsAdmin SecurityAdmin DataAdmin HealthWatcher } enum SttiAppLogLevel{ Debug Information Warning Error Fatal } 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 [bool] $StoreDbInInstanceDataPath [string] $WorkerSslCertificateSubject [uint] $WorkerPort [string] $WebSslCertificateSubject [uint] $WebPort [string] $WorkerHostname [string] $EncryptionCertificateThumbprint [string] $EncryptionMasterKeyName #obsolete [string] hidden $ServiceUsername } 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 SttiInstanceStatus(){ $this.DatabaseExists = $false $this.InstanceExists = $false $this.WebRoleRunning = $false $this.WorkerRoleRunning = $false $this.InstallStatus = $null } } class SttiInstanceDefaults{ [PSCredential] $ServiceCredential [string] $CertificateThumbprint } class SttiDefaults{ [PSCredential] $CustomerCredential [string] $PackageSource [hashtable] $InstanceDefaults = @{} [SttiInstanceDefaults] ForInstance($Instance) { if ($Instance -is [string]){ $InstanceName = $Instance.ToUpperInvariant() } elseif ($Instance -is [SttiInstanceConfig]) { $InstanceName = $Instance.Name } else{ throw "Parameter $Instance is of invalid type" } if (-not $this.InstanceDefaults.ContainsKey($InstanceName)){ $this.InstanceDefaults[$InstanceName] = [SttiInstanceDefaults]::new() } return $this.InstanceDefaults[$InstanceName] } } class SttiShellSession{ [SttiInstanceConfig] $Instance } class SttiSystemSettings{ [string] $InstanceName [string] $DataDirectory [PSCustomObject] hidden $Dto SttiSystemSettings([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.InstanceName = $dto.instance $this.DataDirectory = $dto.dataDirectory } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiModule{ [string] $ModuleId [string] $Name [PSCustomObject] hidden $Dto SttiModule([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.ModuleId = $dto.id $this.Name = $dto.name } static [SttiModule[]] FromArray([PSCustomObject[]] $dtoArray){ if ($null -eq $dtoArray){ return $null } return $dtoArray.ForEach({return [SttiModule]::new($_)}) } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiModuleSettings{ [string[]] $Environments [string] $ActiveEnvironment [string] $SourceEnvironment [PSCustomObject] $Settings [PSCustomObject] hidden $Configurations [PSCustomObject] hidden $Dto SttiModuleSettings([PSCustomObject] $dto, [string] $SourceEnvironment){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.ActiveEnvironment = $dto.currentEnvironment if (![string]::IsNullOrEmpty($SourceEnvironment)){ $this.SourceEnvironment = $SourceEnvironment } else{ $this.SourceEnvironment = $this.ActiveEnvironment } $this.Environments = $dto.configurations | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name $this.Configurations = $dto.configurations $this.Settings = $this.GetSettings($this.SourceEnvironment) } [PSCustomObject] GetSettings([string] $environment){ if (!$this.Environments.Contains($environment)){ throw "Environment $environment does not exist" } return $this.Configurations.$environment.settings } static [SttiModuleSettings[]] FromArray([PSCustomObject[]] $dtoArray, [string] $SourceEnvironment){ if ($null -eq $dtoArray){ return $null } return $dtoArray.ForEach({return [SttiModuleSettings]::new($_, $SourceEnvironment)}) } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiModuleSecrets{ [string[]] $Environments [string] $ActiveEnvironment [string] $SourceEnvironment [PSCustomObject] $Secrets [PSCustomObject] hidden $Configurations [PSCustomObject] hidden $Dto SttiModuleSecrets([PSCustomObject] $dto, [string] $SourceEnvironment){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.ActiveEnvironment = $dto.currentEnvironment if (![string]::IsNullOrEmpty($SourceEnvironment)){ $this.SourceEnvironment = $SourceEnvironment } else{ $this.SourceEnvironment = $this.ActiveEnvironment } $this.Environments = $dto.configurations | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name $this.Configurations = $dto.configurations $this.Secrets = $this.GetSecrets($this.SourceEnvironment) } [PSCustomObject] GetSecrets([string] $environment){ if (!$this.Environments.Contains($environment)){ throw "Environment $environment does not exist" } return $this.Configurations.$environment.secrets } static [SttiModuleSecrets[]] FromArray([PSCustomObject[]] $dtoArray, [string] $SourceEnvironment){ if ($null -eq $dtoArray){ return $null } return $dtoArray.ForEach({return [SttiModuleSecrets]::new($_, $SourceEnvironment)}) } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiClient{ [string] $ClientId [string] $DisplayName [bool] $IsActive [PSCustomObject] hidden $Dto SttiClient([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.ClientId = $dto.id $this.DisplayName = $dto.displayName $this.IsActive = $dto.isActive } static [SttiClient[]] FromArray([PSCustomObject[]] $dtoArray){ if ($null -eq $dtoArray){ return $null } return $dtoArray.ForEach({return [SttiClient]::new($_)}) } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiClientSettings{ [string] $ClientId [string] $DisplayName [bool] $IsActive [PSCustomObject] hidden $Dto SttiClientSettings([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.ClientId = $dto.id $this.DisplayName = $dto.displayName $this.IsActive = $dto.isActive } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } } class SttiCredential{ [string] $CredentialId [string] $Thumbprint [string] $Description [PSCustomObject] hidden $Dto SttiCredential([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.CredentialId = $dto.id $this.Thumbprint = $dto.thumbprint $this.Description = $dto.description } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } static [SttiCredential[]] FromArray([PSCustomObject[]] $dtos){ if ($null -eq $dtos){ return $null } return $dtos.ForEach({[SttiCredential]::new($_)}) } } class SttiEncryptionKey{ [string] $KeyId [string] $Key [string] $Algorithm [bool] $Disabled [PSCustomObject] hidden $Dto SttiEncryptionKey([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.KeyId = $dto.keyId $this.Key = $dto.key $this.Algorithm = $dto.algorithm $this.Disabled = $dto.disabled } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } static [SttiEncryptionKey[]] FromArray([PSCustomObject[]] $dtos){ if ($null -eq $dtos){ return $null } return $dtos.ForEach({[SttiEncryptionKey]::new($_)}) } } class SttiUser{ [string] $UserId [string] $DisplayName [bool] $IsActive [PSCustomObject] hidden $Dto SttiUser([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.UserId = $dto.id $this.DisplayName = $dto.displayName $this.IsActive = $dto.isActive } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } static [SttiUser[]] FromArray([PSCustomObject[]] $dtos){ if ($null -eq $dtos){ return $null } return $dtos.ForEach({[SttiUser]::new($_)}) } } class SttiUserSettings{ [string] $UserId [string] $DisplayName [bool] $IsActive [SttiUserRoles[]] $Roles [PSCustomObject] hidden $Dto SttiUserSettings([PSCustomObject] $dto){ if ($null -eq $dto){ throw 'Parameter dto must not be null' } $this.Dto = $dto $this.UserId = $dto.id $this.DisplayName = $dto.displayName $this.IsActive = $dto.isActive $this.Roles = Convert-ObjectToRoles($dto.roles) } [string] AsJson (){ return $this.Dto | ConvertTo-Json -Depth 10 } static [SttiUserSettings[]] FromArray([PSCustomObject[]] $dtos){ if ($null -eq $dtos){ return $null } return $dtos.ForEach({[SttiUserSettings]::new($_)}) } } #region Installation function Test-UserHasAdminRole { #Check if the user is in the administrator group. Warns and stops if the user is not. if (-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." } } function Install-SttiInstance { [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [ValidateNotNullOrEmpty()] [SttiPackage] $Package = (Get-SttiPackage -Version latest), [Parameter(Mandatory)] [PSCredential] $ServiceCredential, [string] $InstallCertificateThumbprint = $initialUserCertificateThumbprint, [switch] $Force, [switch] $SkipCredentialTest, [switch] $UseExistingDatabase, [switch] $UseLocalServiceUser ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Test-UserHasAdminRole -ErrorAction Stop Write-SttiLog "Start install instance $($Instance.Name) with version $($Package.Version) from $($Package.Path)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose $errorAction = $Force ? "Continue" : "Stop" $serviceUsername = $ServiceCredential.UserName $installStatus = (Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop).InstallStatus if ($installStatus.Installed -eq $true){ Write-SttiLog "The instance $($Instance.Name) is already installed" -Level Error -ErrorAction $errorAction } $steps = @( @{Name="Test-Credential"; Expression={Test-Credential $ServiceCredential -ErrorAction Stop > $null}; CheckExpression={!$SkipCredentialTest}}, @{Name="Test-SttiInstanceBinding"; Expression={Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null}}, @{Name="Test-SttiDatabaseConnection"; Expression={Test-SttiDatabaseConnection $Instance -SkipPermissionCheck:$UseExistingDatabase -ErrorAction Stop > $null}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Test-SttiDatabase"; Expression={Test-SttiDatabase $Instance -UseExistingDatabase:$UseExistingDatabase -ErrorAction Stop > $null}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}}, @{Name="Clear-SttiInstanceDirectory"; Expression={Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction}}, @{Name="Build-SttiInstanceDirectory"; Expression={Build-SttiInstanceDirectory -Instance $Instance -ServiceUsername $serviceUsername -ErrorAction $errorAction}}, @{Name="Expand-SttiPackage"; Expression={Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction}}, @{Name="Install-SttiDatabaseEncryptionCertificate"; Expression={Install-SttiDatabaseEncryptionCertificate -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker) -and !$Instance.EncryptionCertificateThumbprint}}, @{Name="Edit-SttiInstanceConfigFiles"; Expression={Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction}}, @{Name="New-SttiDatabase"; Expression={New-SttiDatabase -Instance $instance -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker) -and !$UseExistingDatabase}}, @{Name="Update-SttiDatabaseUser"; Expression={Update-SttiDatabaseUser -Instance $instance -ServiceUsername $serviceUsername -SkipServerChanges:$UseExistingDatabase -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker) -and !$UseLocalServiceUser}}, @{Name="Add-SttiDatabaseEncryption"; Expression={Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Update-SttiDatabase"; Expression={Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Add-SttiInstanceServices"; Expression={Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}}, @{Name="Add-SttiFirewallRules"; Expression={Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction}}, @{Name="Start-SttiInstance"; Expression={Start-SttiInstance -Instance $Instance -ErrorAction $errorAction}}, @{Name="Install-SttiInitialUserCertificate"; Expression={Install-SttiInitialUserCertificate -Instance $Instance -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}} @{Name="Update-SttiInstanceSettings"; Expression={Update-SttiInstanceSettings -Instance $Instance -CertificateThumbprint $InstallCertificateThumbprint -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}} @{Name="Set-SttiInstanceInstallStatus"; Expression={Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null}}, @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}} ) Invoke-SttiDeploymentSteps -Activity "Install STTI instance $($Instance.Name)" -Steps $steps -ErrorAction $errorAction Write-SttiLog "Finished install instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during installation 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, [ValidateNotNullOrEmpty()] [SttiPackage] $Package = (Get-SttiPackage -VersionNumber latest), [Parameter(Mandatory)] [PSCredential] $ServiceCredential, [Parameter(Mandatory)] [string] $CertificateThumbprint, [switch] $Force, [switch] $SkipCredentialTest, [switch] $UseLocalServiceUser ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Test-UserHasAdminRole -ErrorAction Stop Write-SttiLog "Start update instance $($Instance.Name) with version $($Package.Version) from $($Package.Path)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose $errorAction = $Force ? "Continue" : "Stop" $serviceUsername = $ServiceCredential.UserName $installStatus = (Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop).InstallStatus if ($installStatus.Installed -eq $false -and $Force -eq $false){ Write-SttiLog "The instance $($Instance.Name) is not installed" -Level Error -ErrorAction $errorAction } $steps = @( @{Name="Test-Credential"; Expression={Test-Credential $ServiceCredential -ErrorAction Stop > $null}; CheckExpression={!$SkipCredentialTest}}, @{Name="Test-SttiInstanceBinding"; Expression={Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null}}, @{Name="Test-SttiDatabaseConnection"; Expression={Test-SttiDatabaseConnection $Instance -SkipPermissionCheck -ErrorAction Stop > $null}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Test-SttiDatabase"; Expression={Test-SttiDatabase $Instance -UseExistingDatabase -ErrorAction Stop > $null}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}}, @{Name="Stop-SttiInstance"; Expression={Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction}}, @{Name="Clear-SttiInstanceDirectory"; Expression={Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction}}, @{Name="Build-SttiInstanceDirectory"; Expression={Build-SttiInstanceDirectory -Instance $Instance -ServiceUsername $serviceUsername -ErrorAction $errorAction}}, @{Name="Expand-SttiPackage"; Expression={Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction}}, @{Name="Edit-SttiInstanceConfigFiles"; Expression={Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction}}, @{Name="Update-SttiDatabaseUser"; Expression={Update-SttiDatabaseUser -Instance $instance -ServiceUsername $serviceUsername -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker) -and !$UseLocalServiceUser}}, @{Name="Add-SttiDatabaseEncryption"; Expression={Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Update-SttiDatabase"; Expression={Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Add-SttiInstanceServices"; Expression={Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction}}, @{Name="Add-SttiFirewallRules"; Expression={Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction}}, @{Name="Start-SttiInstance"; Expression={Start-SttiInstance -Instance $Instance -ErrorAction $errorAction}}, @{Name="Update-SttiInstanceSettings"; Expression={Update-SttiInstanceSettings -Instance $Instance -CertificateThumbprint $CertificateThumbprint -ErrorAction $errorAction}; CheckExpression={$Instance.Roles.Contains([SttiInstanceRoles]::Worker)}}, @{Name="Set-SttiInstanceInstallStatus"; Expression={Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null}}, @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}} ) Invoke-SttiDeploymentSteps -Activity "Update STTI instance $($Instance.Name)" -Steps $steps -ErrorAction $errorAction 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(Mandatory)] [SttiInstanceConfig] $Instance, [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Test-UserHasAdminRole -ErrorAction Stop 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 $errorAction = $Force ? "Continue" : "Stop" $installStatus = (Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop).InstallStatus if ($installStatus.Installed -eq $false -and $Force -eq $false){ Write-SttiLog "The instance $($Instance.Name) is not installed" -Level Error -ErrorAction $errorAction } $steps = @( @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}}, @{Name="Stop-SttiInstance"; Expression={Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction}}, @{Name="Remove-SttiInstanceServices"; Expression={Remove-SttiInstanceServices -Instance $Instance -ErrorAction $errorAction}}, @{Name="Remove-SttiFirewallRules"; Expression={Remove-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction}}, @{Name="Clear-SttiInstanceDirectory"; Expression={Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction}}, @{Name="Set-SttiInstanceInstallStatus"; Expression={Set-SttiInstanceInstallStatus -Instance $Instance -Installed $false -Roles $null -Version $null -ErrorAction Stop > $null}}, @{Name="Write-SttiInstanceStatus"; Expression={Write-SttiInstanceStatus -Instance $Instance}} ) Invoke-SttiDeploymentSteps -Activity "Uninstall STTI instance $($Instance.Name)" -Steps $steps -ErrorAction $errorAction 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 Invoke-SttiDeploymentSteps{ [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Activity, [Parameter(Mandatory)] [pscustomobject] $Steps ) try{ # check if the prerequisites for the step are met $checkPrereqs = { param( [PSCustomObject] $Step ) if ($Step.CheckExpression){ if (!(& $Step.CheckExpression)){ Write-SttiLog "Prerequisite expression {$($Step.CheckExpression)} for step $($Step.Name) evaluated to false" -Level Verbose return $false } else{ return $true } } return $true } $filteredSteps = $steps.Where({& $checkPrereqs -Step $_ }) for ($i = 0; $i -lt $filteredSteps.Count; $i++){ $step = $filteredSteps[$i] Write-Progress -Id 1 -Activity $activity -CurrentOperation $step.Name -PercentComplete (($i + 1) / $filteredSteps.Count * 100) & $step.Expression } } finally{ Write-Progress -Id 1 -Activity $activity -Completed } } 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, [Parameter(Mandatory)] [string] $ServiceUsername ) 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($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($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" } function Update-SttiInstanceSettings{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $CertificateThumbprint ) 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{ Set-SttiSystemSettings -Instance $Instance -CertificateThumbprint $CertificateThumbprint -InstanceName $Instance.Name -DataDirectory "$($Instance.Path)\data" -ErrorAction Stop > $null } catch{ Write-SttiLog $_ -Level Error } } #Uninstall-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) #endregion Installation #region Certificate management function New-SttiUserCertificate{ [OutputType([X509Certificate])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Subject, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(20) ) 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" $cert = New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation $location -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 Write-SttiLog "Created new cert with thumbprint $($cert.Thumbprint) for $Subject in $location" -Level Info return $cert } function New-SttiDatabaseEncryptionCertificate{ [OutputType([X509Certificate])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Subject, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(50) ) 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" $cert = New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation $location -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage KeyEncipherment -KeySpec KeyExchange -KeyLength 2048 -NotAfter $NotAfter -ErrorAction Stop Write-SttiLog "Created new cert with thumbprint $($cert.Thumbprint) for $Subject in $location" -Level Info return $cert } function Import-SttiPfxCertificate{ [OutputType([X509Certificate])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $FilePath, [string] $Location = "Cert:\CurrentUser\My", [SecureString] $Password, [PSCredential] $UserCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $importScript = { $cert = Import-PfxCertificate -FilePath $Using:FilePath -CertStoreLocation $Using:Location -Password:$Using:Password -Exportable return $cert } $cert = Start-Job -ScriptBlock $importScript -Credential:$UserCredential | Receive-Job -Wait -ErrorAction Stop return $cert } function Export-SttiPfxCertificate{ [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $FilePath, [Parameter(Mandatory, ValueFromPipeline)] [X509Certificate] $Certificate, [string] $Location = "Cert:\CurrentUser\My", [SecureString] $Password, [PSCredential] $UserCredential, [switch] $RemoveFromStore ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug } Process{ if (!$Password){ $Password = Read-Password -Prompt "Please enter or paste a password for the exported certificate" -ErrorAction Stop } $exportScript = { $Using:Certificate | Export-PfxCertificate -FilePath $Using:FilePath -CryptoAlgorithmOption AES256_SHA256 -Password:$Using:Password -ErrorAction Stop > $null if ($Using:RemoveFromStore){ $Using:Certificate | Remove-Item } } Start-Job -ScriptBlock $exportScript -Credential:$UserCredential | Receive-Job -Wait -ErrorAction Stop return Get-Item $FilePath } } function Get-SttiDatabaseEncryptionCertificateLocation { [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 } } } function Install-SttiDatabaseEncryptionCertificate{ [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{ $targetLocation = "cert:\CurrentUser\My" $subject = "Stti $($Instance.Name) Database Encryption" $notAfter = (Get-Date).AddYears(50) $newCertThumbprint = Start-Job -ScriptBlock { Param([string] $location, [string] $subject, [DateTime] $notAfter) $cert = New-SelfSignedCertificate -Subject $subject -FriendlyName $subject -CertStoreLocation $location -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage KeyEncipherment -KeySpec KeyExchange -KeyLength 2048 -NotAfter $notAfter -ErrorAction Stop return $cert.Thumbprint } -Credential $ServiceCredential -ArgumentList $targetLocation, $subject, $notAfter | Receive-Job -Wait -ErrorAction Stop Set-SttiInstanceConfig -Instance $Instance -EncryptionCertificateThumbprint $newCertThumbprint -ErrorAction Stop > $null Write-SttiLog "A new database encryption certificate with thumbprint $newCertThumbprint has been created. Do not forget to backup the certificate and store it in a safe location. Therefore Export-SttiDatabaseEncryptionCertificate should be used." -Level Warn } catch{ Write-SttiLog $_ -Level Error } } function Export-SttiDatabaseEncryptionCertificate{ [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{ $location = "cert:\CurrentUser\My" $certPath = "$($location)\$($Instance.EncryptionCertificateThumbprint)" $exportPath = [System.IO.Path]::Combine("$(Get-SttiInstancePath -Instance $Instance)\", "data\", "Stti_$($Instance.Name)_DatabaseEncryption.pfx") $exportPassword = Read-Password -Prompt "Please enter or paste a password for the exported certificate" -ErrorAction Stop Start-Job -ScriptBlock { Param([string] $certPath, [string] $exportPath, [securestring] $exportPassword) if (!(Test-Path -Path $certPath)){ throw "$certPath does not exist" } Get-Item -Path $certPath | Export-PfxCertificate -FilePath $exportPath -CryptoAlgorithmOption AES256_SHA256 -Password $exportPassword -ErrorAction Stop > $null } -Credential $ServiceCredential -ArgumentList $certPath, $exportPath, $exportPassword | Receive-Job -Wait -ErrorAction Stop Write-SttiLog "Database encryption certificate with thumbprint $($Instance.EncryptionCertificateThumbprint) was exported to $exportPath. Backup file to a secure location and remember password. Then delete the file." -Level Info } catch{ Write-SttiLog $_ -Level Error } } function Install-SttiInitialUserCertificate{ [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{ $targetLocation = "cert:\CurrentUser\My" $alreadyExists = Test-Path -Path "$($targetLocation)\$($initialUserCertificateThumbprint)" if (!$alreadyExists){ Write-SttiLog "Initial user certificate with thumbprint $initialUserCertificateThumbprint does not exist in $targetLocation." -Level Verbose $initialUserCertPath = [System.IO.Path]::Combine("$(Get-SttiInstancePath -Instance $Instance)\bin\", "InitialUser.pfx") Import-PfxCertificate -FilePath $initialUserCertPath -CertStoreLocation $targetLocation -Password $initialUserCertificatePassword -ErrorAction Stop > $null Write-SttiLog "Imported initial user certificate with thumbprint $initialUserCertificateThumbprint to $targetLocation." -Level Verbose } else{ Write-SttiLog "Initial user certificate with thumbprint $initialUserCertificateThumbprint already exists in $targetLocation." -Level Verbose } Write-SttiLog "A new STTI instance always has a standard initial user which compromises security. Do not forget to switch to new user account. Therefore Switch-SttiInitialUser should be used." -Level Warn } catch{ Write-SttiLog $_ -Level Error } } function Switch-SttiInitialUser{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [bool] $NewRootCertificate = $true, [bool] $NewUserCertificate = $true, [string] $CertificateThumbprint = $initialUserCertificateThumbprint, [securestring] $RootCertificatePassword ) 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 ($NewRootCertificate){ $subject = "Stti $($Instance.Name) Root User" $cert = New-SttiUserCertificate -Subject $subject -FriendlyName $subject -NotAfter ([datetime]::Now.AddYears(50)) -ErrorAction Stop try{ $exportPath = [System.IO.Path]::Combine("$(Get-SttiInstancePath -Instance $Instance)\", "data\", "Stti_$($Instance.Name)_RootUser.pfx") $cert | Export-SttiPfxCertificate -FilePath $exportPath -Password:$RootCertificatePassword -RemoveFromStore -ErrorAction Stop > $null New-SttiUser -Instance:$Instance -DisplayName "$subject $($cert.Thumbprint)" -UserCertificateThumbprint $cert.Thumbprint -Roles SecurityAdmin -CertificateThumbprint:$CertificateThumbprint -ErrorAction Stop > $null Write-SttiLog "Root user certificate with thumbprint $($cert.Thumbprint) was exported to $exportpath. Backup file to a secure location and remember password. Then delete the file." -Level Info Write-SttiLog "Root user was added to stti with securityadmin role" -Level Info } catch{ $cert | Remove-Item -ErrorAction Continue Write-SttiLog "Error in path NewRootCertificate. The following exception occured: $_" -Level Error -ErrorAction Stop } } if ($NewUserCertificate){ $subject = "Stti User $env:UserName" $cert = New-SttiUserCertificate -Subject $subject -FriendlyName $subject -ErrorAction Stop try{ New-SttiUser -Instance:$Instance -DisplayName "$subject $($cert.Thumbprint)" -UserCertificateThumbprint $cert.Thumbprint -Roles SecurityAdmin, OperationsAdmin -CertificateThumbprint:$CertificateThumbprint -ErrorAction Stop > $null Write-SttiLog "Current user was added to stti with securityadmin, operationsadmin roles" -Level Info } catch{ $cert | Remove-Item -ErrorAction Continue Write-SttiLog "Error in path NewUserCertificate. The following exception occured: $_" -Level Error -ErrorAction Stop } } try{ $initialUser = Get-SttiUser -Instance:$Instance -UserId $initialUserId -CertificateThumbprint:$CertificateThumbprint -ErrorAction Stop if ($initialUser.IsActive){ $initialUser | Disable-SttiUser -Instance:$Instance -CertificateThumbprint:$CertificateThumbprint -ErrorAction Stop > $null Write-SttiLog "Initial installation user was disabled. Please use the new certificate thumbprint." -Level Info } } catch{ Write-SttiLog "Could not disable initial user in STTI. The following exception occured: $_" -Level Error -ErrorAction Stop } } #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 | Where-Object { (Test-Path (Get-SttiInstanceConfigFileName -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 Error 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()] [SttiInstanceRoles[]] $Roles = @([SttiInstanceRoles]::Worker, [SttiInstanceRoles]::Web), [Parameter()] [switch] $StoreDbInInstanceDataPath = $false, [Parameter(Mandatory)] [string] $WorkerSslCertificateSubject, [Parameter(Mandatory)] [uint] $WorkerPort, [Parameter(Mandatory)] [string] $WebSslCertificateSubject, [Parameter(Mandatory)] [uint] $WebPort, [Parameter()] [string] $WorkerHostname = "localhost", [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.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")] [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 } else{ $Instance = Get-SttiInstanceConfig ($Name) -ErrorAction Stop } $actualConfig = Get-SttiInstanceConfig ($Name) -ErrorAction Stop $Instance.SqlServer = ($PSBoundParameters.ContainsKey("SqlServer") ? $SqlServer : $actualConfig.SqlServer) $Instance.Database = ($PSBoundParameters.ContainsKey("Database") ? $Database : $actualConfig.Database) $Instance.Roles = ($PSBoundParameters.ContainsKey("Roles") ? $Roles : $actualConfig.Roles) $Instance.StoreDbInInstanceDataPath = ($PSBoundParameters.ContainsKey("StoreDbInInstanceDataPath") ? $StoreDbInInstanceDataPath : $actualConfig.StoreDbInInstanceDataPath) $Instance.WorkerSslCertificateSubject = ($PSBoundParameters.ContainsKey("WorkerSslCertificateSubject") ? $WorkerSslCertificateSubject : $actualConfig.WorkerSslCertificateSubject) $Instance.WorkerPort = ($PSBoundParameters.ContainsKey("WorkerPort") ? $WorkerPort : $actualConfig.WorkerPort) $Instance.WebSslCertificateSubject = ($PSBoundParameters.ContainsKey("WebSslCertificateSubject") ? $WebSslCertificateSubject : $actualConfig.WebSslCertificateSubject) $Instance.WebPort = ($PSBoundParameters.ContainsKey("WebPort") ? $WebPort : $actualConfig.WebPort) $Instance.WorkerHostname = ($PSBoundParameters.ContainsKey("WorkerHostname") ? $WorkerHostname : $actualConfig.WorkerHostname) if ($PSBoundParameters.ContainsKey("EncryptionCertificateThumbprint")){ if (![string]::IsNullOrEmpty($actualConfig.EncryptionCertificateThumbprint) -and $EncryptionCertificateThumbprint -ne $actualConfig.EncryptionCertificateThumbprint -and (Get-SttiInstanceInstallStatus -Instance $Instance).Installed){ Write-SttiLog "The EncryptionCertificateThumbprint has already been set and cannot be changed. Use Rotate-SttiEncryptionMasterKey" -Level Error -ErrorAction Stop } $Instance.EncryptionCertificateThumbprint = $EncryptionCertificateThumbprint $Instance.EncryptionMasterKeyName = "STTI_$EncryptionCertificateThumbprint" } else{ $Instance.EncryptionCertificateThumbprint = $actualConfig.EncryptionCertificateThumbprint $Instance.EncryptionMasterKeyName = $actualConfig.EncryptionMasterKeyName } $instanceConfigFile = Get-SttiInstanceConfigFileName $Name $Instance | ConvertTo-Json -ErrorAction Stop | Out-File $instanceConfigFile -Encoding utf8 -ErrorAction Stop $PSCmdlet.WriteObject($Instance) } } #Set-SttiInstanceConfig -InstanceName dev -Database "Test" -Roles Web, Worker #Get-SttiInstanceConfig dev | Set-SttiInstanceConfig -Database "Test" -Roles Web function Set-SttiSessionValues { [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $InstanceName, [Parameter(Position=1, ParameterSetName="Instance")] [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 if ($PSBoundParameters.ContainsKey("InstanceName")){ $Instance = Get-SttiInstanceConfig -Name $InstanceName -ErrorAction Stop } $script:shellSession.Instance = $Instance Restore-SttiDefaultParameterValues } 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 ?? $script:defaultBasePath 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 Write-SttiLog "STTI base path was set to $BasePath." -Level Warn 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 = " 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 = 130 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" Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database "master" -Query $command -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Get-SttiInstanceConfig dev | New-SttiDatabase -EncryptionMasterKey $null function Update-SttiDatabaseUser{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ServiceUsername, [switch] $SkipServerChanges ) 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{ $command = (!$SkipServerChanges ? " IF NOT EXISTS (SELECT name FROM master.sys.server_principals WHERE name = '$($ServiceUsername)') BEGIN CREATE LOGIN [$($ServiceUsername)] FROM WINDOWS WITH DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english] END GO ": "") ` + "USE [$($Instance.Database)] GO DECLARE @uid INT SELECT @uid = principal_id from sys.database_principals WHERE sid = SUSER_SID('$($ServiceUsername)') IF @uid IS NULL BEGIN CREATE USER [$($ServiceUsername)] FOR LOGIN [$($ServiceUsername)] ALTER ROLE [db_owner] ADD MEMBER [$($ServiceUsername)] END ELSE IF NOT EXISTS (SELECT * FROM sys.database_role_members WHERE role_principal_id = DATABASE_PRINCIPAL_ID('db_owner') AND member_principal_id = USER_ID('HALVOTEC\sttiint')) BEGIN ALTER ROLE [db_owner] ADD MEMBER [$($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( $Instance, $EncryptColumnEncryptionKeyFunc ) # search for the key $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)" } } # construct script for creating a new column master key $masterKeyCertificatePath = "$certificateStoreLocation/My/$($Instance.EncryptionCertificateThumbprint)" $masterKeyCertificate = $key $newMasterKeyScript = "CREATE COLUMN MASTER KEY [$($Instance.EncryptionMasterKeyName)] WITH ( KEY_STORE_PROVIDER_NAME = 'MSSQL_CERTIFICATE_STORE', KEY_PATH = '$masterKeyCertificatePath' ); GO " # create a new random encryption key $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() try{ $encryptionKey = [byte[]]::new(32) $rng.GetBytes($encryptionKey) } finally{ $rng.Dispose() } $encryptionKeyName = "CEK" # encrypt the key if ($EncryptColumnEncryptionKeyFunc -is [string]){ $EncryptColumnEncryptionKeyFunc = [scriptblock]::Create($EncryptColumnEncryptionKeyFunc) } $encryptedKey = (& $EncryptColumnEncryptionKeyFunc -Certificate $masterKeyCertificate -MasterKeyPath $masterKeyCertificatePath -ColumnEncryptionKey $encryptionKey) # seralize to format that sql server wants $encryptedKeySerialized = "0x" + [BitConverter]::ToString($encryptedKey).Replace("-", "") # construct script for creating a new column encryption key $newEncryptionKeyScript = "CREATE COLUMN ENCRYPTION KEY [$encryptionKeyName] WITH VALUES ( COLUMN_MASTER_KEY = [$($Instance.EncryptionMasterKeyName)], ALGORITHM = 'RSA_OAEP', ENCRYPTED_VALUE = $encryptedKeySerialized ); GO " $db = Get-SqlDatabase -Name $Instance.Database -ServerInstance $Instance.SqlServer -ErrorAction Stop $masterKeyExists = ($null -ne (Get-SqlColumnMasterKey -Name $Instance.EncryptionMasterKeyName -InputObject $db -ErrorAction SilentlyContinue)) $encryptionKeyExists = ($null -ne (Get-SqlColumnEncryptionKey -Name "CEK" -InputObject $db -ErrorAction SilentlyContinue)) $script = " BEGIN TRANSACTION $(!$masterKeyExists ? $newMasterKeyScript : '') $(!$encryptionKeyExists ? $newEncryptionKeyScript : '') COMMIT TRANSACTION " Invoke-SqlCmd -ServerInstance $Instance.SqlServer -Database $Instance.Database -Query $script -ErrorAction Stop } $argumentList = @($Instance, $encryptColumnEncryptionKeyFunc) # 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 | Receive-Job -Wait -ErrorAction Stop } else{ Start-Job -Credential $ServiceCredential -ScriptBlock $scriptBlock -ArgumentList $argumentList | Receive-Job -Wait -ErrorAction Stop } } catch{ Write-SttiLog $_ -Level Error } } $encryptColumnEncryptionKeyFunc = { Param( [X509Certificate] $Certificate, [string] $MasterKeyPath, [byte[]] $ColumnEncryptionKey ) # Construct the encryptedColumnEncryptionKey # Format is # version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature # We currently only support one version $version = [byte[]] @( 0x01 ) # Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath $masterKeyPathBytes = [System.Text.UnicodeEncoding]::Unicode.GetBytes($masterKeyPath.ToLowerInvariant()) $keyPathLength = [System.BitConverter]::GetBytes([Int16]$masterKeyPathBytes.Length) # Encrypt the plain text $rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($Certificate) $cipherText = $rsa.Encrypt($columnEncryptionKey, [System.Security.Cryptography.RSAEncryptionPadding]::OaepSHA1) $cipherTextLength = [System.BitConverter]::GetBytes([Int16]$cipherText.Length) # Compute hash # SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) $sha256 = [System.Security.Cryptography.SHA256CryptoServiceProvider]::new() try { $sha256.TransformBlock($version, 0, $version.Length, $version, 0) > $null $sha256.TransformBlock($keyPathLength, 0, $keyPathLength.Length, $keyPathLength, 0) > $null $sha256.TransformBlock($cipherTextLength, 0, $cipherTextLength.Length, $cipherTextLength, 0) > $null $sha256.TransformBlock($masterKeyPathBytes, 0, $masterKeyPathBytes.Length, $masterKeyPathBytes, 0) > $null $sha256.TransformFinalBlock($cipherText, 0, $cipherText.Length) > $null $hash = $sha256.Hash } finally{ $sha256.Dispose() } # Sign the hash $rsaPrivate = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate) $rsaFormatter = [System.Security.Cryptography.RSAPKCS1SignatureFormatter]::new($rsaPrivate) $rsaFormatter.SetHashAlgorithm('SHA256') $signedHash = $rsaFormatter.CreateSignature($hash) # Construct the encrypted column encryption key # EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature $encryptedColumnEncryptionKeyLength = $version.Length + $cipherTextLength.Length + $keyPathLength.Length + $cipherText.Length + $masterKeyPathBytes.Length + $signedHash.Length $encryptedColumnEncryptionKey = [byte[]]::new($encryptedColumnEncryptionKeyLength) # Copy version byte $currentIndex = 0; [System.Buffer]::BlockCopy($version, 0, $encryptedColumnEncryptionKey, $currentIndex, $version.Length) > $null $currentIndex += $version.Length # Copy key path length [System.Buffer]::BlockCopy($keyPathLength, 0, $encryptedColumnEncryptionKey, $currentIndex, $keyPathLength.Length) > $null $currentIndex += $keyPathLength.Length # Copy ciphertext length [System.Buffer]::BlockCopy($cipherTextLength, 0, $encryptedColumnEncryptionKey, $currentIndex, $cipherTextLength.Length) > $null $currentIndex += $cipherTextLength.Length # Copy key path [System.Buffer]::BlockCopy($masterKeyPathBytes, 0, $encryptedColumnEncryptionKey, $currentIndex, $masterKeyPathBytes.Length) > $null $currentIndex += $masterKeyPathBytes.Length # Copy ciphertext [System.Buffer]::BlockCopy($cipherText, 0, $encryptedColumnEncryptionKey, $currentIndex, $cipherText.Length) > $null $currentIndex += $cipherText.Length # copy the signature [System.Buffer]::BlockCopy($signedHash, 0, $encryptedColumnEncryptionKey, $currentIndex, $signedHash.Length) > $null return $encryptedColumnEncryptionKey } function Test-SttiDatabaseConnection { [OutputType([bool])] [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [switch] $SkipPermissionCheck ) 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{ #$sqlInstance = Get-SqlInstance -ServerInstance $Instance.SqlServer -ErrorAction Stop Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database master -Query "SELECT 1" -ErrorAction Stop > $null } catch{ Write-SttiLog "Could not connect to Sql Server instance $($Instance.SqlServer): $($_.Exception)" -Level Error -ErrorAction Stop return $false } #TODO: checks if version and settings of server are correct if (!$SkipPermissionCheck){ try{ $query = "SELECT permission_name FROM fn_my_permissions (NULL, 'SERVER') WHERE permission_Name IN ('CREATE ANY DATABASE', 'ALTER ANY LOGIN')" $result = Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database master -Query $query -ErrorAction Stop } catch{ Write-SttiLog "Could not query server permissions: $($_.Exception)" -Level Error -ErrorAction Stop return $false } $createDatabasePermission = $null -ne ($result | Where-Object permission_name -Like "CREATE ANY DATABASE") $createLoginPermission = $null -ne ($result | Where-Object permission_name -Like "ALTER ANY LOGIN") if (!$createDatabasePermission || !$createLoginPermission){ Write-SttiLog "Not enough permissions in Sql Server for installation. CREATE ANY DATABASE=$createDatabasePermission. ALTER ANY LOGIN=$createLoginPermission" -Level Error -ErrorAction Stop return $false } } return $true } function Test-SttiDatabase { [OutputType([bool])] [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [switch] $UseExistingDatabase, [switch] $SkipPermissionCheck ) 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{ $query = "SELECT name FROM sys.databases WHERE name LIKE '$($Instance.Database)'" $databases = Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database master -Query $query -ErrorAction Stop } catch{ Write-SttiLog "Could not enumerate Sql databases: $($_.Exception)" -Level Error -ErrorAction Stop return $false } $databaseExists = $null -ne ($databases | Where-Object name -Like $Instance.Database) if (!$UseExistingDatabase) { if ($databaseExists){ Write-SttiLog "Database $($Instance.Database) already exists." -Level Error -ErrorAction Stop return $false } } else{ if (!$databaseExists){ Write-SttiLog "Database $($Instance.Database) does not exist." -Level Error -ErrorAction Stop return $false } #TODO: checks if settings of database are correct if (!$SkipPermissionCheck){ try{ $query = "SELECT permission_name FROM fn_my_permissions (NULL, 'DATABASE') WHERE permission_Name IN ('ALTER ANY ROLE', 'ALTER ANY USER')" $result = Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database $Instance.Database -Query $query -ErrorAction Stop } catch{ Write-SttiLog "Could not query database permissions: $($_.Exception)" -Level Error -ErrorAction Stop return $false } $alterRolePermission = $null -ne ($result | Where-Object permission_name -Like "ALTER ANY ROLE") if (!$alterRolePermission){ Write-SttiLog "Not enough permissions in Sql Server database $($Instance.Database) for installation. ALTER ANY ROLE=$alterRolePermission." -Level Error -ErrorAction Stop return $false } } } return $true } 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"){ $package = $packages | Sort-Object -Property Version -Descending | Select-Object -First 1 } else { $version = [System.Version]::Parse($VersionNumber) $package = $packages | Where-Object -Property Version -eq $version } if ($null -eq $package){ throw "no package found" } return $package } # Get-SttiPackage -VersionNumber "latest" function Set-SttiDefaultValues{ [CmdletBinding(DefaultParameterSetName="InstanceName")] Param( [Parameter(ParameterSetName="Instance")] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [string] $InstanceName, [Parameter(ParameterSetName="Instance")] [Parameter(ParameterSetName="InstanceName")] [PSCredential] $CustomerCredential, [Parameter(ParameterSetName="Instance")] [Parameter(ParameterSetName="InstanceName")] [PSCredential] $ServiceCredential, [Parameter(ParameterSetName="Instance")] [Parameter(ParameterSetName="InstanceName")] [string] $CertificateThumbprint, [Parameter(ParameterSetName="Instance")] [Parameter(ParameterSetName="InstanceName")] [string] $PackageSource ) 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 ($PSBoundParameters.ContainsKey("Instance")){ $InstanceName = $Instance.Name.ToUpperInvariant() } else{ if ($InstanceName){ $InstanceName = $InstanceName.ToUpperInvariant() } } # Restore $defaults = Get-SttiDefaultValues # Set new Values if ($PSBoundParameters.ContainsKey("CustomerCredential")){ $defaults.CustomerCredential = $CustomerCredential } if ($PSBoundParameters.ContainsKey("PackageSource")){ $defaults.PackageSource = $PackageSource } if ($PSBoundParameters.ContainsKey("ServiceCredential")){ if (!$InstanceName) { throw "For parameter ServiceCredential also parameter Instance or Instancename must be specified" } $defaults.ForInstance($InstanceName).ServiceCredential = $ServiceCredential } if ($PSBoundParameters.ContainsKey("CertificateThumbprint")){ if (!$InstanceName) { throw "For parameter CertificateThumbprint also parameter Instance or Instancename must be specified" } $defaults.ForInstance($InstanceName).CertificateThumbprint = $CertificateThumbprint } #Serialize $defaultsFile = [System.IO.Path]::Combine((Get-SttiUserHomePath), "defaults.json") $defaultsData = @{ InstanceDefaults = @{} } foreach ($key in ($defaults | Get-Member -Type Properties | Select-Object -ExpandProperty Name)){ if ($key -eq "InstanceDefaults"){ $instanceDefaultsTable = $defaults.$Key foreach ($in in $instanceDefaultsTable.Keys){ $instanceDefaults = $defaults.ForInstance($in) $instanceDefaultsData = $defaultsData.InstanceDefaults[$in] = @{} foreach ($ikey in ($instanceDefaults | Get-Member -Type Properties | Select-Object -ExpandProperty Name)){ if ($ikey -like "*Credential"){ $instanceDefaultsData[$ikey] = Convert-CredentialToHashtable -Credential $instanceDefaults.$ikey } else{ $instanceDefaultsData[$ikey] = $instanceDefaults.$ikey } } } } elseif ($key -like "*Credential"){ $defaultsData[$key] = Convert-CredentialToHashtable -Credential $defaults.$key } else{ $defaultsData[$key] = $defaults.$key } } $defaultsData | ConvertTo-Json -Depth 10 -ErrorAction Stop | Out-File $defaultsFile -Encoding utf8 -ErrorAction Stop Restore-SttiDefaultParameterValues $PSDefaultParameterValues } #Set-SttiDefaults function Get-SttiDefaultValues{ [OutputType([SttiDefaults])] [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 $defaults = [SttiDefaults]::new() $defaults.InstanceDefaults = @{} $defaultsFile = [System.IO.Path]::Combine((Get-SttiUserHomePath), "defaults.json") if (!(Test-Path $defaultsFile)){ return $defaults } $defaultsData = Get-Content $defaultsFile -Raw -Encoding utf8 | ConvertFrom-Json -AsHashtable foreach ($key in $defaultsData.Keys){ if ($key -eq "InstanceDefaults"){ $instanceDefaultsTable = $defaultsData[$key] foreach ($in in $instanceDefaultsTable.Keys){ $instanceDefaultsData = $instanceDefaultsTable[$in] $instanceDefaults = $defaults.ForInstance($in) foreach ($ikey in $instanceDefaultsData.Keys){ if ($ikey -like "*Credential"){ $instanceDefaults.$ikey = Convert-HashtableToCredential -Data $instanceDefaultsData[$ikey] } else{ $instanceDefaults.$ikey = $instanceDefaultsData[$ikey] } } } } elseif ($key -like "*Credential"){ $defaults.$key = Convert-HashtableToCredential -Data $defaultsData[$key] } else{ $defaults.$key = $defaultsData[$key] } } return $defaults } #Get-SttiDefaults function Restore-SttiDefaultParameterValues{ [CmdletBinding()] Param( ) $defaults = Get-SttiDefaultValues $params = @{} foreach ($prop in ($defaults | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name)){ if ($prop -eq "InstanceDefaults"){ $instanceDefaults = [SttiInstanceDefaults]::new() if ($script:shellSession.Instance){ $instanceDefaults = $defaults.ForInstance($script:shellSession.Instance) } foreach ($iprop in ($instanceDefaults | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name)){ $value = $instanceDefaults.$iprop $params.Add($iprop, $value) } } else{ $value = $defaults.$prop $params.Add($prop, $value) } } foreach ($prop in ($script:shellSession | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name)){ $value = $script:shellSession.$prop $params.Add($prop, $value) } foreach ($param in $params.Keys){ $value = $params[$param] if ($value){ $Global:PSDefaultParameterValues["*-Stti*:$param"] = $value } else{ $Global:PSDefaultParameterValues.Remove("*-Stti*:$param") } } } function Get-SttiUserHomePath{ [OutputType([string])] Param() $sttiUserHomePath = [System.IO.Path]::Combine([Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData), "Stti") if (!(Test-Path $sttiUserHomePath)){ New-Item $sttiUserHomePath -ItemType Directory -Force > $null } return $sttiUserHomePath } function Convert-CredentialToHashtable{ [OutputType([hashtable])] param( [pscredential]$Credential ) if ($Credential){ return @{ UserName = $Credential.UserName Password = ($Credential.Password | ConvertFrom-SecureString) } } else { return $null } } function Convert-HashtableToCredential{ [OutputType([pscredential])] param( [hashtable]$Data ) if ($Data){ return [System.Management.Automation.PsCredential]::new($Data.UserName, ($Data.Password | ConvertTo-SecureString)) } else { return $null } } function Find-SttiPackage{ [OutputType([SttiPackage[]])] [CmdletBinding()] Param( [ValidatePattern("^\d+\.\d+\.\d+(?:\.\d+)?$|^latest$")] [string] $VersionNumber, [string] $PackageSource = $script:defaultPackageSource, [Parameter(Mandatory)] [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 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{ [OutputType([SttiPackage])] [CmdletBinding()] Param( [Parameter(Mandatory, ValueFromPipeline)] [SttiPackage] $Package, [Parameter(Mandatory)] [pscredential] $CustomerCredential ) 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 Test-UserHasAdminRole -ErrorAction Stop if ($Package.IsLocal){ Write-SttiLog "Package must not be local" } $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 Get-SttiPackage -Version $Package.Version } } #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 "ModuleInfo:" -Level Verbose Get-Module | Where-Object Name -in @("Stti", "SqlServer") | Select-Object -Property Name, Version, PreRelease, ModuleType | Format-Table | Out-String | Write-SttiLog -Level Verbose Write-SttiLog "HostInfo:" -Level Verbose Get-Host | Write-SttiLog -Level Verbose Write-SttiLog "ComputerInfo:" -Level Verbose Get-ComputerInfo | 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-Host $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" function Write-SttiInstanceStatus{ [CmdletBinding()] param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose } #endregion Logging #region Helper function Read-Password { [OutputType([SecureString])] Param( [string] $Prompt ) $pw = $null $validValue = $false while (!$validValue){ $pw1 = [SecureString](Read-Host -Prompt $Prompt -AsSecureString -ErrorAction Stop) $pw2 = [SecureString](Read-Host -Prompt "Please repeat the password" -AsSecureString -ErrorAction Stop) $pw1Text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pw1)) $pw2Text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pw2)) $validValue = $pw1Text.CompareTo($pw2Text) -eq 0 if (!$validValue){ Write-Host "The password do not match" } else { $pw = $pw1 } } return $pw } function Test-Credential { [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 $user = $credential.UserName $password = $credential.GetNetworkCredential().Password # Check that all components are present if (!($user) -or !($password)) { Write-SttiLog "Please specify both username and password" -Level Error return $false } # Check format if ($user -match "^[^\\]+\\[^\\]+$"){ $components = $user -split "\\" $domain = $components[0] #$username = $components[1] if ($domain -eq "."){ Write-SttiLog "Don't use '.' as domain or computername." -Level Error return $false } } else{ Write-SttiLog "Username must be specified in format DOMAIN\USERNAME. For local accounts use COMPUTERNAME\USERNAME." -Level Error return $false } $isValid = $false try{ $isValid = Start-Job -ScriptBlock { return $true } -Credential $Credential -ErrorAction Stop| Receive-Job -Wait -ErrorAction Stop return $isValid } catch{ Write-SttiLog "Could not validate the credentials. An error occurred: $($_.Exception)" -Level Error -ErrorAction Stop } if (-not ($isValid)){ Write-SttiLog "Could not validate the credentials. Username or password seems to be invalid." -Level Error -ErrorAction Stop return $isValid } } function Test-Parameter{ Param( [Parameter(Position=1)] $Value, [Parameter(Mandatory, Position=2)] [string]$Name, [ValidateNotNullOrEmpty] [string]$CheckExpression = '$null -ne $Value', [ValidateNotNullOrEmpty] [string]$Message = "Parameter $Name is null" ) if (!(Invoke-Expression $CheckExpression)){ throw $Message } } #endregion Helper #region Deployment function Deploy-Stti { [CmdletBinding()] [OutputType([SttiInstanceStatus])] Param( [string] $InstanceName, [PSCredential] $ServiceCredential, [string] $Version="latest", [string] $SqlServer, [string] $Database="STTI_$($InstanceName.ToUpperInvariant())", [string] $AdminCertificateThumbprint, [string] $EncryptionCertificateThumbprint, [bool] $StoreDbInInstanceDataPath = $true, [string] $WorkerSslCertificateSubject, [uint] $WorkerPort, [string] $WebSslCertificateSubject, [uint] $WebPort, [string] $WorkerHostname, [switch] $Uninstall=$false, [switch] $Force=$false, [SttiInstanceRoles[]] $Roles = @([SttiInstanceRoles]::Worker, [SttiInstanceRoles]::Web) ) Test-UserHasAdminRole -ErrorAction Stop # Create instance stub $instance = Get-SttiInstanceConfig -Name $InstanceName -ErrorAction SilentlyContinue if ($null -eq $instance) { $instance = New-SttiInstanceConfig -Name $InstanceName -SqlServer $SqlServer -Database $Database -Roles $Roles -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 $Roles -StoreDbInInstanceDataPath:$StoreDbInInstanceDataPath -WorkerSslCertificateSubject $WorkerSslCertificateSubject -WorkerPort $WorkerPort -WebPort $WebPort -WebSslCertificateSubject $WebSslCertificateSubject -WorkerHostname $WorkerHostname -EncryptionCertificateThumbprint $EncryptionCertificateThumbprint } $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 -Message "Please enter the credentials for the service user") Install-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force } elseif ($status.InstallStatus.Version -ne $package.Version -or $Force){ $svcCred = $ServiceCredential ?? (Get-Credential -Message "Please enter the credentials for the service user") Update-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force -CertificateThumbprint $AdminCertificateThumbprint } 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 #region Config function Get-SttiApiUrl{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) return "https://$($Instance.WorkerHostname):$($Instance.WorkerPort)/api" } function Get-SttiSystemSettings{ [OutputType([SttiSystemSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance try{ $config = Invoke-RestMethod "$apiUrl/config" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiSystemSettings]::new($config.data.system) } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiSystemSettings{ [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName="SettingUpdates")] [Parameter(Mandatory, ParameterSetName="AsParameters")] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ParameterSetName="SettingUpdates")] $SettingUpdates, [Parameter(ParameterSetName="AsParameters")] [string] $InstanceName, [Parameter(ParameterSetName="AsParameters")] [string] $DataDirectory, [Parameter(Mandatory, ParameterSetName="SettingUpdates")] [Parameter(Mandatory, ParameterSetName="AsParameters")] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance if (!$PSBoundParameters.ContainsKey("SettingUpdates")){ $SettingUpdates = @{} if ($PSBoundParameters.ContainsKey("InstanceName")){ $SettingUpdates.instance = $InstanceName } if ($PSBoundParameters.ContainsKey("DataDirectory")){ $SettingUpdates.dataDirectory = $DataDirectory } } try{ $result = Invoke-RestMethod "$apiUrl/config/system" -Method Put -Body (ConvertTo-Json $SettingUpdates) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiSystemSettings]::new($result.data.system) } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiModule{ [OutputType([SttiModule])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [string] $ModuleId, [string] $Name, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance try{ $result = Invoke-RestMethod "$apiUrl/config/modules" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop $modules = $result.data $filteredModules = $modules | Where-Object { (!$PSBoundParameters.ContainsKey("ModuleId") -or $_.id -like $ModuleId) -and (!$PSBoundParameters.ContainsKey("Name") -or $_.name -like $Name) } return [SttiModule]::FromArray($filteredModules) } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiModuleEnvironment{ [OutputType([SttiModuleSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [Parameter(Mandatory)] [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId/env/$Environment" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSettings]::new($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } function Get-SttiModuleSettings{ [OutputType([SttiModuleSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSettings]::new($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } function Set-SttiModuleSettings{ [OutputType([SttiModuleSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [Parameter(Mandatory)] [PSCustomObject] $SettingUpdates, [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId$($PSBoundParameters.ContainsKey("Environment") ? ""/env/$Environment"" : """")/settings" -Method Put -Body (ConvertTo-Json $SettingUpdates) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSettings]::new($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } function Get-SttiModuleSecrets{ [OutputType([SttiModuleSecrets])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSecrets]::new($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } function New-SttiModuleTokenSecret{ [OutputType([SttiModuleSecrets])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [Parameter(Mandatory)] [string] $Token, [datetime] $ValidFrom, [datetime] $ValidThru, [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $Secret = @{ type = "TokenSecretInfo" token = $Token } if ($PSBoundParameters.ContainsKey("ValidFrom")){ $Secret.validFrom = $ValidFrom } if ($PSBoundParameters.ContainsKey("ValidThru")){ $Secret.validThrough = $ValidFrom } $Data = @{ moduleId = "" secret = $Secret } try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId$($PSBoundParameters.ContainsKey("Environment") ? ""/env/$Environment"" : """")/secrets" -Method Post -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSecrets]::new($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } function Remove-SttiModuleSecret{ [CmdletBinding()] [OutputType([SttiModuleSecrets])] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ModuleId, [Parameter(Mandatory)] [string] $SecretId, [string] $Environment, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $result = Invoke-RestMethod "$apiUrl/config/modules/$ModuleId$($PSBoundParameters.ContainsKey("Environment") ? ""/env/$Environment"" : """")/secrets/$SecretId" -Method Delete -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiModuleSecrets]::FromArray($result.data, $Environment) } catch{ Write-SttiLog $_ -Level Error } } } #endregion Config #region Clients function Get-SttiClient{ [OutputType([SttiClient[]])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [string] $ClientId, [string] $DisplayName, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance try{ $response = Invoke-RestMethod "$apiUrl/security/clients" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop $clients = $response.data $filteredClients = $clients | Where-Object { (!$PSBoundParameters.ContainsKey("ClientId") -or $_.id -like $ClientId) -and (!$PSBoundParameters.ContainsKey("DisplayName") -or $_.displayName -like $DisplayName) } return [SttiClient]::FromArray($filteredClients) } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiClientSettings{ [OutputType([SttiClientSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiClientSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function New-SttiClient{ [OutputType([SttiClient])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $DisplayName, [string] $ClientCertificateThumbprint, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance $Data = @{ displayName = $DisplayName encryptionKey = @{ algorithm = "Aes256" } } if ($PSBoundParameters.ContainsKey("ClientCertificateThumbprint")){ $Data.certificateCredential = @{ thumbprint = $ClientCertificateThumbprint description = "Set on $(Get-Date -Format "s")" } } try{ $response = Invoke-RestMethod "$apiUrl/security/clients" -Method Post -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiClient]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiClientSettings{ [OutputType([SttiClientSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [string] $DisplayName, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $Data = @{ } $hasModifications = $false if ($PSBoundParameters.ContainsKey("DisplayName")){ $Data.displayName = $DisplayName $hasModifications = $true } if (!$hasModifications){ throw "Parameters do not result in any modification" } try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId" -Method Put -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiClientSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Get-SttiClientCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::FromArray($response.data.credentials) } catch{ Write-SttiLog $_ -Level Error } } } function Add-SttiClientCertificateCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $ClientCertificateThumbprint, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $Data = @{ } $Data.certificateCredential = @{ thumbprint = $ClientCertificateThumbprint description = "Set on $(Get-Date -Format "s")" } try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/credentials" -Method Post -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::new(($response.data.credentials | Select-Object -Last 1)) } catch{ Write-SttiLog $_ -Level Error } } } function Remove-SttiClientCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CredentialId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/credentials/$CredentialId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::FromArray($response.data.credentials) } catch{ Write-SttiLog $_ -Level Error } } } function Enable-SttiClient{ [OutputType([SttiClientSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiClientSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Disable-SttiClient{ [OutputType([SttiClientSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiClientSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Get-SttiClientEncryptionKey{ [OutputType([SttiEncryptionKey])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiEncryptionKey]::FromArray($response.data.encryption.keys) } catch{ Write-SttiLog $_ -Level Error } } } function New-SttiClientEncryptionKey{ [OutputType([SttiEncryptionKey])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $data = @{ key = @{ algorithm = "Aes256" } } try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/encryption/symmetrickeys" -Method Post -Body (ConvertTo-Json $data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiEncryptionKey]::new(($response.data.encryption.keys | Select-Object -Last 1)) } catch{ Write-SttiLog $_ -Level Error } } } function Enable-SttiClientEncryptionKey{ [OutputType([SttiEncryptionKey])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/encryption/symmetrickeys/$KeyId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiEncryptionKey]::new(($response.data.encryption.keys | Where-Object { $_.keyId -like $KeyId })) } catch{ Write-SttiLog $_ -Level Error } } } function Disable-SttiClientEncryptionKey{ [OutputType([SttiEncryptionKey])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/encryption/symmetrickeys/$KeyId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiEncryptionKey]::new(($response.data.encryption.keys | Where-Object { $_.keyId -like $KeyId })) } catch{ Write-SttiLog $_ -Level Error } } } function Remove-SttiClientEncryptionKey{ [OutputType([SttiEncryptionKey])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/encryption/symmetrickeys/$KeyId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiEncryptionKey]::FromArray($response.data.encryption.keys) } catch{ Write-SttiLog $_ -Level Error } } } #endregion Clients #region Users function Get-SttiUser{ [OutputType([SttiUser])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [string] $UserId, [string] $DisplayName, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance try{ $response = Invoke-RestMethod "$apiUrl/security/users" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop $users = $response.data $filteredUsers = $users | Where-Object { (!$PSBoundParameters.ContainsKey("UserId") -or $_.id -like $UserId) -and (!$PSBoundParameters.ContainsKey("DisplayName") -or $_.displayName -like $DisplayName) } return [SttiUser]::FromArray($filteredUsers) } catch{ Write-SttiLog $_ -Level Error } } function Convert-ObjectToRoles{ [OutputType([SttiUserRoles[]])] Param( [PSCustomObject] $rolesObj ) $roles = $rolesObj | ConvertTo-Json -Depth 10 | ConvertFrom-Json -Depth 10 -AsHashtable $result = [System.Collections.ArrayList]::new() if ($roles.operationsAdminRole -eq $true) { [void]$result.Add([SttiUserRoles]::OperationsAdmin) } if ($roles.dataAdminRole -eq $true) { [void]$result.Add([SttiUserRoles]::DataAdmin) } if ($roles.securityAdminRole -eq $true) { [void]$result.Add([SttiUserRoles]::SecurityAdmin) } if ($roles.healthWatcherRole -eq $true) { [void]$result.Add([SttiUserRoles]::HealthWatcher) } return $result } function Convert-RolesToObject{ [OutputType([pscustomobject])] Param( [SttiUserRoles[]] $roles, $objectToUse ) if ($objectToUse){ $result = $objectToUse } else{ $result = @{} } $result.operationsAdminRole = $Roles.Contains([SttiUserRoles]::OperationsAdmin) $result.securityAdminRole = $Roles.Contains([SttiUserRoles]::SecurityAdmin) $result.dataAdminRole = $Roles.Contains([SttiUserRoles]::DataAdmin) $result.healthWatcherRole = $Roles.Contains([SttiUserRoles]::HealthWatcher) if (!$objectToUse){ return $result } } function New-SttiUser{ [OutputType([SttiUser])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $DisplayName, [string] $UserCertificateThumbprint, [Parameter(Mandatory)] [SttiUserRoles[]] $Roles, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance $data = @{ displayName = $DisplayName } if ($PSBoundParameters.ContainsKey("UserCertificateThumbprint")){ $Data.certificateCredential = @{ thumbprint = $UserCertificateThumbprint description = "Set on $(Get-Date -Format "s")" } } if ($PSBoundParameters.ContainsKey("Roles")){ Convert-RolesToObject -Roles $Roles -ObjectToUse $data } try{ $response = Invoke-RestMethod "$apiUrl/security/users" -Method Post -Body (ConvertTo-Json $data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiUser]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiUserSettings{ [OutputType([SttiUserSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiUserSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Set-SttiUserSettings{ [OutputType([SttiUserSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [string] $DisplayName, [SttiUserRoles[]] $Roles, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $Data = @{ } $hasModifications = $false if ($PSBoundParameters.ContainsKey("DisplayName")){ $Data.displayName = $DisplayName $hasModifications = $true } if ($PSBoundParameters.ContainsKey("Roles")){ Convert-RolesToObject -Roles $Roles -ObjectToUse $Data $hasModifications = $true } if (!$hasModifications){ throw "Parameters do not result in any modification" } try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId" -Method Put -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiUserSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Get-SttiUserCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::FromArray($response.data.credentials) } catch{ Write-SttiLog $_ -Level Error } } } function Add-SttiUserCertificateCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $UserCertificateThumbprint, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ $Data = @{ } $Data.certificateCredential = @{ thumbprint = $UserCertificateThumbprint description = "Set on $(Get-Date -Format "s")" } try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId/credentials" -Method Post -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::new(($response.data.credentials | Select-Object -Last 1)) } catch{ Write-SttiLog $_ -Level Error } } } function Remove-SttiUserCredential{ [OutputType([SttiCredential])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $CredentialId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId/credentials/$CredentialId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiCredential]::FromArray($response.data.credentials) } catch{ Write-SttiLog $_ -Level Error } } } function Enable-SttiUser{ [OutputType([SttiUserSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiUserSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } function Disable-SttiUser{ [OutputType([SttiUserSettings])] [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string] $UserId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Begin{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance } Process{ try{ $response = Invoke-RestMethod "$apiUrl/security/users/$UserId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return [SttiUserSettings]::new($response.data) } catch{ Write-SttiLog $_ -Level Error } } } #endregion Users #region Diagnostics function Get-SttiLogEntry{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [datetime] $From, [datetime] $To, [SttiAppLogLevel] $MinimumLevel, [string] $ClientId, [string] $FlowId, [Parameter(Mandatory)] [string] $CertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $apiUrl = Get-SttiApiUrl -Instance $Instance $filter = [System.Collections.Generic.List[string]]::new() if ($PSBoundParameters.ContainsKey("MinimumLevel")){ $filter.Add("MinimumLogLevel=$MinimumLevel") } if ($PSBoundParameters.ContainsKey("ClientId")){ $filter.Add("ClientId=$ClientId") } if ($PSBoundParameters.ContainsKey("FlowId")){ $filter.Add("FlowId=$FlowId") } if ($PSBoundParameters.ContainsKey("From")){ $filter.Add("From=" + [System.Uri]::EscapeDataString("{0:o}" -f $From)) } if ($PSBoundParameters.ContainsKey("To")){ $filter.Add("To=" + [System.Uri]::EscapeDataString("{0:o}" -f $To)) } if ($filter.Count -gt 0){ $params = [string]::Join('&', $filter) } try{ $response = Invoke-RestMethod "$apiUrl/diag/logs$($params ? '?' + $params : '')" -Body (ConvertTo-Json $data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.entries } catch{ Write-SttiLog $_ -Level Error } } #endregion Diagnostics # Ensure Stti BasePath if (!$env:SttiBasePath){ Set-SttiBasePath $defaultBasePath } # Ensure Shell Session $shellSession = [SttiShellSession]::new() # Restore default parameter Values from user defaults Restore-SttiDefaultParameterValues |