Stti.psm1
#Load or install SqlServer Module in Windows Powershell for compatibility $sqlServerModule = Import-Module SqlServer -UseWindowsPowerShell -ErrorAction SilentlyContinue -PassThru if ($null -eq $sqlServerModule){ Start-Job -PSVersion 5.1 { Install-Module SqlServer -Force -AllowClobber } | Receive-Job -Wait $sqlServerModule = Import-Module SqlServer -UseWindowsPowerShell -ErrorAction SilentlyContinue -PassThru if ($null -eq $sqlServerModule){ throw "Could not load SqlServer Module under Windows Powershell" } } $initialUserCertificatePassword = "0#dJB87I8UsL" $defaultPackageSource = "https://appdemo8.halvotec.de/v3/index.json" $defaultBasePath = "C:\STTI\" enum SttiInstanceRoles { Worker Web } enum LogLevels { Debug = 1 Verbose = 2 Info = 3 Warn = 4 Error = 5 } class SttiPackage{ [System.Version] $Version [string] $Path [bool] $IsLocal SttiPackage([Version] $version, [string] $path, [bool] $isLocal){ $this.Version = $version $this.Path = $path $this.IsLocal = $isLocal } } class SttiInstanceConfig{ [string] $Name [string] $SqlServer [string] $Database [SttiInstanceRoles[]] $Roles [string] $Path [string] $ServiceUsername [bool] $StoreDbInInstanceDataPath [string] $WorkerSslCertificateSubject [uint] $WorkerPort [string] $WebSslCertificateSubject [uint] $WebPort [string] $WorkerHostname [string] $EncryptionCertificateThumbprint [string] $EncryptionMasterKeyName } class SttiInstanceInstallStatus{ [bool] $Installed [SttiInstanceRoles[]] $Roles [Datetime] $Timestamp [string] $Version SttiInstanceInstallStatus(){ $this.Installed = $false } [string] ToString(){ if ($this.Installed -eq $true){ return "Installed Version $($this.Version) with Roles [$($this.Roles)] at $($this.Timestamp)" } else { return "Not installed" } } } class SttiInstanceStatus{ [bool] $DatabaseExists [bool] $InstanceExists [SttiInstanceInstallStatus] $InstallStatus [bool] $WorkerRoleRunning [bool] $WebRoleRunning 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 } #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, [Parameter(Mandatory)] [string] $CertificateThumbprint, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Test-UserHasAdminRole -ErrorAction Stop Write-SttiLog "Start install instance $($Instance.Name)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose Test-ADCredential $ServiceCredential -ErrorAction Stop > $null Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $true){ throw "The instance $($Instance.Name) is already installed" } $errorAction = $Force ? "Continue" : "Stop" Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Build-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction New-SttiDatabase -Instance $instance -ErrorAction $errorAction Update-SttiDatabaseUser -Instance $instance -ErrorAction $errorAction Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Start-SttiInstance -Instance $Instance -ErrorAction $errorAction Update-SttiInstanceSettings -Instance $Instance -CertificateThumbprint $CertificateThumbprint -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished install instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during install of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Install-SttiInstance -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage "latest") function Update-SttiInstance { [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [ValidateNotNullOrEmpty()] [SttiPackage] $Package = (Get-SttiPackage -VersionNumber latest), [Parameter(Mandatory)] [PSCredential] $ServiceCredential, [Parameter(Mandatory)] [string] $CertificateThumbprint, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { Test-UserHasAdminRole -ErrorAction Stop Write-SttiLog "Start update instance $($Instance.Name)" -Level Info Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Verbose Test-ADCredential $ServiceCredential -ErrorAction Stop > $null Test-SttiInstanceBinding $Instance -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $false -and $Force -eq $false){ throw "The instance $($Instance.Name) is not installed" } $errorAction = $Force ? "Continue" : "Stop" Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Build-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Expand-SttiPackage -Instance $Instance -Package $Package -ErrorAction $errorAction Edit-SttiInstanceConfigFiles -Instance $Instance -ErrorAction $errorAction Update-SttiDatabaseUser -Instance $instance -ErrorAction $errorAction Add-SttiDatabaseEncryption -Instance $instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Update-SttiDatabase -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiInstanceServices -Instance $Instance -ServiceCredential $ServiceCredential -ErrorAction $errorAction Add-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Start-SttiInstance -Instance $Instance -ErrorAction $errorAction Update-SttiInstanceSettings -Instance $Instance -CertificateThumbprint $CertificateThumbprint -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $true -Roles $Instance.Roles -Version $Package.Version -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished update instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during update of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Update-SttiInstance -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage "latest") function Uninstall-SttiInstance { [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [switch] $Force ) Start-SttiInstallLogFile -Instance $Instance -Level Verbose Write-SttiInstallLogHeader try { 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 $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose if ($installStatus.Installed -eq $false -and $Force -eq $false){ throw "The instance $($Instance.Name) is not installed" } $errorAction = $Force ? "Continue" : "Stop" Stop-SttiInstance -Instance $Instance -ErrorAction $errorAction Remove-SttiInstanceServices -Instance $Instance -ErrorAction $errorAction Remove-SttiFirewallRules -Instance $Instance -ErrorAction $errorAction Clear-SttiInstanceDirectory -Instance $Instance -ErrorAction $errorAction Set-SttiInstanceInstallStatus -Instance $Instance -Installed $false -Roles $null -Version $null -ErrorAction Stop > $null $instanceStatus = Get-SttiInstanceStatus -Instance $Instance -ErrorAction Stop $installStatus = $instanceStatus.InstallStatus Write-SttiLog "Current instance status" -Level Verbose $instanceStatus | Write-SttiLog -Level Verbose Write-SttiLog "Finished uninstall instance $($Instance.Name)" -Extraline -Level Info } catch { Write-SttiLog "An error occurred during uninstall of the instance" -Level Warn Write-SttiLog $_ -Level Error Write-SttiLog $_.Exception -Level Info Write-SttiLog $_.ScriptStackTrace -Level Info } finally{ Write-SttiInstallLogFooter Stop-SttiInstallLogFile } } #Uninstall-SttiInstance -Instance (Get-SttiInstanceConfig dev) function Get-SttiInstanceInstallStatus{ [OutputType([SttiInstanceInstallStatus])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance")] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [X509Certificate] $Certificate ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $installStatus = [SttiInstanceInstallStatus]::new() if ($null -eq $Instance){ $Instance = Get-SttiInstanceConfig -Name $Name -ErrorAction Stop if ($null -eq $Instance){ return $installStatus } } $instanceInstallStatusFile = Get-SttiInstanceInstallStatusFileName -Name $Instance.Name if (-not (Test-Path $instanceInstallStatusFile)){ Write-SttiLog "The instance installation status $instanceInstallStatusFile does not exists" -Level Verbose return $installStatus } return [SttiInstanceInstallStatus](Get-Content $instanceInstallStatusFile -Raw | ConvertFrom-Json) } #Get-SttiInstanceInstallStatus -Name dev #Get-SttiInstanceInstallStatus -Instance (Get-SttiInstanceConfig -Name dev) function Set-SttiInstanceInstallStatus{ [OutputType([SttiInstanceInstallStatus])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance")] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [bool] $Installed, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $Version, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [SttiInstanceRoles[]] $Roles ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug if ($null -ne $Instance){ $Name = $Instance.Name } $config = Get-SttiInstanceConfig -Name $Name -ErrorAction Stop if ($null -eq $config){ throw "The instance configuration does not exist" } $installStatus = Get-SttiInstanceInstallStatus -Name $Name -ErrorAction Stop if ($PSBoundParameters.ContainsKey("Installed")){ $installStatus.Installed = $Installed } if ($PSBoundParameters.ContainsKey("Version")){ $installStatus.Version = $Version } if ($PSBoundParameters.ContainsKey("Roles")){ $installStatus.Roles = $Roles } $installStatus.Timestamp = Get-Date $instanceInstallStatusFile = Get-SttiInstanceInstallStatusFileName $Name $installStatus | ConvertTo-Json -ErrorAction Stop | Out-File $instanceInstallStatusFile -Encoding UTF8 -ErrorAction Stop $PSCmdlet.WriteObject($installStatus) } #Set-SttiInstanceInstallStatus -Name dev -Installed $true -Roles Web, Worker -Version 1.0.0.0 #Set-SttiInstanceInstallStatus -Name dev -Installed $false -Roles @() -Version $null function Clear-SttiInstanceDirectory{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $instanceDirectory = Get-Item -Path $Instance.Path -ErrorAction Stop $instanceDirectory | Get-ChildItem -Exclude "data", "config.json", "install.json" -ErrorAction Stop | Remove-Item -Recurse -Force -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Clear-SttiInstanceDirectory -Instance (Get-SttiInstanceConfig dev) function Build-SttiInstanceDirectory{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $instanceDirectory = Get-Item -Path $Instance.Path New-Item -Path $instanceDirectory -Name "bin" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null $dataDirectory = Get-Item -Path ([System.IO.Path]::Combine($instanceDirectory, "data")) -ErrorAction SilentlyContinue || New-Item -Path $instanceDirectory -Name "data" -ItemType Directory -ErrorAction Stop New-Item -Path $dataDirectory -Name "logs" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null New-Item -Path $dataDirectory -Name "clients" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null if ($Instance.StoreDbInInstanceDataPath -eq $true){ New-Item -Path $dataDirectory -Name "db" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null } # Grant permissions for service user # Read permissions for instance directory Write-SttiLog "Set instance directory permissions" -Level Verbose $instanceDirectoryAcl = Get-Acl -Path $instanceDirectory -ErrorAction Stop $instanceDirectoryAcl.SetAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($Instance.ServiceUsername, "Read", [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.PropagationFlags]::None, "Allow")) Set-Acl -Path $instanceDirectory -AclObject $instanceDirectoryAcl -ErrorAction Stop # Full control for data directory Write-SttiLog "Set data directory permissions" -Level Verbose $dataDirectoryAcl = Get-Acl -Path $dataDirectory -ErrorAction Stop $dataDirectoryAcl.SetAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new($Instance.ServiceUsername, "FullControl", [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit, [System.Security.AccessControl.PropagationFlags]::None, "Allow")) Set-Acl -Path $dataDirectory -AclObject $dataDirectoryAcl -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Build-SttiInstanceDirectory -Instance (Get-SttiInstanceConfig dev) function Expand-SttiPackage{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiPackage] $Package ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $tempPath = [System.IO.Path]::GetTempPath() $tempWorkingDir = New-Item -Name (Get-Random) -Path $tempPath -ItemType Directory -ErrorAction Stop Expand-Archive $Package.Path -DestinationPath $tempWorkingDir -ErrorAction Stop $tempContentPath = [System.IO.Path]::Combine($tempWorkingDir.FullName, "Content") $binPath = [System.IO.Path]::Combine($Instance.Path, "bin\") Get-ChildItem -Path $tempContentPath | Copy-Item -Destination $binPath -Recurse -Force -ErrorAction Stop $tempWorkingDir | Remove-Item -Recurse -Force -ErrorAction Continue } catch{ Write-SttiLog $_ -Level Error } } #Expand-SttiPackage -Instance (Get-SttiInstanceConfig dev) -Package (Get-SttiPackage 0.0.0.1) function Edit-SttiInstanceConfigFiles{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $sqlConnectionString = Get-SttiDatabaseConnectionString -Instance $Instance try{ # Set database connection Get-ChildItem $Instance.Path -Exclude "data" -Include "appsettings.json" -Recurse -PipelineVariable "currentFile" -ErrorAction Stop | Copy-Item -Destination {"$($_.FullName).bak"} -Force -PassThru -PipelineVariable "backupFile" -ErrorAction Stop # create backup file | ForEach-Object { Get-Content -Path $currentFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } # Read content as string in one block and deserialize json | ForEach-Object { if ($currentFile.FullName -like "*\halvotec.stti.worker\*"){ $port = $Instance.WorkerPort $sslCertificateSubject = $Instance.WorkerSslCertificateSubject } elseif ($currentFile.FullName -like "*\halvotec.stti.web\*"){ $port = $Instance.WebPort $sslCertificateSubject = $Instance.WebSslCertificateSubject } if ($_.Stti -and $_.Stti.Connections -and $_.Stti.Connections.DBConnection) { $_.Stti.Connections.DBConnection = $sqlConnectionString } if ($_.SttiWeb -and $_.SttiWeb.SttiWorkerService ) { $_.SttiWeb.SttiWorkerService = "https://$($Instance.WorkerHostname):$($Instance.WorkerPort)" } if ($_.Kestrel){ if ($_.Kestrel.Endpoints -and $_.Kestrel.Endpoints.HttpsDefault -and $_.Kestrel.Endpoints.HttpsDefault.Url){ $_.Kestrel.Endpoints.HttpsDefault.Url = "https://*:$($port)" } if ($_.Kestrel.Certificates -and $_.Kestrel.Certificates.Default){ $_.Kestrel.Certificates.Default = [PSCustomObject]@{ Subject = $sslCertificateSubject Store = "My" Location = "CurrentUser" AllowInvalid = $true } } } $_ } # Replace connection string | ForEach-Object { ConvertTo-Json -InputObject $_ -Depth 50 -ErrorAction Stop | Out-File $currentFile -Encoding utf8 -ErrorAction Stop } # Serialize to Json and write to file | ForEach-Object { Remove-Item $backupFile -ErrorAction Continue } # Remove backup file } catch{ Write-SttiLog $_ -Level Error } } #Edit-SttiInstanceConfigFiles -Instance (Get-SttiInstanceConfig dev) function Add-SttiInstanceServices{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ Write-SttiLog "Grant LogonAsService right to service user" -Level Verbose Grant-LogonAsServiceRight $ServiceCredential.UserName -ErrorAction Stop $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) if ($Instance.Roles.Contains([SttiInstanceRoles]::Worker)){ if ($null -eq (Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue)){ $workerBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker\Halvotec.Stti.Worker.exe") Write-SttiLog "Create service for worker" -Level Verbose New-Service -Name $workerServiceName -BinaryPathName $workerBinaryPath -StartupType Automatic -Credential $ServiceCredential -Description "Halvotec STTI $($Instance.Name) Worker" -ErrorAction Stop > $null } else{ Write-SttiLog "Service for worker already exists" -Level Verbose } } else { Write-SttiLog "Remove service for worker" Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) if ($Instance.Roles.Contains([SttiInstanceRoles]::Web)){ if ($null -eq (Get-Service -Name $webServiceName -ErrorAction SilentlyContinue)){ $webBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Web\Halvotec.Stti.Web.exe") Write-SttiLog "Create service for web" -Level Verbose New-Service -Name $webServiceName -BinaryPathName $webBinaryPath -StartupType Automatic -Credential $ServiceCredential -Description "Halvotec STTI $($Instance.Name) Web" -ErrorAction Stop > $null } else{ Write-SttiLog "Service for web already exists" -Level Verbose } } else { Write-SttiLog "Remove service for web" Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } } catch{ Write-SttiLog $_ -Level Error } } #Install-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) -ServiceCredential (Get-Credential) function Remove-SttiInstanceServices{ [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $workerServiceName = (Get-SttiServiceName -Instance $Instance -Role Worker) Write-SttiLog "Remove service for worker" -Level Verbose Get-Service -Name $workerServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } try{ $webServiceName = (Get-SttiServiceName -Instance $Instance -Role Web) Write-SttiLog "Remove service for web" -Level Verbose Get-Service -Name $webServiceName -ErrorAction SilentlyContinue | Remove-Service -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Uninstall-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) function Get-SttiServiceName{ [OutputType([string])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [SttiInstanceRoles] $Role ) return "STTI.$($Instance.Name).$Role" } 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" } catch{ Write-SttiLog $_ -Level Error } } #Uninstall-SttiInstanceServices -Instance (Get-SttiInstanceConfig dev) #endregion Installation #region Certificate management function New-SttiUserCertificate{ [OutputType([X509Certificate])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [string] $Subject, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(10) ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug return New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation Cert:CurrentUser\My -KeyExportPolicy Exportable -Type Custom -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -NotAfter $NotAfter -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2") -ErrorAction Stop } function New-SttiDatabaseEncryptionKey{ [OutputType([X509Certificate])] [CmdletBinding()] Param( # Parameter help description [Parameter(Mandatory)] [string] $Subject, [Parameter(Mandatory)] [string] $User, [string] $FriendlyName = $Subject, [DateTime] $NotAfter = (Get-Date).AddYears(10) ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $location = "Cert:CurrentUser\My" return New-SelfSignedCertificate -Subject $Subject -FriendlyName $FriendlyName -CertStoreLocation $location -KeyExportPolicy Exportable -Type DocumentEncryptionCert -KeyUsage KeyEncipherment -KeySpec KeyExchange -KeyLength 2048 -NotAfter $NotAfter -ErrorAction Stop } function Get-SttiDatabaseEncryptionKey { [OutputType([X509Certificate])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Thumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -eq $key){ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Thumbprint } if ($null -eq $key){ Write-SttiLog "No database encryption key was found with thumbprint $Thumbprint" -Level Error } return $key } function Get-SttiDatabaseEncryptionKeyLocation { [OutputType([string])] [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Thumbprint ) $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -ne $key){ Write-SttiLog "Found database encryption key in Cert:CurrentUser\My" -Level Verbose return "CurrentUser" } else{ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Thumbprint if ($null -ne $key){ Write-SttiLog "Found database encryption key in Cert:LocalMachine\My" -Level Verbose return "LocalMachine" } else { Write-SttiLog "No database encryption key was found with thumbprint $Thumbprint" -Level Error return $null } } } #endregion Certificate management #region Instance Management function Get-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [string] $Name ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $instanceNames = $PSBoundParameters.ContainsKey("Name") ? @($Name) : (Get-ChildItem -Path "$([System.IO.Path]::Combine((Get-SttiBasePath), "instances\*"))" | Select-Object -ExpandProperty Name) foreach ($name in $instanceNames) { $instanceConfigFile = Get-SttiInstanceConfigFileName -Name $Name if (-not (Test-Path $instanceConfigFile)){ Write-SttiLog "The instance configuration $instanceConfigFile does not exists" -Level Verbose continue } $config = [SttiInstanceConfig](Get-Content $instanceConfigFile -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) $config.Path = Get-SttiInstancePath -Name $Name Write-Output $config } } #Get-SttiInstanceConfig -Name "dev" function New-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter(Mandatory=$true)] [string] $SqlServer, [Parameter()] [string] $Database = "STTI_$($Name.ToUpperInvariant())", [Parameter(Mandatory=$true)] [SttiInstanceRoles[]] $Roles, [Parameter(Mandatory=$true)] [string] $ServiceUsername, [Parameter()] [switch] $StoreDbInInstanceDataPath = $false, [Parameter(Mandatory)] [string] $WorkerSslCertificateSubject, [Parameter(Mandatory)] [uint] $WorkerPort, [Parameter(Mandatory)] [string] $WebSslCertificateSubject, [Parameter(Mandatory)] [uint] $WebPort, [Parameter()] [string] $WorkerHostname = "localhost", [Parameter(Mandatory)] [string] $EncryptionCertificateThumbprint ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $Name = $Name.ToUpperInvariant() $instancePath = Get-SttiInstancePath $Name if (-not (Test-Path $instancePath)){ New-Item $instancePath -ItemType Directory -ErrorAction Stop | Out-Null } $instanceConfigFile = Get-SttiInstanceConfigFileName $Name if ((Test-Path $instanceConfigFile)){ throw "The instance configuration $instanceConfigFile already exists" } $config = [SttiInstanceConfig]::new() $config.Name = $Name $config.SqlServer = $SqlServer $config.Database = $Database $config.Roles = $Roles $config.Path = $instancePath $config.ServiceUsername = $ServiceUsername $config.StoreDbInInstanceDataPath = $StoreDbInInstanceDataPath $config.WorkerSslCertificateSubject = $WorkerSslCertificateSubject $config.WorkerPort = $WorkerPort $config.WebSslCertificateSubject = $WebSslCertificateSubject $config.WebPort = $WebPort $config.WorkerHostname = $WorkerHostname $config.EncryptionCertificateThumbprint = $EncryptionCertificateThumbprint $config.EncryptionMasterKeyName = "STTI_$EncryptionCertificateThumbprint" $config | ConvertTo-Json -ErrorAction Stop | Out-File $instanceConfigFile -Encoding utf8 -ErrorAction Stop return $config } #New-SttiInstanceConfig -Name "DEV" -SqlServer localdb -Database SttiDev -Roles Worker, Web function Set-SttiInstanceConfig{ [OutputType([SttiInstanceConfig])] [CmdletBinding()] Param( [Parameter(Mandatory, ParameterSetName="InstanceName")] [string] $Name, [Parameter(Mandatory, ParameterSetName="Instance", ValueFromPipeline)] [SttiInstanceConfig] $Instance, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $SqlServer, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $Database, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [SttiInstanceRoles[]] $Roles, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $ServiceUsername, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [switch] $StoreDbInInstanceDataPath = $false, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WorkerSslCertificateSubject, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [uint] $WorkerPort, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WebSslCertificateSubject, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [uint] $WebPort, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $WorkerHostname, [Parameter(ParameterSetName="InstanceName")] [Parameter(ParameterSetName="Instance")] [string] $EncryptionCertificateThumbprint ) Process{ Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug if ($null -ne $Instance){ $Name = $Instance.Name } $config = Get-SttiInstanceConfig ($Name) -ErrorAction Stop if ($null -eq $config){ throw "The instance configuration does not exist" } if ($PSBoundParameters.ContainsKey("SqlServer")){ $config.SqlServer = $SqlServer } if ($PSBoundParameters.ContainsKey("Database")){ $config.Database = $Database } if ($PSBoundParameters.ContainsKey("Roles")){ $config.Roles = $Roles } if ($PSBoundParameters.ContainsKey("ServiceUsername")){ $config.ServiceUsername = $ServiceUsername } if ($PSBoundParameters.ContainsKey("StoreDbInInstanceDataPath")){ $config.StoreDbInInstanceDataPath = $StoreDbInInstanceDataPath } if ($PSBoundParameters.ContainsKey("WorkerSslCertificateSubject")){ $config.WorkerSslCertificateSubject = $WorkerSslCertificateSubject } if ($PSBoundParameters.ContainsKey("WorkerPort")){ $config.WorkerPort = $WorkerPort } if ($PSBoundParameters.ContainsKey("WebSslCertificateSubject")){ $config.WebSslCertificateSubject = $WebSslCertificateSubject } if ($PSBoundParameters.ContainsKey("WebPort")){ $config.WebPort = $WebPort } if ($PSBoundParameters.ContainsKey("WorkerHostname")){ $config.WorkerHostname = $WorkerHostname } if ($PSBoundParameters.ContainsKey("EncryptionCertificateThumbprint")){ if ($null -ne $config.EncryptionCertificateThumbprint -and $EncryptionCertificateThumbprint -ne $config.EncryptionCertificateThumbprint){ Write-SttiLog "The EncryptionCertificateThumbprint has already been set and cannot be changed. Use Rotate-SttiEncryptionMasterKey" -Level Error } $config.EncryptionCertificateThumbprint = $EncryptionCertificateThumbprint $config.EncryptionMasterKeyName = "STTI_$EncryptionCertificateThumbprint" } $instanceConfigFile = Get-SttiInstanceConfigFileName $Name $config | ConvertTo-Json -ErrorAction Stop | Out-File $instanceConfigFile -Encoding utf8 -ErrorAction Stop $PSCmdlet.WriteObject($config) } } #Set-SttiInstanceConfig -InstanceName dev -Database "Test" -Roles Web, Worker #Get-SttiInstanceConfig dev | Set-SttiInstanceConfig -Database "Test" -Roles Web function 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 if ($null -eq $Instance){ throw "Instance $Name does not exist" } } $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 patth 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 ) 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 = " IF NOT EXISTS (SELECT name FROM master.sys.server_principals WHERE name = '$($Instance.ServiceUsername)') BEGIN CREATE LOGIN [$($Instance.ServiceUsername)] FROM WINDOWS WITH DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english] END GO USE [$($Instance.Database)] GO IF NOT EXISTS (SELECT [login].name, [dbuser].name FROM sys.syslogins AS [login] INNER JOIN sys.database_principals [dbuser] ON [login].sid = [dbuser].sid WHERE [login].name = '$($Instance.ServiceUsername)') BEGIN CREATE USER [$($Instance.ServiceUsername)] FOR LOGIN [$($Instance.ServiceUsername)] ALTER ROLE [db_owner] ADD MEMBER [$($Instance.ServiceUsername)] END GO" Invoke-Sqlcmd -ServerInstance $Instance.SqlServer -Database "master" -Query $command -ErrorAction Stop } catch{ Write-SttiLog $_ -Level Error } } #Get-SttiInstanceConfig dev | New-SttiDatabase -EncryptionMasterKey $null function Add-SttiDatabaseEncryption{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter()] [PSCredential] $ServiceCredential = $null ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ $scriptBlock = { param( [string] $ScriptRoot, $Instance ) $key = Get-ChildItem -Path "Cert:CurrentUser\My" | Where-Object Thumbprint -eq $Instance.EncryptionCertificateThumbprint if ($null -ne $key){ Write-Verbose "Found database encryption key in Cert:CurrentUser\My" $certificateStoreLocation = "CurrentUser" } else{ $key = Get-ChildItem -Path "Cert:LocalMachine\My" | Where-Object Thumbprint -eq $Instance.EncryptionCertificateThumbprint if ($null -ne $key){ Write-Verbose "Found database encryption key in Cert:LocalMachine\My" $certificateStoreLocation = "LocalMachine" } else { throw "No database encryption key was found with thumbprint $($Instance.EncryptionCertificateThumbprint)" } } $db = Get-SqlDatabase -Name $Instance.Database -ServerInstance $Instance.SqlServer -ErrorAction Stop if ($null -eq (Get-SqlColumnMasterKey -Name $Instance.EncryptionMasterKeyName -InputObject $db -ErrorAction SilentlyContinue)){ $masterKeySettings = New-SqlCertificateStoreColumnMasterKeySettings -Thumbprint $Instance.EncryptionCertificateThumbprint -CertificateStoreLocation $certificateStoreLocation -ErrorAction Stop New-SqlColumnMasterKey -Name $Instance.EncryptionMasterKeyName -InputObject $db -ColumnMasterKeySettings $masterKeySettings -ErrorAction Stop > $null } if ($null -eq (Get-SqlColumnEncryptionKey -Name "CEK" -InputObject $db -ErrorAction SilentlyContinue)){ New-SqlColumnEncryptionKey -Name "CEK" -InputObject $db -ColumnMasterKeyName $Instance.EncryptionMasterKeyName -ErrorAction Stop > $null } } $argumentList = @($PSScriptRoot, $Instance) # Workaround, since start-job with credentials cannot be called in remoting session if ($null -eq $ServiceCredential -or ($env:useCurrentUser -and $env:useCurrentUser -eq $true)){ Start-Job -ScriptBlock $scriptBlock -ArgumentList $argumentList -PSVersion 5.1 | Receive-Job -Wait -ErrorAction Stop } else{ Start-Job -Credential $ServiceCredential -ScriptBlock $scriptBlock -ArgumentList $argumentList -PSVersion 5.1 | Receive-Job -Wait -ErrorAction Stop } } catch{ Write-SttiLog $_ -Level Error } } function Update-SttiDatabase{ [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ if ($Instance.Roles.Contains([SttiInstanceRoles]::Worker)){ try{ $workerPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker") $workerBinaryPath = [System.IO.Path]::Combine($Instance.Path, "bin\Halvotec.Stti.Worker\Halvotec.Stti.Worker.exe") $outPath = [System.IO.Path]::Combine($Instance.Path, "data\logs\migratedb-out.txt") $errorPath = [System.IO.Path]::Combine($Instance.Path, "data\logs\migratedb-err.txt") #Workaround for running in remoting session where no different credentials can be used if ($env:useCurrentUser -and $env:useCurrentUser -eq $true){ $process = Start-Process -FilePath $workerBinaryPath -ArgumentList "migratedb" -WorkingDirectory $workerPath -Wait -PassThru -RedirectStandardOutput $outPath -RedirectStandardError $errorPath -ErrorAction Stop } else{ $process = Start-Process -FilePath $workerBinaryPath -ArgumentList "migratedb" -WorkingDirectory $workerPath -Credential $ServiceCredential -Wait -PassThru -RedirectStandardOutput $outPath -RedirectStandardError $errorPath -ErrorAction Stop } $exitCode = $process.ExitCode Get-Content $outPath -ErrorAction SilentlyContinue | Write-SttiLog -Level Verbose Get-Content $errorPath -ErrorAction SilentlyContinue | Write-SttiLog -Level Warn if ($exitCode -ne 0){ Write-SttiLog "Halvotec.Stti.Worker.exe exited with code $exitCode" -Level Error } } finally{ Remove-Item $outPath -ErrorAction SilentlyContinue Remove-Item $errorPath -ErrorAction SilentlyContinue } } } catch{ Write-SttiLog $_ -Level Error } } function Test-SttiDatabaseEncryption{ [OutputType([bool])] [CmdletBinding()] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance # [Parameter(Mandatory)] # [PSCredential] $ServiceCredential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug try{ return $true } catch{ Write-SttiLog $_ -Level Error return $false } } function Get-SttiDatabaseConnectionString{ [OutputType([string])] Param( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance ) return "Server=$($Instance.SqlServer);Database=$($Instance.Database);Integrated Security=true;Column Encryption Setting=enabled;ApplicationIntent=ReadWrite;Language=English" } #endregion Database Management #region Package Management $deploymentFilePattern = "^SttiApp.(?<version>\d+\.\d+\.\d+(?:\.\d+)?)\.nupkg$" function Get-SttiPackage { [OutputType([SttiPackage])] [CmdletBinding()] Param( [ValidatePattern("^\d+\.\d+\.\d+(?:\.\d+)?$|^latest$")] [string] $VersionNumber = "latest" ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $deploymentFiles = Get-ChildItem "$([System.IO.Path]::Combine((Get-SttiBasePath), "Packages", "*.nupkg"))" $packages = $deploymentFiles | Where-Object -Property Name -Match $deploymentFilePattern | ForEach-Object {([SttiPackage]::new([System.Version]::Parse($Matches.version), $_.FullName, $true))} if ($VersionNumber -eq "latest"){ $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 "ComputerInfo:" -Level Verbose Get-ComputerInfo | Write-SttiLog -Level Verbose Write-SttiLog "HostInfo:" -Level Verbose Get-Host | Write-SttiLog -Level Verbose Write-SttiLog "----------------------------------------------------------------" -Level Info -Extraline } function Write-SttiInstallLogFooter{ [CmdletBinding()] Param( ) Write-SttiLog "----------------------------------------------------------------" -Level Info } function Write-SttiLog{ [CmdletBinding()] Param( [switch] $Extraline, [LogLevels] $Level = [LogLevels]::Info, [Parameter(ValueFromPipeline, Position=0)] [object[]] $InputObject ) Process{ if ($null -ne $_){ $i = $_ } elseif ($null -ne $InputObject) { $i = $InputObject } else { throw "InputObject has no value" } # Write to log if existing if ($Script:logFile -and $null -ne $Script:logFile ` -and $Level -ge ($Script:logLevel ?? [LogLevels]::Verbose)) { $i | ForEach-Object{ if ($_ -is [string]) { "$(Get-Date) $($Level.ToString().ToUpperInvariant()) $_" } else { "$(Get-Date) $($Level.ToString().ToUpperInvariant()) $($_ | Format-List | Out-String)" }} | Out-File -FilePath $Script:logFile -Append } switch ($Level){ "Error" { if ($i -is [System.Management.Automation.ErrorRecord]){ Write-Error -ErrorRecord $i } elseif ($i -is [System.Exception]){ Write-Error -Exception $i } else{ Write-Error ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } } "Warn" { Write-Warning ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } "Info" { Write-Output $i } "Verbose" { Write-Verbose ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } "Debug" { Write-Debug ($i | ForEach-Object{ if ($_ -is [string]) {$_} else { $_ | Format-List | Out-String }}) } } } End{ if ($Extraline -eq $true){ if ($Script:logFile -and $null -ne $Script:logFile) { "" | Out-File -FilePath $Script:logFile -Append } switch ($Level){ "Error" { #Write-Error "" } "Warn" { Write-Warning "" } "Info" { Write-Output "" } "Verbose" { Write-Verbose "" } "Debug" { Write-Debug "" } } } } } #"Hallo" | Write-SttiLog -FilePath "Test.txt" #endregion Logging #region Helper function Test-ADCredential { [OutputType([bool])] [CmdletBinding()] Param ( [Parameter(Mandatory)] [pscredential] $credential ) Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand)" -Level Verbose Write-SttiLog "$($PSCmdlet.MyInvocation.MyCommand) was called with parameters $($PSCmdlet.MyInvocation.BoundParameters | ConvertTo-Json -Compress)" -Level Debug $username = $credential.UserName $password = $credential.GetNetworkCredential().Password if (!($UserName) -or !($Password)) { Write-SttiLog "Please specify both user name and password" -Level Error return $false } else { Add-Type -AssemblyName System.DirectoryServices.AccountManagement $isValid = $false try{ $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('domain') $isValid = $DS.ValidateCredentials($UserName, $Password) return $isValid } catch{ Write-SttiLog -Exception $_ -Message "Could not validate the credentials. An error occurred." -Level Error } if (-not ($isValid)){ Write-SttiLog "Could not validate the credentials. Username or password seems to be invalid." -Level Error } } } 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] $ServiceUserName, [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 ) Test-UserHasAdminRole -ErrorAction Stop if ($ServiceCredential){ $ServiceUserName = $ServiceCredential.UserName } # Create instance stub $instance = Get-SttiInstanceConfig -Name $InstanceName if ($null -eq $instance) { $instance = New-SttiInstanceConfig -Name $InstanceName -SqlServer $SqlServer -Database $Database -Roles Worker, Web -ServiceUserName $ServiceUserName -StoreDbInInstanceDataPath:$StoreDbInInstanceDataPath -WorkerSslCertificateSubject $WorkerSslCertificateSubject -WorkerPort $WorkerPort -WebPort $WebPort -WebSslCertificateSubject $WebSslCertificateSubject -WorkerHostname $WorkerHostname -EncryptionCertificateThumbprint $EncryptionCertificateThumbprint } else { $instance = Set-SttiInstanceConfig -Instance $instance -SqlServer $SqlServer -Database $Database -Roles Worker, Web -ServiceUserName $ServiceUserName -StoreDbInInstanceDataPath:$StoreDbInInstanceDataPath -WorkerSslCertificateSubject $WorkerSslCertificateSubject -WorkerPort $WorkerPort -WebPort $WebPort -WebSslCertificateSubject $WebSslCertificateSubject -WorkerHostname $WorkerHostname -EncryptionCertificateThumbprint $EncryptionCertificateThumbprint } $status = Get-SttiInstanceStatus -Instance $instance -Certificate $adminCert if ($Uninstall -eq $false){ $package = Get-SttiPackage $Version if ($status.InstallStatus.Installed -eq $false){ # Get credentials for service user $svcCred = $ServiceCredential ?? (Get-Credential -UserName $instance.ServiceUserName -Message "Please enter the credentials for the service user") Install-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force -CertificateThumbprint $AdminCertificateThumbprint } elseif ($status.InstallStatus.Version -ne $package.Version -or $Force){ $svcCred = $ServiceCredential ?? (Get-Credential -UserName $instance.ServiceUserName -Message "Please enter the credentials for the service user") Update-SttiInstance -Instance $instance -Package $package -ServiceCredential $svcCred -Force:$Force -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{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [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 $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(ParameterSetName="AsParameters")] [uint] $AcceleratedTimeFactor, [Parameter(ParameterSetName="SettingUpdates")] [Parameter(ParameterSetName="AsParameters")] [string] $CertificateThumbprint, [Parameter(ParameterSetName="SettingUpdates")] [Parameter(ParameterSetName="AsParameters")] [switch] $PassThrough = $false ) 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 } if ($PSBoundParameters.ContainsKey("AcceleratedTimeFactor")){ if ($null -eq $AcceleratedTimeFactor){ $SettingUpdates.time = @{} } else{ $SettingUpdates.time = @{ acceleratedTimeService = @{ factor = $AcceleratedTimeFactor} } } } } try{ Invoke-RestMethod "$apiUrl/config/system" -Method Put -Body (ConvertTo-Json $SettingUpdates) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop > $null if ($PassThrough){ Get-SttiSystemSettings -Instance $Instance -Credential:$Credential } } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiModules{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [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/modules" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $config.data } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiModuleSettings{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ModuleId, [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/modules/$ModuleId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $config.data.settings } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiModuleSettings{ [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName="SettingUpdates")] [Parameter(Mandatory, ParameterSetName="AsParameters")] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ParameterSetName="SettingUpdates")] [Parameter(Mandatory, ParameterSetName="AsParameters")] [string] $ModuleId, [Parameter(Mandatory, ParameterSetName="SettingUpdates")] $SettingUpdates, [Parameter(ParameterSetName="AsParameters")] $M2MFlowSimulationHandler, [Parameter(ParameterSetName="SettingUpdates")] [Parameter(ParameterSetName="AsParameters")] [string] $CertificateThumbprint, [Parameter(ParameterSetName="SettingUpdates")] [Parameter(ParameterSetName="AsParameters")] [switch] $PassThrough = $false ) 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("M2MFlowSimulationHandler")){ $SettingUpdates.m2MFlowSimulationHandler = $M2MFlowSimulationHandler } } try{ Invoke-RestMethod "$apiUrl/config/modules/$ModuleId/settings" -Method Put -Body (ConvertTo-Json $SettingUpdates) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop > $null if ($PassThrough){ Get-SttiModuleSettings -Instance $Instance -ModuleId $ModuleId -Credential:$Credential } } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiModuleSecrets{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ModuleId, [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/modules/$ModuleId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $config.data.secrets } catch{ Write-SttiLog $_ -Level Error } } function New-SttiModuleSecret{ [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory, ParameterSetName="TokenSecret")] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ParameterSetName="TokenSecret")] [string] $ModuleId, [Parameter(Mandatory, ParameterSetName="TokenSecret")] [string] $Token, [Parameter(ParameterSetName="TokenSecret")] [datetime] $ValidFrom, [Parameter(ParameterSetName="TokenSecret")] [datetime] $ValidThru, [Parameter(ParameterSetName="TokenSecret")] [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("Token")){ $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/secrets" -Method Post -Body (ConvertTo-Json $Data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop $result.data.id } catch{ Write-SttiLog $_ -Level Error } } function Remove-SttiModuleSecret{ [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory, ParameterSetName="TokenSecret")] [SttiInstanceConfig] $Instance, [Parameter(Mandatory, ParameterSetName="TokenSecret")] [string] $ModuleId, [Parameter(Mandatory, ParameterSetName="TokenSecret")] [string] $Id, [Parameter(ParameterSetName="TokenSecret")] [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{ Invoke-RestMethod "$apiUrl/config/modules/$ModuleId/secrets/$Id" -Method Delete -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop > $null } catch{ Write-SttiLog $_ -Level Error } } #endregion Config #region Clients function Get-SttiClients{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [string] $ClientId, [string] $DisplayName, [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 $clients | Where-Object { (!$PSBoundParameters.ContainsKey("ClientId") -or $_.id -like $ClientId) -and (!$PSBoundParameters.ContainsKey("DisplayName") -or $_.displayName -like $DisplayName) } } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiClientSettings{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [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/$ClientId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function New-SttiClient{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $DisplayName, [string] $ClientCertificateThumbprint, [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 $response.data } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiClientSettings{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [string] $DisplayName, [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 = @{ } $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 $response.data } catch{ Write-SttiLog $_ -Level Error } } function Add-SttiClientCredential{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [string] $ClientCertificateThumbprint, [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 = @{ } $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 $response.data } catch{ Write-SttiLog $_ -Level Error } } function Remove-SttiClientCredential{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [string] $CredentialId, [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/$ClientId/credentials/$CredentialId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Enable-SttiClient{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [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/$ClientId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Disable-SttiClient{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [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/$ClientId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiClientEncryptionKeys{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [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/$ClientId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data.encryption.keys } catch{ Write-SttiLog $_ -Level Error } } function New-SttiClientEncryptionKey{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [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 $data = @{ key = @{ algorithm = "Aes256" } } $apiUrl = Get-SttiApiUrl -Instance $Instance try{ $response = Invoke-RestMethod "$apiUrl/security/clients/$ClientId/encryption/symmetrickeys" -Method Post -Body (ConvertTo-Json $data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data.encryption.keys | Select-Object -Last 1 } catch{ Write-SttiLog $_ -Level Error } } function Enable-SttiClientEncryptionKey{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [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/$ClientId/encryption/symmetrickeys/$KeyId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data.encryption.keys | Where-Object { $_.keyId -like $KeyId } } catch{ Write-SttiLog $_ -Level Error } } function Disable-SttiClientEncryptionKey{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [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/$ClientId/encryption/symmetrickeys/$KeyId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data.encryption.keys | Where-Object { $_.keyId -like $KeyId } } catch{ Write-SttiLog $_ -Level Error } } function Remove-SttiClientEncryptionKey{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $ClientId, [Parameter(Mandatory)] [string] $KeyId, [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/$ClientId/encryption/symmetrickeys/$KeyId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data.encryption.keys } catch{ Write-SttiLog $_ -Level Error } } #endregion Clients #region Users function Get-SttiUsers{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [string] $UserId, [string] $DisplayName, [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 $users | Where-Object { (!$PSBoundParameters.ContainsKey("UserId") -or $_.id -like $UserId) -and (!$PSBoundParameters.ContainsKey("DisplayName") -or $_.displayName -like $DisplayName) } } catch{ Write-SttiLog $_ -Level Error } } function Get-SttiUserSettings{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [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/$UserId" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function New-SttiUser{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $DisplayName, [string] $UserCertificateThumbprint, [ValidateSet("OperationsAdmin", "SecurityAdmin", "DataAdmin", "HealthWatcher")] [string[]] $Roles, [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")){ $Data.operationsAdminRole = $Roles.Contains("OperationsAdmin") $Data.securityAdminRole = $Roles.Contains("SecurityAdmin") $Data.dataAdminRole = $Roles.Contains("DataAdmin") $Data.healthWatcherRole = $Roles.Contains("HealthWatcher") } try{ $Data $response = Invoke-RestMethod "$apiUrl/security/users" -Method Post -Body (ConvertTo-Json $data) -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Set-SttiUserSettings{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [string] $DisplayName, [ValidateSet("OperationsAdmin", "SecurityAdmin", "DataAdmin", "HealthWatcher")] [string[]] $Roles, [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 = @{ } $hasModifications = $false if ($PSBoundParameters.ContainsKey("DisplayName")){ $Data.displayName = $DisplayName $hasModifications = $true } if ($PSBoundParameters.ContainsKey("Roles")){ $Data.operationsAdminRole = $Roles.Contains("OperationsAdmin") $Data.securityAdminRole = $Roles.Contains("SecurityAdmin") $Data.dataAdminRole = $Roles.Contains("DataAdmin") $Data.healthWatcherRole = $Roles.Contains("HealthWatcher") $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 $response.data } catch{ Write-SttiLog $_ -Level Error } } function Add-SttiUserCredential{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [Parameter(Mandatory)] [string] $UserCertificateThumbprint, [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 = @{ } $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 $response.data } catch{ Write-SttiLog $_ -Level Error } } function Remove-SttiUserCredential{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [Parameter(Mandatory)] [string] $CredentialId, [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/$UserId/credentials/$CredentialId" -Method Delete -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Enable-SttiUser{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [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/$UserId/activate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } function Disable-SttiUser{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [Parameter(Mandatory)] [string] $UserId, [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/$UserId/deactivate" -Method Put -ContentType "application/json" -CertificateThumbprint $CertificateThumbprint -ErrorAction Stop return $response.data } catch{ Write-SttiLog $_ -Level Error } } #endregion Users #region Diagnostics function Get-SttiLogEntries{ [CmdletBinding()] param ( [Parameter(Mandatory)] [SttiInstanceConfig] $Instance, [datetime] $From, [datetime] $Thru, [ValidateSet("Fatal", "Error", "Warn", "Info", "Debug")] [string] $Level, [string] $Component, [string] $FlowId, [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 = @{} try{ $response = Invoke-RestMethod "$apiUrl/diag/logs" -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 |