DTX.Cloud.Management.psm1
class DTXPSException : System.Exception { [string]$DTXStackInfo DTXPSException([string]$Message):base($Message) { $this.DTXStackInfo = "" } DTXPSException([string]$Message, [string]$scriptStackTrace):base($Message) { $this.DTXStackInfo = $scriptStackTrace } DTXPSException([string]$Message, [string]$scriptStackTrace, [Exception]$InnerException):base($Message, $InnerException) { $this.DTXStackInfo = $scriptStackTrace } } class DTXLogger { hidden static [void] Log([string]$LogLevel, [string]$Message) { $_logLevel = $LogLevel.ToUpper() $_message = "$( Get-Date -Format "yyyy:MM:dd-hh:mm:ss" ) [$_logLevel] $Message" $colors = @{ WARNING = "Yellow" ERROR = "Red" CRITICAL = "Red" DEBUG = "Cyan" } if ($Env:DTX_DISABLE_COLORS -or $_logLevel -eq "INFO") { Write-Host $_message } else { Write-Host -ForegroundColor $colors[$_logLevel] $_message } } static [void] LogInfo([string]$Message) { [DTXLogger]::Log("Info", $Message) } static [void] LogError([string]$Message) { [DTXLogger]::Log("Error", $Message) } static [void] LogWarning([string]$Message) { [DTXLogger]::Log("Warning", $Message) } static [void] LogCritical([string]$Message, [object]$Exception, [bool]$ThrowException) { $_message = $Message if ([string]::IsNullOrEmpty($Message) -and ($Exception -is [System.Exception])) { $_message = $Exception.Message } elseif ([string]::IsNullOrEmpty($Message) -and ($Exception -is [System.Management.Automation.ErrorRecord])) { $_message = $Exception.Exception.Message } elseif ([string]::IsNullOrEmpty($Message)) { $_message = "An unknown error occurred." } [DTXLogger]::Log("Critical", $_message) if ($ThrowException) { switch ($Exception) { { $_ -is [System.Management.Automation.ErrorRecord] } { throw [DTXPSException]::new($_message, $_.ScriptStackTrace, $_.Exception) } { $_ -is [System.Exception] } { throw [DTXPSException]::new($_message, "", $_) } { $_ -eq $null } { throw [DTXPSException]::new($_message, "", $null) } default { throw "[DTXLogger]::LogCritical You have passed an invalid exception type. Please provide an ErrorRecord or an Exception object." } } } } static [void] LogCritical([string]$Message) { [DTXLogger]::LogCritical($Message, $null, $false) } static [void] LogCritical([object]$Exception) { [DTXLogger]::LogCritical($null, $Exception, $false) } static [void] LogCritical([string]$Message, [object]$Exception) { [DTXLogger]::LogCritical($Message, $null, $false) } static [void] LogCriticalAndThrow([string]$Message) { [DTXLogger]::LogCritical($Message, $null, $true) } static [void] LogCriticalAndThrow([object]$Exception) { [DTXLogger]::LogCritical($null, $Exception, $true) } static [void] LogCriticalAndThrow([string]$Message, [object]$Exception) { [DTXLogger]::LogCritical($Message, $Exception, $true) } static [void] LogDebug([string]$Message) { if (($Env:DTX_DEBUG_MODE -eq "true") -or ($Env:DTX_DEBUG_MODE -eq $true)) { [DTXLogger]::Log("Debug", $Message) } } static [void] LogSeparator() { [DTXLogger]::Log("Info", "---------------------------------------------------------------------") } } class DTXUtils { static [object] UsingObject( [object] $InputObject, [scriptblock] $ScriptBlock ) { try { if ($null -ne $InputObject) { [DTXLogger]::LogDebug("[DTXUtils::UsingObject] Executing script block with input object: $($InputObject.GetType().Name)") return (& $ScriptBlock $InputObject) } else { [DTXLogger]::LogDebug("[DTXUtils::UsingObject] Executing script block without input object...") return (& $ScriptBlock) } } finally { if ($null -ne $InputObject -and $InputObject -is [System.IDisposable]) { [DTXLogger]::LogDebug("[DTXUtils::UsingObject] Disposing object: $($InputObject.GetType().Name)") $InputObject.Dispose() [DTXLogger]::LogDebug("[DTXUtils::UsingObject] Object disposed.") } } } static [void] WriteStringToFileWithRetry( [string]$StringContent, [string]$FilePath, [int]$RetryCount, [int]$WaitSeconds ) { [DTXLogger]::LogDebug("[DTXUtils::WriteStringToFileWithRetry] Writing string to file '$FilePath' with retry parameters: RetryCount=$RetryCount, WaitSeconds=$WaitSeconds") $retryAttempts = 0 do { try { $StringContent | Out-File -Force -FilePath $FilePath break } catch { if ($retryAttempts -ge $RetryCount) { [DTXLogger]::LogCriticalAndThrow("[DTXUtils::WriteStringToFileWithRetry] Failed to write to file '$FilePath' after $($RetryCount + 1) attempts.") } [DTXLogger]::LogWarning("[DTXUtils::WriteStringToFileWithRetry] Failed to write to file '$FilePath'. Retrying in $WaitSeconds seconds... Attempt: $($retryAttempts + 1)") Start-Sleep -Seconds $WaitSeconds $retryAttempts++ } } while ($retryAttempts -le $RetryCount) } static [void] WriteStringToFileWithRetry([string]$StringContent, [string]$FilePath) { [DTXUtils]::WriteStringToFileWithRetry($StringContent, $FilePath, 3, 2) } static [string] GetSSLCertificateFingerprint( [string]$Hostname, [int]$Port, [string]$HashAlgorithm ) { if ($HashAlgorithm -notin @('SHA1', 'SHA256', 'SHA384', 'SHA512')) { [DTXLogger]::LogCriticalAndThrow("[DTXUtils::GetSSLCertificateFingerprint] Invalid HashAlgorithm: $HashAlgorithm. Supported values are 'SHA1', 'SHA256', 'SHA384', 'SHA512'.") } if ($Port -lt 1 -or $Port -gt 65535) { [DTXLogger]::LogCriticalAndThrow("[DTXUtils::GetSSLCertificateFingerprint] Invalid Port: $Port. Port number must be between 1 and 65535.") } Add-Type -AssemblyName System.Security $tcpClient = New-Object System.Net.Sockets.TcpClient $tcpClient.Connect($hostname, $port) $sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, { param($s, $certificate, $chain, $sslPolicyErrors) return $true } ) $fingerprint = $null try { $sslStream.AuthenticateAsClient($hostname) $remoteCertificate = $sslStream.RemoteCertificate if ($HashAlgorithm -eq 'SHA1') { $sha1 = New-Object System.Security.Cryptography.SHA1Managed $fingerprint = ($sha1.ComputeHash($remoteCertificate.GetRawCertData()) | ForEach-Object { $_.ToString("x2") }) -join '' } if ($HashAlgorithm -eq 'SHA256') { $sha256 = New-Object System.Security.Cryptography.SHA256Managed $fingerprint = ($sha256.ComputeHash($remoteCertificate.GetRawCertData()) | ForEach-Object { $_.ToString("x2") }) -join '' } if ($HashAlgorithm -eq 'SHA384') { $sha384 = New-Object System.Security.Cryptography.SHA384Managed $fingerprint = ($sha384.ComputeHash($remoteCertificate.GetRawCertData()) | ForEach-Object { $_.ToString("x2") }) -join '' } if ($HashAlgorithm -eq 'SHA512') { $sha512 = New-Object System.Security.Cryptography.SHA512Managed $fingerprint = ($sha512.ComputeHash($remoteCertificate.GetRawCertData()) | ForEach-Object { $_.ToString("x2") }) -join '' } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Failed to get SSL Cert Fingerprint" -ThrowException -Exception $_ } finally { if ($sslStream) { $sslStream.Dispose() } if ($tcpClient) { $tcpClient.Dispose() } } if ($null -ne $fingerprint) { $fingerprint = $fingerprint.ToLower() } return $fingerprint } static [string] GetSSLCertificateFingerprint() { return [DTXUtils]::GetSSLCertificateFingerprint('localhost', 443, 'SHA256') } } class DTXKeyValueStore { hidden [string]$Path DTXKeyValueStore( [string]$Path ) { $this.Path = $Path $this.InitStore() } hidden [void] InitStore() { if (-not (Test-Path -Path $this.Path)) { New-Item -Path $this.Path -ItemType File -Force Set-Content -Path $this.Path -Value "{}" -Force } } hidden [hashtable] ReadStoreContents() { $store = Get-Content -Path $this.Path -Raw | ConvertFrom-Json -AsHashtable -Depth 100 if ($null -eq $store) { $store = New-Object System.Collections.Hashtable } return $store } [hashtable] GetStoreContent() { return $this.ReadStoreContents() } [void] ResetStore() { Remove-Item -Path $this.Path -Force $this.InitStore() } [void] SetValue( [string]$Key, [object]$Value ) { $store = $this.ReadStoreContents() $store[$Key] = $Value $storeJson = $store | ConvertTo-Json -Depth 100 -EnumsAsStrings Set-Content -Path $this.Path -Value $storeJson -Force } [object] GetValue( [string]$Key ) { $store = $this.ReadStoreContents() if (-not $store.ContainsKey($Key)) { return $null } return $store[$Key] } [void] RemoveKey( [string]$Key ) { $store = $this.ReadStoreContents() if ($store.ContainsKey($Key)) { $store.Remove($Key) $storeJson = $store | ConvertTo-Json -Depth 100 -EnumsAsStrings Set-Content -Path $this.Path -Value $storeJson -Force } } [bool] HasKey( [string]$Key ) { $store = $this.ReadStoreContents() return $store.ContainsKey($Key) } } class ScheduledTasks { ScheduledTasks( ) { } [void] WriteLog ( [string]$LogLevel, [string]$Message ) { $_logLevel = $LogLevel.ToUpper() $_message = "$( Get-Date -Format "yyyy:MM:dd-hh:mm:ss" ) [$_logLevel] $Message" $colors = @{ WARNING = "Yellow" ERROR = "Red" CRITICAL = "Red" } if ($Env:DTX_DISABLE_COLORS -or $_logLevel -eq "INFO") { Write-Host $_message } else { Write-Host -ForegroundColor $colors[$_logLevel] $_message } } [void] RemoveScheduledTask( [string] $Name ) { $the_task = $this.GetScheduledTask($Name, $false) if ($the_task -eq $null) { $this.WriteLog("INFO", "Task doesnt exist, returning success for task name: $Name") } else { $this.WriteLog("INFO", "Task does exist, performing delete for task name: $Name") $Service = new-object -ComObject("Schedule.Service") $Service.Connect() $TaskFolder = $Service.GetFolder("\") $TaskFolder.DeleteTask($Name, $null) $this.WriteLog("INFO", "Task deleted") } } [object] GetScheduledTask( [string]$Name, [bool] $Throw ) { $mytasks = $this.ListNonSystemScheduledTasks() foreach ($task in $mytasks) { if ($task.Name -eq $Name) { return $task } } if ($Throw) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Unable to find Task with name : $Name")) } return $null } [array] ListNonSystemScheduledTasks( ) { $sch = New-Object -ComObject Schedule.Service $sch.Connect("localhost") $tasks = $sch.GetFolder("\").GetTasks(0) $ret_val = @() foreach ($task in $tasks) { $Author = ([regex]::split($task.xml, '<Author>|</Author>'))[1] $UserId = ([regex]::split($task.xml, '<UserId>|</UserId>'))[1] $Description = ([regex]::split($task.xml, '<Description>|</Description>'))[1] $Action = ([regex]::split($task.xml, '<Command>|</Command>'))[1] $Arguments = ([regex]::split($task.xml, '<Arguments>|</Arguments>'))[1] $RunLevel = ([regex]::split($task.xml, '<RunLevel>|</RunLevel>'))[1] $LogonType = ([regex]::split($task.xml, '<LogonType>|</LogonType>'))[1] $DateRegistered = ([regex]::split($task.xml, '<Date>|</Date>'))[1] Switch ($task.State) { 0 { $Status = "Unknown" } 1 { $Status = "Disabled" } 2 { $Status = "Queued" } 3 { $Status = "Ready" } 4 { $Status = "Running" } } $myoutput = $task | select @{ label = "ComputerName"; expression = { $computer } }, Name, Path, Enabled, @{ label = "Action"; expression = { $Action } }, @{ label = "Arguments"; expression = { $Arguments } }, @{ label = "UserId"; expression = { $UserId } }, LastRunTime, NextRunTime, @{ label = "Status"; expression = { $Status } }, @{ label = "Author"; expression = { $Author } }, @{ label = "RunLevel"; expression = { $RunLevel } }, @{ label = "Description"; expression = { $Description } }, @{ label = "DateCreated"; expression = { $DateRegistered } }, NumberOfMissedRuns, LastTaskResult $ret_val += $myoutput } return $ret_val } } class DTXXmlUtils { static [bool] CompareXmlAttribute([System.Xml.XmlElement] $Element, [string] $Attribute, [string] $Value) { if ($Element -and $Element.HasAttribute($Attribute)) { return $Element.GetAttribute($Attribute) -eq $Value } return $false } static [void] UpdateXmlAttribute([System.Xml.XmlElement] $Element, [string] $Attribute, [string] $Value) { if ($Element -and $Element.HasAttribute($Attribute)) { $Element.SetAttribute($Attribute, $Value) } } static [string] FormatXml([xml]$XmlContent, [bool]$EnableIndentation, [int]$IndentChars, [bool]$NewLineOnAttributes) { if ($null -eq $XmlContent) { throw [System.ArgumentNullException]::new("XmlContent cannot be null.") } $result = [DTXUtils]::UsingObject( ($memoryStream = New-Object System.IO.MemoryStream), { $settings = New-Object System.Xml.XmlWriterSettings $settings.Indent = $EnableIndentation $settings.IndentChars = " " * $IndentChars $settings.NewLineOnAttributes = $NewLineOnAttributes [DTXUtils]::UsingObject(($writer = [System.Xml.XmlWriter]::Create($memoryStream, $settings)), { $XmlContent.Save($writer) $writer.Flush() }) | Out-Null $memoryStream.Position = 0 return [DTXUtils]::UsingObject(($formattedXml = New-Object System.IO.StreamReader($memoryStream)), { return $formattedXml.ReadToEnd() }) } ) $result = $result -replace '^\s*(<\?xml)', '$1' return $result } static [string] FormatXml([xml]$XmlContent) { return [DTXXmlUtils]::FormatXml($XmlContent, $true, 4, $true) } static [bool] TestXmlContentIsValid([string] $XmlContent) { try { $xmlDocument = New-Object System.Xml.XmlDocument $xmlDocument.LoadXml($XmlContent) $xmlDocument = $null return $true } catch { return $false } } } class DTXJsonUtils { static [string] RemoveJsonComments( [string]$JsonContent ) { $inString = $false $escaped = $false $inSingleLineComment = $false $inMultiLineComment = $false $result = "" for ($i = 0; $i -lt $JsonContent.Length; $i++) { $char = $JsonContent[$i] if ($inSingleLineComment) { if ($char -eq "`n" -or $char -eq "`r") { $inSingleLineComment = $false $result += $char } continue } if ($inMultiLineComment) { if ($char -eq "*" -and ($i + 1) -lt $JsonContent.Length -and $JsonContent[$i + 1] -eq "/") { $inMultiLineComment = $false $i++ } continue } if ($inString) { if ($escaped) { $escaped = $false } elseif ($char -eq "\") { $escaped = $true } elseif ($char -eq '"') { $inString = $false } } else { if ($char -eq "/" -and ($i + 1) -lt $JsonContent.Length) { $nextChar = $JsonContent[$i + 1] if ($nextChar -eq "/") { $inSingleLineComment = $true $i++ continue } elseif ($nextChar -eq "*") { $inMultiLineComment = $true $i++ continue } } elseif ($char -eq '"') { $inString = $true } } $result += $char } return $result } } class DTXTestUtils { static [bool] HTTPService($Uri, $ExpectedStatusCode, $ExpectedStatusCodeRange, $WaitTimeInSeconds, $MaxRetryCount, $SkipCertificateCheck) { if (!$ExpectedStatusCode -and !$ExpectedStatusCodeRange) { [DTXLogger]::LogCriticalAndThrow("[DTXTestUtils] Either 'ExpectedStatusCode' or 'ExpectedStatusCodeRange' params have to be provided.") } if ($ExpectedStatusCode -and $ExpectedStatusCodeRange) { [DTXLogger]::LogCriticalAndThrow("[DTXTestUtils] Both 'ExpectedStatusCode' and 'ExpectedStatusCodeRange' are set. You need to provide only one of them.") } $retryCount = 0 do { $retryCount++ try { $params = @{ Uri = $Uri SkipCertificateCheck = $SkipCertificateCheck TimeoutSec = 1 } $req = Invoke-WebRequest @params $statusCode = $req.StatusCode if ($ExpectedStatusCode -and ($statusCode -eq $ExpectedStatusCode)) { [DTXLogger]::LogInfo("[DTXTestUtils] The service at $Uri is healthy.") return $true } if ($ExpectedStatusCodeRange) { $startRange = ($ExpectedStatusCodeRange -split "-")[0] $endRange = ($ExpectedStatusCodeRange -split "-")[1] if (($statusCode -ge $startRange) -and ($statusCode -le $endRange)) { [DTXLogger]::LogInfo("[DTXTestUtils] The service at $Uri is healthy.") return $true } } } catch [System.Net.Sockets.SocketException] { [DTXLogger]::LogWarning("[DTXTestUtils] The service at $Uri is not ready to accept connections.") [DTXLogger]::LogWarning("[DTXTestUtils] Retrying in $WaitTimeInSeconds seconds... (Attempt $retryCount of $MaxRetryCount)") Start-Sleep -Seconds $WaitTimeInSeconds continue } catch [System.Threading.Tasks.TaskCanceledException] { if ($_.Exception.Message -like "*request*canceled*HttpClient.Timeout*elapsing*") { [DTXLogger]::LogWarning("[DTXTestUtils] The service at $Uri is not ready to accept connections.") [DTXLogger]::LogWarning("[DTXTestUtils] Retrying in $WaitTimeInSeconds seconds... (Attempt $retryCount of $MaxRetryCount)") Start-Sleep -Seconds $WaitTimeInSeconds continue } throw $_ } catch [System.Security.Authentication.AuthenticationException] { throw $_ } catch { [DTXLogger]::LogWarning("[DTXTestUtils] Error occurred: $_") [DTXLogger]::LogWarning("[DTXTestUtils] Retrying in $WaitTimeInSeconds seconds... (Attempt $retryCount of $MaxRetryCount)") Start-Sleep -Seconds $WaitTimeInSeconds continue } } while ($retryCount -le ($MaxRetryCount - 1)) [DTXLogger]::LogInfo("[DTXTestUtils] The service at $Uri is NOT healthy.") return $false } } class DTXS3Utils { static [string] GetFile($BucketName, $BucketKey, $DirectoryPath) { return [DTXS3Utils]::GetFile($BucketName, $BucketKey, $DirectoryPath, $false) } static [string] GetFile($BucketName, $BucketKey, $DirectoryPath, $PreserveFileName) { $ProgressPreference = 'SilentlyContinue' $bucketRegion = $null $path = $null try { if (!$DirectoryPath) { $DirectoryPath = [System.IO.Path]::GetTempPath() if (!$DirectoryPath) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Failed to generate a temporary file path")) } } if ($PreserveFileName) { $path = (Join-Path $DirectoryPath $([System.IO.Path]::GetFileName($BucketKey))) } else { $extension = [System.IO.Path]::GetExtension($BucketKey) if ($extension) { $path = (Join-Path $DirectoryPath "$( Get-Random )$extension") } else { $path = (Join-Path $DirectoryPath "$( Get-Random )") } } $bucketRegion = (Get-S3BucketLocation -BucketName $BucketName -ErrorAction Stop).Value [DTXLogger]::LogInfo("[DTXS3Utils] Downloading file '$BucketKey' from bucket '$BucketName' in region '$bucketRegion' to file path: $path") $params = @{ BucketName = $BucketName Key = $BucketKey File = $path } if ($bucketRegion) { $params.Region = $bucketRegion } Read-S3Object @params -ErrorAction Stop | Out-Null } catch { [DTXLogger]::LogCriticalAndThrow("[DTXS3Utils] Failed to download file '$BucketKey' from bucket '$BucketName' in region '$bucketRegion'.", $_) } [DTXLogger]::LogInfo("[DTXS3Utils] File downloaded to $path") return $path } } class DTXModuleMetadataProvider { hidden [System.Management.Automation.InvocationInfo]$Invocation hidden [hashtable]$ModuleManifest DTXModuleMetadataProvider( [System.Management.Automation.InvocationInfo]$Invocation ) { $this.Invocation = $Invocation if (-not $this.Invocation.MyCommand.Scriptblock.Module) { $this.LoadModuleManifest() } } hidden [void] LoadModuleManifest() { if ($env:DTX_IS_RUNNING_TESTS) { $manifestFiles = Get-ChildItem -Path "$PSScriptRoot/.." -Filter '*.psd1' } else { $manifestFiles = Get-ChildItem -Path $PSScriptRoot -Filter '*.psd1' } foreach ($file in $manifestFiles) { try { $manifest = Import-PowerShellDataFile -Path $file.FullName if ($manifest.ContainsKey('RootModule') -and $manifest.ContainsKey('ModuleVersion') -and $manifest.ContainsKey('GUID')) { $this.ModuleManifest = $manifest break } } catch { } } if (-not $this.ModuleManifest) { throw "Module manifest (.psd1) file not found in the current directory." } } [string] GetPSModuleVersion() { if ($this.Invocation.MyCommand.Scriptblock.Module) { return $this.Invocation.MyCommand.Scriptblock.Module.Version.ToString() } return $this.ModuleManifest.ModuleVersion.ToString() } [string] GetPSModulePrereleaseVersion() { if ($this.Invocation.MyCommand.Scriptblock.Module) { return $this.Invocation.MyCommand.Scriptblock.Module.PrivateData.PSData.Prerelease } return $this.ModuleManifest.PrivateData.PSData.Prerelease } [string] GetPSModuleRealVersion() { $branchName = $this.GetBranchName() $myRealVersion = $this.GetPSModuleVersion() if (@("master", "main") -notcontains $branchName) { $myRealVersion += "-" + $this.GetPSModulePrereleaseVersion() } return $myRealVersion } [string] GetPSModuleName() { $theName = $null if ($this.Invocation.MyCommand.Scriptblock.Module) { $theName = $this.Invocation.MyCommand.Scriptblock.Module.Name } else { $theName = $this.ModuleManifest.RootModule } if (!$theName) { throw "Module name not found" } return $theName -replace '\.psm1$' } [string] GetBranchName() { if ($this.Invocation.MyCommand.Scriptblock.Module) { return $this.Invocation.MyCommand.Scriptblock.Module.PrivateData.BranchName } return $this.ModuleManifest.PrivateData.BranchName } [string] GetEnvironmentType() { $branchName = $this.GetBranchName() $envType = "production" if (@("master", "main") -notcontains $branchName) { $envType = "development" } return $envType } } class DTXPathProvider { hidden static [void] CreatePathIfNotExist([string]$thePath) { if (!(Test-Path $thePath)) { New-Item -Path $thePath -ItemType Directory -Force | Out-Null } } static [System.IO.DirectoryInfo] GetLocalDotmaticsPath() { $thePath = $null if ($env:DTX_LOCAL_DOTMATICS_PATH) { $thePath = $env:DTX_LOCAL_DOTMATICS_PATH } else { if ($global:IsWindows) { $thePath = Join-Path $env:PROGRAMDATA "dotmatics" } elseif ($global:IsLinux -or $global:IsMacOS) { $thePath = Join-Path "/" "var" "local" "dotmatics" } } [DTXPathProvider]::CreatePathIfNotExist($thePath) return New-Object System.IO.DirectoryInfo($thePath) } static [System.IO.DirectoryInfo] GetLocalTempPath() { $thePath = [System.IO.Path]::Combine([DTXPathProvider]::GetLocalDotmaticsPath(), "temp") [DTXPathProvider]::CreatePathIfNotExist($thePath) return New-Object System.IO.DirectoryInfo($thePath) } static [System.IO.DirectoryInfo] GetLocalCachePath() { $thePath = [System.IO.Path]::Combine([DTXPathProvider]::GetLocalDotmaticsPath(), "cache") [DTXPathProvider]::CreatePathIfNotExist($thePath) return New-Object System.IO.DirectoryInfo($thePath) } static [System.IO.DirectoryInfo] GetLocalExternalFilesCachePath() { $thePath = [System.IO.Path]::Combine([DTXPathProvider]::GetLocalCachePath(), "external_files") [DTXPathProvider]::CreatePathIfNotExist($thePath) return New-Object System.IO.DirectoryInfo($thePath) } static [System.IO.DirectoryInfo] GetDevelopmentExternalFilesPath() { $rootPath = [System.IO.DirectoryInfo]::new($PSScriptRoot) $parentPath = $rootPath.Parent $devExternalFilesPath = [System.IO.Path]::Combine($parentPath.FullName, "ExternalFiles") return New-Object System.IO.DirectoryInfo([System.IO.Path]::GetFullPath($devExternalFilesPath)) } static [System.IO.DirectoryInfo] GetLocalStateFilePath() { return New-Object System.IO.DirectoryInfo([System.IO.Path]::Combine([DTXPathProvider]::GetLocalDotmaticsPath(), "dtx_state.json")) } static [System.IO.DirectoryInfo] GetDiscoveryFilePath() { return New-Object System.IO.DirectoryInfo([System.IO.Path]::Combine([DTXPathProvider]::GetLocalDotmaticsPath(), "dtx_discovery.json")) } static [System.IO.DirectoryInfo] GetLocalDotmaticsSharedPath() { $thePath = $null if ($env:DTX_LOCAL_DOTMATICS_PATH) { $thePath = Join-Path $env:DTX_LOCAL_DOTMATICS_PATH "shared" } else { if ($global:IsWindows) { if (-not (Test-Path "D:\")) { $thePath = Join-Path "C:" "dotmatics" "shared" } else { $thePath = Join-Path "D:" "dotmatics" "shared" } } if ($global:IsLinux -or $global:IsMacOS) { $thePath = Join-Path "/" "var" "local" "dotmatics" "shared" } } [DTXPathProvider]::CreatePathIfNotExist($thePath) return $thePath } static [System.IO.DirectoryInfo] GetLocalDotmaticsSharedCertPath () { $thePath = Join-Path ([DTXPathProvider]::GetLocalDotmaticsSharedPath()) "tls" [DTXPathProvider]::CreatePathIfNotExist($thePath) return $thePath } } class DTXExternalFilesProvider { hidden [string]$className = $this.GetType().Name hidden [string]$S3BucketNameSSSMParamName hidden [string]$DefaultAwsRegion hidden [DTXKeyValueStore]$State hidden [DTXModuleMetadataProvider]$ModuleMeta hidden [System.IO.DirectoryInfo]$ExternalFilesCachePath hidden [System.IO.DirectoryInfo]$ExternalFilesCacheRootPath DTXExternalFilesProvider( [System.Management.Automation.InvocationInfo]$Invocation ) { $this.ModuleMeta = [DTXModuleMetadataProvider]::new($Invocation) $this.S3BucketNameSSSMParamName = "/cloud/public/regional/s3/v1/buckets/cf_extensions/name" $this.DefaultAwsRegion = "eu-west-1" $this.ExternalFilesCacheRootPath = [DTXPathProvider]::GetLocalExternalFilesCachePath() $this.ExternalFilesCachePath = [System.IO.Path]::Combine($this.ExternalFilesCacheRootPath , $this.ModuleMeta.GetPSModuleRealVersion()) $this.State = [DTXKeyValueStore]::new([System.IO.Path]::Combine($this.ExternalFilesCachePath, "__state.json")) } hidden [string] GetExternalFilesS3Path() { return (@( "bin", $this.ModuleMeta.GetEnvironmentType(), "dot_po_cloudops_modules", $this.ModuleMeta.GetBranchName(), $this.ModuleMeta.GetPSModuleRealVersion() ) -join "/") } hidden [string] GetS3BucketName() { $params = @{ Name = $this.S3BucketNameSSSMParamName Region = $this.GetDefaultAwsRegion() WithDecryption = $true ErrorAction = "Stop" } return (Get-SSMParameterValue @params).Parameters[0].Value.ToString() } hidden [string] GetDefaultAwsRegion() { $theRegion = Get-DefaultAWSRegion if ($null -eq $theRegion) { $theRegion = $this.DefaultAwsRegion } else { $theRegion = $theRegion.Region } return $theRegion } hidden [string] GetS3BucketRegion() { $result = Get-S3BucketLocation -BucketName $this.GetS3BucketName() if ($result.Value) { return $result.Value } return "us-east-1" } hidden [void] ClearCache() { $installedModuleVersions = New-Object System.Collections.Generic.HashSet[string] Get-InstalledModule -Name $this.ModuleMeta.GetPSModuleName() -AllVersions | Select-Object -ExpandProperty Version | ForEach-Object { $installedModuleVersions.Add($_.ToString()) } $installedModuleVersions.Add($this.ModuleMeta.GetPSModuleRealVersion()) | Out-Null $cachePath = $this.ExternalFilesCacheRootPath $cachedModuleVersions = Get-ChildItem -Path $cachePath -Directory | Select-Object -ExpandProperty Name foreach ($cachedVersion in $cachedModuleVersions) { if ($installedModuleVersions -contains $cachedVersion) { continue } $cachedModuleVersionPath = Join-Path -Path $cachePath -ChildPath $cachedVersion try { Remove-Item -Path $cachedModuleVersionPath -Recurse -Force -ErrorAction Stop [DTXLogger]::LogInfo("[$($this.className)] Removed cached external files for module version $cachedVersion at $cachedModuleVersionPath") } catch { [DTXLogger]::LogError("[$($this.className)] Failed to remove cached external files for module version $cachedVersion at $cachedModuleVersionPath") throw $_ } } } hidden [void] SyncFilesFromS3() { if ($this.State.GetValue("__status") -eq "synced") { [DTXLogger]::LogInfo("[$($this.className)] External files already synced. Nothing to do.") return } else { $this.State.SetValue("__status", "syncing") [DTXLogger]::LogInfo("[$($this.className)] Syncing external files...") try { $bucketName = $this.GetS3BucketName() $bucketRegion = $this.GetS3BucketRegion() $s3Objects = Get-S3Object -BucketName $bucketName -KeyPrefix $this.GetExternalFilesS3Path() -Region $bucketRegion foreach ($s3Object in $s3Objects) { $s3ObjectFiltered = (($s3Object.Key -split $this.ModuleMeta.GetPSModuleRealVersion())[-1] -split "/")[-1] $localFilePath = Join-Path -Path $this.ExternalFilesCachePath -ChildPath $s3ObjectFiltered if ($this.State.HasKey($s3Object.Key) -and (Test-Path -Path $localFilePath)) { continue } Read-S3Object -BucketName $bucketName -Key $s3Object.Key -File $localFilePath -Region $bucketRegion $this.State.SetValue($s3Object.Key, @{ "Downloaded" = (Get-Date -Format "yyyy-MM-dd-hh-mm-ss") }) } } catch { [DTXLogger]::LogError("[$($this.className)] Failed to sync external files.") $this.State.SetValue("__status", "failed") throw } $this.State.SetValue("__status", "synced") [DTXLogger]::LogInfo("[$($this.className)] External files synced.") } } hidden [void] Sync() { $this.SyncFilesFromS3() $this.ClearCache() } [System.IO.DirectoryInfo] GetFilePath([string]$FileName) { if ($FileName -eq "__state.json") { [DTXLogger]::LogWarning("[$($this.className)] File $FileName is a reserved name. Cannot be retrieved.") return $null } $devExternalFilesPath = [DTXPathProvider]::GetDevelopmentExternalFilesPath() if (Test-Path -Path $devExternalFilesPath) { $localPath = Join-Path $devExternalFilesPath $FileName if (Test-Path $localPath) { return New-Object System.IO.DirectoryInfo($localPath) } } $filePath = Join-Path -Path $this.externalFilesCachePath -ChildPath $FileName if (-not (Test-Path -Path $filePath)) { [DTXLogger]::LogInfo("[$($this.className)] File $FileName not found in cache. Trying to sync...") $this.Sync() } if (Test-Path -Path $filePath) { return New-Object System.IO.DirectoryInfo($filePath) } else { throw [System.IO.FileNotFoundException] "File $FileName not found in cache or S3." } } } class DTXStateProvider { hidden static [DTXKeyValueStore] GetKVStore() { return [DTXKeyValueStore]::new([DTXPathProvider]::GetLocalStateFilePath()) } static [object] GetValue([string]$Key) { return [DTXStateProvider]::GetKVStore().GetValue($Key) } static [object] RemoveKey([string]$Key) { return [DTXStateProvider]::GetKVStore().RemoveKey($Key) } static [void] SetValue([string]$Key, [object]$Value) { [DTXStateProvider]::GetKVStore().SetValue($Key, $Value) } } class DTXBrowserSysMonitor { [System.Collections.Hashtable] static Perform($monitoringIpsKey, $ApiEndpoint, $browserPropertiesDict) { $headers = [DTXBrowserSysMonitor]::GetApiCallHeaders($browserPropertiesDict, $monitoringIpsKey) return [DTXBrowserSysMonitor]::InvokeApi($ApiEndpoint, $headers) } hidden [hashtable] static GetApiCallHeaders($PropertiesData, $PropertyKey) { $headers = @{} if ($PropertiesData.Contains($PropertyKey)) { $ok_ip = $PropertiesData.$PropertyKey.split(",")[0] $headers.Add("X-Forwarded-For", $ok_ip) } else { } return $headers } hidden [System.Collections.Hashtable] static InvokeApi($ApiEndpoint, $headers) { try { $response = Invoke-WebRequest -Uri $APIEndpoint -TimeoutSec 15 -SkipCertificateCheck -Headers $headers if ($response -ne $null) { if (Test-Json -Json $response.Content -ErrorAction SilentlyContinue) { return $response.Content | ConvertFrom-Json -AsHashtable } else { [DTXLogger]::LogWarning("[DTXBrowserSysMonitor] Failed to detect data from Web Response as JSON, returning empty dict") [DTXLogger]::LogWarning("[DTXBrowserSysMonitor] Data: $($response.Content)") return New-Object System.Collections.Hashtable } } else { [DTXLogger]::LogWarning("[DTXBrowserSysMonitor] Failed to see response as non null, returning empty dict") return New-Object System.Collections.Hashtable } } catch { [DTXLogger]::LogWarning("[DTXBrowserSysMonitor] Failed to execute web request to SystemMonitor.do: `n $($_.Exception.Message | Out-String)") [DTXLogger]::LogWarning("[DTXBrowserSysMonitor] returning empty dict") return New-Object System.Collections.Hashtable } } } class DTXBrowser { hidden [string]$className = $this.GetType().Name hidden [String]$TomcatWebAppPath DTXBrowser( [string]$TomcatWebAppPath ) { $this.TomcatWebAppPath = $TomcatWebAppPath } [string] GetBrowserPlatformPath() { return Join-Path $this.TomcatWebAppPath "browser" } [string] GetBrowserPlatformPropertiesFilePath() { return Join-Path $this.GetBrowserPlatformPath() "WEB-INF" "browser.properties" } [string] GetBrowserPlatformGifCachePath() { $thePath = Join-Path $this.GetBrowserPlatformPath() "gifcache" if (!(Test-Path $thePath)) { [DTXLogger]::LogCriticalAndThrow("[$($this.className)] Unable to locate the Browser Platform Gifcache directory. The path $thePath does not exist") } return $thePath } [string] GetBrowserPlatformGifCacheToolkitPath() { $thePath = Join-Path $this.GetBrowserPlatformGifCachePath() "toolkit" if (!(Test-Path $thePath)) { [DTXLogger]::LogCriticalAndThrow("[$($this.className)] Unable to locate the Browser Platform Gifcache directory. The path $thePath does not exist") } return $thePath } [Boolean] IsBrowserSystem() { if (Test-Path -Path $this.GetBrowserPlatformPropertiesFilePath()) { [DTXLogger]::LogInfo("[$($this.className)] Is a Browser System.") return $true } else { [DTXLogger]::LogInfo("[$($this.className)] Is NOT a Browser System.") return $false } } [hashtable]GetBrowserPropertiesData() { return ConvertFrom-StringData (Get-Content -Raw $this.GetBrowserPlatformPropertiesFilePath()) } } class DTXTomcatUtils { static [hashtable] GetDesiredServerSettings( [hashtable]$DTXSystemInfo, [hashtable]$DTXDefaults, [int] $SettingsVersion ) { $_settings = $DTXDefaults.Tomcat.ServerSettings | Where-Object { $_.Version -eq $SettingsVersion } if ($_settings.Count -eq 0) { [DTXLogger]::LogCriticalAndThrow("[DTXTomcatUtils]::GetDesiredServerSettings - No server settings found for the current server settings version.") } $tomcatVersion = $DTXSystemInfo.AppInfo.Tomcat.Version $javaVersion = $DTXSystemInfo.AppInfo.Tomcat.Java.Version [DTXLogger]::LogInfo("[DTXTomcatUtils]::GetDesiredServerSettings - Detected Apache Tomcat version: $tomcatVersion") [DTXLogger]::LogInfo("[DTXTomcatUtils]::GetDesiredServerSettings - Detected Java version: $javaVersion") $unsupportedVersion = $false if ([System.Version] $_settings.JavaMinVersion -gt [System.Version] $javaVersion) { [DTXLogger]::LogWarning("[DTXTomcatUtils]::GetDesiredServerSettings - The Java version ($javaVersion) is not supported by the server settings.") $unsupportedVersion = $true } if ([System.Version] $_settings.TomcatMinVersion -gt [System.Version] $tomcatVersion) { [DTXLogger]::LogWarning("[DTXTomcatUtils]::GetDesiredServerSettings - The Tomcat version ($tomcatVersion) is not supported by the server settings.") $unsupportedVersion = $true } if ($unsupportedVersion) { throw "[DTXTomcatUtils]::GetDesiredServerSettings - Unable to find server settings for the current Tomcat and/or Java versions." } return $_settings } } class DTXTomcatServerXml { hidden [string]$className = $this.GetType().Name hidden [System.Xml.XmlDocument] $serverXmlContents hidden [string] $serverXmlPath DTXTomcatServerXml([string]$Path) { if (-not (Test-Path -Path $Path)) { [DTXLogger]::LogCriticalAndThrow("[$($this.className)]::new - The server.xml file at '$Path' does not exist.") } $this.serverXmlPath = $Path $this.Reload() } [System.Xml.XmlDocument] GetContents() { return $this.serverXmlContents } [System.Xml.XmlElement[]] GetConnectorElements() { return $this.serverXmlContents.Server.Service.Connector } [System.Xml.XmlElement] GetConnectorElement([int]$ConnectorPort) { $connectorElement = $this.GetConnectorElements() | Where-Object { $_.Port -eq $ConnectorPort } if ($null -eq $connectorElement) { [DTXLogger]::LogCriticalAndThrow("[$($this.className)]::GetConnectorElement - Unable to find the connector node for port $ConnectorPort in the server.xml file.") } return $connectorElement } [System.Xml.XmlElement] GetConnectorElementWithSslEnabled() { $sslConnector = $this.GetConnectorElements() | Where-Object { ($_.Attributes["SSLEnabled"].Value -eq "true") } if ($null -eq $sslConnector) { return $null } if (($sslConnector -is [System.Collections.IEnumerable]) -and ($sslConnector.Count -gt 1)) { [DTXLogger]::LogCriticalAndThrow("[$($this.className)]::GetConnectorElementWithSslEnabled - Multiple SSL enabled connectors found in the server.xml file.") } return $sslConnector } [void] RemoveConnectorElement([int]$ConnectorPort) { $connectorElement = $this.GetConnectorElement($ConnectorPort) $connectorElement.ParentNode.RemoveChild($connectorElement) } [string] GetConnectorProperty([int]$ConnectorPort, [string]$PropName) { $connectorElement = $this.GetConnectorElement($ConnectorPort) $propValue = $null if ($null -ne $connectorElement) { $normalizedPropName = $PropName.ToLower() $existingAttr = $null foreach ($attr in $connectorElement.Attributes) { if ($attr.Name.ToLower() -eq $normalizedPropName) { $existingAttr = $attr break } } if ($null -ne $existingAttr) { $propValue = $existingAttr.Value } else { [DTXLogger]::LogWarning("[$($this.className)]::GetConnectorProperty - Unable to find the property '$PropName' for the connector on port $ConnectorPort.") } } return $propValue } [void] SetConnectorProperty([int]$ConnectorPort, [string]$PropName, [string]$PropValue) { $connectorElement = $this.GetConnectorElement($ConnectorPort) if ($null -ne $connectorElement ) { $normalizedPropName = $PropName.ToLower() $existingAttr = $null foreach ($attr in $connectorElement.Attributes) { if ($attr.Name.ToLower() -eq $normalizedPropName) { $existingAttr = $attr break } } if ($null -ne $existingAttr) { $existingAttr.Value = $PropValue } else { $newAttr = $connectorElement.OwnerDocument.CreateAttribute($PropName) $newAttr.Value = $PropValue $connectorElement.Attributes.Append($newAttr) } } } [void] RemoveConnectorProperty([int] $ConnectorPort, [string] $PropName) { $connectorElement = $this.GetConnectorElement($ConnectorPort) if ($null -ne $connectorElement) { $normalizedPropName = $PropName.ToLower() $existingAttr = $null foreach ($attr in $connectorElement.Attributes) { if ($attr.Name.ToLower() -eq $normalizedPropName) { $existingAttr = $attr break } } if ($null -ne $existingAttr) { $connectorElement.Attributes.Remove($existingAttr) } } } [bool] GetConnectorDnsLookupsEnabled([int] $ConnectorPort) { return ($this.GetConnectorProperty($ConnectorPort, "enableLookups") -eq "true") } [void] SetConnectorDnsLookups([int] $ConnectorPort, [bool] $EnableLookups) { $this.SetConnectorProperty($ConnectorPort, "enableLookups", $EnableLookups.ToString().ToLower()) } [string] GetConnectorSslEnabledProtocols([int] $ConnectorPort) { return $this.GetConnectorProperty($ConnectorPort, "sslEnabledProtocols") } [void] SetConnectorSslEnabledProtocols([int] $ConnectorPort, [string] $Protocols) { $this.SetConnectorProperty($ConnectorPort, "sslEnabledProtocols", $Protocols) } [string] GetConnectorSslCiphers([int] $ConnectorPort) { return $this.GetConnectorProperty($ConnectorPort, "ciphers") } [void] SetConnectorSslCiphers([int] $ConnectorPort, [string] $Ciphers) { $this.SetConnectorProperty($ConnectorPort, "ciphers", $Ciphers) } [bool] IsConnectorHttp2Enabled([int] $ConnectorPort) { $connectorElement = $this.GetConnectorElement($ConnectorPort) $upgradeProtocolElement = $connectorElement.SelectSingleNode("UpgradeProtocol") if ($null -eq $upgradeProtocolElement) { return $false } $classNameValue = $upgradeProtocolElement.GetAttribute("className") return ($classNameValue -eq "org.apache.coyote.http2.Http2Protocol") } [void] SetConnectorHttp2([int] $ConnectorPort, [bool] $EnableHttp2) { $connectorElement = $this.GetConnectorElement($ConnectorPort) if ($EnableHttp2) { $upgradeProtocolElement = $connectorElement.SelectSingleNode("UpgradeProtocol") if ($null -eq $upgradeProtocolElement) { $upgradeProtocolElement = $connectorElement.OwnerDocument.CreateElement("UpgradeProtocol") $upgradeProtocolElement.SetAttribute("className", "org.apache.coyote.http2.Http2Protocol") $connectorElement.AppendChild($upgradeProtocolElement) } else { $upgradeProtocolElement.SetAttribute("className", "org.apache.coyote.http2.Http2Protocol") } } else { $upgradeProtocolElement = $connectorElement.SelectSingleNode("UpgradeProtocol") if ($null -ne $upgradeProtocolElement) { $connectorElement.RemoveChild($upgradeProtocolElement) } } $this.RemoveConnectorProperty($ConnectorPort, "upgradeProtocol") } [string] GetConnectorKeystoreFilePath([int] $ConnectorPort) { return $this.GetConnectorProperty($ConnectorPort, "keystoreFile") } [void] SetConnectorKeystoreFilePath([int] $ConnectorPort, [string] $KeystoreFilePath) { if (-not (Test-Path -Path $KeystoreFilePath)) { [DTXLogger]::LogCriticalAndThrow("[DTXTomcatServerXml]::SetConnectorKeystoreFilePath - The key store file path '$KeystoreFilePath' is invalid.") } $this.SetConnectorProperty($ConnectorPort, "keystoreFile", $KeystoreFilePath) } [string] GetServerInfoStatus() { $_node = $this.serverXmlContents.Server.Service.Engine.Host.SelectSingleNode("Valve[@className='org.apache.catalina.valves.ErrorReportValve']") if ($_node) { $showServerInfoAttribute = $_node.GetAttribute("showServerInfo") if ($showServerInfoAttribute -eq "true") { return "true" } if ($showServerInfoAttribute -eq "false") { return "false" } } return "not configured" } [bool] IsServerInfoEnabled() { return ($this.GetServerInfoStatus() -eq "true") } [void] SetServerInfo([bool] $EnableServerInfo) { $showServerInfoValue = if ($EnableServerInfo) { "true" } else { "false" } $_node = $this.serverXmlContents.Server.Service.Engine.Host.SelectSingleNode("Valve[@className='org.apache.catalina.valves.ErrorReportValve']") if ($_node) { $_node.SetAttribute("showServerInfo", $showServerInfoValue) } else { $_newNode = $this.serverXmlContents.CreateElement("Valve") $_newNode.SetAttribute("className", "org.apache.catalina.valves.ErrorReportValve") $_newNode.SetAttribute("showServerInfo", $showServerInfoValue) $this.serverXmlContents.Server.Service.Engine.Host.AppendChild($_newNode) | Out-Null } } [string] GetErrorReportStatus() { $_node = $this.serverXmlContents.Server.Service.Engine.Host.SelectSingleNode("Valve[@className='org.apache.catalina.valves.ErrorReportValve']") if ($_node) { $showReportAttribute = $_node.GetAttribute("showReport") if ($showReportAttribute -eq "true") { return "true" } if ($showReportAttribute -eq "false") { return "false" } } return "not configured" } [bool] IsErrorReportEnabled() { return ($this.GetErrorReportStatus() -eq "true") } [void] SetErrorReports([bool] $EnableErrorReports) { $showReportValue = if ($EnableErrorReports) { "true" } else { "false" } $_node = $this.serverXmlContents.Server.Service.Engine.Host.SelectSingleNode("Valve[@className='org.apache.catalina.valves.ErrorReportValve']") if ($_node) { $_node.SetAttribute("showReport", $showReportValue) } else { $_newNode = $this.serverXmlContents.CreateElement("Valve") $_newNode.SetAttribute("className", "org.apache.catalina.valves.ErrorReportValve") $_newNode.SetAttribute("showReport", $showReportValue) $this.serverXmlContents.Server.Service.Engine.Host.AppendChild($_newNode) | Out-Null } } [void] CleanUp() { $allUpgradeProtocolElements = $this.serverXmlContents.SelectNodes("//UpgradeProtocol") foreach ($element in $allUpgradeProtocolElements) { if ($element.ParentNode.Name -ne "Connector") { $element.ParentNode.RemoveChild($element) } } } [void] Reload() { $this.serverXmlContents = $null try { $this.serverXmlContents = [xml](Get-Content -Path $this.serverXmlPath -ErrorAction Stop) [DTXLogger]::LogInfo("[$($this.className)]::Reload - Loaded the server.xml file from $($this.serverXmlPath)") } catch { [DTXLogger]::LogCriticalAndThrow("[$($this.className)]::Reload - Unable to read the server.xml file at $($this.serverXmlPath)", $_) } } [void] Save() { $this.CleanUp() $formattedServerXmlContents = [DTXXmlUtils]::FormatXml($this.serverXmlContents) [DTXUtils]::WriteStringToFileWithRetry($formattedServerXmlContents, $this.serverXmlPath) $this.Reload() } } class DTXTomcatChangeEngineSettings { hidden [string] $className = $this.GetType().Name hidden [string] $dtxStateStatusKey = "tomcat-changes-$($this.className.ToLower())-status" hidden [boolean] $isChangeRequired = $false hidden [hashtable] $dtxSystemInfo hidden [hashtable] $dtxDefaults hidden [string] $tomcatHostname = "localhost" hidden [int]$tomcatHttpPort hidden [int] $settingsVersion hidden [hashtable] $desiredServerSettings DTXTomcatChangeEngineSettings( [Hashtable]$DTXSystemInfo, [Hashtable]$DTXDefaults, [int] $SettingsVersion ) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "UNKNOWN") $this.dtxSystemInfo = $DTXSystemInfo $this.dtxDefaults = $DTXDefaults $this.tomcatHttpPort = [int]($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Port) $this.settingsVersion = $SettingsVersion $this.desiredServerSettings = [DTXTomcatUtils]::GetDesiredServerSettings($this.dtxSystemInfo, $this.dtxDefaults, $this.settingsVersion) } hidden [bool] IsTomcatEngineChangeRequired() { $this.isChangeRequired = $false $tomcatServerXml = [DTXTomcatServerXml]::new($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Path) $desiredShowServerInfo = $this.desiredServerSettings.ShowServerInfo $currentShowServerInfo = $tomcatServerXml.GetServerInfoStatus() if ($currentShowServerInfo -eq "not configured") { Write-LogWarning -Message "[$($this.className)] The current Show Server Info setting is not configured. Setting to desired: $desiredShowServerInfo." $this.isChangeRequired = $true } if ($currentShowServerInfo -eq "true" -and $desiredShowServerInfo -ne $true) { Write-LogWarning -Message "[$($this.className)] The current Show Server Info setting is true, but desired is $desiredShowServerInfo. Update required." $this.isChangeRequired = $true } if ($currentShowServerInfo -eq "false" -and $desiredShowServerInfo -ne $false) { Write-LogWarning -Message "[$($this.className)] The current Show Server Info setting is false, but desired is $desiredShowServerInfo. Update required." $this.isChangeRequired = $true } $desiredShowErrorReports = $this.desiredServerSettings.ShowErrorReports $currentShowErrorReports = $tomcatServerXml.GetErrorReportStatus() if ($currentShowErrorReports -eq "not configured") { Write-LogWarning -Message "[$($this.className)] The current Show Error Reports setting is not configured. Setting to desired: $desiredShowErrorReports." $this.isChangeRequired = $true } if ($currentShowErrorReports -eq "true" -and $desiredShowErrorReports -ne $true) { Write-LogWarning -Message "[$($this.className)] The current Show Error Reports setting is true, but desired is $desiredShowErrorReports. Update required." $this.isChangeRequired = $true } if ($currentShowErrorReports -eq "false" -and $desiredShowErrorReports -ne $false) { Write-LogWarning -Message "[$($this.className)] The current Show Error Reports setting is false, but desired is $desiredShowErrorReports. Update required." $this.isChangeRequired = $true } return $this.isChangeRequired } [bool] ChangeRequired() { if ($this.IsTomcatEngineChangeRequired()) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_REQUIRED") return $true } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_SUCCESSFUL") return $false } [void] PerformChange() { if ($this.isChangeRequired) { $tomcatServerXml = [DTXTomcatServerXml]::new($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Path) $tomcatServerXml.SetServerInfo($this.desiredServerSettings.ShowServerInfo) $tomcatServerXml.SetErrorReports($this.desiredServerSettings.ShowErrorReports) $tomcatServerXml.Save() } } [void] TestAfterChange() { if (-not ($this.ChangeRequired())) { [DTXLogger]::LogInfo("[$($this.className)] The Tomcat engine settings change was successful.") return } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_FAILED") [DTXLogger]::LogCriticalAndThrow("[$($this.className)] The Tomcat engine settings change failed.") } } class DTXTomcatChangeConnectorSettings { hidden [string] $className = $this.GetType().Name hidden [string] $dtxStateStatusKey = "tomcat-changes-$($this.className.ToLower())-status" hidden [boolean] $isChangeRequired = $false hidden [hashtable] $dtxSystemInfo hidden [hashtable] $dtxDefaults hidden [string] $tomcatHostname = "localhost" hidden [int]$tomcatHttpPort hidden [int] $settingsVersion hidden [hashtable] $desiredServerSettings DTXTomcatChangeConnectorSettings( [Hashtable]$DTXSystemInfo, [Hashtable]$DTXDefaults, [int] $SettingsVersion ) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "UNKNOWN") $this.dtxSystemInfo = $DTXSystemInfo $this.dtxDefaults = $DTXDefaults $this.tomcatHttpPort = [int]($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Port) $this.settingsVersion = $SettingsVersion $this.desiredServerSettings = [DTXTomcatUtils]::GetDesiredServerSettings($this.dtxSystemInfo, $this.dtxDefaults, $this.settingsVersion) } [boolean] GetOverrideEnableHttp2Value([boolean]$defaultValue) { $desiredValue = $defaultValue if ($this.desiredServerSettings.ContainsKey("OverrideEnableHTTP2")) { $instanceId = $this.dtxSystemInfo.GenericInfo.IdentityInfo.instanceId if ($this.desiredServerSettings.OverrideEnableHTTP2.ContainsKey($instanceId)) { Write-LogWarning -Message "[$($this.className)] The EnableHTTP2 setting is overridden for instance $instanceId." $desiredValue = $this.desiredServerSettings.OverrideEnableHTTP2[$instanceId] } } return $desiredValue } [bool] IsTomcatConnectorChangeRequired() { $this.isChangeRequired = $false $tomcatServerXml = [DTXTomcatServerXml]::new($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Path) $desiredDnsLookpsValue = $this.desiredServerSettings.EnableDnsLookups $currentDnsLookupsValue = $tomcatServerXml.GetConnectorDnsLookupsEnabled($this.tomcatHttpPort) if ($desiredDnsLookpsValue -ne $currentDnsLookupsValue) { Write-LogWarning -Message "[$($this.className)] The desired DNS Lookups setting is $desiredDnsLookpsValue. The current setting is $currentDnsLookupsValue." $this.isChangeRequired = $true } $desiredSslEnabledProtocols = $this.desiredServerSettings.SSLSettings.Protocols -join "," $currentSslEnabledProtocols = $tomcatServerXml.GetConnectorSslEnabledProtocols($this.tomcatHttpPort) if ($desiredSslEnabledProtocols -ne $currentSslEnabledProtocols) { Write-LogWarning -Message "[$($this.className)] The desired SSL Enabled Protocols setting is $desiredSslEnabledProtocols. The current setting is $currentSslEnabledProtocols." $this.isChangeRequired = $true } $desiredSslCiphers = $this.desiredServerSettings.SSLSettings.Ciphers $currentSslCiphers = $tomcatServerXml.GetConnectorSslCiphers($this.tomcatHttpPort) if ($desiredSslCiphers -ne $currentSslCiphers) { Write-LogWarning -Message "[$($this.className)] The desired SSL Ciphers setting is $desiredSslCiphers. The current setting is $currentSslCiphers." $this.isChangeRequired = $true } $desiredHttp2Enabled = $this.GetOverrideEnableHttp2Value($this.desiredServerSettings.EnableHTTP2) $currentHttp2Enabled = $tomcatServerXml.IsConnectorHttp2Enabled($this.tomcatHttpPort) if ($desiredHttp2Enabled -ne $currentHttp2Enabled) { Write-LogWarning -Message "[$($this.className)] The desired HTTP2 setting is $desiredHttp2Enabled. The current setting is $currentHttp2Enabled." $this.isChangeRequired = $true } return $this.isChangeRequired } [bool] ChangeRequired() { if ($this.IsTomcatConnectorChangeRequired()) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_REQUIRED") return $true } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_SUCCESSFUL") return $false } [void] PerformChange() { if ($this.isChangeRequired) { $tomcatServerXml = [DTXTomcatServerXml]::new($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Path) $tomcatServerXml.SetConnectorDnsLookups($this.tomcatHttpPort, $this.desiredServerSettings.EnableDnsLookups) $tomcatServerXml.SetConnectorSslEnabledProtocols($this.tomcatHttpPort, ($this.desiredServerSettings.SSLSettings.Protocols -join ",")) $tomcatServerXml.SetConnectorSslCiphers($this.tomcatHttpPort, $this.desiredServerSettings.SSLSettings.Ciphers) $desiredHttp2Enabled = $this.GetOverrideEnableHttp2Value($this.desiredServerSettings.EnableHTTP2) $tomcatServerXml.SetConnectorHttp2($this.tomcatHttpPort, $desiredHttp2Enabled) $tomcatServerXml.Save() } } [void] TestAfterChange() { if (-not ($this.ChangeRequired())) { [DTXLogger]::LogInfo("[$($this.className)] The Tomcat connector settings change was successful.") return } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_FAILED") [DTXLogger]::LogCriticalAndThrow("[$($this.className)] The Tomcat connector settings change failed.") } } class DTXTomcatChangeCertificate { hidden [string] $className = $this.GetType().Name hidden [string] $dtxStateStatusKey = "tomcat-changes-$($this.className.ToLower())-status" hidden [hashtable] $dtxSystemInfo hidden [hashtable] $dtxDefaults hidden [boolean] $isTomcatServerXmlUpdateRequired = $false hidden [string] $certNamespace = "star.dotmatics.net" hidden [string] $certPath hidden [string] $tomcatHostname = "localhost" hidden [int] $tomcatHttpPort hidden [DTXTomcatServerXml] $tomcatServerXml DTXTomcatChangeCertificate( [Hashtable]$DTXSystemInfo, [Hashtable]$DTXDefaults ) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "UNKNOWN") $this.dtxSystemInfo = $DTXSystemInfo $this.dtxDefaults = $DTXDefaults $this.tomcatHttpPort = [int]($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Port) $this.tomcatServerXml = [DTXTomcatServerXml]::new($this.dtxSystemInfo.AppInfo.Tomcat.ServerXML.Path) $this.certPath = Join-Path ($this.dtxSystemInfo.StateInfo["certificates-[$($this.certNamespace)]-path"]) "$($this.certNamespace).jks" } [bool] IsTomcatCertChangeRequired() { $liveCertFingerprint = $null $installedCertFingerprint = $null try { Wait-ForWebServerToServeRequests -Hostname $this.tomcatHostname -Port $this.tomcatHttpPort -WaitSeconds 30 -SkipCertificateCheck $liveCertFingerprint = (Get-SSLCertificateFingerprint -Hostname $this.tomcatHostname -Port $this.tomcatHttpPort -HashAlgorithm "SHA1").ToLower() } catch { [DTXLogger]::LogCriticalAndThrow("[$($this.className)] Unable to obtain the live certificate fingerprint from Tomcat. Is the Tomcat service running?", $_.Exception) } try { $installedCertFingerprint = ($this.dtxSystemInfo.StateInfo["certificates-[$($this.certNamespace)]-fingerprint-sha1"]).ToLower() } catch { [DTXLogger]::LogCriticalAndThrow("[$($this.className)] The fingerprint for the Tomcat certificate is not set in the state file. Did the Sync-StarDotmaticsNetCertificate cmdlet run successfully?", $_.Exception) } if ($liveCertFingerprint -ne $installedCertFingerprint) { Write-LogInfo -Message "[$($this.className)] The live certificate fingerprint ($liveCertFingerprint) does not match the installed certificate fingerprint ($installedCertFingerprint)." return $true } return $false } [bool] IsTomcatServerXmlChangeRequired () { $configuredCertPath = $this.tomcatServerXml.GetConnectorKeystoreFilePath($this.tomcatHttpPort) if ([System.IO.Path]::GetFullPath($configuredCertPath) -ne [System.IO.Path]::GetFullPath($this.certPath)) { Write-LogInfo -Message "[$($this.className)] The configured keystore path ($configuredCertPath) does not match the desired keystore path $($this.certPath)." $this.isTomcatServerXmlUpdateRequired = $true return $true } return $false } [void] UpdateTomcatServerXmlFile() { $this.tomcatServerXml.Reload() $this.tomcatServerXml.SetConnectorKeystoreFilePath($this.tomcatHttpPort, $this.certPath) $this.tomcatServerXml.Save() } [bool] ChangeRequired() { $isTomcatCertChangeRequired = $this.IsTomcatCertChangeRequired() $isTomcatServerXmlChangeRequired = $this.IsTomcatServerXmlChangeRequired() if ($isTomcatCertChangeRequired -or $isTomcatServerXmlChangeRequired) { [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_REQUIRED") return $true } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_SUCCESSFUL") return $false } [void] PerformChange() { if ($this.isTomcatServerXmlUpdateRequired) { $this.UpdateTomcatServerXmlFile() } } [void] TestAfterChange() { if (-not ($this.ChangeRequired())) { [DTXLogger]::LogInfo("[$($this.className)] The Tomcat certificate change was successful.") return } [DTXStateProvider]::SetValue($this.dtxStateStatusKey, "CHANGE_FAILED") [DTXLogger]::LogCriticalAndThrow("[$($this.className)] The Tomcat certificate change failed.") } } function Add-InstanceLocalGroupMember { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [String] $LocalGroup, [Parameter(Mandatory = $true)] [String] $Member, [Parameter(Mandatory = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $documentParams = @{ LocalGroupName = $LocalGroup GroupMember = $Member } $invokeParams = @{ Name = "DTX-AddInstanceLocalGroupMember" Region = $Region InstanceId = $InstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Adding '$Member' as a group member to local instance group '$LocalGroup' on instance '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Operation succeeded." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Assert-True { [CmdletBinding()] param( [Parameter(Position = 0)] [object] $Condition, [Parameter(Position = 1)] [string] $Message ) Set-StrictMode -Version 'Latest' if ( -not $condition ) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Expected true but was false: $message")) } } function Backup-OpenEye { try { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Starting an Open Eye backup procedure..." $gifCachePath = Get-BrowserPlatformGifcachePath Push-Location -Path $gifCachePath $toolkitPath = Get-BrowserPlatformGifcacheToolkitPath if ((Get-ChildItem -Path $toolkitPath).Count -eq 0) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The toolkit path '$toolkitPath' is empty. Skipping taking a backup." return $null } $backupFilePath = Join-Path $gifCachePath "toolkit.bak.$(Get-Date -Format "yyyy-MM-dd_hh-mm-ss").zip" $rootFiles = Get-ChildItem -path $toolkitPath -File $totalSizeInMB=0 foreach($topLevelFile in $rootFiles){ $totalSizeInMB+=$topLevelFile.Length / 1MB } if ($totalSizeInMB -gt 1024){ Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Size of files too large to perform a backup, please investigate. Total size(MB): $($totalSizeInMb)" -ThrowException } $rootFiles | Compress-Archive -CompressionLevel NoCompression -DestinationPath $backupFilePath Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Backup file saved at: $($backupFilePath)" return Get-Item -Path $backupFilePath } finally { Pop-Location } } function Confirm-AWSAMI { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Id, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating AMI..." try { [void]$( Get-EC2Image -ImageId $Id -Region $Region ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validation complete!" } catch [InvalidOperationException] { if ($_.Exception.Message -like "*does not exist*") { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The AMI id '$Id' does not exist in the '$Region' region." -ThrowException -Exception $_ } Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-AWSCredentials { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$AWSAccountNumber, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating AWS credentials..." try { $currentCallerIdentity = Get-STSCallerIdentity -Region $Region if ($currentCallerIdentity.Account -eq $AWSAccountNumber) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validation complete!" } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Your AWS credentials have access to AWS account $( $currentCallerIdentity.Account ) instead of the requested account $AWSAccountNumber" throw } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ throw } } } function Confirm-AWSEC2InstanceQuota { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$InstanceType, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking the AWS EC2 vCPU quota limit..." try { if (@("a", "c", "d", "h", "i", "m", "r", "t", "z") -contains $InstanceType.ToLower()[0]) { $serviceLimit = (Get-SQServiceQuota -QuotaCode "L-1216C47A" -ServiceCode ec2 -Region $Region).Value [Amazon.CloudWatch.Model.Dimension[]]$dimensions = @() $d1 = New-Object Amazon.CloudWatch.Model.Dimension $d1.Name = "Service" $d1.Value = "EC2" $d2 = New-Object Amazon.CloudWatch.Model.Dimension $d2.Name = "Type" $d2.Value = "Resource" $d3 = New-Object Amazon.CloudWatch.Model.Dimension $d3.Name = "Class" $d3.Value = "Standard/OnDemand" $d4 = New-Object Amazon.CloudWatch.Model.Dimension $d4.Name = "Resource" $d4.Value = "vCPU" [Amazon.CloudWatch.Model.Dimension[]]$dimensions = @($d1, $d2, $d3, $d4) $cwMetricStatsParams = @{ MetricName = "ResourceCount" Namespace = "AWS/Usage" Statistic = "Maximum" Dimension = $dimensions Period = 3600 UtcStartTime = (Get-Date).ToUniversalTime().AddHours(-2) UtcEndTime = (Get-Date).ToUniversalTime() } $vCPUCount = (Get-CWMetricStatistic @cwMetricStatsParams -Region $Region).Datapoints[0].Maximum if ($vCPUCount -ge $serviceLimit) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The EC2 vCPU quota limit is reached in the '$Region' region. Please request an increase before proceeding." throw } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 vCPU quota: $serviceLimit" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 vCPU used: $vCPUCount" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 vCPU remaining: $( $serviceLimit - $vCPUCount )" } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-AWSElasticIPQuota { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking the AWS Elastic IP quota limit..." try { $elasticIPCount = (Get-EC2Address -Filter @{ Name = "domain"; Values = "vpc" } -Region $Region).Count $serviceQuotaLimit = (Get-SQServiceQuota -QuotaCode "L-0263D0A3" -ServiceCode ec2 -Region $Region).Value if ($elasticIPCount -ge $serviceQuotaLimit) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The Elastic IP quota limit is reached in the '$Region' region. Please request an increase before proceeding." throw } $elasticIPremaining = ($serviceQuotaLimit - $elasticIPCount) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Elastic IPs quota: $serviceQuotaLimit" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Elastic IPs used: $elasticIPCount" if ($elasticIPremaining -lt 5) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Elastic IPs remaining: $( $serviceQuotaLimit - $elasticIPCount )" } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Elastic IPs remaining: $( $serviceQuotaLimit - $elasticIPCount )" } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-AWSIAMRole { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$RoleName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidatePattern('^\d{12}$')] [String]$AWSAccountNumber ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating that the IAM Role exists in account: $AWSAccountNumber..." try { $iamRoleExists = Get-IAMRole -RoleName $RoleName -Region "us-east-1" if ($iamRoleExists.Arn -notlike "*" + $AWSAccountNumber + "*") { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] IAM role $RoleName does not exist in account: $AWSAccountNumber..." throw } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] IAM role $RoleName exists in account: $AWSAccountNumber..." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-AWSSubnet { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$SubnetId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$VPCId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating VPC Subnet..." try { $subnet = Get-EC2Subnet -SubnetId $SubnetId -Region $Region if ($subnet.VpcId -ne $VPCId) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Validation failed. Subnet '$SubnetId' does not belong to VPC '$VPCId'." throw } if ($subnet.AvailableIpAddressCount -lt 1) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Validation failed. Subnet '$SubnetId' does not have enough IP addresses left." throw } if ($subnet.AvailableIpAddressCount -lt 10) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Available IP addreses in subnet '$SubnetId': $( $subnet.AvailableIpAddressCount )" } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Available IP addreses in subnet '$SubnetId': $( $subnet.AvailableIpAddressCount )" } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validation complete!" } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-AWSVPC { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$VPCId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating VPC..." try { [void](Get-EC2Vpc -VpcId $VPCId -Region $Region) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validation complete!" } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Confirm-ComputerName { param ( [Parameter(Mandatory = $true)] [String]$ComputerName, [Parameter(Mandatory = $true)] [String]$DomainControllerInstanceId, [Parameter(Mandatory = $true)] [String]$DomainControllerRegion ) process { try { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Confirming computer name '$ComputerName' is available." $documentParams = @{ ComputerName = $ComputerName } $invokeParams = @{ Name = "DTX-ConfirmComputerName" Region = $DomainControllerRegion InstanceId = $DomainControllerInstanceId Parameters = $documentParams } $command = Invoke-SSMDocumentAndRetry @invokeParams $commandOutputParams = @{ CommandId = $command.CommandId InstanceId = $DomainControllerInstanceId Region = $DomainControllerRegion } $commandOutput = Get-SSMCommandOutput @commandOutputParams $message = ($commandOutput.ConfirmComputerName.StandardOutput | ConvertFrom-Json).Message if ($message -eq "NotFound") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Computer name $ComputerName is available." return } if (Read-BasicUserResponse -Prompt "The computer name '$ComputerName' is already in use. If you are running this script for the first time, you most likely need to stop here and investigate why the instance name exists already. Or rerun the script to generate a new random suffix. Would you like to continue? (y/n)") { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Computer name '$ComputerName' is already in use." return } else { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Computer name '$ComputerName' is already in use." } } catch { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to confirm computer name '$ComputerName' is available. Error message: $( $_.Exception.Message )" -Exception $_ } } } function Confirm-InstanceName { param ( [Parameter(Mandatory = $true)] [String]$InstanceName, [Parameter(Mandatory = $true)] [String]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Confirming instance name '$InstanceName' is available." $instance = Get-EC2Instance -Region $Region -Filter @{Name = "tag:Name"; Values = "$InstanceName" } if ($instance) { if (Read-BasicUserResponse -Prompt "The instance name '$InstanceName' is already in use. If you are running this script for the first time, you most likely need to stop here and investigate why the instance name exists already. Or rerun the script to generate a new random suffix. Would you like to continue? (y/n)") { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Instance name '$InstanceName' is already in use." return } else { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Instance name '$InstanceName' is already in use." } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Instance name '$InstanceName' is available." } } function Confirm-PRTGConnection { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Username, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Password, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Hostname ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating PRTG connection..." try { [void](Connect-PrtgServer -Server $Hostname -IgnoreSSL -Credential (New-Credential -Username $Username -Password $Password) -Force -ErrorAction Stop) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validation complete!" } catch [System.Net.WebException] { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Could not resolve server with name: $Hostname" -ThrowException -Exception $_ } catch [System.UriFormatException] { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Invalid hostname: This may be caused by whitespace or the server name is too long" -ThrowException -Exception $_ } catch [System.Net.Http.HttpRequestException] { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] PRTG username and/or password are incorrect. Please try again." -ThrowException -Exception $_ } catch [System.Net.Sockets.SocketException] { Write-LogCritical "[$( $MyInvocation.MyCommand )] Unable to connect to $Hostname. Resource is not reachable. Do you have network access to the server?" -ThrowException -Exception $_ } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Convert-EC2SCSITargetIdToDeviceName { param( [Parameter(Mandatory = $true)] [int] $SCSITargetId ) if ($SCSITargetId -eq 0) { return "sda1" } $deviceName = "xvd" if ($SCSITargetId -gt 25) { $deviceName += [char](0x60 + [int]($SCSITargetId / 26)) } $deviceName += [char](0x61 + $SCSITargetId % 26) return $deviceName } function ConvertTo-FormattedXmlString { param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [xml] $XmlContent, [Boolean] $EnableIndentation = $true, [Int] $IndentChars = 4, [Boolean] $NewLineOnAttributes = $true ) process { Using-Object($memoryStream = New-Object System.IO.MemoryStream) { $settings = New-Object System.Xml.XmlWriterSettings $settings.Indent = $EnableIndentation $settings.IndentChars = " " * $IndentChars $settings.NewLineOnAttributes = $NewLineOnAttributes Using-Object($writer = [System.Xml.XmlWriter]::Create($memoryStream, $settings)) { $XmlContent.Save($writer) $writer.Flush() } $memoryStream.Position = 0 Using-Object($formattedXml = New-Object System.IO.StreamReader($memoryStream)) { return $formattedXml.ReadToEnd() } } } } function Copy-ItemWithRetry { param ( [string]$Path, [string]$Destination, [int]$WaitTimeInSeconds = 3, [int]$MaxRetryCount = 20 ) $retryCount = 0 $success = $false do { $retryCount++ try { Copy-Item -Path $Path -Destination $Destination -Force -ErrorAction Stop | Out-Null $success = $true break } catch { Write-LogWarrning -Message "[$( $MyInvocation.MyCommand )] Error occurred: $_" Write-LogWarrning -Message "[$( $MyInvocation.MyCommand )] Retrying in $WaitTimeInSeconds seconds..." Start-Sleep -Seconds $WaitTimeInSeconds } } while ($retryCount -le $MaxRetryCount) if (!$success) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Failed to copy $Path to $Destination after $retryCount attempts." -ThrowException } } function Format-EnvironmentType { param ( [Parameter(Mandatory = $true)] [String]$Environment ) $result = [PSCustomObject]@{ LongName = "" TagValue = "" MediumName = "" ShortName = "" } switch ($Environment) { "customer_production" { $result.LongName = "customer_production" $result.MediumName = "prod" $result.ShortName = "p" } "customer_non_production" { $result.LongName = "customer_non_production" $result.MediumName = "nonprod" $result.ShortName = "n" } "internal" { $result.LongName = "internal" $result.MediumName = "int" $result.ShortName = "i" } } $result.TagValue = $result.LongName return $result } function Get-AntivirusName { if ($IsWindows) { $isCrowdStrikeInstalled = Test-IsAppInstalled -SearchTerm "*CrowdStrike*Win*Sensor*" if ($isCrowdStrikeInstalled) { return 'Crowdstrike'; } $isTrendInstalled = Test-IsAppInstalled -SearchTerm "*Trend*" if ($isTrendInstalled) { return 'Trend'; } } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet. Skipping." } } function Get-AWSCurrentUser { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Region ) process { try { $stsCaller = Get-STSCallerIdentity -Region $Region if ($stsCaller.Arn -like "arn:aws:iam::*:user/*") { return $stsCaller.arn.Split("user/")[1].ToString() } if ($stsCaller.Arn -like "arn:aws:sts::*:assumed-role/*") { return $stsCaller.Arn.Split("assumed-role/")[1].Split("/")[-1].ToString() } return $stsCaller.Arn.ToString() } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Get-AWSManagementSecurityGroups { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$VPCId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Region ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Getting the AWS management security groups..." try { $suffixes = @("-mgmt-services", "-mgmt-2-sg") $ids = @() $suffixes | ForEach-Object { $ids += (Get-EC2SecurityGroup -Region $Region -Filter @{ Name = "group-name"; Values = $Region + $_ }, @{ Name = "vpc-id"; Values = $VPCId }).GroupId } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Groups found: $ids" return $ids } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Get-AWSRoute53Record { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] [ValidateSet("A", "CNAME", IgnoreCase = $false)] $Type, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $HostedZoneId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { $hostedZoneName = (Get-R53HostedZone -Id $HostedZoneId -Region $Region).HostedZone.Name.Trim(".") Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Searching for '$Type' record with name '$Name' in hosted zone '$hostedZoneName' ($HostedZoneId)..." $getResourceRecordSetParams = @{ Id = $HostedZoneId MaxItems = 300 } $recordSets = @() $isTruncated = $true while ($isTruncated) { $batch = Get-R53ResourceRecordSet @getResourceRecordSetParams $recordSets += $batch.ResourceRecordSets if ($batch.IsTruncated) { $getResourceRecordSetParams["StartRecordName"] = $batch.NextRecordName } else { $isTruncated = $false } } foreach ($recordSet in $recordSets) { $recordName = ($recordSet.Name -split ".$hostedZoneName.")[0].ToLower() if ($recordName -eq $Name.ToLower() -and $recordSet.Type -eq $Type) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record found!" return $recordSet } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record not found!" } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Get-BrowserPlatformPath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSObject] $TomcatWebAppPath ) return [DTXBrowser]::new($TomcatWebAppPath).GetBrowserPlatformPath() } function Get-BrowserPlatformPropertiesFilePath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSObject] $TomcatWebAppPath ) return [DTXBrowser]::new($TomcatWebAppPath).GetBrowserPlatformPropertiesFilePath() } function Get-BrowserPlatformGifcachePath { return [DTXBrowser]::new((Get-TomcatWebAppPath)).GetBrowserPlatformGifCachePath() } function Get-BrowserPlatformGifcacheToolkitPath { return [DTXBrowser]::new((Get-TomcatWebAppPath)).GetBrowserPlatformGifCacheToolkitPath() } function Get-BrowserSysMonitor { [CmdletBinding()] param() function Get-BrowserPropertiesHashTable{ param( [Parameter(Mandatory = $true)] [string] $PropertiesFilePath ) if (!(Test-Path $PropertiesFilePath)){ throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] browser properties file path doesnt exist")) } return ConvertFrom-StringData (Get-Content $PropertiesFilePath -raw) } function Get-ApiCallHeaders{ param( [Parameter(Mandatory = $true)] [hashtable] $PropertiesData, [string] $PropertyKey ) $headers= @{} if ($PropertiesData.Contains($PropertyKey)){ $ok_ip = $PropertiesData.$PropertyKey.split(",")[0] $headers.Add("X-Forwarded-For", $ok_ip) } else{ } return $headers } function Invoke-Api { param( [Parameter(Mandatory = $true)] $headers, [string] $APIEndpoint ) try{ $response = Invoke-WebRequest -Uri $APIEndpoint -TimeoutSec 15 -SkipCertificateCheck -Headers $headers if ($response -ne $null) { if (Test-Json -Json $response.Content -ErrorAction SilentlyContinue) { return $response.Content | ConvertFrom-Json -AsHashtable } else{ Write-LogWarning "[$( $MyInvocation.MyCommand )] Failed to detect data from Web Response as JSON, returning empty dict" Write-LogWarning "[$( $MyInvocation.MyCommand )] Data: $($response.Content)" return New-Object System.Collections.Hashtable } } else{ Write-LogWarning "[$( $MyInvocation.MyCommand )] Failed to see response as non null, returning empty dict" return New-Object System.Collections.Hashtable } } catch{ Write-LogWarning "[$( $MyInvocation.MyCommand )] Failed to execute web request to SystemMonitor.do: $($_.Exception.Message | Out-String)" Write-LogWarning "[$( $MyInvocation.MyCommand )] returning empty dict" return New-Object System.Collections.Hashtable } } if(!(Test-IsBrowserSystem)){ Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Is not a browser system but calling health check API" } $defaults = Get-Defaults $monitoringIpsKey = $defaults.Browser.Properties.MonitoringIpsKey $ApiEndpoint = $defaults.Browser.HealthCheck.APIEndpoint $browserProperties = [DTXBrowser]::new((Get-TomcatWebAppPath)).GetBrowserPropertiesData() [DTXBrowserSysMonitor]::Perform($monitoringIpsKey, $ApiEndpoint, $browserProperties) } function Get-ComputerName { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String]$CustomerName, [Parameter(Mandatory = $true)] [String]$Environment, [Parameter(Mandatory = $true)] [String]$RandomString ) process { if ($CustomerName.Length -ge 8) { $CustomerName = $CustomerName.Substring(0, 7) } $CustomerName = $CustomerName -replace '[^a-zA-Z0-9]', '' $name = "$CustomerName-$Environment-$RandomString".ToLower() Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] ComputerName: $name" if ($name.Length -gt 15) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] ComputerName is too long. Max length is 15 characters." } return $name } } function Get-DBInfoFilePath { return Join-Path (Get-LocalDotmaticsPath) "db-info.txt" } function Get-Defaults { function Get-FileContent { param ( [string]$FilePath ) if (Test-Path -Path $FilePath) { return Get-Content -Path $FilePath -Raw } else { throw [System.IO.FileNotFoundException] "File $FilePath not found." } } $jsonContent = "" if ($env:DTX_DEFAULTS_JSON_FILE_PATH) { $jsonContent = Get-FileContent -FilePath $env:DTX_DEFAULTS_JSON_FILE_PATH } else { $externalFilePath = Get-ExternalFilePath -FileName "defaults.json" $jsonContent = Get-FileContent -FilePath $externalFilePath } $jsonContentWithoutComments = [DTXJsonUtils]::RemoveJsonComments($jsonContent) return $jsonContentWithoutComments | ConvertFrom-Json -Depth 100 -AsHashtable } function Get-DiskDriveLetter { param( [Parameter(Mandatory = $true)] [string] $DiskPath ) if ($IsWindows) { $diskNumber = (Get-Disk -Path $DiskPath).Number $driveLetter = $null if ($diskNumber -eq 0) { $driveLetter = "C" } else { try { $driveLetter = (Get-Partition -DiskNumber $diskNumber).DriveLetter if (-not $driveLetter) { $driveLetter = ((Get-Partition -DiskId $DiskPath).AccessPaths).Split(",")[0] } if ($driveLetter.Count -gt 1) { $driveLetter = $driveLetter | Where-Object { $_ -match "[A-Z]" } } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Cannot get drive letter for disk number '$diskNumber'. Skipping..." return $null } } return $driveLetter } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This function is only supported on Windows. Skipping..." } } function Get-DiskInformation { $returnObj = New-Object Collections.Generic.List[PSObject] $sysInfo = Get-SystemInfo if (-not $IsWindows) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Cannot get disk information on a non-Windows machine for now. Skipping..." return , $returnObj } if ($IsWindows -and $sysInfo.Windows.VersionAsYear -le 2012) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Cannot get disk information on Windows 2012 or older. Skipping..." return , $returnObj } foreach ($disk in Get-Disk) { $diskNumber = $disk.Number $deviceName = $disk.FriendlyName $partitionsCount = $disk.NumberOfPartitions $driveLetter = Get-DiskDriveLetter -DiskPath $disk.Path $ebsVolumeId = Get-EBSVolumeId -DiskPath $disk.Path $virtualDevice = $null $blockDeviceName = $null $volumeName = (Get-PSDrive | Where-Object { $_.Name -in @($driveLetter) }).Description | Where-Object { $_ -notin @("", $null) } $blockDeviceMappings = (Get-EC2InstanceMetadata -Category "BlockDeviceMapping") | Where-Object { $_.Key -ne "ami" } if ($disk.Path -like "*PROD_PVDISK*") { $blockDeviceName = Convert-EC2SCSITargetIdToDeviceName((Get-CimInstance -Class Win32_Diskdrive | Where-Object { $_.DeviceID -eq ("\\.\PHYSICALDRIVE" + $diskNumber) }).SCSITargetId) $blockDeviceName = "/dev/" + $blockDeviceName $virtualDevice = ($blockDeviceMappings | Where-Object { $_.Value -eq $blockDeviceName }).Key | Select-Object -First 1 } if ($disk.Path -like "*PROD_AMAZON_EC2_NVME*") { $blockDeviceName = $blockDeviceMappings.ephemeral((Get-CimInstance -Class Win32_Diskdrive | Where-Object { $_.DeviceID -eq ("\\.\PHYSICALDRIVE" + $diskNumber ) }).SCSIPort - 2) $virtualDevice = ($blockDeviceMappings | Where-Object { $_.Value -eq $blockDeviceName }).Key | Select-Object -First 1 } $diskToAdd = New-Object PSObject -Property @{ Disk = $disk | Select-Object -ExcludeProperty Cim* Partitions = $partitionsCount DriveLetter = $driveLetter ?? "N/A"; EbsVolumeId = $ebsVolumeId ?? "N/A"; Device = $blockDeviceName ?? "N/A"; VirtualDevice = $virtualDevice ?? "N/A"; VolumeName = $volumeName ?? "N/A"; DeviceName = $deviceName ?? "N/A"; } $returnObj.Add($diskToAdd) } $sysVolumes = Get-Volume foreach ($volume in $sysVolumes) { $matchedDisk = $returnObj | Where-Object { $_.DriveLetter -eq $volume.DriveLetter } if ($matchedDisk) { $matchedDisk | Add-Member -MemberType NoteProperty -Name 'VolumeSizeGB' -Value ([math]::Round($volume.Size / 1GB, 2)) $matchedDisk | Add-Member -MemberType NoteProperty -Name 'VolumeSpaceLeftGB' -Value ([math]::Round($volume.SizeRemaining / 1GB, 2)) $matchedDisk | Add-Member -MemberType NoteProperty -Name 'VolumePercentFree' -Value ([math]::round(($volume.SizeRemaining / $volume.Size) * 100, 2)) } } return $returnObj } function Get-EBSVolumeId { param( [Parameter(Mandatory = $true)] [string] $DiskPath ) if ($IsWindows) { $serialNumber = (Get-Disk -Path $DiskPath).SerialNumber $ebsVolumeId = $null if ($serialNumber -like 'vol*') { $ebsVolumeId = $serialNumber.Substring(0, 20).Replace("vol", "vol-") } elseif ($serialNumber -like 'aws*') { $ebsVolumeId = $serialNumber.Substring(0, 20).Replace("AWS", "AWS-") } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Could not find EBS volume ID for disk '$DiskPath'." } return $ebsVolumeId } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This function is only supported on Windows. Skipping..." } } function Get-EC2Tags { param ( [string] $InstanceId ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Getting Tags for EC2" $instanceTags = Get-EC2Tag -Filter @{ Name = "resource-id"; Values = $InstanceId } Assert-True -Condition ($instanceTags.Length -gt 0) -message "Instance Tags was length 0" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Total tag count returned: $($instanceTags.Length)" return $instanceTags } function Get-TagValue { param ( [string] $Key, [array] $Tags ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Finding tag value for key: $key" foreach ($tag in $Tags) { if ($tag.Key -eq $Key) { return $tag.Value } } Write-LogWarning "[$( $MyInvocation.MyCommand )] Tag key is NOT found in list of tags" throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Tag key: $Key not found")) } function Get-ExternalFilePath { param( [string] $FileName ) return (New-ExternalFilesProvider).GetFilePath($FileName) } function Get-FileFromS3 { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $BucketName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $BucketKey, [Parameter(Mandatory = $false)] [string] $DirectoryPath, [switch] $PreserveFileName ) return [DTXS3Utils]::GetFile($BucketName, $BucketKey, $DirectoryPath, $PreserveFileName) } function Get-GlowrootInfo { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSObject] $TomcatJavaOptions ) $res = [ordered]@{ IsGlowrootInJavaOptions = "UNKNOWN" IsGlowrootLaunched = "UNKNOWN" TextInfo = "UNKNOWN" } if (-not $TomcatJavaOptions ) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine Glowroot info since Tomcat Options are not available" return $res } $glowrootMessage1 = "" $glowrootMessage2 = "" if (([string]::Join('', $TomcatJavaOptions).Contains("glowroot"))) { $res.IsGlowrootInJavaOptions = $true $glowrootMessage1 = "Glowroot is present in tomcat java options" } else { $res.IsGlowrootInJavaOptions = $false $glowrootMessage1 = "Glowroot is absent in tomcat java options" } try { $webResponse = Invoke-WebRequest -uri "http://localhost:4000" -UseBasicParsing $s = $webResponse.content } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Glowroot URL localhost:4000 invoke failed. $( $_.Exception.Message ) " $s = "" } if ([string]::IsNullOrEmpty($s)) { $res.IsGlowrootLaunched = $false $glowrootMessage2 = "Glowroot is not launched" } else { $res.IsGlowrootLaunched = $true $glowrootMessage2 = "Glowroot is launched" } $res.TextInfo = "$glowrootMessage1. $glowrootMessage2" return $res } function Get-InfoFromDatabase { param( [psobject] [ValidateNotNullOrEmpty()] $ReturnObjParam) function Write-DBInfoFile { $sid = Get-OraclePluggableDbSid if ($sid) { $dbInfoFile = Get-DBInfoFilePath $orclSqlFilePath = Get-ExternalFilePath -FileName "sql-orcl.sql" $orclpdbSqlFilePath = Get-ExternalFilePath -FileName "sql-orclpdb.sql" "{oracle_version}" > $dbinfoFile try { & 'sqlplus.exe' '/ as sysdba' "@$orclSqlFilePath" >> $dbinfoFile & 'sqlplus.exe' "/@$sid as sysdba" "@$orclpdbSqlFilePath" >> $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } "{end}" >> $dbinfoFile } } function Get-OracleVersion { param([string]$OracleStartOutput) $i = $OracleStartOutput.IndexOf("Connected to:") if ($i -eq -1) { return $null; } $res = $OracleStartOutput.Substring($i + 14) return $res } function Read-AllFromDBInfoFile { $res = @{ OracleVersion = "UNKNOWN" Memory = [ordered]@{ MemoryMaxTarget = "UNKNOWN" MemoryTarget = "UNKNOWN" PgaAggregateLimit = "UNKNOWN" PgaAggregateTarget = "UNKNOWN" SgaMaxSize = "UNKNOWN" SgaTarget = "UNKNOWN" TextInfo = "UNKNOWN" } ProcessesSessions = "UNKNOWN" Processes = "UNKNOWN" Sessions = "UNKNOWN" OpenCursors = "UNKNOWN" ProjectsNumber = "UNKNOWN" DatasourceCount = [ordered]@{ DynamicDatasourceCount = "UNKNOWN" BrowserManagedTablesCount = "UNKNOWN" BrowserManagedViewsCount = "UNKNOWN" DatasourceCountInfo = "UNKNOWN" } MaterialisedViewCount = "UNKNOWN" PivotTableCount = "UNKNOWN" Version = [ordered]@{ BrowserVersion = "UNKNOWN" BioregisterVersion = "UNKNOWN" PinpointVersion = "UNKNOWN" AllVersionsText = "UNKNOWN" } } $allProperties = Read-AllPropertiesFromDBInfoFile if ($null -eq $allProperties) { return $null } $oracleStartOutput = $allProperties["{oracle_version}"] $res.OracleVersion = Get-OracleVersion $oracleStartOutput $res.Memory.TextInfo = $allProperties["{oracle_memory}"] $res.Memory.MemoryMaxTarget = $allProperties["{memory_max_target}"] $res.Memory.MemoryTarget = $allProperties["{memory_target}"] $res.Memory.PgaAggregateLimit = $allProperties["{pga_aggregate_limit}"] $res.Memory.PgaAggregateTarget = $allProperties["{pga_aggregate_target}"] $res.Memory.SgaMaxSize = $allProperties["{sga_max_size}"] $res.Memory.SgaTarget = $allProperties["{sga_target}"] $res.ProcessesSessions = $allProperties["{processes_sessions}"] $res.Processes = $allProperties["{processes}"] $res.Sessions = $allProperties["{sessions}"] $res.OpenCursors = $allProperties["{open_cursors}"] $res.ProjectsNumber = $allProperties["{projects_number}"] $res.DatasourceCount.DatasourceCountInfo = $allProperties["{ds_counts}"] $res.DatasourceCount.DynamicDatasourceCount = $allProperties["{cnt_dynamic_datasources}"] $res.DatasourceCount.BrowserManagedTablesCount = $allProperties["{cnt_br_managed_tables}"] $res.DatasourceCount.BrowserManagedViewsCount = $allProperties["{cnt_br_managed_views}"] $res.MaterialisedViewCount = $allProperties["{mv_count}"] $res.PivotTableCount = $allProperties["{pivot_count}"] $res.Version.AllVersionsText = $allProperties["{version}"] $res.Version.BrowserVersion = $allProperties["{browser_version_number}"] $res.Version.BioregisterVersion = $allProperties["{bioregister_version}"] $res.Version.PinpointVersion = $allProperties["{pinpoint_version_number}"] return $res } if ($IsWindows) { Write-DBInfoFile $res = Read-AllFromDBInfoFile return $res } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet. Skipping." } } function Get-InfoFromOracleLogs { function Write-FrequencyInfoFiles { function OutputResult { param ( [array]$Keys, [hashtable]$Freq, [string]$OutputFile ) for ($i = 0; $i -lt $Keys.count; $i++) { $str = $Keys[$i] + " = " + $Freq[$Keys[$i]] Add-Content -path $OutputFile -Value $str } } function Get-OracleRuntimeSid { $dbInfoFile = Join-Path $(Get-LocalDotmaticsPath) "db-sid.txt" $sqlFilePath = Get-ExternalFilePath -FileName "sql-get-sid.sql" try { & 'sqlplus.exe' '/ as sysdba' "@$sqlFilePath" > $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } return Read-PropertyFromDBInfoFile "{sid}" $dbInfoFile } function Get-OracleAlertLogFileFolder { $dbInfoFile = Join-Path $(Get-LocalDotmaticsPath) "db-alert-log-path.txt" $sqlFilePath = Get-ExternalFilePath -FileName "sql-get-alert-log-path.sql" try { & 'sqlplus.exe' '/ as sysdba' "@$sqlFilePath" > $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } return Read-PropertyFromDBInfoFile "{alert_log_file_path}" $dbInfoFile } function CalculateFrequency { param ( [string] $SearchFor, [string] $OutputFile ) if (Test-Path $OutputFile) { Clear-Content $OutputFile -Force } $freq = @{ } [string[]]$arrayFromFile = Get-Content -Path $oracleAlertLogPath $i = 0 foreach ($line in $arrayFromFile) { if ($line -clike "*" + $SearchFor + "*" ) { $j = $i - 1; for ($j = $i - 1; ; $j--) { if ($arrayFromFile[$j] -match '^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d' ) { $dateRegexp = 'yyyy-MM-ddTHH:mm:ss' $dateSubstrFrom = 0 $dateSubstrTo = 19 break; } if ($arrayFromFile[$j] -match '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d \d\d:\d\d:\d\d \d\d\d\d' ) { $dateRegexp = 'MMM dd HH:mm:ss yyyy' $dateSubstrFrom = 4 $dateSubstrTo = 20 break; } } $timeStr = $arrayFromFile[$j].Substring($dateSubstrFrom, $dateSubstrTo) $d = [datetime]::parseexact($timeStr, $dateRegexp, $null) $dateKey = $d.ToString("yyyy-MM-dd") if ($freq.ContainsKey($dateKey)) { $freq[$dateKey]++ } else { $freq[$dateKey] = 1 } } $i++ } $Keys = ($Freq.Keys | Sort-Object) OutputResult $Keys $Freq $OutputFile } $folder = Get-OracleAlertLogFileFolder if (-not ([System.IO.File]::Exists($folder)) ) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Oracle Alert log file folder not found" return $null; } $sid = Get-OracleRuntimeSid $oracleAlertLogPath = Join-Path $($folder) "alert_$sid.log" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Oracle Alert log file path: $($oracleAlertLogPath)" $searchFor = "WARNING: Heavy swapping observed on system in last 5 mins." $OutputFile = Get-OracleLogsheavySwappingFile CalculateFrequency $searchFor $OutputFile $searchFor1 = "Checkpoint not complete" $OutputFile1 = Get-OracleLogsCheckpointNotCompleteFile CalculateFrequency $searchFor1 $OutputFile1 $searchFor2 = "ORA-04036: PGA memory used by the instance exceeds PGA_AGGREGATE_LIMIT" $OutputFile2 = Get-OracleLogsPgaLimitFile CalculateFrequency $searchFor2 $OutputFile2 } function Read-DatabaseAlertLogsFile { $res = [ordered]@{ LatestLogsCheckpointNotComplete = "UNKNOWN" LatestLogsHeavySwapping = "UNKNOWN" LatestLogsPgaLimit = "UNKNOWN" } try { $res.LatestLogsCheckpointNotComplete = Get-Content -Path $(Get-OracleLogsCheckpointNotCompleteFile) -tail 10 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] File Frequency-checkpoint-not-complete.txt not found. Skipping." } try { $res.LatestLogsHeavySwapping = Get-Content -Path $(Get-OracleLogsHeavySwappingFile) -tail 10 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] File Frequency-warning-heavy-swapping.txt not found. Skipping." } try { $res.LatestLogsPgaLimit = Get-Content -Path $(Get-OracleLogsPgaLimitFile) -tail 10 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] File Frequency-PGA_AGGREGATE_LIMIT.txt not found. Skipping." } return $res } function Get-OracleLogsHeavySwappingFile { return Join-Path $(Get-LocalDotmaticsPath) "Frequency-warning-heavy-swapping.txt" } function Get-OracleLogsCheckpointNotCompleteFile { return Join-Path $(Get-LocalDotmaticsPath) "Frequency-checkpoint-not-complete.txt" } function Get-OracleLogsPgaLimitFile { return Join-Path $(Get-LocalDotmaticsPath) "Frequency-PGA_AGGREGATE_LIMIT.txt" } if ($IsWindows) { Write-FrequencyInfoFiles return Read-DatabaseAlertLogsFile } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet. Skipping." } } function Get-InstalledApps { if ($IsWindows) { $WindowsUninstallRegKeys = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" ) return Get-ChildItem $WindowsUninstallRegKeys | Get-ItemProperty | Where-Object { $_.PSObject.Properties.Name -contains "DisplayName" } } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet." -ThrowException } } function Get-InstanceMetadata { param ( [string] $BaseUrl = "http://169.254.169.254/latest", [string] $MetadataBaseUrl = "$BaseUrl/meta-data", [string] $InstanceIdentityBaseUrl = "$BaseUrl/dynamic/instance-identity/document" ) $instanceId = (Invoke-RestMethod -Uri "$MetadataBaseUrl/instance-id").ToString() $instanceType = (Invoke-RestMethod -Uri "$MetadataBaseUrl/instance-type").ToString() $region = (Invoke-RestMethod -Uri $InstanceIdentityBaseUrl).region.ToString() return [PSCustomObject]@{ InstanceId = $instanceId InstanceType = $instanceType Region = $region } } function Get-InstanceName { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String]$CustomerName, [Parameter(Mandatory = $true)] [String]$Region, [Parameter(Mandatory = $true)] [String]$Environment, [Parameter(Mandatory = $true)] [String]$RandomString ) process { $name = "$Region-$CustomerName-$Environment-$RandomString".ToLower() Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Instance name: $name" return $name } } function Get-InstanceUrl { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $InstanceTags ) $urlPattern = "https://[\w\.\-]+.dotmatics.net" try { $url = Get-TagValue -Key 'Url' -Tags $InstanceTags -ErrorAction "SilentlyContinue" } catch { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] URL was not identified using Url key in instance tags." } if ($url -match $urlPattern) { return $url } foreach ($value in $InstanceTags.Values) { if ($value -match $urlPattern) { return $value } } return "UNKNOWN" } function Get-JChemCartridgeHomePathUsingProcess { $thePath = $null $process = Get-JChemCartridgeProcess if (-not $process) { return $null } $thePath = [System.IO.Path]::GetDirectoryName($process.Path) if ($null -eq $thePath) { return $null } if (-not (Test-Path -Path $thePath)) { return $null } return $thePath } function Get-JChemCartridgeHomePathUsingServices { if ($IsWindows) { $service = Get-JChemCartridgeService if (!$service) { return $null } return [System.IO.Path]::GetDirectoryName((Join-Path -Path ($service.BinaryPathName -Split "cartridge")[0] -ChildPath ("cartridge" + [System.IO.Path]::DirectorySeparatorChar))) } return $null } function Get-JChemCartridgeHomePath { $thePath = $null if ($IsWindows) { $thePath = Get-JChemCartridgeHomePathUsingServices if ($thePath) { return $thePath } } $thePath = Get-JChemCartridgeHomePathUsingProcess if ($thePath) { return $thePath } Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Unable to determine JChem Cartridge Service's home path." } function Get-JChemCartridgeProcess { $process = Get-Process | Where-Object { ($_.ProcessName -like "*prunsrv-*") -and ($_.CommandLine -like "*CartridgeService*") } if ($null -eq $process) { return $null } if ($proces -is [System.Array]) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Multiple JChem Cartridge processes found. This is not expected." -ThrowException } return $process } function Get-JChemCartridgeService { $service = Get-Service | Where-Object { ($_.Name -like "*jchem*cartridge*") -and ($_.BinaryPathName -like "*prunsrv*") } if (!$service) { $process = Get-JChemCartridgeProcess $service = Get-CimInstance -Class Win32_Service -Filter ("ProcessId LIKE '" + $process.Id + "'") $service = Get-Service $service.Name } if (!$service) { return $null } if ($service.Length -gt 1) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the JChem Service. Multiple services match the search criteria. Do we have two or more JChem.exe processes running on this machine?" } return $service } function Get-JChemCartridgeVersion { $homePath = Get-JChemCartridgeHomePath $versionPropsFile = Join-Path -Path (Split-Path $homePath -Parent) -ChildPath "version.properties" if (!(Test-Path $versionPropsFile)) { return $null } $fileContents = Get-Content $versionPropsFile -Raw | ConvertFrom-StringData if ($fileContents.ContainsKey("version")) { return $fileContents.Version } return $null } function Get-JChemMetadata { $returnObj = @{ Cartridge = @{ Process = $null Service = $null HomePath = $null Version = $null } } try { $returnObj.Cartridge.Process = Get-JChemCartridgeProcess } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the JChem Cartridge process." } try { $returnObj.Cartridge.Service = Get-JChemCartridgeService } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the JChem Cartridge service." } try { $returnObj.Cartridge.HomePath = Get-JChemCartridgeHomePath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the JChem Cartridge home path." } try { $returnObj.Cartridge.Version = Get-JChemCartridgeVersion } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the JChem Cartridge version." } return $returnObj } function Get-LicenseNumber { [CmdletBinding()] param( [string] $WebAppPath ) try { if ([string]::IsNullOrEmpty($WebAppPath)) { $WebAppPath = Get-TomcatWebAppPath } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine WebApp folder path" } if (-not $WebAppPath) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine license number since WebApp folder path is uknown" return "UNKNOWN" } $defaults = Get-Defaults $pathToLicenseFile = Join-Path $WebAppPath $defaults.Browser.LicenseFilePath if (-not (Test-Path $pathToLicenseFile)) { return "UNKNOWN" } $fileLineLicenseNumber = Get-Content $pathToLicenseFile | where {$_ -like '*browser license seats:*'} | Select-Object -First 1 if ($fileLineLicenseNumber -match '(\d{1,})') { $licenseCount = $Matches[1] return $licenseCount } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to get license number." return "UNKNOWN" } } function Get-LocalDotmaticsPath { return [DTXPathProvider]::GetLocalDotmaticsPath() } function Get-LocalDotmaticsSharedPath { return [DTXPathProvider]::GetLocalDotmaticsSharedPath() } function Get-LocalStateFilePath { return [DTXPathProvider]::GetLocalStateFilePath() } function Get-LocalTempPath { return [DTXPathProvider]::GetLocalTempPath() } function Get-OpenEyeDefaults { param ( [string] $Version ) $defaults = Get-Defaults if (!$defaults) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Unable to get the defaults.json file. Are you authenticated?" } $openEyeDefaults = $defaults.OpenEye."v$Version" if (!$openEyeDefaults) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Unable to locate OpenEye version '$Version' in the defaults.json file." } return $openEyeDefaults } function Get-OpenEyeMetadata { $returnObj = @{ UserFriendlyVersion = $null Mol2NameChecksum = $null Mol2NameBatchChecksum = $null } if (!$IsWindows -and $IsLinux) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Only Windows and Linux operating systems are supported." return $returnObj } try { $gifcacheToolkitPath = Get-BrowserPlatformGifcacheToolkitPath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to locate the Browser gifcache/toolkit directory." return $returnObj } try { if ($IsWindows) { $mol2NameFileName = "mol2name.exe" $mol2NameFilePath = Join-Path $gifcacheToolkitPath $mol2NameFileName $mol2NameBatchFileName = "mol2name_batch.exe" $mol2NameBatchFilePath = Join-Path $gifcacheToolkitPath $mol2NameBatchFileName } if ($IsLinux) { $mol2NameFileName = "mol2name" $mol2NameFilePath = Join-Path $gifcacheToolkitPath $mol2NameFileName $mol2NameBatchFileName = "mol2name_batch" $mol2NameBatchFilePath = Join-Path $gifcacheToolkitPath $mol2NameBatchFileName } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to set the mol2name and mol2namebatch file paths." return $returnObj } try { if (Test-Path $mol2NameFilePath) { $returnObj.Mol2NameChecksum = (Get-FileHash -Path $mol2NameFilePath -Algorithm MD5).Hash } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the the OpenEye Mol2Name binary." } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the the OpenEye Mol2Name checksum." } try { if (Test-Path $mol2NameBatchFilePath) { $returnObj.Mol2NameBatchChecksum = (Get-FileHash -Path $mol2NameBatchFilePath -Algorithm MD5).Hash } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the the OpenEye Mol2NameBatch binary." } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the the OpenEye Mol2NameBatch checksum." } try { if ($returnObj.Mol2NameChecksum -or $returnObj.Mol2NameBatchChecksum) { $openEyeDefaults = (Get-Defaults).OpenEye foreach ($prop in $openEyeDefaults.Keys) { if ($prop -like "v20*") { $versionNamespace = $prop $selectedNamespace = $openEyeDefaults.$versionNamespace $selectedFiles = $selectedNamespace.FileChecksums.Files if (($returnObj.Mol2NameChecksum -eq $selectedFiles.$mol2NameFileName) -or ($returnObj.Mol2NameBatchChecksum -eq $selectedFiles.$mol2NameBatchFileName)) { $returnObj.UserFriendlyVersion = $selectedNamespace.UserFriendlyVersion break } } } } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the the OpenEye user friendly version" } return $returnObj } function Get-OracleDatabaseBasePath { if ($IsWindows) { $dbInfoFile = Join-Path $(Get-LocalDotmaticsPath) "db-oraclebase-path.txt" $sqlFilePath = Get-ExternalFilePath -FileName "sql-get-oraclebase-path.sql" try { & 'sqlplus.exe' '/ as sysdba' "@$sqlFilePath" > $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } return Read-PropertyFromDBInfoFile "{oracle_base_path}" $dbInfoFile } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet. Skipping." } } function Get-OracleDatabaseHomePathUsingProcess { $thePath = $null $process = Get-OracleDatabaseProcess if (-not $process) { return $null } $thePath = [System.IO.Path]::GetDirectoryName($process.Path) if ($null -eq $thePath) { return $null } if ($thePath -like "*bin") { $thePath = ([System.IO.Path]::GetDirectoryName($process.Path)) | Split-Path -Parent } if (-not (Test-Path -Path $thePath)) { return $null } return $thePath } function Get-OracleDatabaseHomePathUsingServices { if ($IsWindows) { $service = Get-OracleDatabaseService if (!$service) { return $null } $servicePath = $service.BinaryPathName if ($servicePath -like "*bin*") { return [System.IO.Path]::GetDirectoryName(($servicePath -split "bin")[0]) } } return $null } function Get-OracleDatabaseHomePathUsingRegistry { if ($IsWindows) { function Get-HomePath { param( [string] $RegKey ) $regEntries = Get-ChildItem -Recurse -Path $RegKey $filteredEntries = @() foreach ($entry in $regEntries) { if ($entry.Property -contains "ORACLE_HOME") { $filteredEntries += $entry } } if (!$filteredEntries) { return $null } if ($filteredEntries.Length -gt 1) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Multiple version of Oracle DB detected in the Registry $($regKey)." } $thePath = (Get-ItemProperty -Path $filteredEntries.PSPath).ORACLE_HOME if (-not (Test-Path -Path $thePath)) { return $null } return [System.IO.Path]::GetDirectoryName($thePath + [System.IO.Path]::DirectorySeparatorChar) } $regKeys = @( "HKLM:\SOFTWARE\Oracle", "HKLM:\SOFTWARE\WOW6432Node\Oracle" ) $thePath = $null foreach ($key in $regKeys) { if (Test-Path $key) { $thePath = Get-HomePath -RegKey $key } if ($thePath) { break } } if ($null -eq $thePath) { return $null } return $thePath } return $null } function Get-OracleDatabaseHomePath { $thePath = $null if ($IsWindows) { $thePath = Get-OracleDatabaseHomePathUsingServices if ($thePath) { return $thePath } } $thePath = Get-OracleDatabaseHomePathUsingProcess if ($thePath) { return $thePath } if ($IsWindows) { $thePath = Get-OracleDatabaseHomePathUsingRegistry if ($thePath) { return $thePath } } Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Unable to determine Oracle DB's home path." } function Get-OracleDatabaseListenerProcess { $process = Get-Process | Where-Object { $_.ProcessName -imatch "tnslsnr" } if ($null -eq $process) { return $null } if ($proces -is [System.Array]) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Multiple Oracle Listener processes found. This is not expected." } return $process } function Get-OracleDatabaseListenerService { $service = Get-Service | Where-Object { ($_.Name -like "*ora*") -and ($_.BinaryPathName -like "*TNSLSNR*") } if (!$service) { $process = Get-OracleDatabaseListenerProcess $service = Get-CimInstance -Class Win32_Service -Filter ("ProcessId LIKE '" + $process.Id + "'") $service = Get-Service $service.Name } if (!$service) { return $null } if ($service.Length -gt 1) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Oracle Database Listener Service. Multiple services match the search criteria. Do we have two or more listeners running on this machine?" } return $service } function Get-OracleDatabaseProcess { $process = Get-Process | Where-Object { $_.ProcessName -imatch "oracle" } if ($null -eq $process) { return $null } if ($proces -is [System.Array]) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Multiple Oracle processes found. This is not expected." } return $process } function Get-OracleDatabaseService { $service = Get-Service | Where-Object { ($_.Name -like "*ora*") -and ($_.BinaryPathName -like "*ORACLE.EXE*") } if (!$service) { $process = Get-OracleDatabaseProcess $service = Get-CimInstance -Class Win32_Service -Filter ("ProcessId LIKE '" + $process.Id + "'") $service = Get-Service $service.Name } if (!$service) { return $null } if ($service.Length -gt 1) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Oracle DB Service. Multiple services match the search criteria. Do we have two or more Oracle.exe processes running on this machine?" } return $service } function Get-OracleDatabaseVersionUsingOraversionBin { $oracleHome = Get-OracleDatabaseHomePath if ($IsWindows) { $oraversionPath = Join-Path -Path $oracleHome -ChildPath "bin\oraversion.exe" } else { $oraversionPath = Join-Path -Path $oracleHome -ChildPath "bin\oraversion" } if (Test-Path $oraversionPath) { $versionOutput = & $oraversionPath -compositeVersion return $versionOutput } return $null } function Get-OracleDatabaseVersion { $oracleVersion = Get-OracleDatabaseVersionUsingOraversionBin if ($oracleVersion) { return $oracleVersion } return $null } function Get-OracleMetadata { $returnObj = @{ Database = @{ Process = $null Service = $null HomePath = $null Version = $null } Listener = @{ Process = $null Service = $null HomePath = $null Version = $null } } function Get-OracleDatabaseMetadata { try { $returnObj.Database.Process = Get-OracleDatabaseProcess } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the Oracle Database process." } try { $returnObj.Database.Service = Get-OracleDatabaseService } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the Oracle Database service." } try { $returnObj.Database.HomePath = Get-OracleDatabaseHomePath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the Oracle Database home path." } try { $returnObj.Database.Version = Get-OracleDatabaseVersion } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Oracle Database version." } } Get-OracleDatabaseMetadata function Get-OracleDatabaseListenerMetadata { try { $returnObj.Listener.Process = Get-OracleDatabaseListenerProcess } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the Oracle Database Listener process." } try { $returnObj.Listener.Service = Get-OracleDatabaseListenerService } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to find the Oracle Database Listener service." } $returnObj.Listener.HomePath = $returnObj.Database.HomePath $returnObj.Listener.Version = $returnObj.Database.Version } Get-OracleDatabaseListenerMetadata return $returnObj } function Get-OraclePluggableDbSid { [CmdletBinding()] param( [string] $BrowerPropertiesPath) function Get-OracleDbId { $dbInfoFile = Join-Path $(Get-LocalDotmaticsPath) "db-oracle-id.txt" $sqlFilePath = Get-ExternalFilePath -FileName "sql-get-database-id.sql" try { & 'sqlplus.exe' '/ as sysdba' "@$sqlFilePath" > $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } $pdbs = Read-PropertyFromDBInfoFile "{pdbs}" $dbInfoFile if ($pdbs) { return $pdbs } $dbInfoFile = Join-Path $(Get-LocalDotmaticsPath) "db-oracle-id-vservices.txt" $sqlFilePath = Get-ExternalFilePath -FileName "sql-get-database-id-vservices.sql" try { & 'sqlplus.exe' '/ as sysdba' "@$sqlFilePath" > $dbinfoFile } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] sqlplus is not found. Skipping." } $pdbs = Read-PropertyFromDBInfoFile "{vservices}" $dbInfoFile if ($pdbs) { return $pdbs } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Database id was not read from DB!" return $null } try { if ([string]::IsNullOrWhiteSpace($BrowerPropertiesPath)) { $webAppPath = Get-TomcatWebAppPath $BrowerPropertiesPath = Get-BrowserPlatformPropertiesFilePath -TomcatWebAppPath $webAppPath } } catch { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Browser Properties file not found!" } if (Test-Path -Path $BrowerPropertiesPath) { $dtxSystem = Get-DTXSystem $line = $dtxSystem.AppInfo.BrowserPlatform.Properties['db.description'] if ($line -match 'SID=([A-Za-z0-9]+)\)') { $sid = $Matches[1] return $sid } if ($line -match 'SERVICE_NAME=([A-Za-z0-9]+)\)') { $sid = $Matches[1] return $sid } $line = $dtxSystem.AppInfo.BrowserPlatform.Properties['db.description'] if (![string]::IsNullOrWhiteSpace($line)) { return $line } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Database sid not found!" } return Get-OracleDbId } function Get-PreferredDomainController { [cmdletbinding()] param( [Parameter(Mandatory = $true)] [String]$Region ) $_domainControllers = (Get-Defaults).ActiveDirectory.DomainControllers $_activeDomainControllers = [System.Collections.ArrayList]::new() Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Chosing an Active Directory domain controller..." foreach ($dc in $_domainControllers) { $reachable = Test-SSMReachability -InstanceId $dc.InstanceId -Region $dc.Region if ($reachable) { [void]$_activeDomainControllers.Add($dc) } } foreach ($adc in $_activeDomainControllers) { if ($adc.Region.ToLower() -eq $Region.ToLower()) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Chose '$( $adc.Name )' in '$( $adc.Region )' region." return $adc } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Chose '$( $_activeDomainControllers[0].Name )' in '$( $_activeDomainControllers[0].Region )' region." return $_activeDomainControllers[0] } function Get-PRTGDeviceName { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String]$CustomerName, [Parameter(Mandatory = $true)] [String]$PRTGRegion, [Parameter(Mandatory = $true)] [String]$Environment ) process { $name = "$PRTGRegion-$CustomerName-$Environment".ToLower() Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] PRTG device name: $name" return $name } } function Get-PRTGGroupId { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [String]$PRTGRegion ) process { try { $GroupId = (Get-Group -Name $PRTGRegion).Id Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] PRTG group id: $GroupId" return $GroupId } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Get-PRTGRegion { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$AWSRegion ) process { $regions = (Get-Defaults).PRTG.RegionMapping if ($regions.Keys -notcontains $AWSRegion) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] AWS region $AWSRegion is not set up in PRTG." throw } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] PRTG region: $($regions[$AWSRegion].ToString() )" return $regions[$AWSRegion] } } function Get-RandomString { param( [Parameter(Mandatory = $false)] [int]$Length = 8 ) $characters = 'abcdefghijkmnpqrstuvwxyz123456789' $randomString = '' $seed = [int]((Get-Date).Ticks % [int]::MaxValue) $random = New-Object System.Random($seed) for ($i = 0; $i -lt $length; $i++) { $randomIndex = $random.Next(0, $characters.Length) $randomChar = $characters[$randomIndex] $randomString += $randomChar } return $randomString } function Get-ScheduledTasksInfo { $scheduedTaskObj = New-Object ScheduledTasks return $scheduedTaskObj.ListNonSystemScheduledTasks() | Select-Object name, status, enabled } function Get-SSLCertificateFingerprint { param( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String] $Hostname = 'localhost', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [Int] $Port = 443, [Parameter(Mandatory = $false)] [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512')] [ValidateNotNullOrEmpty()] [String] $HashAlgorithm = 'SHA1' ) return [DTXUtils]::GetSSLCertificateFingerprint($Hostname, $Port, $HashAlgorithm) } function Get-SSMCommandOutput { param ( [Parameter(Mandatory = $true)] [string] $CommandId, [Parameter(Mandatory = $true)] [string] $InstanceId, [Parameter(Mandatory = $true)] [string] $Region ) process { $output = @{} $commandPlugins = Get-SSMCommandInvocation -InstanceId $InstanceId -CommandId $CommandId -Region $Region -Detail:$true | Select-Object -ExpandProperty CommandPlugins foreach ($plugin in $commandPlugins) { if ($plugin.Output -like "*skipped due to unsupported plugin*") { continue } $invokeResult = Get-SSMCommandInvocationDetail -InstanceId $InstanceId -CommandId $CommandId -Region $Region -PluginName $plugin.Name if (-not $invokeResult) { Write-LogError -Message "[$($MyInvocation.MyCommand)] No SSM command invocation result found for command id '$CommandId' with plugin name '$($plugin.Name)' in region '$Region'." return } $output[$plugin.Name] = @{ StandardOutput = $invokeResult.StandardOutputContent StandardError = $invokeResult.StandardErrorContent } } return $output } } function Get-StateContent { param ( [string] $StateFilePath ) return (New-KeyValueStore -Path $StateFilePath).GetStoreContent() } function Get-StateItem { param( [Parameter(Mandatory)] [string] $Key, [string] $StateFilePath ) return (New-KeyValueStore -Path $StateFilePath).GetValue($Key) } function Get-StringHash { param ( [Parameter(Mandatory = $true)] [String]$String ) $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256') $hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String)) return [System.BitConverter]::ToString($hash).Replace('-', '') } function Get-SystemInfo { $returnValue = [psobject]@{ IsWindows = $IsWindows IsLinux = $IsLinux IsMacOS = $IsMacOS Windows = [psobject]@{ Build = $null EditionId = $null InstallationType = $null ProductName = $null Version = $null VersionAsYear = $null } Linux = [psobject]@{ } MacOS = [psobject]@{ } } if ($IsWindows) { $props = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" if ($props.ProductName -match '\b\d+\b') { $returnValue.Windows.VersionAsYear = $matches[0] -as [int] } $returnValue.Windows.Build = $props.CurrentBuild -as [string] $returnValue.Windows.EditionId = $props.EditionId -as [string] $returnValue.Windows.InstallationType = $props.InstallationType -as [string] $returnValue.Windows.ProductName = $props.ProductName -as [string] $returnValue.Windows.Version = $props.CurrentVersion -as [version] } elseif ($IsLinux) {} elseif ($IsMacOS) {} else { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Unsupported OS")) } return $returnValue } function Get-TomcatHomePathUsingProcess { $tomcatPath = $null $process = Get-TomcatProcess if (-not $process) { return $null } $tomcatPath = [System.IO.Path]::GetDirectoryName($process.Path) if ($null -eq $tomcatPath) { return $null } if ($tomcatPath -like "*bin") { $tomcatPath = ([System.IO.Path]::GetDirectoryName($process.Path)) | Split-Path -Parent } if (-not (Test-Path -Path $tomcatPath)) { return $null } return $tomcatPath } function Get-TomcatHomePathUsingWMI { $tomcatPath = $null $service = Get-CimInstance -Class Win32_Service | Where-Object { $_.Name -like "*tomcat*" } | Where-Object { $_.State -like "*run*" } | Select-Object StartMode, State, Name, PathName if ($service -is [System.Array]) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Multiple Tomcat services found and set to automatic start. Please ensure only one Tomcat service is installed or set to start automatically." -ThrowException } if ($null -eq $service) { return $null } if (-not $service.PathName) { return $null } $tomcatPathRaw = (($service.PathName -split "bin")[0] -replace '"', "") $tomcatPath = [System.IO.Path]::GetDirectoryName($tomcatPathRaw) if (-not (Test-Path -Path $tomcatPath)) { return $null } return $tomcatPath } function Get-TomcatHomePath { [OutputType([string])] param() $tomcatPath = Get-TomcatHomePathUsingProcess if ($null -ne $tomcatPath) { return $tomcatPath } $tomcatPath = Get-TomcatHomePathUsingWMI if ($null -ne $tomcatPath) { return $tomcatPath } Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine Tomcat home path. Please ensure Tomcat is installed and at least one Tomcat service is set to start automatically." } function Get-TomcatServerXMLPath { return join-path (Get-TomcatHomePath) "conf" "server.xml" } function Get-TomcatWebAppPath { [XML]$xmlfile = Get-Content (Get-TomcatServerXMLPath) return Join-Path (Get-TomcatHomePath) $xmlfile["Server"]["Service"]["Engine"]["Host"].appBase } function ConvertFrom-RawJavaVersionString { param ( [Parameter(Mandatory = $true)] [string]$RawString ) $_version = ($RawString -split "version")[1] $_version = $_version.Trim() $_version = ($_version -split " ")[0] $_version = $_version -replace '"', "" if ($_version -like "1.*") { $_version = $_version.Replace("0_", "") } if ($_version.Split(".").Count -eq 1) { $_version = $_version + ".0.0" } elseif ($_version.Split(".").Count -eq 2) { $_version = $_version + ".0" } elseif ($_version.Split(".").Count -eq 3) { } elseif ($_version.Split(".").Count -eq 4) { $_split = $_version.Split(".") $_version = "$($_split[0]).$($_split[1]).$($_split[3])" } else { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Java version" } return $_version } function Get-TomcatJavaMetadata { $returnObj = @{ JavaVersion = $null JavaVendor = $null JavaHomePath = $null } $jvmPath = (Get-TomcatJvmPath).FullName.ToString() $javaHomePath = ($jvmPath -split "bin")[0] $javaExec = Join-Path $javaHomePath "bin" "java.exe" if (-not (Test-Path -Path $javaExec)) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to locate the Java executable" } $javaVersionOutput = & $javaExec -version 2>&1 if (-not $javaVersionOutput) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Java version" } $javaVersionOutput = $javaVersionOutput -split "`r`n" if ($javaVersionOutput.Count -ne 3) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Java version" } $javaVersionString = $javaVersionOutput[0] $javaRuntimeString = $javaVersionOutput[1] switch -Wildcard ($javaRuntimeString) { "*zulu*" { $returnObj.JavaVendor = "Azul" break } "*corretto*" { $returnObj.JavaVendor = "AmazonCorretto" break } "*adoptopenjdk*" { $returnObj.JavaVendor = "AdoptOpenJDK" break } "*temurin*" { $returnObj.JavaVendor = "AdoptOpenJDK" break } "*openjdk*" { $returnObj.JavaVendor = "OpenJDK" break } "*java*se*runtime*environment*" { $returnObj.JavaVendor = "Oracle" break } default { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Java vendor" } } $returnObj.JavaVersion = ConvertFrom-RawJavaVersionString -RawString $javaVersionString $returnObj.JavaHomePath = $javaHomePath return $returnObj } function Get-TomcatJavaOptions { $tomcatServiceName = (Get-TomcatService).Name $returnObj = @{ Options = "UNKNOWN" JVM = "UNKNOWN" JavaMinMemoryMB = "UNKNOWN" JavaMaxMemoryMB = "UNKNOWN" JavaThreadStackSizeKB = "UNKNOWN" } $tomcatParams = Get-ChildItem "HKLM:\SOFTWARE\WOW6432Node\Apache Software Foundation\Procrun 2.0\$TomcatServiceName\Parameters" foreach ($param in $tomcatParams) { if ($param.PSChildName -eq "Java") { $returnObj.Options = $param.GetValue("Options").split("`r`n") | Where-Object { $_ -ne "" } $returnObj.JVM = $param.GetValue("Jvm") $returnObj.JavaMinMemoryMB = [int]$param.GetValue("JvmMs") $returnObj.JavaMaxMemoryMB = [int]$param.GetValue("JvmMx") $returnObj.JavaThreadStackSizeKB = [int] $param.GetValue("JvmSs") } } return $returnObj } function Get-TomcatJvmPath { $tomcatJvmPath = Get-TomcatJavaOptions | Select-Object -ExpandProperty JVM $tomcatJvmPath = $tomcatJvmPath -replace '[|/]', '\' $index = $tomcatJvmPath.LastIndexOf("\bin\server\jvm.dll") if ($index -gt 0) { return Get-Item $tomcatJvmPath.Substring(0, $index) } Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to locate the Tomcat JRE path" } function Get-TomcatKeyStoreFile { [OutputType([string])] param( [Parameter(Mandatory = $true)] [string] $TomcatHomePath ) $serverXmlPath = Join-Path -Path $TomcatHomePath -ChildPath "conf" -AdditionalChildPath "server.xml" if (-not (Test-Path -Path $serverXmlPath)) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to find server.xml file at $serverXmlPath" } $serverXml = [xml](Get-Content $serverXmlPath) $keyStorePath = $serverXml.Server.Service.Connector.KeyStoreFile $fullkeyStorePath = $TomcatHomePath if ($keyStorePath -like "*/*") { $pathParts = $keyStorePath -split "/" foreach ($pathPart in $pathParts) { $fullkeyStorePath = Join-Path $fullkeyStorePath $pathPart } } elseif ($keyStorePath -like "*\*") { $pathParts = $keyStorePath -split "\\" foreach ($pathPart in $pathParts) { $fullkeyStorePath = Join-Path $fullkeyStorePath $pathPart } } else { $fullkeyStorePath = Join-Path $fullkeyStorePath $keyStorePath } if ($fullkeyStorePath.EndsWith("\") -or $fullkeyStorePath.EndsWith("/")) { $fullkeyStorePath = $fullkeyStorePath.Substring(0, $fullkeyStorePath.Length - 1) } if (-not (Test-Path -Path $fullkeyStorePath)) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to find key store file at $fullkeyStorePath" } $tomcatKeyStoreName = [string](Get-Defaults).Tomcat.KeyStoreName if ([System.IO.Path]::GetFileName($fullkeyStorePath) -ne $tomcatKeyStoreName) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Key store file at $fullkeyStorePath is not named $tomcatKeyStoreName. This is a potential problem. Need to investigate manually. File Found: $([System.IO.Path]::GetFileName($fullkeyStorePath))" } return $fullkeyStorePath } function Get-TomcatMetadata { $returnObj = @{ TomcatHomePath = $null TomcatVersion = $null TomcatProcess = $null TomcatService = $null TomcatServiceUser = $null TomcatServerXmlPath = $null TomcatJavaOptions = $null TomcatJavaVersion = $null TomcatJavaVendor = $null TomcatJavaHomePath = $null TomcatCertificate = $null } try { $returnObj.TomcatHomePath = Get-TomcatHomePath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat home path" } try { $returnObj.TomcatVersion = Get-TomcatVersionV2 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat version" } try { $returnObj.TomcatProcess = Get-TomcatProcess } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat process" } try { $returnObj.TomcatService = Get-TomcatService } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat service" } try { $returnObj.TomcatServiceUser = Get-TomcatUser } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat service user" } try { $returnObj.TomcatServerXmlPath = Get-TomcatServerXMLPath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat server.xml path" } try { $returnObj.TomcatJavaOptions = Get-TomcatJavaOptions } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat Java options" } try { $returnObj.TomcatCertificate = @{ Fingerprint = @{ "SHA1" = Get-SSLCertificateFingerprint -Hostname "localhost" -Port 443 -HashAlgorithm "SHA1" } } } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat certificate fingerprint" } try { $tomcatJavaMetadata = Get-TomcatJavaMetadata $returnObj.TomcatJavaVersion = $tomcatJavaMetadata.JavaVersion $returnObj.TomcatJavaVendor = $tomcatJavaMetadata.JavaVendor $returnObj.TomcatJavaHomePath = $tomcatJavaMetadata.JavaHomePath } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine the Tomcat Java metadata" } return $returnObj } function Get-TomcatProcess { $process = Get-Process | Where-Object { $_.ProcessName -imatch ".*tomcat[0-9\.]+$" } if ($null -eq $process) { return $null } if ($proces -is [System.Array]) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Multiple Tomcat processes found. This is not expected." } return $process } function Get-TomcatService { $process = Get-TomcatProcess $service = Get-CimInstance -Class Win32_Service -Filter ("ProcessId LIKE '" + $process.Id + "'") $service = Get-Service $service.Name return $service } function Get-TomcatUser { if ($IsWindows) { $theUser = $null $tomcatUser = Get-TomcatService | Select-Object -ExpandProperty UserName if ($tomcatUser -match "\\") { $tomcatUser = $tomcatUser -split "\\" | Select-Object -Last 1 } try { if (Get-Command "Get-LocalUser" -ErrorAction SilentlyContinue) { $theUser = (Get-LocalUser -Name $tomcatUser).Name } else { $theUser = (Get-CimInstance -ClassName Win32_UserAccount -Filter "Name='$tomcatUser'").Name } } catch { Write-LogWarning "[$($MyInvocation.MyCommand)] Unable to get the Tomcat user." } return $theUser } else { Write-LogInfo "[$($MyInvocation.MyCommand)] Getting the Tomcat user on Linux or MacOS is not supported yet." } } function Get-TomcatVersion { [OutputType([string])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $TomcatHomePath ) if ($TomcatHomePath -notlike "*tomcat*") { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Invalid Tomcat home path." } $version = ($TomcatHomePath -split "tomcat" | Select-Object -Last 1).Trim() $version = $version[0] if (-not $version) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Could not determine Tomcat version." } return $version } function Get-TomcatVersionUsingInstalledApps { $tomcatInstalledApps = Get-InstalledApps | Where-Object { $_.DisplayName -like "*tomcat*" } $tomcatService = Get-TomcatService foreach ($app in $tomcatInstalledApps) { if ($app.UninstallString -like "*$($tomcatService.Name)*") { if ($app.DisplayVersion -match '([0-9]+)\.([0-9]+)\.([0-9]+)') { return $Matches[0] } } } return $null } function Get-TomcatVersionUsingReleaseNotesFile { $tomcatPath = Get-TomcatHomePath $releaseNotesPath = Join-Path $tomcatPath "RELEASE-NOTES" if (Test-Path $releaseNotesPath) { $releaseNotes = Get-Content $releaseNotesPath -Raw if ($releaseNotes -match 'Apache Tomcat Version ([0-9\.]+)') { return $Matches[1] } } return $null } function Get-TomcatVersionUsingVersionBatchFile { $tomcatPath = Get-TomcatHomePath $versionBatchFile = Join-Path $tomcatPath "bin" "version.bat" $tomcatJreHomePath = (Get-TomcatJvmPath).FullName.ToString() $env:CATALINA_HOME = $tomcatPath $env:JRE_HOME = $tomcatJreHomePath $tempFile = [System.IO.Path]::GetTempFileName() & $versionBatchFile > $tempFile $output = Get-Content $tempFile -Raw if ($output -match 'Server version:\s+Apache Tomcat/([0-9\.]+)') { return $Matches[1] } Remove-Item $tempFile -Force return $null } function Get-TomcatVersionV2 { $tomcatVersion = Get-TomcatVersionUsingInstalledApps if (-not $tomcatVersion) { $tomcatVersion = Get-TomcatVersionUsingReleaseNotesFile } if (-not $tomcatVersion) { $tomcatVersion = Get-TomcatVersionUsingVersionBatchFile } return $tomcatVersion } function Get-UserCountInfo { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $BaseUrl ) if (-not $BaseUrl) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine user count becasue instance URL is unknown." return "UNKNOWN" } $loginUrl = $BaseUrl + "/browser/login.jsp" try { $webResponse = Invoke-WebRequest -uri $loginUrl -UseBasicParsing } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to determine user count becasue invoking /browser/login.jsp failed. $( $_.Exception.Message )" $webResponse = $null } $loginPageOutputFile = Join-Path (Get-LocalDotmaticsPath) "login-page-full.txt" if ($null -ne $webResponse) { $webResponse.content > $loginPageOutputFile $s = Get-Content -Path $loginPageOutputFile | Where-Object { $_ -like '*licensee:*' } if ($s -match 'users:\s+(\d+\/\d+)') { return $Matches[0] } } return "UNKNOWN" } function Get-VirtualmemoryInfo { $res = [ordered]@{ isManagedAutomatically = $null initialSizeGB = "UNKNOWN" maxSizeGB = "UNKNOWN" textInfo = "UNKNOWN" } if ($IsWindows) { $vmAutoManged = (Get-CimInstance Win32_ComputerSystem).AutomaticManagedPagefile if ($vmAutoManged) { $res.isManagedAutomatically = $true; $res.textInfo = 'Virtual memory is managed automatically' } else { $res.isManagedAutomatically = $false; $vmInitSize = (Get-CimInstance Win32_PageFileSetting | select -ExpandProperty InitialSize) / 1024 $vmMaxSze = (Get-CimInstance Win32_PageFileSetting | select -ExpandProperty MaximumSize) / 1024 $res.initialSizeGB = $vmInitSize $res.maxSizeGB = $vmMaxSze $res.textInfo = @" Virtual memory GB: Initial size: $vmInitSize Maximum size: $vmMaxSze "@ } } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] This platform is not supported yet." } return $res } function Install-AutomationDependencies { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $invokeParams = @{ Name = "DTX-InstallAutomationDependencies" Region = $Region InstanceId = $InstanceId Parameters = @{} } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing the automation dependencies on '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The automation dependencies have been installed successfully." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Install-CrowdStrikeAgent { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $CustomerId, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $InstallationFile, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $ServiceName = "CS*Falcon*" ) if (-not $IsWindows) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] This cmdlet is only supported on Windows.")) } if (-not (Test-Path -Path $InstallationFile)) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] The installation file '$InstallationFile' does not exist.")) } $installArgs = @( "/install", "/quiet", "/norestart", "ProvNoWait=1", "CID=$CustomerId" ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Install parameters: $installArgs " $installResult = Start-Process -FilePath $InstallationFile -ArgumentList $installArgs -Wait -PassThru if (@(0, 1638) -notcontains $installResult.ExitCode) { throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Error installing CrowdStrike Agent. Exit code: $($installResult.ExitCode).")) } Start-Sleep -Seconds 5 Start-Service -Name $ServiceName if (-not (Test-IsServiceRunning -SearchTerm $ServiceName)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The service '$ServiceName' is not running. Installation not successful." -ThrowException } } function Install-DuoAgent { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [String] $Name = "DuoAgent" ) process { try { $defaults = Get-Defaults Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $documentParams = @{ RepositoryBucketName = $defaults.AWS.S3.Buckets.SoftwareRepository.Name RepositoryBucketKey = $defaults.AWS.S3.Buckets.SoftwareRepository.Objects.DuoAgentInstaller.Windows RepositoryBucketRegion = (Get-S3BucketLocation -BucketName $defaults.AWS.S3.Buckets.SoftwareRepository.Name).Value DuoCredentials = $defaults.AWS.SSM.ParameterStore.Parameters.DuoCredentials SSMParameterStoreRegion = $defaults.AWS.SSM.ParameterStore.DefaultRegion } $invokeParams = @{ Name = "DTX-InstallDuoAgent" Region = $Region InstanceId = $InstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing software package '$Name' on instance '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] '$Name' installed successfully." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Install-OpenEye { param ( [string]$Version ) try { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing Open Eye version $Version..." $openEyeDefaults = Get-OpenEyeDefaults -Version $Version Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Downloading Open Eye from S3..." $installFile = Get-FileFromS3 -BucketName $openEyeDefaults.DownloadSource.S3.BucketName -BucketKey $openEyeDefaults.DownloadSource.S3.BucketKey -PreserveFileName Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Downloaded to $installFile" $tempDir = New-TempDir Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Extracting Open Eye files to $tempDir ..." Expand-Archive -Path $installFile -DestinationPath $tempDir -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Extracted successfully." $unzippedFiles = Get-ChildItem -Path $tempdir -Recurse -File $unzippedFilesParentPath = $unzippedFiles[0].PSParentPath Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing the integrity of the extracted files pre-install..." if (!(Test-OpenEyeIntegrity -Version $Version -TargetPath $unzippedFilesParentPath)) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] The Open Eye installation files are not passing integrity validation. The file checksums are not matching or files are missing. The installation is aborted. No changes have been made." } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Integrity check successfull." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing Mol2NameBatch Output pre-install..." if (!(Test-Mol2NameBatchOutput -WorkingDirectory $unzippedFilesParentPath)) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] The Open Eye Mol2Name Batch Output test is inconsistent. The installation is aborted. No changes have been made. Source files path: $unzippedFilesParentPath" } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Mol2NameBatch check successfull." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Copy new files to the Open Eye directory..." $browserGifcacheToolkitPath = Get-BrowserPlatformGifcacheToolkitPath foreach ($file in $unzippedFiles) { Copy-ItemWithRetry -Path $file.FullName -Destination $browserGifcacheToolkitPath } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] File copy successfull." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing the integrity of the extracted files post-install..." if (!(Test-OpenEyeIntegrity -Version $Version -TargetPath $browserGifcacheToolkitPath)) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] The Open Eye installation is not passing integrity validation. The file checksums are not matching or files are missing. Open Eye is now broken!" } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Integrity check successfull." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing Mol2NameBatch Output post-install..." if (!(Test-Mol2NameBatchOutput -WorkingDirectory $browserGifcacheToolkitPath)) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] The Open Eye Mol2Name Batch Output test is inconsistent. Open Eye is now broken! Source files path: $browserGifcacheToolkitPath" } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Mol2NameBatch check successfull." } finally { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Cleaning up the temp files..." Remove-Item -Path $installFile -Force Remove-Item -Path $tempDir -Recurse -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Clean up is complete." } } function Install-PowerShellCoreViaSSM { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory)] [ValidateSet( "pre_prod", "prod", IgnoreCase = $false )] [string] $PatchGroup ) process { try { $defaults = Get-Defaults Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $PowerShellCoreInstallerDocumentName = $defaults.AWS.SSM.ParameterStore.Parameters.CAMPowerShellCoreInstallerDocumentName -replace "<<patch-group>>", $PatchGroup $EC2RoleArnDocumentName = $defaults.AWS.SSM.ParameterStore.Parameters.CAMEC2RoleArnDocumentName -replace "<<patch-group>>", $PatchGroup $invokeParams = @{ Name = (Get-SSMParameter -Name $PowerShellCoreInstallerDocumentName -Region $Region).Value Region = $Region InstanceId = $InstanceId Parameters = @{ EC2Role = (Get-SSMParameter -Name $EC2RoleArnDocumentName -Region $Region).Value InMaintWindow = "False" RebootRequested = "False" RebootExitCode = "52" ContinueOnErrorExitCode = "55" DryRun = "False" } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing PowerShell Core on '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The automation dependencies have been installed successfully." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Install-SumoLogic { param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $AccessId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $AccessKey, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $BucketName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $BucketObjectKey ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' try { $installFile = Get-FileFromS3 -BucketName $BucketName -BucketKey $BucketObjectKey -PreserveFileName $dtxSystem=Get-DTXSystem $instanceId = $dtxSystem.GenericInfo.IdentityInfo.instanceId Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing $installFile for instance $instanceId ..." Assert-True -Condition ($null -ne $instanceId) -message "[$( $MyInvocation.MyCommand )] Instance ID was null" Assert-True -Condition ($instanceId.Length -gt 0) -message "[$( $MyInvocation.MyCommand )] Instance ID was length 0" Start-Process $installFile -Wait -ArgumentList "-q","-console","-Vsumo.accessid=$AccessId", "-Vsumo.accesskey=$AccessKey", "-Vcollector.name=$instanceId" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $BucketObjectKey installed." Start-Service -Name "sumo-collector" } finally { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Cleaning up the temp files..." if ($installFile -ne $null -and (Test-Path -Path $installFile)) { Remove-Item -Path $installFile -Force } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Clean up is complete." } } function Install-VortexWeb { param ( [Parameter(Mandatory)] [string]$Version, [switch] $Force ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' try { $today = (Get-Date -Format "yyyy-MM-dd") $installStatus = Get-StateItem -Key "vortex-web-install-status" $installVersion = Get-StateItem -Key "vortex-web-install-version" Set-StateItem -Key "vortex-web-install-check-date" -Value $today if ( (($installStatus -eq "installed") -and ($installVersion -eq $Version)) -and !$Force) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Vortex Web Version [$Version] is already installed. Nothing to do." return } $meta = Get-DTXSystem $vortexWebPath = Join-Path -Path $meta.AppInfo.Tomcat.WebAppsPath -ChildPath "vortexweb" if (!(Test-Path $vortexWebPath)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The Vortex Web path [$vortexWebPath] is not found. Nothing to do." return } $vortexWebDefaults = (Get-Defaults).VortexWeb."v$Version" if (!$vortexWebDefaults) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] Unable to locate Vortex Web version '$Version' in the defaults.json file." } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installing Vortex Web version $Version ..." Set-StateItem -Key "vortex-web-install-status" -Value "inprogress" Remove-StateItem -Key "vortex-web-install-date" Remove-StateItem -Key "vortex-web-install-version" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Downloading Vortex Web from S3..." $installFile = Get-FileFromS3 -BucketName $vortexWebDefaults.DownloadSource.S3.BucketName -BucketKey $vortexWebDefaults.DownloadSource.S3.BucketKey -PreserveFileName Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Validating the install file checksum..." $installFileHash = (Get-FileHash -Path $installFile -Algorithm $vortexWebDefaults.DownloadSource.S3.FileChecksum.Algorithm).Hash if ($installFileHash -ne $vortexWebDefaults.DownloadSource.S3.FileChecksum.Hash) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] The file hash of the downloaded file ($installFile) does not match the expected file hash that is defined in the defaults.json." } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Backing up the Vortex Web license details..." $vortexWebLicensePath = Join-Path -Path $vortexWebPath -ChildPath "vortex" $vortexWebLicenseTempPath = Join-Path -Path (Get-LocalTempPath) -ChildPath "vortex-web-license" Copy-Item -Recurse -Force -Path $vortexWebLicensePath -Destination $vortexWebLicenseTempPath -ErrorAction Stop Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopping the Tomcat service..." Stop-Service -Force -Name $meta.AppInfo.Tomcat.Service.Name Remove-Item -Recurse -Force $vortexWebPath Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Extracting Vortex Web files to $vortexWebPath ..." Expand-Archive -Path $installFile -DestinationPath $vortexWebPath -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Restoring the Vortex Web license details..." Copy-Item -Recurse -Force -Path $vortexWebLicenseTempPath/* -Destination $vortexWebLicensePath Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Starting the Tomcat service..." Start-Service -Name $meta.AppInfo.Tomcat.Service.Name Set-StateItem -Key "vortex-web-install-version" -Value $Version Set-StateItem -Key "vortex-web-install-date" -Value $today Set-StateItem -Key "vortex-web-install-status" -Value "installed" Remove-Item -Path $vortexWebLicenseTempPath -Force -Recurse Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Installation complete" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing if the Vortex Web service is healthy." if (Test-HTTPService -MaxRetryCount 100 -SkipCertificateCheck -ExpectedStatusCode 200 -Uri "https://localhost/vortexweb") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Vortex Web is healthy." } else { Write-LogWarning "[$( $MyInvocation.MyCommand )] Unable to determine if Vortex Web is healthy." } } finally { if ($meta) { Start-Service -Name $meta.AppInfo.Tomcat.Service.Name } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Cleaning up the temp files..." if ($installFile) { Remove-Item -Path $installFile -Force } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Clean up is complete." } } function Invoke-ADDomainJoin { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $defaults = Get-Defaults $documentParams = @{ DomainName = $defaults.ActiveDirectory.FQDN TargetOUPath = $defaults.ActiveDirectory.OUPaths.ComputerGroupPaths[$Region] ServiceAccountUsername = $defaults.AWS.SSM.ParameterStore.Parameters.AdServiceAccountUsername ServiceAccountPassword = $defaults.AWS.SSM.ParameterStore.Parameters.AdServiceAccountPassword SSMParameterStoreRegion = $defaults.AWS.SSM.ParameterStore.DefaultRegion } $invokeParams = @{ Name = "DTX-ADDomainJoin" Region = $Region InstanceId = $InstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Joining instance '$InstanceId' to the AD domain..." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Start-Sleep -Seconds 30 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The operation succeded!" Wait-ForSSMReachability -InstanceId $InstanceId -Region $Region } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Invoke-PostDeploymentTasks { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $AccessUrl, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $ActiveDirectoryGroupName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $documentParams = @{ AccessUrl = $AccessUrl ActiveDirectoryGroupName = $ActiveDirectoryGroupName } $invokeParams = @{ Name = "DTX-PostDeploymentTasks" Region = $Region InstanceId = $InstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Running post-deployment tasks on '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Post-deployment tasks completed" } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Invoke-SSMDocument { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String] $Name, [Parameter(Mandatory = $false)] [String] $Version = '$LATEST', [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [Hashtable] $Parameters, [Parameter(Mandatory = $true)] [String] $Region, [Parameter(Mandatory = $false)] [Int32] $TimeoutSeconds = 60, [Parameter(Mandatory = $false)] [String] $MaxConcurrency = "50", [Parameter(Mandatory = $false)] [String] $MaxErrors = "0" ) $isOnlineDoc = $false $isUploadedDoc = $false $uploadedDocName = "" $randomString = ( -join ((65..90) + (97..122) | Get-Random -Count 5 | % { [char]$_ })) $sendCommandParams = @{ DocumentVersion = $Version Parameters = $Parameters TimeoutSeconds = $TimeoutSeconds MaxConcurrency = $MaxConcurrency MaxErrors = $MaxErrors Region = $Region InstanceId = $InstanceId } $ssmDoc = (Get-SSMDocumentList -Region $Region -Filter @{ Key = "Name"; Values = $Name } | Select-Object -First 1) if ($ssmDoc.Name) { $isOnlineDoc = $true } else { try { $localSSMDocs = Get-ChildItem -Path $PSScriptRoot/SSMDocuments -Filter *.yml foreach ($localDoc in $localSSMDocs) { if ($Name.ToLower() -eq $localDoc.BaseName.ToLower()) { $uploadedDocName = "$( $localDoc.Basename )-$randomString" [void]$( New-SSMDocument -Region $Region -DocumentFormat YAML -DocumentType "Command" (Get-Content -Path $localDoc -Raw) -Name $uploadedDocName ) $isUploadedDoc = $true } } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } if ($isUploadedDoc) { $sendCommandParams["DocumentName"] = $uploadedDocName $command = Send-SSMCommand @sendCommandParams Remove-SSMDocument -Name $uploadedDocName -Enforce:$true -Confirm:$false -Region $Region return $command } elseif ($isOnlineDoc) { $sendCommandParams["DocumentName"] = $ssmDoc.Name return Send-SSMCommand @sendCommandParams } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException } } function Invoke-SSMDocumentAndRetry { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String] $Name, [Parameter(Mandatory = $false)] [String] $Version = '$LATEST', [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [Hashtable] $Parameters, [Parameter(Mandatory = $true)] [String] $Region, [Parameter(Mandatory = $false)] [Int32] $TimeoutSeconds = 60, [Parameter(Mandatory = $false)] [String] $MaxConcurrency = "50", [Parameter(Mandatory = $false)] [String] $MaxErrors = "0" ) function _Test-IsRetriableError { param ( $CommandId, $InstanceId, $Region, [String[]]$RetriableErrorMessages ) $commandOutput = Get-SSMCommandOutput -CommandId $CommandId -InstanceId $InstanceId -Region $Region foreach ($step in $commandOutput.GetEnumerator()) { foreach ($errorMessage in $RetriableErrorMessages) { if ($step.Value.StandardOutput -like $errorMessage -or $step.Value.StandardError -like $errorMessage) { return $true } } } return $false } $retriableErrorMessages = @( "*document worker timed out*", "*The term 'pwsh' is not recognized as the name of a cmdlet*" ) $maxRetries = 3 for ($retryCount = 0; $retryCount -le $maxRetries; $retryCount++) { try { $invokeParams = @{ Name = $Name Version = $Version InstanceId = $InstanceId Parameters = $Parameters Region = $Region TimeoutSeconds = $TimeoutSeconds MaxConcurrency = $MaxConcurrency MaxErrors = $MaxErrors } $command = Invoke-SSMDocument @invokeParams if (Test-SSMCommandResultV2 -CommandId $command.CommandId -InstanceId $InstanceId -Region $Region -Wait) { return $command } $isRetriable = _Test-IsRetriableError -CommandId $command.CommandId -InstanceId $InstanceId -Region $Region -RetriableErrorMessages $retriableErrorMessages if (-not $isRetriable) { return $command } Start-Sleep -Seconds 5 } catch { Start-Sleep -Seconds 5 } } if ($null -eq $command) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Unable to invoke SSM document '$Name' on instance '$InstanceId' in region '$Region'." -ThrowException } return $command } function Write-LogInfo { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) [DTXLogger]::LogInfo($Message) } function Write-LogWarning { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) [DTXLogger]::LogWarning($Message) } function Write-LogError { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) [DTXLogger]::LogError($Message) } function Write-LogCritical { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message, [Parameter(Mandatory = $false)] [switch]$ThrowException, [object] $Exception ) if ($ThrowException) { if ($Exception) { [DTXLogger]::LogCriticalAndThrow($Message, $Exception) } else { [DTXLogger]::LogCriticalAndThrow($Message) } } else { [DTXLogger]::LogCritical($Message) } } function Write-LogSeparator { [DTXLogger]::LogSeparator() } function New-ADSecurityGroup { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Description, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $DomainControllerInstanceId, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [String] $TargetOU, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Array] $Members, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $DomainControllerInstanceId -Region $Region -SkipCommandExecution -ThrowException $targetOu = (Get-Defaults).ActiveDirectory.OUPaths.SecurityGroupPath $documentParams = @{ Name = $Name Description = $Description Category = "Security" Scope = "DomainLocal" Path = $targetOu GroupMember = $Members[0] } $invokeParams = @{ Name = "DTX-NewADGroup" Region = $Region InstanceId = $DomainControllerInstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating Active Directory security group '$Name' in OU '$targetOU' with group members '$Members'." [void]$( Invoke-SSMDocumentAndRetry @invokeParams ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Active Directory security group created." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-AWSEC2Instance { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceType, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceIamProfileName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $SubnetId, [Parameter(Mandatory = $true)] [Array] $SecurityGroupIds, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $AMIId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Hashtable] $Tags ) process { try { $instanceCheck = (Get-EC2Instance -Region $Region -Filter @{ Name = "tag:Name"; Values = $InstanceName }, @{ Name = "instance-state-code"; Values = "0", "16", "64", "80" }).Instances if ($instanceCheck.Count -gt 1) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Multiple instances with the name '$InstanceName' are found. Please resolve this problem before proceeding." throw } if ($instanceCheck.Count -eq 1) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The AWS EC2 instance '$InstanceName' in region '$Region' already exists." if (Read-BasicUserResponse -Prompt "Do you want to reuse the AWS EC2 instance '$InstanceName'? (y/n)") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS EC2 instance '$InstanceName' in region '$Region' will be reused." return $instanceCheck[0] } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] You can either reuse the AWS EC2 instance '$InstanceName' or delete it and let the script recreate it. Please ensure the EC2 instance is not in use and does not have customer data before deleting it." throw } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating AWS EC2 instance '$InstanceName' in region '$Region'..." $ec2CreateInstanceParams = @{ Region = $Region ImageId = $AMIId MinCount = 1 MaxCount = 1 SubnetId = $SubnetId InstanceType = $InstanceType IamInstanceProfile_Name = $InstanceIamProfileName SecurityGroupId = $SecurityGroupIds DisableApiTermination = $true } $ec2Instance = (New-EC2Instance @ec2CreateInstanceParams).Instances Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS EC2 instance created: $( $ec2Instance.InstanceId )" $counter = 0 $ec2InstanceStatusChecksInProgress = $true do { if ($counter -ge 61) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The AWS EC2 instance '$InstanceName' failed to reach 'running' state or to pass the reachability tests within a 10 minute period. Please try again." throw } $status = Get-EC2InstanceStatus -Region $Region -InstanceId $ec2Instance.InstanceId if ($status.InstanceState.Code -eq 16 -and $status.Status.Status.Value -eq "ok") { $ec2InstanceStatusChecksInProgress = $false Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The AWS EC2 instance '$InstanceName' is ready." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Waiting for the AWS EC2 instance '$InstanceName' to transition to 'running' state and to pass the reachability tests." Start-Sleep -Seconds 10 } $counter += 1 } while ($ec2InstanceStatusChecksInProgress) $Tags.Add("Name", $InstanceName) $Tags.Keys | ForEach-Object { [void]$( New-EC2Tag -Region $Region -ResourceId $ec2Instance.InstanceId -Tag @{ Key = $_; Value = $Tags[$_] } ) } Restart-EC2Instance -InstanceId $ec2Instance.InstanceId -Region $Region return $ec2instance } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-AWSElasticIP { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Hashtable] $Tags, [Switch] $AttachToEC2Instance ) process { try { $elasticIpName = $InstanceName $elasticIpCheck = Get-EC2Address -Region $Region -Filter @{ Name = "tag:Name"; Values = $elasticIpName } if ($elasticIpCheck.InstanceId -eq $InstanceId) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The AWS elastic IP '$elasticIpName' in region '$Region' is already attached to instance '$InstanceName'." return $elasticIpCheck } if ($elasticIpCheck) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The AWS elastic IP '$elasticIpName' in region '$Region' already exists." if (Read-BasicUserResponse -Prompt "Do you want to reuse the AWS elastic IP '$elasticIpName'? (y/n)") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS elastic IP '$elasticIpName' in region '$Region' will be reused." if ($AttachToEC2Instance) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Attaching elastic IP '$elasticIpName' in region '$Region' to instance '$InstanceName' ($InstanceId)..." [void]$( Register-EC2Address -InstanceId $InstanceId -AllocationId $elasticIpCheck.AllocationId -Region $Region ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Attached." } return $elasticIpCheck } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] You can either reuse the AWS elastic IP '$elasticIpName' or delete it and let the script recreate it. Please ensure the elastic IP is not in use before deleting it." throw } } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating AWS elastic IP '$elasticIpName' in region '$Region'..." $elasticIp = New-EC2Address -Region $Region -Domain Vpc $Tags.Add("Name", $elasticIpName) $Tags.Keys | ForEach-Object { [void]$( New-EC2Tag -Region $Region -ResourceId $elasticIp.AllocationId -Tag @{ Key = $_; Value = $Tags[$_] } ) } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS elastic IP '$elasticIpName' in region '$Region' created sucessfully" if ($AttachToEC2Instance) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Attaching elastic IP '$elasticIpName' in region '$Region' to instance '$InstanceName' ($InstanceId)..." [void]$( Register-EC2Address -InstanceId $InstanceId -AllocationId $elasticIp.AllocationId -Region $Region ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Attached." } return $elasticIp } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-AWSRoute53ARecord { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $IPAddress, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $HostedZoneId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [Int] $TTL = 300 ) process { try { $hostedZoneName = (Get-R53HostedZone -Id $HostedZoneId -Region $Region).HostedZone.Name.Trim(".") $recordName = $Name.ToLower() $recordCheck = Get-AWSRoute53Record -Name $recordName -Type A -HostedZoneId $HostedZoneId -Region $Region if ($recordCheck) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] 'A' record with name '$recordName' already exists in the hosted zone '$hostedZoneName' ($HostedZoneId)" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The 'A' record '$( $recordCheck.Name )' points to IP address '$( $recordCheck.ResourceRecords.Value )'" if ($recordCheck.ResourceRecords.Value -contains $IPAddress) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No changes required." return $recordCheck } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] This is different from the IP address requested '$IPAddress'" if (Read-BasicUserResponse -Prompt "Do you want to replace the existing IP address '$( $recordCheck.ResourceRecords.Value )' for '$( $recordCheck.Name )' with '$IPAddress'? (y/n)") { if (Read-BasicUserResponse -Prompt "Are you sure? (y/n)") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating the record '$( $recordCheck.Name )' to point to IP address '$IPAddress'..." $updatedRecord = New-Object Amazon.Route53.Model.Change $updatedRecord.Action = "UPSERT" $updatedRecord.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet $updatedRecord.ResourceRecordSet.Name = "$recordName.$hostedZoneName." $updatedRecord.ResourceRecordSet.Type = "A" $updatedRecord.ResourceRecordSet.TTL = $TTL $updatedRecord.ResourceRecordSet.ResourceRecords.Add(@{ Value = $IPAddress }) [void]$( Edit-R53ResourceRecordSet -HostedZoneId $HostedZoneId -ChangeBatch_Change $updatedRecord -ChangeBatch_Comment "Updated on $( Get-Date -f -- FileDateTimeUniversal )" ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record updated!" return $updatedRecord.ResourceRecordSet } } } } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating 'A' record '$recordName' with IP address '$IPAddress' on hosted zone '$hostedZoneName' ($HostedZoneId)..." $newRecord = New-Object Amazon.Route53.Model.Change $newRecord.Action = "CREATE" $newRecord.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet $newRecord.ResourceRecordSet.Name = "$recordName.$hostedZoneName." $newRecord.ResourceRecordSet.Type = "A" $newRecord.ResourceRecordSet.TTL = $TTL $newRecord.ResourceRecordSet.ResourceRecords.Add(@{ Value = $IPAddress }) [void]$( Edit-R53ResourceRecordSet -HostedZoneId $HostedZoneId -ChangeBatch_Change $newRecord -ChangeBatch_Comment "Added on $( Get-Date -f -- FileDateTimeUniversal )" ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record created!" return $newRecord.ResourceRecordSet } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-AWSRoute53CnameRecord { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Target, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $HostedZoneId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [Int] $TTL = 300 ) process { try { $hostedZoneName = (Get-R53HostedZone -Id $HostedZoneId -Region $Region).HostedZone.Name.Trim(".") $recordName = $Name.ToLower() $recordCheck = Get-AWSRoute53Record -Name $recordName -Type CNAME -HostedZoneId $HostedZoneId -Region $Region if ($recordCheck) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] 'CNAME' record with name '$recordName' already exists in the hosted zone '$hostedZoneName' ($HostedZoneId)" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The 'CNAME' record '$( $recordCheck.Name )' points to '$( $recordCheck.ResourceRecords.Value )'" if ($recordCheck.ResourceRecords.Value -contains $Target) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No changes required." return $recordCheck } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] This is different from the target requested '$Target'" if (Read-BasicUserResponse -Prompt "Do you want to replace the existing target '$( $recordCheck.ResourceRecords.Value )' for '$( $recordCheck.Name )' with '$Target'? (y/n)") { if (Read-BasicUserResponse -Prompt "Are you sure? (y/n)") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating the record '$( $recordCheck.Name )' to point to '$Target'..." $updatedRecord = New-Object Amazon.Route53.Model.Change $updatedRecord.Action = "UPSERT" $updatedRecord.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet $updatedRecord.ResourceRecordSet.Name = "$recordName.$hostedZoneName." $updatedRecord.ResourceRecordSet.Type = "CNAME" $updatedRecord.ResourceRecordSet.TTL = $TTL $updatedRecord.ResourceRecordSet.ResourceRecords.Add(@{ Value = $Target }) [void]$( Edit-R53ResourceRecordSet -HostedZoneId $HostedZoneId -ChangeBatch_Change $updatedRecord -ChangeBatch_Comment "Updated on $( Get-Date -f -- FileDateTimeUniversal )" ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record updated!" return $updatedRecord.ResourceRecordSet } } } } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating 'CNAME' record '$recordName' with target '$Target' on hosted zone '$hostedZoneName' ($HostedZoneId)..." $newRecord = New-Object Amazon.Route53.Model.Change $newRecord.Action = "CREATE" $newRecord.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet $newRecord.ResourceRecordSet.Name = "$recordName.$hostedZoneName." $newRecord.ResourceRecordSet.Type = "CNAME" $newRecord.ResourceRecordSet.TTL = $TTL $newRecord.ResourceRecordSet.ResourceRecords.Add(@{ Value = $Target }) [void]$( Edit-R53ResourceRecordSet -HostedZoneId $HostedZoneId -ChangeBatch_Change $newRecord -ChangeBatch_Comment "Added on $( Get-Date -f -- FileDateTimeUniversal )" ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Record created!" return $newRecord.ResourceRecordSet } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-AWSSecurityGroup { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Description, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $VPCId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Hashtable] $Tags ) process { try { $sgNameCheck = Get-EC2SecurityGroup -Region $Region -Filter @{ Name = "group-name"; Values = $Name }, @{ Name = "vpc-id"; Values = $VPCId } if ($sgNameCheck) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The AWS security group '$Name' in region '$Region' already exists." if (Read-BasicUserResponse -Prompt "Do you want to reuse the AWS security group '$Name'? (y/n)") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS security group '$Name' in region '$Region' will be reused." return $sgNameCheck.GroupId } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] You can either reuse the AWS security group '$Name' or delete it and let the script recreate it. Please ensure the security group is not in use before deleting it." throw } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating AWS security group '$Name' in region '$Region'..." $sgId = New-EC2SecurityGroup -Region $Region -GroupName $Name -Description $Description -VpcId $VPCId $Tags.Add("Name", $Name) $Tags.Keys | ForEach-Object { [void]$( New-EC2Tag -Region $Region -ResourceId $sgId -Tag @{ Key = $_; Value = $Tags[$_] } ) } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] AWS security group '$Name' in region '$Region' created sucessfully" return $sgId } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } $script:ExternalFilesProviderInstance = $null function New-ExternalFilesProvider { if (-not $script:ExternalFilesProviderInstance) { $script:ExternalFilesProviderInstance = [DTXExternalFilesProvider]::new($MyInvocation) } return $script:ExternalFilesProviderInstance } function New-KeyValueStore { param( [string] $Path ) if (!$Path) { $Path = Get-LocalStateFilePath } return [DTXKeyValueStore]::new($Path) } function New-PRTGDevice { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String]$Name, [Parameter(Mandatory = $true)] [String]$GroupId, [Parameter(Mandatory = $true)] [String]$TemplateDeviceId ) process { try { $deviceCheck = Get-Device | Where-Object { $_.Name.ToLower() -eq $Name.ToLower() } if ($deviceCheck.Count -gt 1) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] More than one PRTG device with the name '$Name' exists. This is an edge case. Please make sure no more than one device with the same name exists." throw } if ($deviceCheck) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] PRTG device named $Name already exists..." if (Read-BasicUserResponse -Prompt "Do you want to reuse the PRTG device '$( $deviceCheck.Name )' (ID: $( $deviceCheck.Id ))? (y/n)") { return $deviceCheck } else { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] You can either reuse the PRTG device '$( $deviceCheck.Name )' (ID: $( $deviceCheck.Id )) or delete it and let the script recreate it. Please ensure the device is not in use before deleting it." throw } } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating a new PRTG device..." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Device name: $Name" $result = Clone-Object -SourceId $TemplateDeviceId -DestinationId $GroupId -Name $Name Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Created!" return $result } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function New-TempDir { return New-Item -ItemType Directory -Path ([System.IO.Path]::GetTempPath()) -Name (Get-RandomString -Length 18) -Force } function Read-AllPropertiesFromDBInfoFile { param ( [string] $DbInfoFile) if ([string]::IsNullOrEmpty($DbInfoFile)) { $DbInfoFile = Get-DBInfoFilePath } $res = [ordered]@{} $foundPlaceholder = ""; $value = "" $valueReadingStarted = $false; $resultLines = 0; if (-not ([System.IO.File]::Exists($DbInfoFile))) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] DB info file not found" return $null; } foreach ($line in Get-Content $DbInfoFile) { if ( $line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) { continue; } if ($line -like "{end}") { $res.Add($foundPlaceholder, $value); return $res; } if ($line -like "{*}") { if ($valueReadingStarted) { $res.Add($foundPlaceholder, $value); $resultLines = 0; $value = "" } else { $valueReadingStarted = $true; } $foundPlaceholder = $line; if ($line -like "{end_*}") { $valueReadingStarted = $false; } } else { if ($valueReadingStarted) { $resultLines++; if ($resultLines -gt 1) { $value = $value + "`n" + $line.Trim(); } else { $value = $line.Trim(); } } } } } function Read-PropertyFromDBInfoFile { param ( [string] $Placeholder, [string] $DbInfoFile) if ([string]::IsNullOrEmpty($DbInfoFile)) { $DbInfoFile = Get-DBInfoFilePath } $res = "" $dbPlaceHolderFound = $false; $resultLines = 0; if (-not ([System.IO.File]::Exists($DbInfoFile))) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] DB info file not found" return $null; } foreach ($line in Get-Content $DbInfoFile) { if ($line -like $Placeholder) { $dbPlaceHolderFound = $true; continue; } if ($dbPlaceHolderFound) { if ( $line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) { continue; } if ($line -like "{*}") { return $res; } $resultLines++; if ($resultLines -gt 1) { $res = $res + "`n" + $line.Trim(); } else { $res = $line.Trim(); } } } } function Read-BasicUserResponse { [cmdletbinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$Prompt ) Do { Write-Host -ForegroundColor Yellow $Prompt $answer = (Read-Host).ToLower() switch ($answer) { "y" { return $true } "n" { return $false } } } while ($answer -ne "y" -or $answer -ne "n") } function Remove-OpenEyeBackup { param ( [Parameter(Mandatory)] [int]$BackupsToKeep ) try { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Removing all Open Eye backups, except the last $BackupsToKeep." Push-Location -Path (Get-BrowserPlatformGifcachePath) $backups = (Get-ChildItem -Path "*.bak.2*.zip" | Sort-Object -Property "LastWriteTime" -Descending) | Select-Object -Skip $BackupsToKeep if (!$backups) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] No backup files found for removal." return } Write-LogInfo -Message "[$($MyInvocation.MyCommand)] $($backups.Count) backups selected for removal." $backups | Remove-Item -Force Write-LogInfo -Message "[$($MyInvocation.MyCommand)] $($backups.Count) backups removed" } finally { Pop-Location } } function Remove-StateItem { param( [Parameter(Mandatory)] [string] $Key, [string] $StateFilePath ) return (New-KeyValueStore -Path $StateFilePath).RemoveKey($Key) } function Restart-TomcatService { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateSet(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)] [int]$Version, [Parameter(Mandatory = $false)] [int]$WaitBeforeSeconds = 0, [Parameter(Mandatory = $false)] [int]$WaitAfterSeconds = 0 ) $WarningPreference = 'SilentlyContinue' Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Restarting Tomcat..." if ($WaitBeforeSeconds -gt 0) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Waiting $WaitBeforeSeconds seconds before restarting Tomcat." Start-Sleep -Seconds $WaitBeforeSeconds } Restart-Service -Name "Tomcat$Version" -Force if ($WaitAfterSeconds -gt 0) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Waiting $WaitAfterSeconds seconds after restarting Tomcat." Start-Sleep -Seconds $WaitAfterSeconds } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Tomcat has been restarted successfully." } function Set-AWSEC2InstanceProfile { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceIamProfileName ) process { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking EC2 IAM Instance Profile..." try { $instanceRoleDetails = (Get-EC2IamInstanceProfileAssociation -Filter @{ Name = "instance-id"; Values = $InstanceId } -Region $Region) if ($instanceRoleDetails.IamInstanceProfile.Arn -like "*" + $InstanceIamProfileName) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The EC2 Instance Profile is already set to '$InstanceIamProfileName'" return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Setting EC2 Instance Profile to '$InstanceIamProfileName'" [void]$( Set-EC2IamInstanceProfileAssociation -AssociationId $instanceRoleDetails.AssociationId -IamInstanceProfile_Name $InstanceIamProfileName -Region $Region ) } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Set-PRTGDevice { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [String]$Id, [Parameter(Mandatory = $false)] [String]$IPAddress, [Parameter(Mandatory = $false)] [String]$ServiceUrl, [Switch]$Resume, [Switch]$Pause, [Switch]$UpdateSensors ) process { try { $device = Get-Device -Id $Id if (-not$device) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The PRTG device with Id $Id does not exist. Please try again later..." throw } if ($IPAddress) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating IP address (DNS Name) to: $IPAddress for device '$( $device.Name )' (ID: $( $device.Id ))" [void]$( $device | Set-ObjectProperty -Hostv4 $IPAddress ) } if ($ServiceUrl) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating service url to: $ServiceUrl for device '$( $device.Name )' (ID: $( $device.Id ))" [void]$( $device | Set-ObjectProperty -ServiceUrl $ServiceUrl ) } if ($Pause) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Pausing PRTG device '$( $device.Name )' (ID: $( $device.Id ))" [void]$( Pause-Object -Id $device.Id ) } if ($Resume) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Resuming PRTG device '$( $device.Name )' (ID: $( $device.Id ))" [void]$( Resume-Object -Id $device.Id ) } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Set-PRTGDeviceSensor { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String]$DeviceId, [Parameter(Mandatory = $true)] [String]$ServiceUrl, [Switch]$UseDefaults ) process { try { if ($UseDefaults) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Configuring PRTG sensors for device '$DeviceId'." $deviceSensors = Get-Sensor -Filter (New-SearchFilter -Property ParentId -Operator eq -Value $DeviceId) if (-not $deviceSensors) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The PRTG device with Id $DeviceId does not have any sensors configured. Please try again later..." -ThrowException } $sensorDefaults = (Get-Defaults).PRTG.Sensors foreach ($sensorName in $sensorDefaults.Keys) { $sensorConf = $sensorDefaults[$sensorName] foreach ($deviceSensor in $deviceSensors) { if ($deviceSensor.Name.ToLower() -eq $sensorConf.Name.ToLower()) { if ($sensorConf.Action -eq "replace-service-url") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Setting the service url for PRTG sensor '$( $deviceSensor.Name )'." if ($sensorConf.IsRawProperty) { [void]$( Set-ObjectProperty -Id $deviceSensor.Id -RawProperty $sensorConf.PropertyName -RawValue ($sensorConf.PropertyValueTemplate -replace ("<service_url>", $ServiceUrl)) -Force ) } else { [void]$( Set-ObjectProperty -Id $deviceSensor.Id -Property $sensorConf.PropertyName -Value ($sensorConf.PropertyValueTemplate -replace ("<service_url>", $ServiceUrl)) ) } } if ($sensorConf.Action -eq "pause") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Pausing PRTG sensor '$( $deviceSensor.Name )' for '$( [int]$sensorConf.DurationInMinutes / 60 )' hours." [void]$( Pause-Object -Id $deviceSensor.Id -Duration $sensorConf.DurationInMinutes ) } } } } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] PRTG sensor configuration complete." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Set-StateItem { param( [Parameter(Mandatory)] [string] $Key, [Parameter(Mandatory)] [object] $Value, [string] $StateFilePath ) Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Updating the local state file for key: $Key" $result = (New-KeyValueStore -Path $StateFilePath).SetValue($Key, $Value) Write-LogInfo -Message "[$($MyInvocation.MyCommand)] State file updated successfully." return $result } function Set-TimeZone { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $documentParams = @{} $invokeParams = @{ Name = "DTX-SetTimeZone" Region = $Region InstanceId = $InstanceId Parameters = $documentParams } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Setting the time zone on instance '$InstanceId'." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Time zone set successfully." } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Set-WindowsHostname { [cmdletbinding()] param( [Parameter(Mandatory = $true)] [String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $InstanceId, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String] $Region ) process { try { Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution -ThrowException $invokeParams = @{ Name = "DTX-SetWindowsHostname" Region = $Region InstanceId = $InstanceId Parameters = @{ Name = $Name } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Setting the hostname of instance '$InstanceId' to '$Name'..." $commandId = (Invoke-SSMDocumentAndRetry @invokeParams).CommandId Test-SSMCommandResultV2 -CommandId $commandId -InstanceId $InstanceId -Region $Region -Wait -ThrowException | Out-Null Start-Sleep -Seconds 30 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The hostname was set successfully." Wait-ForSSMReachability -InstanceId $InstanceId -Region $Region } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } } function Show-Banner { param( [Parameter(Mandatory)] [ValidateScript({ $_.Length -le 128 })] [string]$Message, [switch]$AsLog ) $count = $Message.Length + 4 $startBar = "#" * $count $endBar = "#" * $count $payload = "# $Message #" if ($AsLog) { Write-LogInfo -Message $startBar Write-LogInfo -Message $payload Write-LogInfo -Message $endBar } else { Write-Host $startBar Write-Host $payload Write-Host $endBar } } function Start-TranscriptLogging { $transcriptFile = "$( Get-Random ).txt" $transcriptFilePath = Join-Path -Path $([System.IO.Path]::GetTempPath() ) -ChildPath $transcriptFile Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Transcript started, output file is $transcriptFilePath" [void]$( Start-Transcript -Path $transcriptFilePath ) } function Stop-ServiceWithRetry { param( [String] $Name, [Int] $Retries = 6, [Int] $WaitSeconds = 10, [switch] $Force ) $count = 0 while ($count -le $Retries) { $service = Get-Service -Name $Name -ErrorAction SilentlyContinue if (!$service) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] A service with the name $Name is not found." -ThrowException } if ($service.Status -eq "Stopped") { break } if ($service.Status -eq "StopPending") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service is stopping..." } else { if ($Force) { Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -NoWait | Out-Null } else { Stop-Service -Name $service.Name -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -NoWait | Out-Null } } Start-Sleep -Seconds $WaitSeconds $count++ } $service = Get-Service -Name $Name -ErrorAction SilentlyContinue if ($service.Status -eq "Stopped") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service stopped" return } Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The service $Name failed to stop within the specified retry timeout of $($Retries * $WaitSeconds) seconds." -ThrowException } function Sync-StarDotmaticsNetCertificate { [CmdletBinding(SupportsShouldProcess = $true)] param() function Test-ChangeRequired { param( [string]$CertPath, [hashtable]$CertMetadata ) if (!(Test-Path $CertPath)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Certificate path '$CertPath' does not exist. Changes required." return $true } try { Push-Location -Path $CertPath $checksumAlgorithm = $CertMetadata.DownloadSource.S3.FileChecksumAlgorithm $expectedFiles = $CertMetadata.DownloadSource.S3.Files $changesRequired = $false foreach ($item in $expectedFiles.GetEnumerator()) { $fileName = $item.Key $expectedFileHash = $item.Value if (!(Test-Path $fileName)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] File '$fileName' is missing. Changes required." $changesRequired = $true break } $currentFileHash = (Get-FileHash -Algorithm $checksumAlgorithm -Path $fileName).Hash if ($currentFileHash -ne $expectedFileHash) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] File '$fileName' has an incorrect checksum. Changes required." $changesRequired = $true break } } return $changesRequired } finally { Pop-Location } } function New-CertPath { param ( [string]$CertPath ) if (!(Test-Path $CertPath)) { if ($PSCmdlet.ShouldProcess($CertPath, "Create the certificate path")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Certificate path '$CertPath' does not exist. Creating..." New-Item -Path $CertPath -ItemType Directory -Force -ErrorAction Stop | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Certificate path '$CertPath' created." } } } function Get-CertFilesFromS3 { param( [string]$CertPath, [hashtable]$CertMetadata ) $bucketName = $CertMetadata.DownloadSource.S3.BucketName $bucketPrefix = $CertMetadata.DownloadSource.S3.BucketPrefix $filesToDownload = $CertMetadata.DownloadSource.S3.Files $checksumAlgorithm = $CertMetadata.DownloadSource.S3.FileChecksumAlgorithm try { if ($PSCmdlet.ShouldProcess($CertPath, "Download the certificate files")) { Push-Location -Path $CertPath } foreach ($item in $filesToDownload.GetEnumerator()) { $fileName = $item.Key $expectedFileHash = $item.Value if ($PSCmdlet.ShouldProcess("s3://$bucketName/$bucketPrefix/$fileName", "Download the file")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Downloading file 's3://$bucketName/$bucketPrefix/$fileName' to path '$CertPath'..." $downloadedFile = [DTXS3Utils]::GetFile($bucketName, "$bucketPrefix/$fileName", (Get-Location), $true) $currentFileHash = (Get-FileHash -Algorithm $checksumAlgorithm -Path $downloadedFile).Hash if ($currentFileHash -ne $expectedFileHash) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] File name 's3://$bucketName/$bucketPrefix/$fileName' has an incorrect checksum. Unable to update the certificate $certNamespace at path '$certPath'." Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Current file hash: $currentFileHash, Expected file hash: $expectedFileHash." break } } } } finally { if ($PSCmdlet.ShouldProcess($CertPath, "Change location back to the original location")) { Pop-Location } } } $ErrorActionPreference = "Stop" try { Show-Banner -AsLog -Message "Start -> Dotmatics - *.dotmatics.net Certificate Sync" $dtxDefaults = Get-Defaults $certNamespace = "star.dotmatics.net" $certCurrentVersion = $dtxDefaults.Certificates[$certNamespace].CurrentVersion $certMetadata = $dtxDefaults.Certificates[$certNamespace].Versions."v$certCurrentVersion" $certPath = Join-Path ([DTXPathProvider]::GetLocalDotmaticsSharedCertPath()) $certNamespace if (!(Test-ChangeRequired -CertPath $certPath -CertMetadata $certMetadata)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No change required. Exiting..." Set-StateItem -Key "certificates-[$certNamespace]-fingerprint-sha1" -Value $certMetadata.Fingerprint.SHA1 Set-StateItem -Key "certificates-[$certNamespace]-path" -Value $certPath Set-StateItem -Key "certificates-[$certNamespace]-version" -Value $certCurrentVersion return } New-CertPath -CertPath $certPath Get-CertFilesFromS3 -CertPath $certPath -CertMetadata $certMetadata if ($PSCmdlet.ShouldProcess($certPath, "Test if the update was successful")) { if (!(Test-ChangeRequired -CertPath $certPath -CertMetadata $certMetadata)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Certificate $certNamespace updated successfully at path '$certPath'." Set-StateItem -Key "certificates-[$certNamespace]-fingerprint-sha1" -Value $certMetadata.Fingerprint.SHA1 Set-StateItem -Key "certificates-[$certNamespace]-path" -Value $certPath Set-StateItem -Key "certificates-[$certNamespace]-version" -Value $certCurrentVersion Set-StateItem -Key "certificates-[$certNamespace]-install-date" -Value (Get-Date -Format "yyyy-MM-dd") } else { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Certificate $certNamespace update failed at path '$certPath'." } } } finally { Show-Banner -AsLog -Message "Stop -> Dotmatics - *.dotmatics.net Certificate Sync" } } function Test-CanOverWriteFile { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$FilePath ) if (Test-Path -Path $FilePath) { $FileStream = [System.IO.File]::Open($FilePath,'Open','Write') $FileStream.Close() $FileStream.Dispose() } } function Test-HTTPService { param( [Parameter(Mandatory)] [string] $Uri, [ValidateRange(100, 599)] [int] $ExpectedStatusCode, [ValidatePattern("[1-5][0-9]{2}-[1-5][0-9]{2}")] [string] $ExpectedStatusCodeRange, [int]$WaitTimeInSeconds = 3, [int]$MaxRetryCount = 20, [switch] $SkipCertificateCheck ) process { return [DTXTestUtils]::HTTPService($Uri, $ExpectedStatusCode, $ExpectedStatusCodeRange, $WaitTimeInSeconds, $MaxRetryCount, $SkipCertificateCheck) } } function Test-IsAppInstalled { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SearchTerm ) if (-not $IsWindows) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] This cmdlet is only supported on Windows." -ThrowException } $isInstalled = Get-InstalledApps | Where-Object { $_.DisplayName -like $SearchTerm } if ($isInstalled.Count -gt 1) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Multiple applications were found with the search term '$SearchTerm'." foreach ($app in $isInstalled) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Application name: $($app.DisplayName)" } return $true } if ($isInstalled) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The application name matching search term '$SearchTerm' is installed. The application name is '$($isInstalled.DisplayName)'." return $true } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The application name matching search term '$SearchTerm' is not installed." return $false } function Test-IsBrowserSystem { try { $tomcatHomePath = Get-TomcatHomePath } catch { return $false } return [DTXBrowser]::new((Get-TomcatWebAppPath)).IsBrowserSystem() } function Test-IsServiceRunning { param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SearchTerm ) if (-not $IsWindows) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] This cmdlet is only supported on Windows." -ThrowException } $service = Get-Service -Name $SearchTerm -ErrorAction SilentlyContinue if (-not $service) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] No service with the name '$SearchTerm' was found." -ThrowException } if ($service.Count -gt 1) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] More than one service with the name '$SearchTerm' was found. Please specify a more specific search term." -ThrowException } if ($service.Status -eq "Running") { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The service '$($service.Name)' is running." return $true } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The service '$($service.Name)' is not running. Current status is '$($service.Status)'." return $false } function Test-IsValidXml { param( [Parameter(Mandatory = $true)] [string]$XmlString ) return [DTXXmlUtils]::TestXmlContentIsValid($XmlString) } function Test-Mol2NameBatchOutput { param( [ValidateScript({ Test-Path $_ -PathType Container })] [string] $WorkingDirectory ) try { Push-Location -Path $WorkingDirectory $outputFile = "auto_output.txt" $sampleOutputFile = "sample_test_out.txt" $sampleDataFile = "batch.sd" if (Test-Path $outputFile) { Remove-Item -Path $outputFile -Force } if ($IsWindows) { & (Join-Path $pwd "mol2name_batch.exe") $sampleDataFile $outputFile if (!(Test-Path $outputFile)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] mol2name_batch.exe is not able to genereate a sample output using the sample data file." -ThrowException } } if ($IsLinux) { & (Join-Path $pwd "mol2name_batch") $sampleDataFile $outputFile if (!(Test-Path $outputFile)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] mol2name_batch is not able to genereate a sample output using the sample data file." -ThrowException } } if ((Get-Content -Path $outputFile -Raw) -ne (Get-Content -Path $sampleOutputFile -Raw)) { return $false } return $true } finally { Remove-Item -Path $outputFile -Force -ErrorAction SilentlyContinue Pop-Location } } function Test-OpenEyeIntegrity { param ( [string] $Version, [string] $TargetPath ) try { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Testing Open Eye file integrity... " $openEyeDefaults = Get-OpenEyeDefaults -Version $Version $expectedFiles = $openEyeDefaults.FileChecksums.Files $expectedFilesChecksumAlgorithm = $openEyeDefaults.FileChecksums.Algorithm $targetFiles = Get-ChildItem -Path $TargetPath foreach ($expectedFile in $expectedFiles.Keys) { $expectedFileName = $expectedFile $expectedFileHash = $expectedFiles[$expectedFile] if ($targetFiles.Name -notcontains $expectedFileName) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] Open Eye file integrity test failed due to missing files..." Write-LogWarning -Message "[$($MyInvocation.MyCommand)] Expected file: $expectedFileName is not found in the Open Eye directory." return $false } $targetFile = $targetFiles | Where-Object { $_.Name -eq $expectedFileName } $targetFileHash = (Get-FileHash -Path $targetFile.PSPath -Algorithm $expectedFilesChecksumAlgorithm).Hash.ToLower() if ($expectedFileHash -ne $targetFileHash) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] Open Eye file integrity test failed due to checksum mismatch..." Write-LogWarning -Message "[$($MyInvocation.MyCommand)] $($targetFile.Name) file hash is not matching the expected file hash of $expectedFileHash." return $false } } return $true } finally { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Open Eye file integrity test complete." } } function Test-OpenEyeIsUpToDate { param ( [string] $Version ) if (!$IsWindows -and !$IsLinux) { Write-LogCritical -ThrowException "[$( $MyInvocation.MyCommand )] This cmdlet is only supported on Windows or Linux operating systems." } Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Checking if Open Eye is installed and up to date..." $openEyeDefaults = Get-OpenEyeDefaults -Version $Version $gifCacheToolkitPath = Get-BrowserPlatformGifcacheToolkitPath $checksumAlgorithm = $openEyeDefaults.FileChecksums.Algorithm $fileName = $null if ($IsWindows) { $fileName = "mol2name_batch.exe" } if ($IsLinux) { $fileName = "mol2name_batch" } $filePath = (Join-Path $gifCacheToolkitPath $fileName) if (!(Test-Path -Path $filePath)) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Open Eye is not installed on this machine." return $false } $actualFileHash = (Get-FileHash -Path $filePath -Algorithm $checksumAlgorithm).Hash.ToLower() $expectedFileHash = ($openEyeDefaults.FileChecksums.Files.$fileName).ToLower() if ($actualFileHash -ne $expectedFileHash) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Open Eye is not up to date on this machine." return $false } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Open Eye is up to date on this machine." return $true } function Test-SSMCommandResult { param ( [Parameter(Mandatory = $true)] [Amazon.SimpleSystemsManagement.Model.Command] $Result, [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [String] $Region ) process { if (-not $Result.CommandId) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The command ID is missing." -ThrowException } $invokeResult = Get-SSMCommandInvocationDetail -InstanceId $InstanceId -CommandId $Result.CommandId -Region $Region if ($invokeResult.Status -ne "Success") { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The operation failed." -ThrowException } $stdOutResult = $invokeResult.StandardOutputContent | ConvertFrom-Json if ($stdOutResult.Status -ne "OK") { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $($stdOutResult.Message)" -ThrowException } return $true } } function Test-SSMCommandResultV2 { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CommandId, [Parameter(Mandatory = $true)] [string] $InstanceId, [Parameter(Mandatory = $true)] [string] $Region, [switch] $Wait, [switch] $ThrowException ) process { if ($Wait) { Wait-ForSSMCommand -CommandId $CommandId -InstanceId $InstanceId -Region $Region } $commandResult = Get-SSMCommandInvocationDetail -InstanceId $InstanceId -CommandId $CommandId -Region $Region if (-not $commandResult) { $message = "[$($MyInvocation.MyCommand)] No SSM command invocation result found for command id '$CommandId' in region '$Region'." Write-LogCritical -Message $message -ThrowException } if ($commandResult.Status -ne "Success") { if ($ThrowException) { Write-LogError -Message "[$($MyInvocation.MyCommand)] Failed SSM command '$CommandId' with doc '$($commandResult.DocumentName)' in region '$Region'." Write-LogError -Message "[$($MyInvocation.MyCommand)] Command status is '$($commandResult.Status)'." Write-LogError -Message "[$($MyInvocation.MyCommand)] More info: https://$($Region).console.aws.amazon.com/systems-manager/run-command/$($CommandId)?region=$($Region)" Write-SSMCommandOutput -InstanceId $InstanceId -CommandId $CommandId -Region $Region -AsError:$true Write-LogCritical -Message "[$($MyInvocation.MyCommand)] Failed SSM command '$CommandId' with doc '$($commandResult.DocumentName)' in region '$Region'." -ThrowException } return $false } return $true } } function Test-SSMReachability { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $InstanceId, [Parameter(Mandatory = $true)] [String] $Region, [Parameter(Mandatory = $false)] [int] $MaxRegistrationAttempts = 60, [Parameter(Mandatory = $false)] [int] $MaxExecutionAttempts = 40, [Parameter(Mandatory = $false)] [int] $SleepDuration = 3, [switch] $SkipCommandExecution, [switch] $ThrowException ) function Test-InstanceRegisteredToSSM { [CmdletBinding()] param ( [string]$InstanceId, [string]$Region ) $filter = @{ Key = "PingStatus" Values = "Online" } $ssmOnlineInstances = Get-SSMInstanceInformation -Filter $filter -Region $Region return $ssmOnlineInstances.InstanceId -contains $InstanceId.ToLower() } function Invoke-SSMCommandAndCheckStatus { param ( [string]$InstanceId, [string]$Region, [int]$MaxAttempts, [int]$SleepDuration ) $command = Send-SSMCommand -DocumentName "AWS-RunPowerShellScript" -Parameter @{ commands = "Get-Date" } -Region $region -Target @{ Key = "instanceids"; Values = @($InstanceId) } for ($i = 0; $i -le $MaxAttempts; $i++) { $commandResult = Get-SSMCommandInvocation -CommandId $command.CommandId -Region $Region -Detail $true if ($commandResult.Status.Value -match "Success") { return $true } Start-Sleep -Seconds $SleepDuration } return $false } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking SSM registration status for instance '$InstanceId'. Please wait..." $isRegistered = $false for ($i = 0; $i -le $MaxRegistrationAttempts; $i++) { if (Test-InstanceRegisteredToSSM -instanceId $InstanceId -region $Region) { $isRegistered = $true Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Instance '$InstanceId' is registered to SSM." break } Start-Sleep -Seconds $SleepDuration } if (-not $isRegistered) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Instance '$InstanceId' is not registered to SSM." if ($TrhrowException) { Write-LogCritical -Message "[$($MyInvocation.MyCommand)] Instance '$InstanceId' is not registered to SSM." -ThrowException } return $false } if (-not $SkipCommandExecution) { $canExecute = Invoke-SSMCommandAndCheckStatus -instanceId $InstanceId -region $Region -maxAttempts $MaxExecutionAttempts -sleepDuration $SleepDuration if (-not $canExecute) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Instance '$InstanceId' is not reachable via SSM." if ($TrhrowException) { Write-LogCritical -Message "[$($MyInvocation.MyCommand)] Instance '$InstanceId' is not reachable via SSM." -ThrowException } return $false } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Instance '$InstanceId' is reachable via SSM." return $true } function Uninstall-SumoLogic { param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ServiceName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $uninstallFile ) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' If (-not (Test-Path $uninstallFile)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $uninstallFile does not exist , uninstall skipped." return } $dtxSystem=Get-DTXSystem $instanceId = $dtxSystem.GenericInfo.IdentityInfo.instanceId Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Executing $uninstallFile for instance $instanceId ..." Start-Process $uninstallFile -Wait -ArgumentList "-q","-console" $WaitAfterSeconds=10 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Waiting $WaitAfterSeconds seconds for service to deregister ..." Start-Sleep -Seconds $WaitAfterSeconds Assert-True -Condition (-not (Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $ServiceName})) -message "[$( $MyInvocation.MyCommand )] $ServiceName should not exist at this point after uninstall process" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] SumoLogic agent uninstalled." } function Update-XmlAttribute { param ( [System.Xml.XmlElement] $Element, [string] $Attribute, [string] $Value ) if ($element -and $element.HasAttribute($attribute)) { $element.SetAttribute($attribute, $value) } } function Using-Object { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyString()] [AllowEmptyCollection()] [AllowNull()] [Object] $InputObject, [Parameter(Mandatory = $true)] [scriptblock] $ScriptBlock ) try { . $ScriptBlock } finally { if ($null -ne $InputObject -and $InputObject -is [System.IDisposable]) { $InputObject.Dispose() } } } function Wait-ForSSMAssociation { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$InstanceId, [Parameter(Mandatory = $true)] [string]$Region ) begin { $MAX_ATTEMPTS = 3 $SLEEP_DURATION = 5 } process { $isInstanceRegisteredToSSM = Test-SSMReachability -InstanceId $InstanceId -Region $Region -SkipCommandExecution if ($isInstanceRegisteredToSSM) { $count = 0 while ($count -le $MAX_ATTEMPTS) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking SSM association status for instance '$InstanceId'..." $filter = @{ Key = "Status" Value = "InProgress" } $result = Get-SSMCommand -InstanceId $InstanceId -Region $Region -Filter $filter if ($result.count -ne 0) { $count = 0 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $($result.count) SSM association(s) in progress for instance '$InstanceId'." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No SSM association in progress for instance '$InstanceId'." $time_left = ($MAX_ATTEMPTS - $count) * $SLEEP_DURATION Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Time left: $time_left seconds" $count++ } Start-Sleep -Seconds $SLEEP_DURATION } return $true } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Instance not registered in SSM. Skipping SSM association check." return $false } } } function Wait-ForSSMCommand { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $CommandId, [Parameter(Mandatory = $true)] [string] $InstanceId, [Parameter(Mandatory = $true)] [String] $Region ) begin { $MAX_ATTEMPTS = 720 $SLEEP_DURATION = 5 function Get-CommandResult { try { return (Get-SSMCommandInvocationDetail -CommandId $CommandId -InstanceId $InstanceId -Region $Region) } catch { if ($_ -like "*InvocationDoesNotExist*") { return $null } else { Write-LogCritical -Message "[$($MyInvocation.MyCommand)] Get-CommandResult Failed" -ThrowException -Exception $_ } } } function Test-IsCommandComplete { $res = Get-CommandResult if (($null -eq $res) -or ($null -eq $res.ExecutionEndDateTime) -or ($res.ExecutionEndDateTime -eq '')) { if ($null -eq $res.Status -or $res.Status -eq '' -or $res.Status -eq 'Pending' -or $res.Status -eq 'InProgress') { return $false } } return $true } } process { try { $attempts = 0 $isComplete = Test-IsCommandComplete while (-not $isComplete -and $attempts -lt $MAX_ATTEMPTS) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Waiting for SSM command '$CommandId' to complete." Start-Sleep -Seconds $SLEEP_DURATION $isComplete = Test-IsCommandComplete $attempts++ } if ($attempts -ge $MAX_ATTEMPTS) { Write-LogCritical -Message "[$($MyInvocation.MyCommand)] Max attempts reached. SSM command '$CommandId' did not complete within the time limit." -ThrowException } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Exception caught: Unable to retrieve the status of SSM command '$CommandId'." -ThrowException -Exception $_ } } } function Wait-ForSSMReachability { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $InstanceId, [Parameter(Mandatory = $true)] [String] $Region ) process { Test-SSMReachability -InstanceId $InstanceId -Region $Region -MaxRegistrationAttempts 12 -SleepDuration 10 } } function Wait-ForWebServerToServeRequests { param ( [ValidateNotNullOrEmpty()] [string]$Hostname = 'localhost', [ValidateNotNullOrEmpty()] [int]$Port = 443, [ValidateSet('http', 'https')] [string]$Scheme = 'https', [ValidateNotNullOrEmpty()] [int]$WaitSeconds = 300, [switch]$SkipCertificateCheck ) $webServerUrl = "$($Scheme)://$($Hostname):$($Port)" if (($Port -eq 443) -and ($Scheme -eq 'https')) { $webServerUrl = "$($Scheme)://$($Hostname)" } $startTime = Get-Date $endTime = $startTime.AddSeconds($WaitSeconds) $isLive = $false $atLeastOneRetry = $false while ((Get-Date) -lt $endTime) { try { $response = Invoke-WebRequest -Uri $webServerUrl -UseBasicParsing -Method Head -TimeoutSec 1 -SkipCertificateCheck:$SkipCertificateCheck if ($response.StatusCode -gt 199 -and $response.StatusCode -lt 399) { Write-LogInfo -Message "[$($MyInvocation.InvocationName)] The web server on $webServerUrl is serving requests." $isLive = $true break } } catch [System.Net.Http.HttpRequestException] { if ($_.Exception.InnerException -and $_.Exception.InnerException.Message -like "*RemoteCertificateNameMismatch*") { Write-LogError -Message "[$($MyInvocation.InvocationName)] Unable to connect to $webServerUrl. The certificate does not match the expected hostname. Use -SkipCertificateCheck to bypass this check." throw $_ } } catch { $atLeastOneRetry = $true } Start-Sleep -Seconds 1 $timeLeft = [math]::Truncate(($endTime - (Get-Date)).TotalSeconds) Write-LogInfo -Message "[$($MyInvocation.InvocationName)] Waiting for the web server on $webServerUrl to start serving requests. Time left: $timeLeft seconds" } if (-not $isLive) { Write-LogWarning -Message "[$($MyInvocation.InvocationName)] The web server on $webServerUrl did not start serving requests within the specified time." } if ($isLive -and $atLeastOneRetry) { $totalWaitTime = [math]::Truncate(((Get-Date) - $startTime).TotalSeconds) Write-LogInfo -Message "[$($MyInvocation.InvocationName)] It took $totalWaitTime seconds for the web server on $webServerUrl to start serving requests." } } function Write-SSMCommandOutput { param ( [Parameter(Mandatory = $true)] [string] $CommandId, [Parameter(Mandatory = $true)] [string] $InstanceId, [Parameter(Mandatory = $true)] [string] $Region, [switch] $AsError ) process { $commandStepsOutput = Get-SSMCommandOutput -CommandId $CommandId -InstanceId $InstanceId -Region $Region foreach ($step in $commandStepsOutput.GetEnumerator()) { $stdOut = $step.Value.StandardOutput $stdErr = $step.Value.StandardError if (-not $stdOut -and -not $stdErr) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] No standard output or standard error content for command id '$CommandId' with step name '$($step.Name)' in region '$Region'." return } if ($AsError) { Write-LogError -Message "[$($MyInvocation.MyCommand)] === Output for CommandId: $($CommandId) ===" if ($stdOut) { Write-LogError -Message "[$($MyInvocation.MyCommand)] === BEGIN Standard Output ===" Write-LogError -Message $stdOut Write-LogError -Message "[$($MyInvocation.MyCommand)] === END Standard Output ===" } if ($stdErr) { Write-LogError -Message "[$($MyInvocation.MyCommand)] === BEGIN Standard Error ===" Write-LogError -Message $stdErr Write-LogError -Message "[$($MyInvocation.MyCommand)] === END Standard Error ===" } continue } Write-LogInfo -Message "[$($MyInvocation.MyCommand)] === Output for CommandId: $($CommandId) ===" if ($stdOut) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] === BEGIN Standard Output ===" Write-LogInfo -Message $stdOut Write-LogInfo -Message "[$($MyInvocation.MyCommand)] === END Standard Output ===" } if ($stdErr) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] === BEGIN Standard Error ===" Write-LogWarning -Message $stdErr Write-LogWarning -Message "[$($MyInvocation.MyCommand)] === END Standard Error ===" } } } } function Write-StringToFileWithRetry { param( [Parameter(Mandatory = $true)] [string]$StringContent, [Parameter(Mandatory = $true)] [string]$FilePath, [Int]$RetryCount = 3, [Int]$WaitSeconds = 2 ) $retryAttempts = 0 do { try { $StringContent | Out-File -Force -FilePath $FilePath break } catch { if ($retryAttempts -ge $RetryCount) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Failed to write to file '$FilePath' after $($RetryCount + 1) attempts." -ThrowException } Start-Sleep -Seconds $WaitSeconds $retryAttempts++ } } while ($retryAttempts -le $RetryCount) } function Assert-CorrectEC2Tag { param ( [string] $Key, [array]$AllowedValues ) $myInfo = Get-DTXSystem $instanceId = $myInfo.GenericInfo.IdentityInfo.instanceId Assert-True -Condition ($instanceId -ne $null) -message "[$( $MyInvocation.MyCommand )] Instance ID was null" Assert-True -Condition ($instanceId.Length -gt 0) -message "[$( $MyInvocation.MyCommand )] Instance ID was length 0" $allTags = Get-EC2Tags -InstanceId $instanceId $myValue = Get-TagValue -Key $Key -Tags $allTags if ($AllowedValues.Contains($myValue)) { Write-LogInfo "[$( $MyInvocation.MyCommand )] Tag value is in the allowed values list, tag value confirmed" } else { Write-LogWarning "[$( $MyInvocation.MyCommand )] Tag value($myValue) is NOT in allowed values: TagKey: $Key TagAllowedValues: $AllowedValues" throw (New-Object DTXPSException("[$( $MyInvocation.MyCommand )] Incorrect Tag Value")) } } function Format-AutomationParams { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateSet('True', 'False', IgnoreCase = $true)] [string] $DryRun, [Parameter(Mandatory)] [ValidateSet('True', 'False', IgnoreCase = $true)] [string] $RebootRequested, [Parameter(Mandatory)] [ValidateSet('True', 'False', IgnoreCase = $true)] [string] $InMaintWindow, [Parameter(Mandatory)] [ValidateScript({ $_ -as [int] })] [string] $RebootExitCode, [Parameter(Mandatory)] [ValidateScript({ $_ -as [int] })] [string] $ContinueOnErrorExitCode, [Parameter(Mandatory)] [string] $EC2Role, [switch] $AsHashtable ) $result = [PSCustomObject]@{ DryRun = [bool]::Parse($DryRun) RebootRequested = [bool]::Parse($RebootRequested) InMaintWindow = [bool]::Parse($InMaintWindow) RebootExitCode = [int]::Parse($RebootExitCode) ContinueOnErrorExitCode = [int]::Parse($ContinueOnErrorExitCode) EC2Role = $EC2Role } if ($AsHashtable) { return @{ DryRun = $result.DryRun RebootRequested = $result.RebootRequested InMaintWindow = $result.InMaintWindow RebootExitCode = $result.RebootExitCode ContinueOnErrorExitCode = $result.ContinueOnErrorExitCode EC2Role = $result.EC2Role } } return $result } function Get-DefaultsT { return Get-Defaults } function Get-DTXLocalDiscoveryFilePath { return [DTXPathProvider]::GetDiscoveryFilePath() } function Get-DTXMyInvocationDetails { [CmdletBinding()] param() return $PSCmdlet.MyInvocation } function Get-DTXSystem { param( [switch]$NoCache ) function Get-InternalDTXSystem { $returnObj = @{ GenericInfo = @{ DiskInfo = New-Object Collections.Generic.List[PSObject] IdentityInfo = @{} InstanceTags = "UNKNOWN" HostName = "UNKNOWN" PublicIPv4 = "UNKNOWN" IAMProfile = "UNKNOWN" } AppInfo = @{ Tomcat = @{ Running = "UNKNOWN" Service = @{ Name = "UNKNOWN" Status = "UNKNOWN" StartType = "UNKNOWN" DependentServices = "UNKNOWN" DependsOnServices = "UNKNOWN" User = "UNKNOWN" } Path = "UNKNOWN" WebAppsPath = "UNKNOWN" Version = "UNKNOWN" Java = @{ Version = "UNKNOWN" Vendor = "UNKNOWN" Path = "UNKNOWN" } ServerXML = @{ Path = "UNKNOWN" Port = "UNKNOWN" HttpProtocol = "UNKNOWN" Http2Enabled = "UNKNOWN" SSLProtocols = "UNKNOWN" SSLCiphers = "UNKNOWN" KeyStoreFile = "UNKNOWN" WebAppBase = "UNKNOWN" } Certificate = @{ Fingerprint = @{ SHA1 = "UNKNOWN" } } Options = @{ JavaOpts = "UNKNOWN" JVM = "UNKNOWN" JavaMinMemoryMB = -1 JavaMaxMemoryMB = -1 JavaThreadStackSizeKB = -1 } } Oracle = @{ Database = @{ Running = "UNKNOWN" Service = @{ Name = "UNKNOWN" Status = "UNKNOWN" StartType = "UNKNOWN" DependentServices = "UNKNOWN" DependsOnServices = "UNKNOWN" } Path = "UNKNOWN" Version = "UNKNOWN" } DatabaseListener = @{ Running = "UNKNOWN" Service = @{ Name = "UNKNOWN" Status = "UNKNOWN" StartType = "UNKNOWN" DependentServices = "UNKNOWN" DependsOnServices = "UNKNOWN" } Path = "UNKNOWN" Version = "UNKNOWN" } } JChem = @{ Cartridge = @{ Running = "UNKNOWN" Service = @{ Name = "UNKNOWN" Status = "UNKNOWN" StartType = "UNKNOWN" DependentServices = "UNKNOWN" DependsOnServices = "UNKNOWN" } Path = "UNKNOWN" Version = "UNKNOWN" } } BrowserPlatform = @{ IsBrowserSystem = "UNKNOWN" PropertiesPath = "UNKNOWN" Properties = @{} Plugins = @{ OpenEye = @{ UserFriendlyVersion = "UNKNOWN" Mol2NameChecksum = "UNKNOWN" Mol2NameBatchChecksum = "UNKNOWN" } } } } StateInfo = New-Object System.Collections.Hashtable ComputedQueries = @{ IsBrowserPlatformSystem = @{ Status = "UNKNOWN" } IsWebServer = @{ Status = "UNKNOWN" AppName = "UNKNOWN" } IsDBServer = @{ Status = "UNKNOWN" AppName = "UNKNOWN" } } } function Get-BasicVMInfo { $returnObj.GenericInfo.HostName = $env:COMPUTERNAME $returnObj.GenericInfo.DiskInfo = Get-DiskInformation $returnObj.GenericInfo.IdentityInfo = (Get-EC2InstanceMetadata -Category "IdentityDocument" -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue) ?? $returnObj.GenericInfo.IdentityInfo $returnObj.GenericInfo.IAMProfile = (Get-EC2InstanceMetadata -Path "/iam/info" -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue).InstanceProfileArn ?? $returnObj.GenericInfo.IAMProfile $returnObj.GenericInfo.PublicIPv4 = (Get-EC2InstanceMetadata -Category "PublicIpv4" -ErrorAction SilentlyContinue) ?? $returnObj.GenericInfo.PublicIPv4 } function Get-TomcatAppInfo { $returnObj.AppInfo.Tomcat.Running = [bool](Get-TomcatProcess) if ($returnObj.AppInfo.Tomcat.Running) { $meta = Get-TomcatMetadata $returnObj.AppInfo.Tomcat.Service.Name = $meta.TomcatService.Name ?? $returnObj.AppInfo.Tomcat.Service.Name $returnObj.AppInfo.Tomcat.Service.Status = $meta.TomcatService.Status ?? $returnObj.AppInfo.Tomcat.Service.Status $returnObj.AppInfo.Tomcat.Service.StartType = (($meta.TomcatService.StartType ?? $meta.TomcatService.StartMode) | Out-String -NoNewline) ?? $returnObj.AppInfo.Tomcat.Service.StartType $returnObj.AppInfo.Tomcat.Service.User = $meta.TomcatServiceUser ?? $returnObj.AppInfo.Tomcat.Service.User if ($meta.TomcatService.DependentServices.Count -eq 0) { $returnObj.AppInfo.Tomcat.Service.DependentServices = $meta.TomcatService.DependentServices } else { $returnObj.AppInfo.Tomcat.Service.DependentServices = $meta.TomcatService.DependentServices | Select-Object Name, DisplayName, Status } if ($meta.TomcatService.ServicesDependedOn.Count -eq 0) { $returnObj.AppInfo.Tomcat.Service.DependsOnServices = $meta.TomcatService.ServicesDependedOn } else { $returnObj.AppInfo.Tomcat.Service.DependsOnServices = $meta.TomcatService.ServicesDependedOn | Select-Object Name, DisplayName, Status } $returnObj.AppInfo.Tomcat.Certificate.Fingerprint.SHA1 = $meta.TomcatCertificate.Fingerprint.SHA1 ?? $returnObj.AppInfo.Tomcat.Certificate.Fingerprint.SHA1 $returnObj.AppInfo.Tomcat.Path = $meta.TomcatHomePath ?? $returnObj.AppInfo.Tomcat.Path $returnObj.AppInfo.Tomcat.Version = $meta.TomcatVersion ?? $returnObj.AppInfo.Tomcat.Version $returnObj.AppInfo.Tomcat.Java.Version = $meta.TomcatJavaVersion ?? $returnObj.AppInfo.Tomcat.Java.Version $returnObj.AppInfo.Tomcat.Java.Vendor = $meta.TomcatJavaVendor ?? $returnObj.AppInfo.Tomcat.Java.Vendor $returnObj.AppInfo.Tomcat.Java.Path = $meta.TomcatJavaHomePath ?? $returnObj.AppInfo.Tomcat.Java.Path $serverXmlPath = $meta.TomcatServerXmlPath ?? $returnObj.AppInfo.Tomcat.ServerXML.Path if (Test-Path $serverXmlPath) { $returnObj.AppInfo.Tomcat.ServerXML.Path = $serverXmlPath try { $xmlFile = [DTXTomcatServerXml]::new($serverXmlPath) $sslConnector = $xmlFile.GetConnectorElementWithSslEnabled() $returnObj.AppInfo.Tomcat.ServerXML.Port = $sslConnector.port ?? $returnObj.AppInfo.Tomcat.ServerXML.Port $returnObj.AppInfo.Tomcat.ServerXML.HttpProtocol = $sslConnector.protocol ?? $returnObj.AppInfo.Tomcat.ServerXML.HttpProtocol $returnObj.AppInfo.Tomcat.ServerXML.Http2Enabled = $xmlFile.IsConnectorHttp2Enabled($sslConnector.port) ?? $returnObj.AppInfo.Tomcat.ServerXML.Http2Enabled $returnObj.AppInfo.Tomcat.ServerXML.SSLProtocols = $sslConnector.sslEnabledProtocols ?? $returnObj.AppInfo.Tomcat.ServerXML.SSLProtocols $returnObj.AppInfo.Tomcat.ServerXML.SSLCiphers = $sslConnector.ciphers ?? $returnObj.AppInfo.Tomcat.ServerXML.SSLCiphers $returnObj.AppInfo.Tomcat.ServerXML.KeyStoreFile = $sslConnector.keystoreFile ?? $returnObj.AppInfo.Tomcat.ServerXML.KeyStoreFile $returnObj.AppInfo.Tomcat.ServerXML.WebAppBase = $xmlFile.GetContents()["Server"]["Service"]["Engine"]["Host"].appBase $returnObj.AppInfo.Tomcat.WebAppsPath = Join-Path $returnObj.AppInfo.Tomcat.Path $returnObj.AppInfo.Tomcat.ServerXML.WebAppBase } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to read the Tomcat server XML file. Please check the file path and permissions." } $returnObj.AppInfo.Tomcat.Options.JavaOpts = $meta.TomcatJavaOptions.Options ?? $returnObj.AppInfo.Tomcat.Options.JavaOpts $returnObj.AppInfo.Tomcat.Options.JVM = $meta.TomcatJavaOptions.JVM ?? $returnObj.AppInfo.Tomcat.Options.JVM $returnObj.AppInfo.Tomcat.Options.JavaMinMemoryMB = $meta.TomcatJavaOptions.JavaMinMemoryMB ?? $returnObj.AppInfo.Tomcat.Options.JavaMinMemoryMB $returnObj.AppInfo.Tomcat.Options.JavaMaxMemoryMB = $meta.TomcatJavaOptions.JavaMaxMemoryMB ?? $returnObj.AppInfo.Tomcat.Options.JavaMaxMemoryMB $returnObj.AppInfo.Tomcat.Options.JavaThreadStackSizeKB = $meta.TomcatJavaOptions.JavaThreadStackSizeKB ?? $returnObj.AppInfo.Tomcat.Options.JavaThreadStackSizeKB } } } function Get-JChemAppInfo { function Get-JChemCartridgeServiceInfo { $returnObj.AppInfo.JChem.Cartridge.Running = [bool](Get-JChemCartridgeProcess) if ($returnObj.AppInfo.JChem.Cartridge.Running) { $meta = Get-JChemMetadata $returnObj.AppInfo.JChem.Cartridge.Service.Name = $meta.Cartridge.Service.Name ?? $returnObj.AppInfo.JChem.Cartridge.Service.Name $returnObj.AppInfo.JChem.Cartridge.Service.Status = $meta.Cartridge.Service.Status ?? $returnObj.AppInfo.JChem.Cartridge.Service.Status $returnObj.AppInfo.JChem.Cartridge.Service.StartType = (($meta.Cartridge.Service.StartType ?? $meta.Cartridge.Service.StartMode) | Out-String -NoNewline) ?? $returnObj.AppInfo.JChem.Cartridge.Service.StartType if ($meta.Cartridge.Service.DependentServices.Count -eq 0) { $returnObj.AppInfo.JChem.Cartridge.Service.DependentServices = $meta.Cartridge.Service.DependentServices } else { $returnObj.AppInfo.JChem.Cartridge.Service.DependentServices = $meta.Cartridge.Service.DependentServices | Select-Object Name, DisplayName, Status } if ($meta.Cartridge.Service.ServicesDependedOn.Count -eq 0) { $returnObj.AppInfo.JChem.Cartridge.Service.DependsOnServices = $meta.Cartridge.Service.ServicesDependedOn } else { $returnObj.AppInfo.JChem.Cartridge.Service.DependsOnServices = $meta.Cartridge.Service.ServicesDependedOn | Select-Object Name, DisplayName, Status } $returnObj.AppInfo.JChem.Cartridge.Path = $meta.Cartridge.HomePath ?? $returnObj.AppInfo.JChem.Cartridge.Path $returnObj.AppInfo.JChem.Cartridge.Version = $meta.Cartridge.Version ?? $returnObj.AppInfo.JChem.Cartridge.Version } } Get-JChemCartridgeServiceInfo } function Get-OracleAppInfo { if ([bool](Get-OracleDatabaseListenerProcess) -or [bool](Get-OracleDatabaseProcess)) { $meta = Get-OracleMetadata function Get-OracleDatabaseInfo { $returnObj.AppInfo.Oracle.Database.Running = [bool](Get-OracleDatabaseProcess) if ($returnObj.AppInfo.Oracle.Database.Running) { $returnObj.AppInfo.Oracle.Database.Service.Name = $meta.Database.Service.Name ?? $returnObj.AppInfo.Oracle.Database.Service.Name $returnObj.AppInfo.Oracle.Database.Service.Status = $meta.Database.Service.Status ?? $returnObj.AppInfo.Oracle.Database.Service.Status $returnObj.AppInfo.Oracle.Database.Service.StartType = (($meta.Database.Service.StartType ?? $meta.Database.Service.StartMode) | Out-String -NoNewline) ?? $returnObj.AppInfo.Oracle.Database.Service.StartType if ($meta.Database.Service.DependentServices.Count -eq 0) { $returnObj.AppInfo.Oracle.Database.Service.DependentServices = $meta.Database.Service.DependentServices } else { $returnObj.AppInfo.Oracle.Database.Service.DependentServices = $meta.Database.Service.DependentServices | Select-Object Name, DisplayName, Status } if ($meta.Database.Service.ServicesDependedOn.Count -eq 0) { $returnObj.AppInfo.Oracle.Database.Service.DependsOnServices = $meta.Database.Service.ServicesDependedOn } else { $returnObj.AppInfo.Oracle.Database.Service.DependsOnServices = $meta.Database.Service.ServicesDependedOn | Select-Object Name, DisplayName, Status } $returnObj.AppInfo.Oracle.Database.Path = $meta.Database.HomePath ?? $returnObj.AppInfo.Oracle.Database.Path $returnObj.AppInfo.Oracle.Database.Version = $meta.Database.Version ?? $returnObj.AppInfo.Oracle.Database.Version } } function Get-OracleDatabaseListenerInfo { $returnObj.AppInfo.Oracle.DatabaseListener.Running = [bool](Get-OracleDatabaseListenerProcess) if ($returnObj.AppInfo.Oracle.DatabaseListener.Running) { $returnObj.AppInfo.Oracle.DatabaseListener.Service.Name = $meta.Listener.Service.Name ?? $returnObj.AppInfo.Oracle.DatabaseListener.Service.Name $returnObj.AppInfo.Oracle.DatabaseListener.Service.Status = $meta.Listener.Service.Status ?? $returnObj.AppInfo.Oracle.DatabaseListener.Service.Status $returnObj.AppInfo.Oracle.DatabaseListener.Service.StartType = (($meta.Listener.Service.StartType ?? $meta.Listener.Service.StartMode) | Out-String -NoNewline) ?? $returnObj.AppInfo.Oracle.DatabaseListener.Service.StartType if ($meta.Listener.Service.DependentServices.Count -eq 0) { $returnObj.AppInfo.Oracle.DatabaseListener.Service.DependentServices = $meta.Listener.Service.DependentServices } else { $returnObj.AppInfo.Oracle.DatabaseListener.Service.DependentServices = $meta.Listener.Service.DependentServices | Select-Object Name, DisplayName, Status } if ($meta.Listener.Service.ServicesDependedOn.Count -eq 0) { $returnObj.AppInfo.Oracle.DatabaseListener.Service.DependsOnServices = $meta.Listener.Service.ServicesDependedOn } else { $returnObj.AppInfo.Oracle.DatabaseListener.Service.DependsOnServices = $meta.Listener.Service.ServicesDependedOn | Select-Object Name, DisplayName, Status } $returnObj.AppInfo.Oracle.DatabaseListener.Path = $meta.Listener.HomePath ?? $returnObj.AppInfo.Oracle.DatabaseListener.Path $returnObj.AppInfo.Oracle.DatabaseListener.Version = $meta.Listener.Version ?? $returnObj.AppInfo.Oracle.DatabaseListener.Version } } Get-OracleDatabaseInfo Get-OracleDatabaseListenerInfo } } function Get-AppInfo { Get-TomcatAppInfo Get-JChemAppInfo Get-OracleAppInfo } function Get-BrowserPlatformInfo { $returnObj.AppInfo.BrowserPlatform.IsBrowserSystem = Test-IsBrowserSystem if ($returnObj.AppInfo.BrowserPlatform.IsBrowserSystem) { $webAppPath = Get-TomcatWebAppPath $browserPropertiesFilePath = Get-BrowserPlatformPropertiesFilePath -TomcatWebAppPath $webAppPath if (Test-Path $browserPropertiesFilePath) { $returnObj.AppInfo.BrowserPlatform.PropertiesPath = $browserPropertiesFilePath ?? $returnObj.AppInfo.BrowserPlatform.PropertiesPath $returnObj.AppInfo.BrowserPlatform.Properties = (ConvertFrom-StringData (Get-Content -Raw $browserPropertiesFilePath)) ?? $returnObj.AppInfo.BrowserPlatform.Properties } $openEyeMeta = Get-OpenEyeMetadata $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.UserFriendlyVersion = $openEyeMeta.UserFriendlyVersion ?? $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.UserFriendlyVersion $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.Mol2NameChecksum = $openEyeMeta.Mol2NameChecksum ?? $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.Mol2NameChecksum $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.Mol2NameBatchChecksum = $openEyeMeta.Mol2NameBatchChecksum ?? $returnObj.AppInfo.BrowserPlatform.Plugins.OpenEye.Mol2NameBatchChecksum } } function Get-ComputedQueries { $returnObj.ComputedQueries.IsBrowserPlatformSystem.Status = $returnObj.AppInfo.BrowserPlatform.IsBrowserSystem if ($returnObj.AppInfo.Tomcat.Running) { $returnObj.ComputedQueries.IsWebServer.Status = $returnObj.AppInfo.Tomcat.Running $returnObj.ComputedQueries.IsWebServer.AppName = "Tomcat" } if ($returnObj.AppInfo.Oracle.Database.Running) { $returnObj.ComputedQueries.IsDBServer.Status = $returnObj.AppInfo.Oracle.Database.Running $returnObj.ComputedQueries.IsDBServer.AppName = "Oracle.Database" } } function Get-InstanceTags { $instanceId = $returnObj.GenericInfo.IdentityInfo.InstanceId $region = $returnObj.GenericInfo.IdentityInfo.Region if ($instanceId -and $region) { $returnObj.GenericInfo.InstanceTags = Get-Ec2Tag -Filter @{Name = "resource-type"; Values = "instance" }, @{Name = "resource-id"; Values = $instanceid } -Region $region | Select-Object Key, Value } } function Get-StateInfo { $returnObj.StateInfo = Get-StateContent } Get-BasicVMInfo Get-AppInfo Get-BrowserPlatformInfo Get-InstanceTags Get-StateInfo Get-ComputedQueries $returnObj | Write-DTXSystem return $returnObj } function Get-DataFromCache { $dtxFile = Get-DTXLocalDiscoveryFilePath if (!(Test-Path $dtxFile)) { return $null } $dtxFile = Get-Item $dtxFile if ($dtxFile.LastWriteTimeUtc -gt (Get-Date -AsUTC).AddHours(-1)) { return Get-Content -Path $dtxFile -Raw | ConvertFrom-Json -Depth 100 -AsHashtable } return $null } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Gathering system information. Please wait..." $returnObj = $null if ($NoCache) { $returnObj = Get-InternalDTXSystem } else { $cachedData = Get-DataFromCache if ($null -eq $cachedData) { $returnObj = Get-InternalDTXSystem } else { $returnObj = $cachedData } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Information gathering complete." return $returnObj } function Get-HealthCheckInfo { [CmdletBinding()] param( [switch]$NoCache ) function Get-InternalHealthCheck { $returnObj = [ordered]@{ GenericInfo = [ordered]@{ Url = "UNKNOWN" PlatformVersion = [ordered]@{ BrowserVersion = "UNKNOWN" BioregisterVersion = "UNKNOWN" PinpointVersion = "UNKNOWN" AllVersionsText = "UNKNOWN" } InstanceType = "UNKNOWN" Jdk = "UNKNOWN" Antivirus = "UNKNOWN" Glowroot = [ordered]@{ IsGlowrootInJavaOptions = "UNKNOWN" IsGlowrootLaunched = "UNKNOWN" TextInfo = "UNKNOWN" } TomcatMemoryGB = "UNKNOWN" JavaTomcatOptions = New-Object Collections.Generic.List[PSObject] PhysicalMemoryGB = "UNKNOWN" VirtualMemoryInfo = [ordered]@{ IsManagedAutomatically = $null InitialSizeGB = "UNKNOWN" MaxSizeGB = "UNKNOWN" TextInfo = "UNKNOWN" } ConnectionPoolBrowserProperties = [ordered]@{ maxconn = "UNKNOWN" maxpool = "UNKNOWN" maxwait = "UNKNOWN" } UserCountInfo = [ordered]@{ LicensesNumber = "UNKNOWN" UserCountText = "UNKNOWN" } ScheduledTasksInfo = New-Object Collections.Generic.List[PSObject] } DatabaseInfo = [ordered]@{ FullOracleVersion = "UNKNOWN" Memory = [ordered]@{ MemoryMaxTarget = "UNKNOWN" MemoryTarget = "UNKNOWN" PgaAggregateLimit = "UNKNOWN" PgaAggregateTarget = "UNKNOWN" SgaMaxSize = "UNKNOWN" SgaTarget = "UNKNOWN" TextInfo = "UNKNOWN" } ProcessesSessions = "UNKNOWN" Processes = "UNKNOWN" Sessions = "UNKNOWN" OpenCursors = "UNKNOWN" ProjectsNumber = "UNKNOWN" DatasourceCount = [ordered]@{ DatasourceCountInfo = "UNKNOWN" DynamicDatasourceCount = "UNKNOWN" BrowserManagedTablesCount = "UNKNOWN" BrowserManagedViewsCount = "UNKNOWN" } MaterialisedViewCount = "UNKNOWN" PivotTableCount = "UNKNOWN" LogsFrequencyInfo = [ordered]@{ LatestLogsCheckpointNotComplete = "UNKNOWN" LatestLogsHeavySwapping = "UNKNOWN" LatestLogsPgaLimit = "UNKNOWN" } } } $dtxSystem = Get-DTXSystem $it = $dtxSystem.GenericInfo.InstanceTags $returnObj.GenericInfo.Url = (Get-InstanceUrl $it) ?? $returnObj.GenericInfo.Url $returnObj.GenericInfo.InstanceType = $dtxSystem.GenericInfo.IdentityInfo.instanceType $returnObj.GenericInfo.Jdk = $dtxSystem.AppInfo.Tomcat.Options.JVM ?? $returnObj.GenericInfo.Jdk $returnObj.GenericInfo.Antivirus = Get-AntivirusName ?? $returnObj.GenericInfo.Antivirus $returnObj.GenericInfo.Glowroot = (Get-GlowrootInfo $dtxSystem.AppInfo.Tomcat.Options.JavaOpts) ?? $returnObj.GenericInfo.Glowroot if ($dtxSystem.AppInfo.Tomcat.Options.JavaMaxMemoryMB -gt 100) { $returnObj.GenericInfo.TomcatMemoryGB = ($dtxSystem.AppInfo.Tomcat.Options.JavaMaxMemoryMB / 1024) ?? $returnObj.GenericInfo.TomcatMemoryGB } $returnObj.GenericInfo.JavaTomcatOptions = $dtxSystem.AppInfo.Tomcat.Options.JavaOpts ?? $returnObj.GenericInfo.JavaTomcatOptions try { $returnObj.GenericInfo.PhysicalMemoryGB = ((Get-CimInstance -Class CIM_PhysicalMemory).Capacity / 1GB) ?? $returnObj.GenericInfo.PhysicalMemoryGB } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to read physical data from database" } $returnObj.GenericInfo.VirtualMemoryInfo = Get-VirtualmemoryInfo ?? $returnObj.GenericInfo.VirtualMemoryInfo $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxconn = $dtxSystem.AppInfo.BrowserPlatform.Properties['db.maxconn'] ?? $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxconn $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxpool = $dtxSystem.AppInfo.BrowserPlatform.Properties['db.maxpool'] ?? $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxpool $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxwait = $dtxSystem.AppInfo.BrowserPlatform.Properties['db.maxwait'] ?? $returnObj.GenericInfo.ConnectionPoolBrowserProperties.maxwait $returnObj.GenericInfo.UserCountInfo.UserCountText = (Get-UserCountInfo $returnObj.GenericInfo.Url) ?? $returnObj.GenericInfo.UserCountInfo.UserCountText $returnObj.GenericInfo.UserCountInfo.LicensesNumber = (Get-LicenseNumber $webAppPath) ?? $returnObj.GenericInfo.UserCountInfo.LicensesNumber $returnObj.GenericInfo.ScheduledTasksInfo = (Get-ScheduledTasksInfo) ?? $returnObj.GenericInfo.ScheduledTasksInfo $res = Get-InfoFromDatabase if ($res -eq $null) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to get data from database" } else { $returnObj.DatabaseInfo.FullOracleVersion = $dtxSystem.AppInfo.Oracle.Database.Version ?? $res.OracleVersion ?? $returnObj.DatabaseInfo.FullOracleVersion if ($dtxSystem.AppInfo.Oracle.Database.Version -eq "UNKNOWN") { $returnObj.DatabaseInfo.FullOracleVersion = $res.OracleVersion ?? $returnObj.DatabaseInfo.FullOracleVersion } $returnObj.GenericInfo.PlatformVersion = $res.Version $returnObj.DatabaseInfo.Memory = $res.Memory $returnObj.DatabaseInfo.ProcessesSessions = $res.ProcessesSessions $returnObj.DatabaseInfo.Processes = $res.Processes $returnObj.DatabaseInfo.Sessions = $res.Sessions $returnObj.DatabaseInfo.OpenCursors = $res.OpenCursors $returnObj.DatabaseInfo.ProjectsNumber = $res.ProjectsNumber $returnObj.DatabaseInfo.DatasourceCount = $res.DatasourceCount $returnObj.DatabaseInfo.MaterialisedViewCount = $res.MaterialisedViewCount $returnObj.DatabaseInfo.PivotTableCount = $res.PivotTableCount } $fromLogs = Get-InfoFromOracleLogs if ($fromLogs -eq $null) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Unable to get data from database alert logs" } else { $returnObj.DatabaseInfo.LogsFrequencyInfo.LatestLogsCheckpointNotComplete = $fromLogs.LatestLogsCheckpointNotComplete $returnObj.DatabaseInfo.LogsFrequencyInfo.LatestLogsHeavySwapping = $fromLogs.LatestLogsHeavySwapping $returnObj.DatabaseInfo.LogsFrequencyInfo.LatestLogsPgaLimit = $fromLogs.LatestLogsPgaLimit } $returnObj | Write-JsonCache -JsonFileName "dtx_health-check.json" return $returnObj } function Get-DataFromCache { $dtxFile = Join-Path (Get-LocalDotmaticsPath) "dtx_health-check.json" if (!(Test-Path $dtxFile)) { return $null } $dtxFile = Get-Item $dtxFile if ($dtxFile.LastWriteTimeUtc -gt (Get-Date -AsUTC).AddHours(-1)) { return Get-Content -Path $dtxFile -Raw | ConvertFrom-Json -Depth 100 -AsHashtable } return $null } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Gathering system information. Please wait..." $returnObj = $null if ($NoCache) { $returnObj = Get-InternalHealthCheck } else { $cachedData = Get-DataFromCache if ($null -eq $cachedData) { $returnObj = Get-InternalHealthCheck } else { $returnObj = $cachedData } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Information gathering complete." return $returnObj } function Get-PatchingExitCode { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$RebootExitCode ) Begin { function Get-PatchInventoryOnWindows { param ( [String] $FilePath ) $hashTable = @{} $csvObj = Import-Csv -Delimiter ":" -Path $FilePath -Header "Key", "Value" foreach ($item in $csvObj) { $key = $item.Key.Trim() $value = if ($null -eq $item.Value) { $item.Value } else { $item.Value.Trim().TrimEnd(",") } $hashTable[$key] = $value } return $hashTable } function Get-PatchInventoryOnLinux { param ( [String] $FilePath ) return (get-content $FilePath -raw | ConvertFrom-Json).Content } } Process { $rebootExitCode = [int]::Parse($RebootExitCode) $windowsFile = "C:\ProgramData\Amazon\PatchBaselineOperations\State\PatchInventoryFromLastOperation.json" $linuxFile = "/var/log/amazon/ssm/patch-configuration/patch-inventory-from-last-operation.json" if ($isWindows) { if (-not (Test-Path -Path $windowsFile)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] No patching completed yet, not expected. Unable to find PatchInventoryFromLastOperation.json file, has something changed?" -ThrowException } $myInventory = Get-PatchInventoryOnWindows -FilePath $windowsFile } else { if (-not (Test-Path -Path $linuxFile)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] No patching completed yet, not expected. Unable to find PatchInventoryFromLastOperation.json file, has something changed?" -ThrowException } $myInventory = Get-PatchInventoryOnLinux -FilePath $linuxFile } if ([int]$myInventory.InstalledPendingRebootCount -gt 0) { Write-LogInfo "[$( $MyInvocation.MyCommand )] InstalledPendingRebootCount greater than 0, current: $($myInventory.InstalledPendingRebootCount), returning reboot exit code" return $rebootExitCode } else { Write-LogInfo "[$( $MyInvocation.MyCommand )] InstalledPendingRebootCount equals 0, no reboot required" return 0 } } } function Install-DTXCrowdStrikeAgent { [CmdletBinding(SupportsShouldProcess = $true)] param( [string] $CrowdStrikeCustomerIdSSMParameterName, [string] $BucketName, [string] $BucketObjectKey, [ValidateNotNullOrEmpty()] $SupportedWindowsVersions = @("2012", "2016", "2019", "2022"), [switch] $Force ) $defaults = Get-Defaults if (!$CrowdStrikeCustomerIdSSMParameterName) { $CrowdStrikeCustomerIdSSMParameterName = $defaults.AWS.SSM.ParameterStore.Parameters.CrowdStrikeCustomerId } if (!$BucketName) { $BucketName = $defaults.AWS.S3.Buckets.SoftwareRepository.Name } if (!$BucketObjectKey) { $BucketObjectKey = $defaults.AWS.S3.Buckets.SoftwareRepository.Objects.CrowdStrikeAgentInstaller.Windows } function _Test-IsWindowsVersionSupported { $osVersion = (Get-SystemInfo).Windows.VersionAsYear if ($SupportedWindowsVersions -notcontains $osVersion) { return $false } return $true } function _Test-IsCrowdStrikeAgentHealthy { $isInstalled = Test-IsAppInstalled -SearchTerm "*CrowdStrike*Win*Sensor*" try { $isServiceRunning = Test-IsServiceRunning -SearchTerm "CS*Falcon*" } catch { $isServiceRunning = $false } if ($isInstalled -and $isServiceRunning) { return $true } return $false } if ($IsWindows) { try { if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Prerequisite Checks for CrowdStrike Agent")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking if the CrowdStrike Agent is installed and running..." if ((_Test-IsCrowdStrikeAgentHealthy) -and (-not $Force)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The CrowdStrike Agent is already installed and running." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No action required." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The CrowdStrike Agent is not installed or running. Starting installation..." if (-not (_Test-IsWindowsVersionSupported)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The CrowdStrike Agent is not supported on this version of Windows. Supported Windows versions are $($SupportedWindowsVersions -join ', ')." -ThrowException } } if ($PSCmdlet.ShouldProcess( "$env:COMPUTERNAME", "Download CrowdStrike Agent" )) { $installationFile = Get-FileFromS3 -BucketName $BucketName -BucketKey $BucketObjectKey -PreserveFileName } if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Install CrowdStrike Agent")) { $customerId = (Get-SSMParameterValue -Name $CrowdStrikeCustomerIdSSMParameterName -Region $defaults.AWS.SSM.ParameterStore.DefaultRegion -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value Install-CrowdStrikeAgent -CustomerId $customerId -InstallationFile $installationFile Remove-Item -Path $installationFile -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The CrowdStrike Agent has been installed successfully." } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Error message: $($_.Exception.Message)" -ThrowException -Exception $_ } } else { Write-LogInfo "[$( $MyInvocation.MyCommand )] Installing the CrowdStrike Agent using this module is only supported on Windows for now." } } function Install-DTXOpenEye { param( [Parameter(Mandatory)] [ValidateSet( "2024" )] [string]$Version, [switch]$Backup, [switch]$Force ) $ErrorActionPreference = "Stop" if ($IsWindows) { try { Show-Banner -AsLog -Message "Start -> Dotmatics - Open Eye Installer Automation" Set-StateItem -Key "openeye-install-check-date" -Value (Get-Date -Format "yyyy-MM-dd") Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Checking if this is a Browser System..." $isBrowserPlatformSystem = (Get-DTXSystem).ComputedQueries.IsBrowserPlatformSystem.Status if ((($isBrowserPlatformSystem -is [string]) -and ($isBrowserPlatformSystem -eq "UNKNOWN")) -or !$isBrowserPlatformSystem) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] This instance is not a browser instance. Skipping Open Eye installation." return } Write-LogInfo -Message "[$($MyInvocation.MyCommand)] This is a Browser System." if (!$Force) { if (Test-OpenEyeIsUpToDate -Version $Version) { return } } if ($Backup) { Backup-OpenEye | Out-Null } Install-OpenEye -Version $Version | Out-Null Set-StateItem -Key "openeye-install-version" -Value $Version | Out-Null Set-StateItem -Key "openeye-install-date" -Value (Get-Date -Format "yyyy-MM-dd") | Out-Null Remove-OpenEyeBackup -BackupsToKeep 1 | Out-Null } finally { Show-Banner -AsLog -Message "Stop -> Dotmatics - Open Eye Installer Automation" } } else { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] This cmdlet is only supported on Windows operating systems. " } } function Install-DTXSumoLogicAgent{ [CmdletBinding(SupportsShouldProcess = $true)] param ( [ValidateNotNullOrEmpty()] $SupportedWindowsVersions = @("2012", "2016", "2019", "2022"), [switch] $Force ) function _Test-IsWindowsVersionSupported { $osVersion = (Get-SystemInfo).Windows.VersionAsYear if ($SupportedWindowsVersions -notcontains $osVersion) { return $false } return $true } try { Show-Banner -AsLog -Message "Start -> Dotmatics - Summologic Agent Install" if ($IsLinux -or $IsMacOS) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] This cmdlet is only supported on Windows operating systems. " return } if (-not (_Test-IsWindowsVersionSupported)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The SumoLogic Agent is not supported on this version of Windows. Supported Windows versions are $($SupportedWindowsVersions -join ', ')." -ThrowException } $sumoLogicServiceName="sumo-collector" if (Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $sumoLogicServiceName}) { $sumoLogicService = Get-Service -Name $sumoLogicServiceName -ErrorAction Stop if ($sumoLogicService.Status -ne 'Running') { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service '$sumoLogicServiceName' is not running. Attempting to start it..." if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Starting Sumologic Agent")) { Start-Service -Name $sumoLogicServiceName } $sumoLogicService = Get-Service -Name $sumoLogicServiceName -ErrorAction Stop $status = $sumoLogicService.Status Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service '$sumoLogicServiceName' status: $status" } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service '$sumoLogicServiceName' exist and running." } }else{ Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service '$sumoLogicServiceName' does not exists. Attempting to install..." $defaults = Get-Defaults $SumoLogicAccessIdSSMParameterName = $defaults.AWS.SSM.ParameterStore.Parameters.SumoLogicAccessId $SumoLogicAccessKeySSMParameterName = $defaults.AWS.SSM.ParameterStore.Parameters.SumoLogicAccessKey $BucketName = $defaults.AWS.S3.Buckets.SoftwareRepository.Name $BucketObjectKey = $defaults.AWS.S3.Buckets.SoftwareRepository.Objects.SumoLogicAgentInstaller.Windows $sumoLogicAccessId=(Get-SSMParameterValue -Name $SumoLogicAccessIdSSMParameterName -Region $defaults.AWS.SSM.ParameterStore.DefaultRegion -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value $sumoLogicAccessKey=(Get-SSMParameterValue -Name $SumoLogicAccessKeySSMParameterName -Region $defaults.AWS.SSM.ParameterStore.DefaultRegion -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Install Sumologic Agent")) { Install-SumoLogic -AccessId $sumoLogicAccessId -AccessKey $sumoLogicAccessKey -BucketName $BucketName -BucketObjectKey $BucketObjectKey } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Service '$sumoLogicServiceName' installed sucessfully and running." } } finally { Show-Banner -AsLog -Message "Stop -> Dotmatics - SumoLogic Install" } } function Install-DTXVortexWebOE { param( [Parameter(Mandatory)] [ValidateSet( "6.1.2.1372-s" )] [string]$Version ) $ErrorActionPreference = "Stop" try { Show-Banner -AsLog -Message "Start -> Dotmatics - VortexWeb Install" if ($IsLinux -or $IsMacOS) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] This cmdlet is only supported on Windows operating systems. " return } if (!(Test-IsBrowserSystem)) { Write-LogWarning "[$( $MyInvocation.MyCommand )] Is not a Browser system, nothing to do here" return } $VortexVersion = -1 $SysMonitorData = Get-BrowserSysMonitor if ($SysMonitorData.ContainsKey("vortexweb_version")) { $VortexVersion = [int]$SysMonitorData.vortexweb_version } Write-LogInfo "[$($MyInvocation.MyCommand)] Detected Vortex Version: $($VortexVersion)" if ($VortexVersion -ge 1124 -and $VortexVersion -le 1140) { Install-VortexWeb -Version $Version } else { Write-LogInfo "[$($MyInvocation.MyCommand)] Vortex does not need to be replaced" } } finally { Show-Banner -AsLog -Message "Stop -> Dotmatics - VortexWeb Install" } } function Invoke-DTXTomcatChanges { [CmdletBinding(SupportsShouldProcess = $true)] param() function Get-TomcatChangeList { param ( [hashtable]$DTXSystemInfo, [hashtable]$DTXDefaults ) $changeCandidates = @( { Write-LogInfo -Message "Initializing DTXTomcatChangeCertificate" [DTXTomcatChangeCertificate]::new($DTXSystemInfo, $DTXDefaults) }, { Write-LogInfo -Message "Initializing DTXTomcatChangeConnectorSettings" [DTXTomcatChangeConnectorSettings]::new($DTXSystemInfo, $DTXDefaults, 2) } ) $changeList = @() foreach ($candidate in $changeCandidates) { try { $changeList += & $candidate } catch { Write-LogWarning -Message "Failed to initialize. Error: $_" } } return $changeList } function Test-IsTomcatWebServer { param ( [hashtable]$DTXSystemInfo ) return ($DTXSystemInfo.ComputedQueries.IsWebServer.Status -eq $true -and $DTXSystemInfo.ComputedQueries.IsWebServer.AppName -eq "Tomcat") } function Test-IsTomcatChangeRequired { param ( [array]$ChangeList ) $changeRequired = $false foreach ($item in $ChangeList) { if ($item.ChangeRequired()) { $changeRequired = $true } } return $changeRequired } function Start-TomcatService { [CmdletBinding(SupportsShouldProcess = $true)] param ( [string]$ServiceName ) if ($PSCmdlet.ShouldProcess($ServiceName, "Start")) { Write-LogInfo -Message "[$($MyInvocation.InvocationName)] Starting Tomcat Service: $($ServiceName)" Start-Service -Name $ServiceName } } function Stop-TomcatService { [CmdletBinding(SupportsShouldProcess = $true)] param ( [string]$ServiceName ) if ($PSCmdlet.ShouldProcess($ServiceName, "Stop")) { Write-LogInfo -Message "[$($MyInvocation.InvocationName)] Stopping Tomcat Service: $($ServiceName)" Stop-ServiceWithRetry -Name $ServiceName -Retries 40 -WaitSeconds 15 } } function Invoke-TomcatChanges { [CmdletBinding(SupportsShouldProcess = $true)] param ( [array]$ChangeList ) foreach ($item in $ChangeList) { if ($PSCmdlet.ShouldProcess($item.GetType().Name, "Performed Tomcat change")) { $item.PerformChange() } } } function Invoke-TomcatTests { param ( [array]$ChangeList ) foreach ($item in $ChangeList) { Write-LogInfo -Message "[$($MyInvocation.InvocationName)] Performing testing for $($item.GetType().Name)" $item.TestAfterChange() } } function Wait-ForTomcatToServeRequests { param ( [int]$Port ) Write-LogInfo -Message "[$($MyInvocation.InvocationName)] Giving Tomcat a chance to start serving requests before running tests..." Wait-ForWebServerToServeRequests -Hostname "localhost" -Port $Port -Scheme "https" -WaitSeconds 900 -SkipCertificateCheck } Show-Banner -AsLog -Message "Start -> Dotmatics - Tomcat Changes" $dtxDefaults = Get-Defaults $dtxSystemInfo = Get-DTXSystem $tomcatServiceName = $dtxSystemInfo.AppInfo.Tomcat.Service.Name $tomcatHttpPort = $dtxSystemInfo.AppInfo.Tomcat.ServerXML.Port if (-not (Test-IsTomcatWebServer -DTXSystemInfo $dtxSystemInfo)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Not a Tomcat Webserver. No changes required." return } $tomcatChangeList = Get-TomcatChangeList -DTXSystemInfo $dtxSystemInfo -DTXDefaults $dtxDefaults Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Checking if change is required..." if (-not (Test-IsTomcatChangeRequired -ChangeList $tomcatChangeList)) { Write-LogInfo -Message "[$($MyInvocation.MyCommand)] No changes required." return } Write-LogInfo -Message "[$($MyInvocation.MyCommand)] Change is required." try { Stop-TomcatService -ServiceName $tomcatServiceName Invoke-TomcatChanges -ChangeList $tomcatChangeList } finally { Start-TomcatService -ServiceName $tomcatServiceName } Wait-ForTomcatToServeRequests -Port $tomcatHttpPort Invoke-TomcatTests -ChangeList $tomcatChangeList Show-Banner -AsLog -Message "Stop -> Dotmatics - Tomcat Changes" } function Invoke-EC2Authentication { param ( [Parameter(Mandatory)] [string] $IAMRoleArn, [bool]$DryRun = $false ) if ($DryRun) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Running Invoke-EC2Authentication in dry run mode. No action will be taken." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Assuming IAM role: $IAMRoleArn" try { Set-DefaultAWSRegion -Region (Get-EC2InstanceMetadata -Category Region -ErrorAction Stop).SystemName -ErrorAction Stop -Scope Global } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] This script must be run on an EC2 instance that has access to the EC2 instance metadata service." -ThrowException -Exception $_ } try { $c = (Use-STSRole -RoleSessionName (New-Guid).Guid -RoleArn $IAMRoleArn -DurationInSeconds 3600 -ErrorAction Stop).Credentials Set-AWSCredential -AccessKey $c.AccessKeyId -SecretKey $c.SecretAccessKey -SessionToken $c.SessionToken -ErrorAction Stop -Scope Global } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Failed to assume IAM role: $IAMRoleArn." -ThrowException -Exception $_ } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Successfully assumed IAM role: $IAMRoleArn" } function Invoke-HookPreReboot { param( [Parameter(Mandatory)] [bool] $DryRun, [Parameter(Mandatory)] [bool] $RebootRequested, [Parameter(Mandatory)] [bool] $InMaintWindow, [Parameter(Mandatory)] [int] $RebootExitCode, [Parameter(Mandatory)] [int] $ContinueOnErrorExitCode, [Parameter(Mandatory)] [string] $EC2Role ) if ($IsWindows) { function Stop-Tomcat { param( [psobject] $Meta, [bool] $DryRun ) if ($DryRun) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Dry Run: Stopping the Tomcat service" return } $app = $Meta.AppInfo.Tomcat if ($app.Running -and ($app.Running -is [bool])) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopping the Tomcat service..." try { Stop-ServiceWithRetry -Name $app.Service.Name -Retries 18 -WaitSeconds 5 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The Tomcat service did not stop in time." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopped." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No running Tomcat service found." } } function Stop-OracleDatabaseListener { param( [psobject] $Meta, [bool] $DryRun ) if ($DryRun) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Dry Run: Stopping the Oracle Database Listener service" return } $app = $Meta.AppInfo.Oracle.DatabaseListener if ($app.Running -and ($app.Running -is [bool])) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopping the Oracle Database Listener service..." try { Stop-ServiceWithRetry -Name $app.Service.Name -Retries 12 -WaitSeconds 5 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The Oracle Database Listener service did not stop in time." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopped." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No running Oracle Database Listener service found." } } function Stop-OracleDatabase { param( [psobject] $Meta, [bool] $DryRun ) if ($DryRun) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Dry Run: Stopping the Oracle Database service" return } $app = $Meta.AppInfo.Oracle.Database if ($app.Running -and ($app.Running -is [bool])) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopping the Oracle Database service..." try { Stop-ServiceWithRetry -Name $app.Service.Name -Retries 36 -WaitSeconds 5 } catch { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The Oracle Database service did not stop in time." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Stopped." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No running Oracle Database service found." } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Starting Invoke-HookPreReboot..." try { Write-LogSeparator Invoke-EC2Authentication -IAMRoleArn $EC2Role Write-LogSeparator Write-LogSeparator $meta = Get-DTXSystem Write-LogSeparator Write-LogSeparator Stop-Tomcat -Meta $meta -DryRun:$DryRun Write-LogSeparator Write-LogSeparator Stop-OracleDatabaseListener -Meta $meta -DryRun:$DryRun Write-LogSeparator Write-LogSeparator Stop-OracleDatabase -Meta $meta -DryRun:$DryRun Write-LogSeparator } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] An error occurred." $_ | Get-Error | Out-Host Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Exiting with code 1." exit 1 } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Completed Invoke-HookPreReboot. Exiting with code 0." } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The pre reboot hook is supported only on Windows for now. Exiting with code 0." } exit 0 } function Invoke-NonOutageAutomation { param( [Parameter(Mandatory)] [bool] $DryRun, [Parameter(Mandatory)] [bool] $RebootRequested, [Parameter(Mandatory)] [bool] $InMaintWindow, [Parameter(Mandatory)] [int] $RebootExitCode, [Parameter(Mandatory)] [int] $ContinueOnErrorExitCode, [Parameter(Mandatory)] [string] $EC2Role ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Starting Invoke-NonOutageAutomation..." try { Write-LogSeparator Invoke-EC2Authentication -IAMRoleArn $EC2Role Write-LogSeparator Write-LogSeparator New-DTXDiscoveryJsonFile -WhatIf:$DryRun Write-LogSeparator Write-LogSeparator Install-DTXOpenEye -Version "2024" -Backup Write-LogSeparator Write-LogSeparator Install-DTXCrowdStrikeAgent -WhatIf:$DryRun Write-LogSeparator Write-LogSeparator Install-DTXSumoLogicAgent -WhatIf:$DryRun Write-LogSeparator Write-LogSeparator Sync-StarDotmaticsNetCertificate -WhatIf:$DryRun Write-LogSeparator Write-LogSeparator Uninstall-DTXAutomoxAgent -WhatIf:$DryRun Write-LogSeparator } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] An error occurred." $_ | Get-Error | Out-Host Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Exiting with code $ContinueOnErrorExitCode." exit $ContinueOnErrorExitCode } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Completed Invoke-NonOutageAutomation. Exiting with code 0" exit 0 } function Invoke-OutageAutomation { param( [Parameter(Mandatory)] [bool] $DryRun, [Parameter(Mandatory)] [bool] $RebootRequested, [Parameter(Mandatory)] [bool] $InMaintWindow, [Parameter(Mandatory)] [int] $RebootExitCode, [Parameter(Mandatory)] [int] $ContinueOnErrorExitCode, [Parameter(Mandatory)] [string] $EC2Role, [switch] $ExitWithRebootExitCode, [switch] $ExitWithContinueOnErrorExitCode, [switch] $ExitWithErrorExitCode ) function Invoke-ExitCodeTesting() { if ($ExitWithRebootExitCode) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Exiting with RebootExitCode: $RebootExitCode" exit $RebootExitCode } if ($ExitWithContinueOnErrorExitCode) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Exiting with ContinueOnErrorExitCode: $ContinueOnErrorExitCode" exit $ContinueOnErrorExitCode } if ($ExitWithErrorExitCode) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Exiting with error: 1" exit 1 } } function Remove-InvalidScheduledTasks() { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Removing Invalid Scheduled Tasks" $invalidScheduledTasks = (Get-Defaults).PrivateData.ScheduledTasksToRemove $invalidScheduledTasks | Remove-ScheduledTasks -WhatIf:$DryRun } function Invoke-ContinueOnErrorAutomation() { try { Write-LogSeparator Invoke-EC2Authentication -IAMRoleArn $EC2Role Write-LogSeparator Write-LogSeparator Remove-InvalidScheduledTasks Write-LogSeparator Write-LogSeparator Install-DTXVortexWebOE -Version "6.1.2.1372-s" Write-LogSeparator Write-LogSeparator Invoke-DTXTomcatChanges Write-LogSeparator } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] An error occurred." $_ | Get-Error | Out-Host Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Exiting in ContinueOnerror with code $ContinueOnErrorExitCode." exit $ContinueOnErrorExitCode } } function Invoke-ExitOnErrorAutomation() { try { } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] An error occurred." $_ | Get-Error | Out-Host Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Exiting in Stop on Failure with code 1." exit 1 } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Starting Invoke-OutageAutomation..." Invoke-ExitCodeTesting Invoke-ContinueOnErrorAutomation Invoke-ExitOnErrorAutomation Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Completed Invoke-OutageAutomation. Exiting with code 0" exit 0 } function New-DTXCloudEnvironment { [CmdletBinding(ConfirmImpact = "High", SupportsShouldProcess)] Param( [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9-]+$')] [string] $CustomerName, [Parameter(Mandatory)] [ValidatePattern('^https:\/\/.+\.dotmatics\.net$')] [string] $URL, [Parameter(Mandatory)] [ValidatePattern('^\d{12}$')] [string] $AWSAccountNumber, [Parameter(Mandatory)] [ValidateSet( "ap-northeast-1", "ap-southeast-1", "ap-southeast-2", "eu-central-1", "eu-west-1", "eu-west-2", "us-east-1", "us-west-1", "sa-east-1", IgnoreCase = $true )] [string] $Region, [Parameter(Mandatory)] [string] $VPCId, [Parameter(Mandatory)] [string] $SubnetId, [Parameter(Mandatory)] [string] $AMIId, [Parameter(Mandatory)] [string] $InstanceType, [Parameter(Mandatory)] [ValidateSet( "customer_production", "customer_non_production", "internal", IgnoreCase = $true )] [string] $EnvironmentType, [Parameter(Mandatory)] [ValidatePattern('\b[a-z0-9]\w{4}0\w{12}|[a-z0-9]\w{4}0\w{9}\b')] [string] $SalesforceAccountId, [Parameter(Mandatory)] [ValidateSet( "persistent", "temporary", IgnoreCase = $true )] [string] $InstancePersistence, [datetime] $InstancePersistenceEndTime, [Parameter(Mandatory)] [ValidateSet( "cloud", "customer_support", "customer_system", "devops_luma", "devops_platform", "engineering_luma", "engineering_platform", "global_service", "presales", "qa_luma", "qa_platform", "sts", "techops", "upgrades", IgnoreCase = $true )] [string] $Department, [Parameter(Mandatory)] [ValidateSet( "platform", "luma", "external_product", IgnoreCase = $true )] $ProductName, [Parameter(Mandatory)] [ValidateSet( "TRUE", "FALSE", IgnoreCase = $false )] [string] $CustomerAccess, [Parameter(Mandatory)] [ValidateSet( "pre_prod", "prod", IgnoreCase = $false )] [string] $PatchGroup, [Parameter(Mandatory)] [ValidateSet( "pre_prod_sun", "pre_prod_sat", "prod_sun", "prod_sat", IgnoreCase = $false )] [string] $AutoExecution, [string] $PRTGUsername, [string] $PRTGPassword, [string] $ADDomainUser = "SSM", [string] $ADDomainPassword = "SSM", [string] $PRTGHostname, [string] $PRTGTemplateDeviceId = "5805", [string] $Route53HostedZoneId, [string] $TemporaryIamProfileName, [String] $PersistentIamProfileName, [Parameter(Mandatory = $true)] [string] $TicketNumber, [switch] $SkipTranscript, [switch] $SkipDomainJoin, [switch] $SkipMonitoring, [switch] $SkipPostDeployment, [switch] $SkipDuo, [switch] $ResetLocalCache, [string] $SoftwareRepositoryBucketName ) try { if ($null -eq $env:DTX_LOCAL_DOTMATICS_PATH) { $env:DTX_LOCAL_DOTMATICS_PATH = Join-Path $HOME ".local" "dotmatics" $env:DTX_LOCAL_DOTMATICS_PATH_WAS_SET = $true } If (-not $SkipTranscript) { $transcriptFile = "$( Get-Random ).txt" $transcriptFilePath = Join-Path -Path $([System.IO.Path]::GetTempPath() ) -ChildPath $transcriptFile Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Transcript started, output file is $transcriptFilePath" [void]$( Start-Transcript -Path $transcriptFilePath ) } if ($InstancePersistence -eq "temporary" -and (-not $InstancePersistenceEndTime)) { Write-LogCritical -ThrowException -Message "The 'InstancePersistenceEndTime' parameter is required when the 'InstancePersistence' parameter is set to 'temporary'." } $inputParamsToHash = @( $CustomerName $Region $EnvironmentType $URL $SalesforceAccountId $InstancePersistence $Department $ProductName ) $inputParamsHash = Get-StringHash -String ($inputParamsToHash -join "") Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Input parameters hash: $inputParamsHash" $kvStorePath = Join-Path -Path ([System.IO.Path]::GetTempPath()) "$inputParamsHash.json" $kvStore = [DTXKeyValueStore]::new($kvStorePath) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Key value store path: $kvStorePath" if ($ResetLocalCache) { $kvStore.ResetStore() } if ($kvStore.GetValue("IsDeploymentCompleted")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The deployment of $instanceName has already been completed." if (Read-BasicUserResponse -Prompt "Do you want to restart the deployment? (y/n)") { $kvStore.ResetStore() } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The deployment of $instanceName has been cancelled." return } } $defaults = Get-Defaults if (-not $SkipMonitoring) { $PRTGHostname = $defaults.PRTG.Hostname } if (-not $Route53HostedZoneId) { $Route53HostedZoneId = $defaults.AWS.Route53.HostedZones.DotmaticsNet.Id } if (-not $TemporaryIamProfileName) { $TemporaryIamProfileName = $defaults.AWS.IAM.InstanceProfiles.Temporary.Name } if (-not $PersistentIamProfileName) { $PersistentIamProfileName = $defaults.AWS.IAM.InstanceProfiles.Persistent.Name } if (-not $SoftwareRepositoryBucketName) { $SoftwareRepositoryBucketName = $defaults.AWS.S3.Buckets.SoftwareRepository.Name } $randomString = ($kvStore.GetValue("RandomString")) ?? (Get-RandomString -Length 4) $kvStore.SetValue("RandomString", $randomString) $envType = Format-EnvironmentType -Environment $EnvironmentType $instanceName = Get-InstanceName -CustomerName $CustomerName -Environment $envType.MediumName -RandomString $randomString -Region $Region $computerName = Get-ComputerName -CustomerName $CustomerName -Environment $envType.ShortName -RandomString $randomString $customTags = @{ 'customer-access' = $CustomerAccess.ToUpper() 'department' = $Department.ToLower() 'environment-type' = $envType.TagValue.ToLower() 'hostname' = $computerName 'persistence' = $InstancePersistence.ToLower() 'product' = $ProductName.ToLower() 'sfdc-account-id' = $SalesforceAccountId 'system-creation-date' = (Get-Date -AsUTC -Format "yyyy-MM-dd").ToString() 'ticket-number' = $TicketNumber 'PatchGroup' = $PatchGroup.ToLower() 'auto_execution' = $AutoExecution.ToLower() 'Url' = $URL } if ($InstancePersistenceEndTime) { $customTags.Add("persistence-end-time", $InstancePersistenceEndTime.ToString("yyyy-MM-dd")) } $domainController = Get-PreferredDomainController -Region $Region Confirm-AWSCredentials -Region $Region -AWSAccountNumber $AWSAccountNumber Confirm-InstanceName -InstanceName $instanceName -Region $Region Confirm-ComputerName -ComputerName $computerName -DomainControllerInstanceId $domainController.InstanceId -DomainControllerRegion $domainController.Region Confirm-AWSVPC -Region $Region -VPCId $VPCId Confirm-AWSSubnet -Region $Region -VPCId $VPCId -SubnetId $SubnetId Confirm-AWSAMI -Region $Region -Id $AMIId if (-not $SkipMonitoring) { Confirm-PRTGConnection -Username $PRTGUsername -Password $PRTGPassword -Hostname $PRTGHostname } Confirm-AWSElasticIPQuota -Region $Region Confirm-AWSEC2InstanceQuota -Region $Region -InstanceType $InstanceType $awsManagementSecurityGroupIds = Get-AWSManagementSecurityGroups -Region $Region -VPCId $VPCId $newAWSSecurityGroupParams = @{ Name = $instanceName Description = "Security rules for customer $CustomerName and instance $instanceName." VPCId = $VPCId Region = $Region Tags = $customTags.Clone() } $awsManagementSecurityGroupIds += New-AWSSecurityGroup @newAWSSecurityGroupParams $newAWSEC2InstanceParams = @{ InstanceName = $instanceName InstanceType = $InstanceType InstanceIamProfileName = $TemporaryIamProfileName SubnetId = $SubnetId SecurityGroupIds = $awsManagementSecurityGroupIds AMIId = $AMIId Region = $Region Tags = $customTags.Clone() } $awsEC2Instance = New-AWSEC2Instance @newAWSEC2InstanceParams $newAWSElasticIpParams = @{ InstanceName = $instanceName InstanceId = $awsEC2Instance.InstanceId Region = $Region AttachToEC2Instance = $true Tags = $customTags.Clone() } $AWSElasticIp = New-AWSElasticIp @newAWSElasticIpParams if ($kvStore.GetValue("IsSSMAssociationCompleted")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The SSM association has already been completed." } else { Wait-ForSSMAssociation -InstanceId $awsEC2Instance.InstanceId -Region $Region | Out-Null $kvStore.SetValue("IsSSMAssociationCompleted", $true) } if ($kvStore.GetValue("IsAutomationDependenciesInstalled")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The automation dependencies have already been installed." } else { Install-PowerShellCoreViaSSM -InstanceId $awsEC2Instance.InstanceId -Region $Region -PatchGroup $PatchGroup | Out-Null Install-AutomationDependencies -InstanceId $awsEC2Instance.InstanceId -Region $Region | Out-Null $kvStore.SetValue("IsAutomationDependenciesInstalled", $true) } if ($kvStore.GetValue("IsWindowsHostnameSet")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The Windows hostname has already been set." } else { $setWindowsHostnameParams = @{ Name = $computerName InstanceId = $awsEC2Instance.InstanceId Region = $Region } [void]$( Set-WindowsHostname @setWindowsHostnameParams ) $kvStore.SetValue("IsWindowsHostnameSet", $true) } if (-not $SkipDomainJoin) { if ($kvStore.GetValue("IsADDomainJoinCompleted")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The instance has already been joined to the active directory domain." } else { $invokeADDomainJoinParams = @{ InstanceId = $awsEC2Instance.InstanceId Region = $Region } [void]$( Invoke-ADDomainJoin @invokeADDomainJoinParams ) $kvStore.SetValue("IsADDomainJoinCompleted", $true) } } $newAWSRoute53ARecordParams = @{ Name = $instanceName IPAddress = $AWSElasticIp.PublicIp HostedZoneId = $Route53HostedZoneId Region = $Region } $awsRoute53ARecord = New-AWSRoute53ARecord @newAWSRoute53ARecordParams $newAWSRoute53CnameRecordParams = @{ Name = (($URL -split "//")[1] -split "\.")[0].ToLower() Target = $awsRoute53ARecord.Name.Trim(".") HostedZoneId = $Route53HostedZoneId Region = $Region } [void]$(New-AWSRoute53CnameRecord @newAWSRoute53CnameRecordParams) if ($kvStore.GetValue("IsTimeZoneSet")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The time zone has already been set." } else { $setTimeZoneParams = @{ InstanceId = $awsEC2Instance.InstanceId Region = $Region } [void]$(Set-TimeZone @setTimeZoneParams) $kvStore.SetValue("IsTimeZoneSet", $true) } $newADSecurityGroupParams = @{ Name = $instanceName Description = "Manage local admin access for customer: $CustomerName" Region = $domainController.Region DomainControllerInstanceId = $domainController.InstanceId Members = @($defaults.ActiveDirectory.Groups.CA.Name) } New-ADSecurityGroup @newADSecurityGroupParams if (-not $SkipPostDeployment) { if ($kvStore.GetValue("IsPostDeploymentTasksCompleted")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The post deployment tasks have already been run." } else { if (-not $SkipDomainJoin) { $domainName = ($defaults.ActiveDirectory.DomainName).ToLower() [void]$( Add-InstanceLocalGroupMember -LocalGroup "Administrators" -Member "$domainName\$instanceName" -InstanceId $awsEC2Instance.InstanceId -Region $Region ) [void]$( Add-InstanceLocalGroupMember -LocalGroup "ORA_DBA" -Member "$domainName\$instanceName" -InstanceId $awsEC2Instance.InstanceId -Region $Region ) } $postDeploymentTasksParams = @{ InstanceId = $awsEC2Instance.InstanceId Region = $Region AccessUrl = $URL ActiveDirectoryGroupName = $instanceName } [void]$( Invoke-PostDeploymentTasks @postDeploymentTasksParams ) $kvStore.SetValue("IsPostDeploymentTasksCompleted", $true) } } if (-not $SkipMonitoring) { if ($kvStore.GetValue("IsMonitoringCompleted")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The monitoring has already been completed." } else { $PRTGRegion = Get-PRTGRegion -AWSRegion $Region $PRTGDeviceName = Get-PRTGDeviceName -CustomerName $CustomerName -PRTGRegion $PRTGRegion -Environment $EnvironmentType $PRTGGroupId = Get-PRTGGroupId -PRTGRegion $PRTGRegion $newPrtgDeviceNameParams = @{ Name = $PRTGDeviceName GroupId = $PRTGGroupId TemplateDeviceId = $PRTGTemplateDeviceId } $NewPRTGDevice = New-PRTGDevice @newPrtgDeviceNameParams [void]$( New-EC2Tag -Region $Region -ResourceId $awsEC2Instance.InstanceId -Tag @{ Key = "prtg-device-id"; Value = $NewPRTGDevice.Id } ) $setPrtgDeviceParams = @{ Id = $NewPRTGDevice.Id IPAddress = $awsEC2Instance.PrivateIpAddress ServiceUrl = $URL Resume = $true } Set-PRTGDevice @setPrtgDeviceParams $setPrtgDeviceSensorParams = @{ DeviceId = $NewPRTGDevice.Id ServiceUrl = $URL UseDefaults = $true } Set-PRTGDeviceSensor @setPrtgDeviceSensorParams $kvStore.SetValue("IsMonitoringCompleted", $true) } } if (-not $SkipDuo) { if ($kvStore.GetValue("IsDuoAgentInstalled")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The Duo agent has already been installed." } else { $installDuoAgentParams = @{ InstanceId = $awsEC2Instance.InstanceId Region = $Region } [void]$( Install-DuoAgent @installDuoAgentParams ) $kvStore.SetValue("IsDuoAgentInstalled", $true) } } $newAWSEC2InstanceProfileParams = @{ Region = $Region InstanceId = $awsEC2Instance.InstanceId InstanceIamProfileName = $PersistentIamProfileName } Set-AWSEC2InstanceProfile @newAWSEC2InstanceProfileParams Write-LogInfo -Message "The deployment is complete. The instance can be accessed at $URL" $kvStore.SetValue("IsDeploymentCompleted", $true) Write-LogWarning -Message "##################################################################################################" Write-LogWarning -Message "Please speak to the DB team to get the Oracle DB configured before handing over to the customer" Write-LogWarning -Message "##################################################################################################" } finally { if (-not $SkipTranscript) { [void]$( Stop-Transcript ) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Transcript stopped." } if ($env:DTX_LOCAL_DOTMATICS_PATH_WAS_SET) { $env:DTX_LOCAL_DOTMATICS_PATH = $null $env:DTX_LOCAL_DOTMATICS_PATH_WAS_SET = $null } } } function New-DTXDiscoveryJsonFile { [CmdletBinding(SupportsShouldProcess = $true)] param () process { if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Generate DTX Discovery JSON File")) { try { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Generating the DTX discovery JSON file..." Get-DTXSystem | Write-DTXSystem Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Successfully generated the DTX discovery JSON file." } catch { Write-LogCritical "[$( $MyInvocation.MyCommand )] Failed to generate the DTX discovery JSON file" -ThrowException -Exception $_ } } } } function Remove-ScheduledTasks { [CmdletBinding(SupportsShouldProcess = $true)] param( [string[]] [Parameter(Mandatory, Position = 0, ValueFromPipeline)] $Names ) begin { $scheduled_task_obj = New-Object ScheduledTasks } process { if ($IsWindows) { try { foreach ($n in $Names) { $mytask = $scheduled_task_obj.GetScheduledTask($n, $false) if ($mytask -eq $null) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The scheduled task: $n doesn't exist, skipping removing" continue } if ($PSCmdlet.ShouldProcess("$n", "Removing Scheduled Task")) { $scheduled_task_obj.RemoveScheduledTask($n) } } } catch { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Failed" -ThrowException -Exception $_ } } else { Write-LogInfo "Removing scheduled tasks not supported on Linux." } } } function Set-DTXAWSEC2IAMInstanceProfiles { [cmdletbinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$RoleName, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateSet( "ap-northeast-1", "ap-southeast-1", "ap-southeast-2", "eu-central-1", "eu-west-1", "eu-west-2", "us-east-1", "us-west-1", "sa-east-1", "All", IgnoreCase = $true )] [Array]$Region, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidatePattern('^\d{12}$')] [String]$AWSAccountNumber, [switch] $SkipTranscript ) If (-not $SkipTranscript) { $transcriptFile = "$( Get-Random ).txt" $transcriptFilePath = Join-Path -Path $([System.IO.Path]::GetTempPath() ) -ChildPath $transcriptFile Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Transcript started, output file is $transcriptFilePath" [void]$( Start-Transcript -Path $transcriptFilePath ) } Confirm-AWSCredentials -Region "us-east-1" -AWSAccountNumber $AWSAccountNumber Confirm-AWSIAMRole -RoleName $RoleName -Region "us-east-1" -AWSAccountNumber $AWSAccountNumber if ($Region -contains "All") { $Region = [System.Collections.ArrayList]::new() $allActiveRegions = (Get-Defaults).AWS.ActiveRegions foreach ($r in $allActiveRegions) { $Region += $r.name } } try { foreach ($r in $Region) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating Region $r..." $ec2Instances = (Get-EC2Instance -Region $r -Filter @{Name = "instance-state-code"; Values = 0, 16, 32, 64, 80 }).Instances $setAlreadyCount = 0 $newlySetCount = 0 $otherRolecount = 0 foreach ($instance in $ec2Instances) { if ($instance.IamInstanceProfile -eq $RoleName) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 Instance $( $instance.InstanceId ) already has $RoleName set..." $setAlreadyCount += 1 } elseif ($instance.IamInstanceProfile -eq $null) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Updating EC2 Instance $( $instance.InstanceId ) with IAM profile $RoleName..." Register-EC2IamInstanceProfile -InstanceId $instance.InstanceId -IamInstanceProfile_Name $RoleName -Region $r $newlySetCount += 1 } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 Instance $( $instance.InstanceId ) has IAM profile $( ($instance.IamInstanceProfile.Arn -split "instance-profile/")[1] ) set..." $otherRolecount += 1 } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $r Standardized already set count: $setAlreadyCount" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $r Newly set count: $newlySetCount" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $r Different role set count: $otherRolecount" } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] EC2 IAM Profile update has completed!" } function Set-DTXTomcatKeyStoreViaS3 { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$BucketName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$BucketRegion, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KeyStoreObjectKey, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$KeyStoreFileHash, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [ValidateSet("SHA256", "SHA384", "SHA512")] [string]$KeyStoreFileHashAlgorithm = "SHA256", [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$CertificateFingerprint, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [ValidateSet("SHA256", "SHA384", "SHA512")] [string]$CertificateFingerprintHashAlgorithm = "SHA256", [switch]$AllowTomcatRestart ) Begin { if (-not $IsWindows) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] This cmdlet should be executed from customer instances running Windows Server with Apache Tomcat installed." -ThrowException } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] ################################################################################" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] START: DOTMATICS - Tomcat Key Store Updater" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] ################################################################################" function _Get-CurrentSSLCertificateFingerprint { return (Get-SSLCertificateFingerprint -Hostname "localhost" -Port 443 -HashAlgorithm $CertificateFingerprintHashAlgorithm) } function _Get-CurrentKeyStoreFileAndHash { $keyStoreFile = Get-TomcatKeyStoreFile -TomcatHomePath (Get-TomcatHomePath) $calculatedFileHash = (Get-FileHash -Path $keyStoreFile -Algorithm $KeyStoreFileHashAlgorithm).Hash return @{ File = $keyStoreFile Hash = $calculatedFileHash } } function _Get-CandidateKeyStoreAndFileHash { $tempPath = [System.IO.Path]::GetTempPath() $keyStoreFile = Join-Path -Path $tempPath -ChildPath "$(Get-Random).jks" Get-S3Object -BucketName $BucketName -Region $BucketRegion -Key $KeyStoreObjectKey | Read-S3Object -File $keyStoreFile -Region $BucketRegion | Out-Null $calculatedFileHash = (Get-FileHash -Path $keyStoreFile -Algorithm $KeyStoreFileHashAlgorithm).Hash if ($calculatedFileHash -ine $KeyStoreFileHash) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The calculated file hash ($calculatedFileHash) of the Tomcat key store file ($KeyStoreObjectKey) does not match the provided file hash ($KeyStoreFileHash)." -ThrowException } return @{ File = $keyStoreFile Hash = $calculatedFileHash } } function _Test-CertFingerprintIsMatch { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Testing if the current SSL certificate fingerprint matches the candidate SSL certificate fingerprint." $currentCertFingerprint = _Get-CurrentSSLCertificateFingerprint $candidateCertFingerprint = $CertificateFingerprint if ($currentCertFingerprint -ieq $candidateCertFingerprint) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Match!" return $true } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No match!" return $false } } function _Restart-TomcatService { if ($PSCmdlet.ShouldProcess("Tomcat Service", "Restart")) { $tomcatVersion = Get-TomcatVersion -TomcatHomePath (Get-TomcatHomePath) Restart-TomcatService -WaitAfterSeconds 30 -Version $TomcatVersion if (_Test-CertFingerprintIsMatch) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Certificate installed successfully. No further action needed." return } Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The new certificate was not installed after restarting the Tomcat service. The system needs to be investigated manually" -ThrowException } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] WhatIf: Will be restarting the Tomcat service." } } function _Set-TomcatKeyStore { param ( [string]$File, [string]$FileHash ) $currentKeyStore = _Get-CurrentKeyStoreFileAndHash if ($currentKeyStore.Hash -ieq $FileHash) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The current Tomcat key store file hash matches the candidate Tomcat key store file hash." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] However, the Tomcat service was never restarted after the key store file was replaced." return } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The current Tomcat key store file hash does not match the candidate Tomcat key store file hash." Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The Tomcat key store file will be replaced." if ($PSCmdlet.ShouldProcess("Tomcat Key Store File", "Replace")) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Creating a backup of the current keystore file..." $backupPath = $currentKeyStore.File + ".$(Get-Date -Format "yyyy-MM-dd-HH-mm").bak" Copy-Item -Path $currentKeyStore.File -Destination $backupPath -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The current keystore file has been backed up to $backupPath" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Replacing the current keystore file with the candidate keystore file..." Copy-Item -Path $candidateKeyStore.File -Destination $currentKeyStore.File -Force Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] The Tomcat key store file has been replaced." } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] WhatIf: Will be replacing $($currentKeyStore.File) with $($candidateKeyStore.File)" } } } Process { try { if (!(Test-IsBrowserSystem)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No action is required, not a Browser system." return } if (_Test-CertFingerprintIsMatch) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] No action is required, SSL certificate is correct." return } $candidateKeyStore = _Get-CandidateKeyStoreAndFileHash _Set-TomcatKeyStore -File $candidateKeyStore.File -FileHash $candidateKeyStore.Hash if ($AllowTomcatRestart) { _Restart-TomcatService } else { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The Tomcat service was not restarted because the -AllowTomcatRestart switch was not specified." Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] You need to manually restart the Tomcat service for the new certificate to be installed." return } } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] $( $_.Exception.Message )" -ThrowException -Exception $_ } } End { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] ################################################################################" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] END: DOTMATICS - Tomcat Key Store Updater" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] ################################################################################" } } function Uninstall-DTXAutomoxAgent { [CmdletBinding(SupportsShouldProcess = $true)] param() function Uninstall-AutomoxAgent { param( [Parameter(Mandatory = $true)] [System.Object] $App ) if ($App.UninstallString -like "MsiExec.exe*/X*") { $arg = "/X $($app.PSChildName) /qn" Write-LogInfo "[$( $MyInvocation.MyCommand )] Uninstalling Automox Agent with arguments: $arg" return (Start-Process -FilePath "MsiExec.exe" -ArgumentList $arg -Wait -NoNewWindow -PassThru) } else { Write-LogInfo "[$( $MyInvocation.MyCommand )] Uninstalling Automox Agent with arguments: /SILENT and $($App.UninstallString)" return (Start-Process -FilePath $App.UninstallString -ArgumentList "/SILENT" -Wait -NoNewWindow -PassThru) } } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Running Uninstall-DTXAutomoxAgent function" if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Uninstall Automox Agent")) { if (-not $IsWindows) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Uninstalling Automox not supported on OS's that are not Windows" return } $apps = Get-InstalledApps | Where-Object { $_.DisplayName -like "Automox*" } if (!$apps) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Automox Agent not found" return } Write-LogInfo "[$( $MyInvocation.MyCommand )] Found $($apps.Count) Automox Agent(s) installed on the machine" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Uninstalling the Automox Agent" $retries = 0 $automoxRemoved = $false do { if ($retries -gt 0) { $apps = Get-InstalledApps | Where-Object { $_.DisplayName -like "Automox*" } } $retries++ $failedToRemove = 0 foreach ($app in $apps) { $process = Uninstall-AutomoxAgent -App $app if ($process.ExitCode -ne 0) { $process | Stop-Process -Force $failedToRemove++ } } if ($failedToRemove -eq 0) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Automox Agent uninstalled successfully" $automoxRemoved = $true break } } while ($retries -lt 3) if (!$automoxRemoved) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Failed to uninstall Automox Agent." } } } function Uninstall-DTXSumoLogicAgent{ [CmdletBinding(SupportsShouldProcess = $true)] param ( [ValidateNotNullOrEmpty()] $SupportedWindowsVersions = @("2012", "2016", "2019", "2022"), [switch] $Force ) function _Test-IsWindowsVersionSupported { $osVersion = (Get-SystemInfo).Windows.VersionAsYear if ($SupportedWindowsVersions -notcontains $osVersion) { return $false } return $true } try { Show-Banner -AsLog -Message "Start -> Dotmatics - Summologic Agent Unininstall" if ($IsLinux -or $IsMacOS) { Write-LogWarning -Message "[$($MyInvocation.MyCommand)] This cmdlet is only supported on Windows operating systems. " return } if (-not (_Test-IsWindowsVersionSupported)) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The SumoLogic Agent is not supported on this version of Windows. Supported Windows versions are $($SupportedWindowsVersions -join ', ')." -ThrowException } $sumoLogicServiceName="sumo-collector" $sumologicUninstallFile = "C:\Program Files\Sumo Logic Collector\uninstall.exe" if (-not (Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $sumoLogicServiceName})) { Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] The SumoLogic Agent service '$sumoLogicServiceName' does not exist on this machine, continuing the uninstall process to remove any previous installation files ..." } if ($PSCmdlet.ShouldProcess("$env:COMPUTERNAME", "Unininstall Sumologic Agent")) { Uninstall-SumoLogic -ServiceName $sumoLogicServiceName -UninstallFile $sumologicUninstallFile } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Sumologic agent uninstalled sucessfully." } finally { Show-Banner -AsLog -Message "Stop -> Dotmatics - SumoLogic Unininstall" } } function Write-DTXSystem { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [PSObject] $InputObject ) Begin { if ($InputObject.Length -gt 1) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Write-DTXSystem only accepts one pipeline input" } $rootPath = (Get-Location).Drive.Root $rootJsonPath = Join-Path $rootPath "dtx_discovery.json" if (Test-Path $rootJsonPath) { Remove-Item -Path $rootJsonPath -Force } } Process { ForEach ($input in $InputObject) { $input | ConvertTo-Json -Depth 100 -EnumsAsStrings | Out-File -Path (Get-DTXLocalDiscoveryFilePath) -Force } } } function Write-JsonCache { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [PSObject] $InputObject, [Parameter(Mandatory = $true, ValueFromPipeline = $false)] [ValidateNotNullOrEmpty()] [PSObject] $JsonFileName ) Begin { $jsonFilePath = Join-Path (Get-LocalDotmaticsPath) $JsonFileName } Process { ForEach ($input in $InputObject) { $input | ConvertTo-Json -Depth 100 -EnumsAsStrings | Out-File -Path $jsonFilePath -Force } } } |