DTX.Cloud.Management.psm1
class KeyValueStore { hidden [string]$Path KeyValueStore( [string]$Path ) { $this.Path = $Path if (-not (Test-Path -Path $this.Path)) { New-Item -Path $this.Path -ItemType File -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 New-Item -Path $this.Path -ItemType File -Force } [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 "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 } } 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 "[$( $MyInvocation.MyCommand )] Expected true but was false: $message" } } 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 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-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 Join-Path $TomcatWebAppPath "browser" } function Get-BrowserPlatformPropertiesFilePath { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSObject] $TomcatWebAppPath ) return Join-Path (Get-BrowserPlatformPath -TomcatWebAppPath $TomcatWebAppPath) "WEB-INF" "browser.properties" } 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-Defaults { function loadJSON($json_file){ return Get-Content -Path $json_file | ConvertFrom-Json -Depth 8 } $local_path = "$PSScriptRoot/../ExternalFiles/defaults.json" if (Test-Path $local_path){ return loadJSON($local_path) } else{ $directory_path = Join-Path $HOME ".DTX.Cloud.Management" function GetFileFromS3($default_state_file){ $mymodule = $MyInvocation.MyCommand.ScriptBlock.Module $myrealversion = $mymodule.Version.ToString() $branch_name = $mymodule.PrivateData.BranchName $folder = "production" if ($branch_name -ne "master"){ $folder="development" $myrealversion +="-"+$mymodule.PrivateData.PSData.Prerelease } $s3_filename = "bin/$folder/dot_po_cloudops_modules/$branch_name/$myrealversion/defaults.json" if(Test-Path $default_state_file){ $mystate_data = cat $default_state_file | ConvertFrom-Json -Depth 8 if ($mystate_data.Key -eq $s3_filename){ Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Returning existing defaults file" try{ return loadJSON($mystate_data.File) } catch{ remove-item -Force $default_state_file Write-Warning -Message "[$( $MyInvocation.MyCommand )] Failed to load json from existing file, falling through to get a new file" } } } $def_region = Get-DefaultAWSRegion if($null -eq $def_region){ $def_region = "eu-west-1" } else{ $def_region = $def_region.Region } $s3BucketName = (Get-SSMParameterValue -Name "/cloud/public/regional/s3/v1/buckets/cf_extensions/name" -Region $def_region -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value Remove-Item -Path "$directory_path/*" -Recurse -Force -EA SilentlyContinue $defaults_file = Get-FileFromS3 -BucketName $s3BucketName -BucketKey $s3_filename -DirectoryPath $directory_path $mystate=@{ Key = $s3_filename File = $defaults_file } ConvertTo-Json $mystate | Out-File -FilePath (New-Item $default_state_file -Force) Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Returning new defaults file" return loadJSON($mystate.File) } $defaults_state_file = join-path -Path $directory_path -ChildPath "GetDefaultsState.json" return GetFileFromS3($defaults_state_file) } } 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 "[$( $MyInvocation.MyCommand )] Tag key: $Key not found" } function Get-FileFromS3 { param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $BucketName, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $BucketKey, [Parameter(Mandatory = $false)] [string] $DirectoryPath, [switch] $PreserveFileName ) $ProgressPreference = 'SilentlyContinue' if (!$DirectoryPath) { $DirectoryPath = [System.IO.Path]::GetTempPath() if (!$DirectoryPath) { throw "[$( $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 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Downloading file '$BucketKey' from bucket '$BucketName' in region '$bucketRegion' to file path: $path" try { $params = @{ BucketName = $BucketName Key = $BucketKey File = $path } if ($bucketRegion) { $params.Region = $bucketRegion } Read-S3Object @params -ErrorAction Stop | Out-Null } catch { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] Failed to download file '$BucketKey' from bucket '$BucketName' in region '$bucketRegion'." -ThrowException -Exception $_ } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] File downloaded to $path" return $path } 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-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-LocalDiscoveryFilePath { return Join-Path (Get-LocalDotmaticsPath) "dtx_discovery.json" } function Get-LocalDotmaticsPath { if ($IsWindows) { $dirPath = Join-Path $env:PROGRAMDATA "dotmatics" if (!(Test-Path $dirPath)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $dirPath does not exist. Creating it..." New-Item -Path $dirPath -ItemType Directory -Force | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Created." } return $dirPath } if ($IsLinux -or $IsMacOS) { $dirPath = Join-Path "/" "var" "local" "dotmatics" if (!(Test-Path $dirPath)) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] $dirPath does not exist. Creating it..." New-Item -Path $dirPath -ItemType Directory -Force | Out-Null Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Created." } return $dirPath } Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Only Windows, Linux and MacOS operating systems are supported." } function Get-LocalStateFilePath { return Join-Path (Get-LocalDotmaticsPath) "dtx_state.json" } 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-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.PSObject.Properties | foreach -begin { $ht = @{ } } -process { $ht[$_.Name] = $_.Value } -end { $ht } 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-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 = 'SHA256' ) try { 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 } ) $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() } } return $fingerprint } 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 "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 TomcatServerXmlPath = $null TomcatJavaOptions = $null TomcatJavaVersion = $null TomcatJavaVendor = $null TomcatJavaHomePath = $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.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 { $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-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 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 "This cmdlet is only supported on Windows." } if (-not (Test-Path -Path $InstallationFile)) { throw "[$( $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 "[$( $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-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 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.PSObject.Properties[$Region].Value 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 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 42) { Write-LogCritical -Message "[$( $MyInvocation.MyCommand )] The AWS EC2 instance '$InstanceName' failed to reach 'running' state or to pass the reachability tests within a 5 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 $_ } } } function New-KeyValueStore { param( [string] $Path ) if (!$Path) { $Path = Get-LocalStateFilePath } return [KeyValueStore]::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 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-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.PSObject.Properties | foreach -begin { $ht = @{ } } -process { $ht[$_.Name] = $_.Value } -end { $ht } 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 ) return (New-KeyValueStore -Path $StateFilePath).SetValue($Key, $Value) } 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 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 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 } $webappDir = Get-TomcatWebAppPath $browserPropertiesFile = Get-BrowserPlatformPropertiesFilePath -TomcatWebAppPath $webappDir if (Test-Path -Path $browserPropertiesFile) { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Is a Browser System." return $true } else { Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Is NOT a Browser System." return $false } } 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 ) try { $xmlDocument = New-Object System.Xml.XmlDocument $xmlDocument.LoadXml($XmlString) return $true } catch { return $false } } 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 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 Write-Log { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateSet("Info", "Warning", "Error", "Critical", IgnoreCase = $false)] [string]$LogLevel, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [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 } } function Write-LogCritical { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message, [Parameter(Mandatory = $false)] [switch]$ThrowException, [System.Management.Automation.ErrorRecord] $Exception ) Write-Log -LogLevel "Critical" -Message $Message if ($ThrowException) { if ($Exception) { throw ( New-Object System.Exception( $Message, $Exception.Exception ) ) } else { throw $Message } } } function Write-LogError { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) Write-Log -LogLevel "Error" -Message $Message } function Write-LogInfo { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) Write-Log -LogLevel "Info" -Message $Message } function Write-LogSeparator { Write-Log -LogLevel "Info" -Message "---------------------------------------------------------------------" } function Write-LogWarning { param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string]$Message ) Write-Log -LogLevel "Warning" -Message $Message } 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 is NOT in allowed values: key: $Key Value: $myValue" throw "[$( $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-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" } Path = "UNKNOWN" WebAppsPath = "UNKNOWN" Version = "UNKNOWN" Java = @{ Version = "UNKNOWN" Vendor = "UNKNOWN" Path = "UNKNOWN" } ServerXML = @{ Path = "UNKNOWN" Port = "UNKNOWN" HttpProtocol = "UNKNOWN" SSLProtocols = "UNKNOWN" SSLCiphers = "UNKNOWN" KeyStoreFile = "UNKNOWN" WebAppBase = "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 = @{} } } 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 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.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 $serverXml = $meta.TomcatServerXmlPath ?? $returnObj.AppInfo.Tomcat.ServerXML.Path if (Test-Path $serverXml) { $returnObj.AppInfo.Tomcat.ServerXML.Path = $serverXml try { [xml]$xmlfile = Get-Content $serverXml -ErrorAction Stop $returnObj.AppInfo.Tomcat.ServerXML.Port = $xmlfile["Server"]["Service"]["Connector"].port $returnObj.AppInfo.Tomcat.ServerXML.HttpProtocol = $xmlfile["Server"]["Service"]["Connector"].protocol $returnObj.AppInfo.Tomcat.ServerXML.SSLProtocols = $xmlfile["Server"]["Service"]["Connector"].sslEnabledProtocols $returnObj.AppInfo.Tomcat.ServerXML.SSLCiphers = $xmlfile["Server"]["Service"]["Connector"].ciphers $returnObj.AppInfo.Tomcat.ServerXML.KeyStoreFile = $xmlfile["Server"]["Service"]["Connector"].keystoreFile $returnObj.AppInfo.Tomcat.ServerXML.WebAppBase = $xmlfile["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 } } } 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-LocalDiscoveryFilePath 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-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 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-DTXCrowdStrikeAgent -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 } 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_mon", "pre_prod_tues", "pre_prod_wed", "pre_prod_thu", "pre_prod_fri", "pre_prod_sat", "prod_sun", "prod_mon", "prod_tues", "prod_wed", "prod_thu", "prod_fri", "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 ) 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 = [KeyValueStore]::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 "##################################################################################################" } 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 Set-DTXTomcatServerSettings { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateSet(1)] [int]$SettingsVersion ) $sysInfo = Get-DTXSystem Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Checking if Tomcat is running on this system..." if ((-not $sysInfo.ComputedQueries.IsWebServer.Status) -or (-not $sysInfo.ComputedQueries.IsWebServer.AppName -eq "Tomcat")) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Tomcat server not detected on this host." } Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Tomcat is running on this system" $serverSettings = (Get-Defaults).Tomcat.ServerSettings | Where-Object { $_.Version -eq $SettingsVersion } if (-not $serverSettings) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Your selected ServerSettings version $SettingsVersion is not valid. Please try again or report this issue to the script maintainers." } $tomcatVersion = $sysInfo.AppInfo.Tomcat.Version $javaVersion = $sysInfo.AppInfo.Tomcat.Java.Version Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Detected Apache Tomcat version: $tomcatVersion" Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Detected Java version: $javaVersion" $selectedSettings = $serverSettings | Where-Object { [System.Version]$tomcatVersion -ge [System.Version] $_.TomcatMinVersion } | Where-Object { [System.Version]$javaVersion -ge [System.Version] $_.JavaMinVersion } | Sort-Object { [System.Version] $_.TomcatMinVersion }, { [System.Version] $_.JavaMinVersion } -Descending if (-not $selectedSettings -or $selectedSettings.Count -eq 0) { $supportedTomcatVersions = $serverSettings | Select-Object -ExpandProperty TomcatMinVersion -Unique $supportedJavaVersions = $serverSettings | Select-Object -ExpandProperty JavaMinVersion -Unique Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Minimum supported Tomcat versions: $($supportedTomcatVersions -join ", ")" Write-LogWarning -Message "[$( $MyInvocation.MyCommand )] Minimum supported Java versions: $($supportedJavaVersions -join ", ")" Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] No server settings found for Tomcat ($tomcatVersion) or Java ($javaVersion)." } $selectedSettings = $selectedSettings[0] $serverXmlPath = $sysInfo.AppInfo.Tomcat.ServerXML.Path try { [xml]$serverXmlContents = Get-Content -Path $serverXmlPath -ErrorAction Stop } catch { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to read the Tomcat server XML file. Please check the file path and permissions." -Exception $_ } $connectorsUpdated = $false foreach ($connector in $serverXmlContents.Server.Service.Connector) { if ((($connector.protocol -like "*HTTP/1.1*") -or ($connector.protocol -like "*Http11NioProtocol*")) -and $connector.SSLEnabled -eq "true") { $connectorsUpdated = $true Update-XmlAttribute -Element $connector -Attribute "enableLookups" -Value $selectedSettings.EnableLookups Update-XmlAttribute -Element $connector -Attribute "sslEnabledProtocols" -Value ($selectedSettings.SSLSettings.Protocols -join ",") Update-XmlAttribute -Element $connector -Attribute "ciphers" -Value $selectedSettings.SSLSettings.ciphers $http2 = $connector.SelectSingleNode("UpgradeProtocol[@className='org.apache.coyote.http2.Http2Protocol']") if ($selectedSettings.EnableHTTP2) { if (-not $http2) { $newHttp2 = $serverXmlContents.CreateElement("UpgradeProtocol") $newHttp2.SetAttribute("className", "org.apache.coyote.http2.Http2Protocol") $connector.AppendChild($newHttp2) | Out-Null } } else { if ($http2) { $connector.RemoveChild($http2) | Out-Null } } } } if (-not $connectorsUpdated) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to find any HTTP connectors with SSL enabled. Please make sure the Tomcat server XML file is configured correctly." } $serviceXmlNode = $serverXmlContents.Server.Service $http2OnServiceXmlNode = $serviceXmlNode.SelectSingleNode("UpgradeProtocol[@className='org.apache.coyote.http2.Http2Protocol']") if ($http2OnServiceXmlNode -and ($http2OnServiceXmlNode.ParentNode -eq $serviceXmlNode)) { $serviceXmlNode.RemoveChild($http2OnServiceXmlNode) | Out-Null } $formattedXml = $serverXmlContents | ConvertTo-FormattedXmlString if (-not (Test-IsValidXml -XmlString $formattedXml)) { Write-LogCritical -ThrowException -Message "[$( $MyInvocation.MyCommand )] Unable to generate a valid XML file to save. Please report this error to the script maintainer." } if ($PSCmdlet.ShouldProcess($serverXmlPath, "Save changes to XML file")) { Write-StringToFileWithRetry -StringContent $formattedXml -FilePath $serverXmlPath -RetryCount 3 -WaitSeconds 5 Write-LogInfo -Message "[$( $MyInvocation.MyCommand )] Successfully updated the Tomcat server settings. You need to restart the Tomcat service for the changes to take effect." } else { Write-Host "################################################################################" Write-Host $formattedXml Write-Host "################################################################################" } } 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 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-LocalDiscoveryFilePath) -Force } } } |