diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6f1ec36b..60c7dff3 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.6 +current_version = 0.4.0 commit = True message = Bumps version to {new_version} tag = False diff --git a/psmodules/P3RemoteAccess/P3RemoteAccess.psd1 b/psmodules/P3RemoteAccess/P3RemoteAccess.psd1 index 7a97a7f2..f900aa46 100644 --- a/psmodules/P3RemoteAccess/P3RemoteAccess.psd1 +++ b/psmodules/P3RemoteAccess/P3RemoteAccess.psd1 @@ -47,7 +47,9 @@ Copyright = '(c) 2018 Maintainers of plus3it/cfn. All rights reserved.' # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module -# RequiredModules = @() +RequiredModules = @( + "RemoteDesktop" +) # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() diff --git a/psmodules/P3RemoteAccess/P3RemoteAccess.psm1 b/psmodules/P3RemoteAccess/P3RemoteAccess.psm1 index aa897fa2..a7a78736 100644 --- a/psmodules/P3RemoteAccess/P3RemoteAccess.psm1 +++ b/psmodules/P3RemoteAccess/P3RemoteAccess.psm1 @@ -4,7 +4,6 @@ function global:Update-RDMS { Process{ Write-Debug "Starting Update-RDMS" Write-Debug "Import RDS cmdlets" - Import-Module RemoteDesktop $ConnectionBrokers = Get-RDServer | Where-Object {$_.Roles -contains "RDS-CONNECTION-BROKER"} $ServerManagerXML = "$env:USERPROFILE\AppData\Roaming\Microsoft\Windows\ServerManager\Serverlist.xml" Write-Debug "Find active Connection Broker" @@ -59,3 +58,29 @@ function global:Update-RDMS { } End{} } + +function global:Clear-RDSessionHost { + Param( + [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] + [Microsoft.RemoteDesktopServices.Management.RDServer[]] + $SessionHost, + + [Parameter(Mandatory=$false)] + [string] + $ConnectionBroker = [System.Net.DNS]::GetHostByName('').HostName + ) + BEGIN { + $Role = "RDS-RD-SERVER" + } + PROCESS { + ForEach ($instance in $SessionHost) + { + Write-Verbose "Removing RD Session Host, $($instance.SessionHost), from the collection, ${CollectionName}..." + Remove-RDSessionHost -SessionHost $instance.SessionHost -ConnectionBroker $ConnectionBroker -Force + + Write-Verbose "Removing ${Role} role from $($instance.SessionHost)..." + Remove-RDServer -Role $Role -Server $instance.SessionHost -ConnectionBroker $ConnectionBroker -Force + } + } + END {} +} diff --git a/psmodules/P3Utils/P3Utils.psm1 b/psmodules/P3Utils/P3Utils.psm1 index beececaf..0a928814 100644 --- a/psmodules/P3Utils/P3Utils.psm1 +++ b/psmodules/P3Utils/P3Utils.psm1 @@ -78,3 +78,275 @@ function global:Invoke-RetryCommand { } End {} } + +function global:Test-RetryNetConnection { + Param( + [Parameter(Mandatory=$true, ValueFromPipeLine=$true)] + [string[]] + $ComputerName, + + [Parameter(Mandatory=$false)] + [string] + $CheckExpression = '$? -and $Return.Result.TcpTestSucceeded', + + [Parameter(Mandatory=$false)] + [int] + $Tries = 5, + + [Parameter(Mandatory=$false)] + [int] + $InitialDelay = 2, # in seconds + + [Parameter(Mandatory=$false)] + [int] + $MaxDelay = 32 # in seconds + ) + BEGIN { + $ValidComputers = @() + $StaleComputers = @() + } + PROCESS { + ForEach ($computer in $ComputerName) + { + Write-Verbose ("Testing connectivity to server: {0}" -f $computer) + try + { + $null = Invoke-RetryCommand -Command Test-NetConnection -ArgList @{ComputerName=$computer; CommonTCPPort="RDP"} -CheckExpression $CheckExpression -Tries $Tries + Write-Verbose ("Successfully connected to server: {0}" -f $computer) + Write-Output @{ ValidComputers = $computer } + $ValidComputers += $computer + } + catch + { + Write-Verbose ("Server is not available, marked as stale: {0}" -f $computer) + Write-Output @{ StaleComputers = $computer } + $StaleComputers += $computer + } + } + } + END { + Write-Verbose "Valid Computers:" + $ValidComputers | ForEach-Object { Write-Verbose "* $_" } + Write-Verbose "Stale Computers:" + $StaleComputers | ForEach-Object { Write-Verbose "* $_" } + } +} + +function global:Edit-AclIdentityReference { + Param( + [Parameter(Mandatory=$true)] + [System.Security.AccessControl.DirectorySecurity] + $Acl, + + [Parameter(Mandatory=$false)] + [string[]] + $IdentityReference, + + [Parameter(Mandatory=$false)] + [string] + $IdentityFilter = "(?i).*\\.*[$]$", + + [Parameter(Mandatory=$false)] + [string] + $FileSystemRights = "FullControl", + + [Parameter(Mandatory=$false)] + [string] + $InheritanceFlags = "ContainerInherit, ObjectInherit", + + [Parameter(Mandatory=$false)] + [string] + $PropagationFlags = "None", + + [Parameter(Mandatory=$false)] + [string] + $AccessControlType = "Allow" + ) + BEGIN { + $AclIdentities = @($Acl.Access.IdentityReference.Value) | Where-Object { $_ -match $IdentityFilter } + + # Test if ACL contains only matching identity references + $DiffIdentities = Compare-Object $IdentityReference $AclIdentities + + # Skip further processing if there are no differences + if (-not $DiffIdentities) + { + Write-Verbose "ACL contains only matching identities, no changes needed..." + return + } + + # Identity in ACL but not in $IdentityReference; need to remove + $RemoveIdentities = $DiffIdentities | Where-Object { $_.SideIndicator -eq "=>" } | ForEach-Object { $_.InputObject } + + # Identity in $IdentityReference but not in ACL; need to add + $AddIdentities = $DiffIdentities | Where-Object { $_.SideIndicator -eq "<=" } | ForEach-Object { $_.InputObject } + + # Remove rules for identity references not present in $IdentityReference + foreach ($Rule in $Acl.Access) + { + $Identity = $Rule.IdentityReference.Value + + if ($Identity -in $RemoveIdentities) + { + Write-Verbose "Identity is NOT VALID, removing rule:" + Write-Verbose "* Rule Identity: $Identity" + $null = $Acl.RemoveAccessRule($Rule) + } + } + + # Add rules for identity references in $IdentityReference that are missing from the ACL + foreach ($Identity in $AddIdentities) + { + Write-Verbose "Adding missing access rule to ACL:" + Write-Verbose "* Rule Identity: $Identity" + $null = $Acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule( + $Identity, + $FileSystemRights, + $InheritanceFlags, + $PropagationFlags, + $AccessControlType + ))) + } + + # Output the new ACL + Write-Verbose ($Acl.Access | Out-String) + Write-Output $Acl + } +} + +function global:Get-File { + Param ( + [Parameter(Mandatory=$true)] + [System.URI] + $Source, + + [Parameter(Mandatory=$true)] + [System.IO.FileInfo] + $Destination, + + [Parameter(Mandatory=$false)] + [ValidateSet("Ssl3","SystemDefault","Tls","Tls11","Tls12")] + [String] + $SecurityProtocol = "Tls12", + + [Parameter(Mandatory=$false)] + [Switch] + $MakeDir, + + [Parameter(Mandatory=$false)] + [Switch] + $OverWrite + ) + try { + $ResolvedDestination = [System.IO.FileInfo]$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination) + $TempFile = New-TemporaryFile + + Write-Verbose "Retrieving file:" + Write-Verbose "* Source: ${Source}" + Write-Verbose "* Destination: ${ResolvedDestination}" + Write-Verbose "* Temporary Destination: ${TempFile}" + + try + { + Write-Verbose "Attempting to retrieve file using .NET method..." + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::$SecurityProtocol + (New-Object Net.WebClient).DownloadFile("$Source","$TempFile") + } + catch + { + try + { + Write-Verbose $PSItem.ToString() + Write-Verbose ".NET method failed, attempting BITS transfer method..." + Start-BitsTransfer -Source $Source -Destination $TempFile -ErrorAction Stop + } + catch + { + Write-Verbose $PSItem.ToString() + $PSCmdlet.ThrowTerminatingError($PSItem) + } + } + + If (-not $ResolvedDestination.Directory.Exists -and $MakeDir) { + $null = New-Item -Path $ResolvedDestination.Directory -ItemType Directory + } + + Move-Item $TempFile $ResolvedDestination -Force:$OverWrite -ErrorAction Stop + Write-Verbose "Retrieved file successfully!" + Write-Output (Get-ChildItem $ResolvedDestination) + } + finally + { + if (Test-Path $TempFile) + { + Remove-Item -Path $TempFile -Force + } + } +} + +function global:New-RepeatingTask { + Param( + [Parameter(Mandatory=$true,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [String] + $Name, + + [Parameter(Mandatory=$true,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [String[]] + $Arguments, + + [Parameter(Mandatory=$true,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [String] + $User, + + [Parameter(Mandatory=$true,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [SecureString] + $SecurePassword, + + [Parameter(Mandatory=$false,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [String] + $Command = "powershell.exe", + + [Parameter(Mandatory=$false,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [DateTime] + $StartTime = (Get-Date).Date, + + [Parameter(Mandatory=$false,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [TimeSpan] + $RepetitionInterval = (New-TimeSpan -Hours 1), + + [Parameter(Mandatory=$false,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [TimeSpan] + $RandomDelay = (New-TimeSpan -Minutes 10), + + [Parameter(Mandatory=$false,ValueFromPipeLine=$false,ValueFromPipeLineByPropertyName=$false)] + [Switch] + $Force + ) + if (Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue) + { + if ($Force) + { + UnRegister-ScheduledTask -TaskName $Name -Confirm:$false + Write-Verbose "Force-unregistered existing job, ${Name}" + } + else + { + throw "Task already exists, ${Name}. Use '-Force' to delete it" + } + } + + $Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $User, $SecurePassword + $Password = $Credentials.GetNetworkCredential().Password + $Action = New-ScheduledTaskAction -Execute $Command -Argument "$Arguments" + $Trigger = New-JobTrigger -Once -At $StartTime -RepeatIndefinitely -RepetitionInterval $RepetitionInterval -RandomDelay $RandomDelay + $Settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel + Register-ScheduledTask -TaskName $Name -Action $Action -Trigger $Trigger -User $User -Password $Password -RunLevel Highest -Settings $Settings + Write-Verbose "Created scheduled job, ${Name}" + + if ($StartTime.CompareTo((Get-Date)) -le 0) + { + # Start time is now or in the past, trigger job immediately + Start-ScheduledTask -TaskName $Name + Write-Verbose "Triggered job, ${Name}" + } +} diff --git a/scripts/cleanup-rdcb.ps1 b/scripts/cleanup-rdcb.ps1 new file mode 100644 index 00000000..6a95e247 --- /dev/null +++ b/scripts/cleanup-rdcb.ps1 @@ -0,0 +1,71 @@ +[CmdLetBinding()] + +#Requires -Modules RemoteDesktop, P3RemoteAccess, P3Utils +#Requires -RunAsAdministrator + +Param( + [Parameter(Mandatory=$true)] + [String] + $UpdPath, + + [Parameter(Mandatory=$true)] + [String] + $DomainNetbiosName, + + [Parameter(Mandatory=$false)] + [String] + $ConnectionBroker = [System.Net.DNS]::GetHostByName('').HostName, + + [Parameter(Mandatory=$false)] + [String] + $CollectionName = "RDS Collection" +) + +# Create a lock before doing work on the connection broker (a shared resource) +$LockFile = "${UpdPath}\cleanup-rdcb-${ConnectionBroker}.lock".ToLower() +$Lock = $false + +# Get an exclusive lock on the lock file +Write-Verbose "Attempting to create exclusive lock on ${LockFile}" +while (-not $Lock) { + try { + $Lock = [System.IO.File]::Open($LockFile, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) + Write-Verbose "Established lock!" + } + catch { + # Sleep for 3 to 20 seconds - randomized to keep from hammering + $Sleep = Get-Random -Minimum 3 -Maximum 20 + Write-Verbose "Detected existing lock, retrying in $Sleep seconds" + $Sleep | Start-Sleep + } +} + +try { + + $SessionHosts = Get-RDSessionHost -CollectionName $CollectionName -ConnectionBroker $ConnectionBroker -ErrorAction Stop + $TestedSessionHosts = Test-RetryNetConnection -ComputerName $SessionHosts.SessionHost -Verbose:$VerbosePreference + + if ($TestedSessionHosts.StaleComputers) { + Clear-RDSessionHost -SessionHost ($SessionHosts | Where-Object { $_.SessionHost -in $TestedSessionHosts.StaleComputers}) -ConnectionBroker $ConnectionBroker -Verbose:$VerbosePreference + } + + $Acl = Get-Acl $UpdPath -ErrorAction Stop + + $IdentityReferences = @() + if ($TestedSessionHosts.ValidComputers) { + $IdentityReferences = $TestedSessionHosts.ValidComputers | ForEach-Object { "${DomainNetbiosName}\{0}$" -f $_.Split(".")[0] } + } + + $ValidAcl = Edit-AclIdentityReference -Acl $Acl -IdentityReference $IdentityReferences -Verbose:$VerbosePreference + + if ($ValidAcl) { + Invoke-RetryCommand -Command Set-Acl -ArgList @{Path=$UpdPath; AclObject=$ValidAcl} -CheckExpression '$?' -Verbose:$VerbosePreference + } +} catch { + Write-Verbose $PSItem.ToString() + $PSCmdlet.ThrowTerminatingError($PSitem) +} finally { + # Release the lock on the shared resource + $Lock.Close() + Write-Verbose "Released lock on ${LockFile}" +} diff --git a/scripts/configure-rdsh.ps1 b/scripts/configure-rdsh.ps1 index bbad3631..430f9edb 100644 --- a/scripts/configure-rdsh.ps1 +++ b/scripts/configure-rdsh.ps1 @@ -129,7 +129,7 @@ $RequiredRoles = @( ) # Create a lock before doing work on the connection broker (a shared resource) -$LockFile = "${UpdPath}\configure-rdsh.lock" +$LockFile = "${UpdPath}\cleanup-rdcb-${ConnectionBroker}.lock".ToLower() $Lock = $false # Get an exclusive lock on the lock file @@ -152,68 +152,19 @@ while (-not $Lock) try { $RDServers = Get-RDServer -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue - $ValidRDServers = @() - $StaleRDServers = @() if (-not $RDServers) { # Create RD Session Deployment New-RDSessionDeployment -ConnectionBroker $ConnectionBroker -SessionHost $SystemName -ErrorAction Stop Write-Verbose "Created the RD Session Deployment!" } - else - { - Write-Verbose "RD Session Deployment already exists; evaluating RDServers:" - $RDServers.Server | % { Write-Verbose " $_" } - - # Cleanup non-responsive RD Servers - ForEach ($RDServer in $RDServers) - { - Write-Verbose ("Testing connectivity to host: {0}" -f $RDServer.Server) - try - { - $null = Invoke-RetryCommand -Command Test-NetConnection -ArgList @{ComputerName=$RDServer.Server; CommonTCPPort="RDP"} -CheckExpression '$? -and $Return.Result.TcpTestSucceeded' -Tries 7 - $ValidRDServers += $RDServer.Server - Write-Verbose "Successfully connected to host, $($RDServer.Server), keeping this RD Server" - } - catch - { - $StaleRDServers += $RDServer.Server - Write-Verbose ("RDServer is not available, marked as stale: {0}" -f $RDServer.Server) - if ($RDServer.Server -in (Get-RDSessionHost -CollectionName $CollectionName -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue).SessionHost) - { - Write-Verbose "Removing RD Session Host, $($RDServer.Server), from the collection..." - Remove-RDSessionHost -SessionHost $RDServer.Server -ConnectionBroker $ConnectionBroker -Force - } - - $Role = "RDS-RD-SERVER" - if ($Role -in @(Get-RDServer -ConnectionBroker $ConnectionBroker | Where { $_.Server -eq $RDServer.Server }).Roles) - { - Write-Verbose "Removing ${Role} role from $($RDServer.Server)..." - Remove-RDServer -Role $Role -Server $RDServer.Server -ConnectionBroker $ConnectionBroker -Force - } - } - } - } - - if ($ValidRDServers) - { - Write-Verbose "Marked VALID RDServers:" - $ValidRDServers | % { Write-Verbose " $_" } - } - - if ($StaleRDServers) - { - Write-Verbose "Marked STALE RDServers:" - $StaleRDServers | % { Write-Verbose " $_" } - } - $CurrentRoles = @(Get-RDServer -ConnectionBroker $ConnectionBroker | Where { $_.Server -eq $SystemName }) + $CurrentRoles = @(Get-RDServer -ConnectionBroker $ConnectionBroker | Where-Object { $_.Server -eq $SystemName }) foreach ($Role in $RequiredRoles) { if (-not ($Role -in $CurrentRoles.Roles)) { Invoke-RetryCommand -Command Add-RDServer -ArgList @{Server=$SystemName; Role=$Role; ConnectionBroker=$ConnectionBroker} - $ValidRDServers += $SystemName.ToUpper() Write-Verbose "Configured system with role, ${Role}" } } @@ -234,8 +185,8 @@ try { Invoke-RetryCommand -Command Add-RDSessionHost -ArgList @{CollectionName=$CollectionName; SessionHost=$SystemName; ConnectionBroker=$ConnectionBroker} -CheckExpression '$?' Write-Verbose "Added system to RD Session Collection" - Write-Verbose " SessionHost=${SystemName}" - Write-Verbose " CollectionName=${CollectionName}" + Write-Verbose "* SessionHost=${SystemName}" + Write-Verbose "* CollectionName=${CollectionName}" } # Disable new sessions until reboot @@ -247,78 +198,15 @@ try Write-Verbose "Current ACL on ${UpdPath}:" Write-Verbose ($UpdAcl.Access | Out-String) - # Get a list of server names from the computer objects in the ACL - $UpdAclServers = @($UpdAcl.Access.IdentityReference.Value | % { - if ($_ -match "(?i)^${DomainNetBiosName}\\(.*)[$]$") - { - [System.Net.DNS]::GetHostEntry($Matches[1]).HostName.ToUpper() - } - }) - - Write-Verbose "Current servers in ACL:" - $UpdAclServers | % { Write-Verbose " $_" } - - # Update the ACL only if the current ACL contains invalid/stale RD Servers - # Note: The Connection Broker *is* a valid RD Server, but should not be in - # the ACL, so we remove it from the list to compare - $ExpectedAclServers = @($ValidRDServers | ? { $_ -ne $ConnectionBroker.ToUpper() }) - Write-Verbose "Expected servers in ACL:" - $ExpectedAclServers | % { Write-Verbose " $_" } - - if (Compare-Object $UpdAclServers $ExpectedAclServers) + # Ensure this host is in the UPD share ACL + $Identities = ($UpdAcl.Access | Select-Object IdentityReference).IdentityReference.Value + $Identity = "${DomainNetBiosName}\$((Get-WmiObject Win32_ComputerSystem).Name)$" + if (-not ($Identity -in $Identities)) { - Write-Verbose "Evaluating ACLs on User Profile Share: $UpdPath" - foreach ($Rule in $UpdAcl.Access) - { - # Test if the rule is a computer object - if ($Rule.IdentityReference.Value -match "(?i)^${DomainNetBiosName}\\(.*)[$]$") - { - # Check previously marked servers - $Server = [System.Net.DNS]::GetHostEntry("$($Matches[1])").HostName - if ($Server -in $ValidRDServers) - { - Write-Verbose "Host previously marked VALID, keeping rule" - Write-Verbose " Host: $Server" - Write-Verbose (" Rule Identity: {0}" -f $Rule.IdentityReference.Value) - } - elseif ($Server -in $StaleRDServers) - { - $UpdAcl.RemoveAccessRule($Rule) - Write-Verbose "Host previously marked STALE, removed rule:" - Write-Verbose " Host: $Server" - Write-Verbose (" Rule Identity: {0}" -f $Rule.IdentityReference.Value) - } - else - { - # Host is in ACL, but was not an RD Server; test connectivity and remove the rule if the host is not responding - try - { - $null = Invoke-RetryCommand -Command Test-NetConnection -ArgList @{ComputerName=$Server; CommonTCPPort="RDP"} -CheckExpression '$? -and $Return.Result.TcpTestSucceeded' -Tries 1 - Write-Verbose "Successfully connected to host, keeping this access rule" - Write-Verbose " Host: $Server" - Write-Verbose (" Rule Identity: {0}" -f $Rule.IdentityReference.Value) - } - catch - { - $UpdAcl.RemoveAccessRule($Rule) - Write-Verbose "Host is non-responsive, removed rule:" - Write-Verbose " Host: $Server" - Write-Verbose (" Rule Identity: {0}" -f $Rule.IdentityReference.Value) - } - } - } - } - - # Ensure this host is in the UPD share ACL - $Identities = ($UpdAcl.Access | Select IdentityReference).IdentityReference.Value - $Identity = "${DomainNetBiosName}\$((Get-WmiObject Win32_ComputerSystem).Name)$" - if (-not ($Identity -in $Identities)) - { - Write-Verbose "Adding missing access rule for this host to UPD share." - Write-Verbose " Rule Identity: $Identity" - $Rule = New-Object System.Security.AccessControl.FileSystemAccessRule("${Identity}", "FullControl", "ContainerInherit, ObjectInherit", "None", "Allow") - $updAcl.AddAccessRule($Rule) - } + Write-Verbose "Adding missing access rule for this host to UPD share." + Write-Verbose "* Rule Identity: $Identity" + $Rule = New-Object System.Security.AccessControl.FileSystemAccessRule("${Identity}", "FullControl", "ContainerInherit, ObjectInherit", "None", "Allow") + $updAcl.AddAccessRule($Rule) # Write the new ACL Write-Verbose "Setting updated ACL on ${UpdPath}:" diff --git a/scripts/logit.cmd b/scripts/logit.cmd new file mode 100644 index 00000000..75c9ce43 --- /dev/null +++ b/scripts/logit.cmd @@ -0,0 +1,29 @@ +@ECHO OFF +SETLOCAL + +SET logsep=------------------------------------------ +SET cmd=%* + +ECHO %logsep% +ECHO Start time: %date% %time% +ECHO Running command: %cmd% +ECHO. + +%cmd% + +SET err=%ERRORLEVEL% + +ECHO. + +IF %err% equ 0 GOTO success +:error +ECHO Command exited with non-zero return code: %err% +GOTO exit + +:success +ECHO Command succeeded^! + +:exit +ECHO End time: %date% %time% +ECHO %logsep% +EXIT /B %err% diff --git a/templates/db_mssql_alwayson.template.cfn.json b/templates/db_mssql_alwayson.template.cfn.json index b2a809f7..e955f56a 100644 --- a/templates/db_mssql_alwayson.template.cfn.json +++ b/templates/db_mssql_alwayson.template.cfn.json @@ -298,7 +298,7 @@ } } }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "MssqlNode1InstanceId": { diff --git a/templates/db_rds_mysql_audit_plugin.element.template.cfn.json b/templates/db_rds_mysql_audit_plugin.element.template.cfn.json index 9f79e55b..29430e67 100644 --- a/templates/db_rds_mysql_audit_plugin.element.template.cfn.json +++ b/templates/db_rds_mysql_audit_plugin.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This template deploys a MySQL RDS instance", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "JDBCConnectionString": { diff --git a/templates/ds_ad_primary_dc.element.template.cfn.json b/templates/ds_ad_primary_dc.element.template.cfn.json index 117c1c88..f3f17e1d 100644 --- a/templates/ds_ad_primary_dc.element.template.cfn.json +++ b/templates/ds_ad_primary_dc.element.template.cfn.json @@ -56,7 +56,7 @@ } }, "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "DomainAdmin": { diff --git a/templates/ds_ad_private_hosted_zone.element.template.cfn.json b/templates/ds_ad_private_hosted_zone.element.template.cfn.json index 144555b1..2507d2a9 100644 --- a/templates/ds_ad_private_hosted_zone.element.template.cfn.json +++ b/templates/ds_ad_private_hosted_zone.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a Route53 private hosted zone, to resolve the domain to the AD Domain Controllers via DHCP.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "HostedZoneId": { diff --git a/templates/ds_ad_replica_dc.element.template.cfn.json b/templates/ds_ad_replica_dc.element.template.cfn.json index 377c4243..818f2058 100644 --- a/templates/ds_ad_replica_dc.element.template.cfn.json +++ b/templates/ds_ad_replica_dc.element.template.cfn.json @@ -56,7 +56,7 @@ } }, "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "DomainControllerID": { diff --git a/templates/ds_ad_security_groups.element.template.cfn.json b/templates/ds_ad_security_groups.element.template.cfn.json index 680f6ac4..27546a03 100644 --- a/templates/ds_ad_security_groups.element.template.cfn.json +++ b/templates/ds_ad_security_groups.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates 2 security groups for an Active Directory domain -- one for Domain Controllers and one for Domain Members.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "DomainControllerSGID": { diff --git a/templates/ds_dhcp_options.element.template.cfn.json b/templates/ds_dhcp_options.element.template.cfn.json index ce3db368..2c29c7ff 100644 --- a/templates/ds_dhcp_options.element.template.cfn.json +++ b/templates/ds_dhcp_options.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates an Active Directory domain with a single domain controller. The default Domain Administrator password will be the one retrieved from the instance.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Parameters": { "DomainControllerIPs": { diff --git a/templates/ds_singleaz_ad.compound.template.cfn.json b/templates/ds_singleaz_ad.compound.template.cfn.json index bf3f195b..0c080a1e 100644 --- a/templates/ds_singleaz_ad.compound.template.cfn.json +++ b/templates/ds_singleaz_ad.compound.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This template creates an Active Directory infrastructure in a Single AZ.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "DomainAdmin": { diff --git a/templates/es_service_domain.element.template.cfn.json b/templates/es_service_domain.element.template.cfn.json index be8805ea..d783e072 100644 --- a/templates/es_service_domain.element.template.cfn.json +++ b/templates/es_service_domain.element.template.cfn.json @@ -39,7 +39,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "DedicatedMasterCount": { diff --git a/templates/nw_create_peer_role.element.template.cfn.json b/templates/nw_create_peer_role.element.template.cfn.json index 2c096812..52d74187 100644 --- a/templates/nw_create_peer_role.element.template.cfn.json +++ b/templates/nw_create_peer_role.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This template creates an assumable role for cross account VPC peering.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "RoleARN": { diff --git a/templates/nw_dualaz_multitier_nat_with_eni.compound.template.cfn.json b/templates/nw_dualaz_multitier_nat_with_eni.compound.template.cfn.json index deafe531..5ef9b39b 100644 --- a/templates/nw_dualaz_multitier_nat_with_eni.compound.template.cfn.json +++ b/templates/nw_dualaz_multitier_nat_with_eni.compound.template.cfn.json @@ -104,7 +104,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_dualaz_multitier_natgateway.compound.template.cfn.json b/templates/nw_dualaz_multitier_natgateway.compound.template.cfn.json index dd20ad85..245845a9 100644 --- a/templates/nw_dualaz_multitier_natgateway.compound.template.cfn.json +++ b/templates/nw_dualaz_multitier_natgateway.compound.template.cfn.json @@ -94,7 +94,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_nat_gateway.element.template.cfn.json b/templates/nw_nat_gateway.element.template.cfn.json index 802b0aed..f45996d9 100644 --- a/templates/nw_nat_gateway.element.template.cfn.json +++ b/templates/nw_nat_gateway.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a NAT Gateway with an Elastic IP, Private route table with route to the NAT Gateway.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "NATGatewayElasticIP": { diff --git a/templates/nw_nat_with_eni.element.template.cfn.json b/templates/nw_nat_with_eni.element.template.cfn.json index d62293f4..28c61f0a 100644 --- a/templates/nw_nat_with_eni.element.template.cfn.json +++ b/templates/nw_nat_with_eni.element.template.cfn.json @@ -56,7 +56,7 @@ } }, "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "NATElasticNetworkInterfaceId": { diff --git a/templates/nw_peered_sg.element.template.cfn.json b/templates/nw_peered_sg.element.template.cfn.json index c4b020f6..b9848242 100644 --- a/templates/nw_peered_sg.element.template.cfn.json +++ b/templates/nw_peered_sg.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a Security Group to allow remote access from instances in the specified security group within the peered account.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "VpcPeerSecurityGroupId": { diff --git a/templates/nw_private_subnet.element.template.cfn.json b/templates/nw_private_subnet.element.template.cfn.json index 89af119f..394436f2 100644 --- a/templates/nw_private_subnet.element.template.cfn.json +++ b/templates/nw_private_subnet.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a Private Subnet and associates it with a given Route Table.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "AvailabilityZoneName": { diff --git a/templates/nw_public_subnet.element.template.cfn.json b/templates/nw_public_subnet.element.template.cfn.json index 4dbfc2a6..258f09eb 100644 --- a/templates/nw_public_subnet.element.template.cfn.json +++ b/templates/nw_public_subnet.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a Public Subnet and associates it with a given Route Table.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "AvailabilityZoneName": { diff --git a/templates/nw_r53_peered_domain.element.template.cfn.json b/templates/nw_r53_peered_domain.element.template.cfn.json index 05a5b4f9..beca31fa 100644 --- a/templates/nw_r53_peered_domain.element.template.cfn.json +++ b/templates/nw_r53_peered_domain.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a Route53 Private Hosted Zone and the associated resource records for a peered domain.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "PrivateHostedZoneId": { diff --git a/templates/nw_singleaz_multitier_nat_with_eni.compound.template.cfn.json b/templates/nw_singleaz_multitier_nat_with_eni.compound.template.cfn.json index 4b3f3ec1..c45403b1 100644 --- a/templates/nw_singleaz_multitier_nat_with_eni.compound.template.cfn.json +++ b/templates/nw_singleaz_multitier_nat_with_eni.compound.template.cfn.json @@ -101,7 +101,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_singleaz_multitier_natgateway.compound.template.cfn.json b/templates/nw_singleaz_multitier_natgateway.compound.template.cfn.json index eec80d18..f8412973 100644 --- a/templates/nw_singleaz_multitier_natgateway.compound.template.cfn.json +++ b/templates/nw_singleaz_multitier_natgateway.compound.template.cfn.json @@ -92,7 +92,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_tripleaz_multitier_nat_with_eni.compound.template.cfn.json b/templates/nw_tripleaz_multitier_nat_with_eni.compound.template.cfn.json index 67e8ecfe..1580b853 100644 --- a/templates/nw_tripleaz_multitier_nat_with_eni.compound.template.cfn.json +++ b/templates/nw_tripleaz_multitier_nat_with_eni.compound.template.cfn.json @@ -108,7 +108,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_tripleaz_multitier_natgateway.compound.template.cfn.json b/templates/nw_tripleaz_multitier_natgateway.compound.template.cfn.json index 9925493b..f0cf883d 100644 --- a/templates/nw_tripleaz_multitier_natgateway.compound.template.cfn.json +++ b/templates/nw_tripleaz_multitier_natgateway.compound.template.cfn.json @@ -96,7 +96,7 @@ } ] }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/nw_vpc_peering_connection.element.template.cfn.json b/templates/nw_vpc_peering_connection.element.template.cfn.json index a0e3ecda..bc0b53d6 100644 --- a/templates/nw_vpc_peering_connection.element.template.cfn.json +++ b/templates/nw_vpc_peering_connection.element.template.cfn.json @@ -52,7 +52,7 @@ }, "Description": "This element creates a VPC peering connection and adds the necessary route to specified route tables.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "VpcPeeringConnection": { diff --git a/templates/nw_vpc_with_igw.element.template.cfn.json b/templates/nw_vpc_with_igw.element.template.cfn.json index 1f1fa996..8e6ec30e 100644 --- a/templates/nw_vpc_with_igw.element.template.cfn.json +++ b/templates/nw_vpc_with_igw.element.template.cfn.json @@ -2,7 +2,7 @@ "AWSTemplateFormatVersion": "2010-09-09", "Description": "This element creates a VPC network with an Internet Gateway.", "Metadata": { - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "InternetGatewayId": { diff --git a/templates/ra_guac_autoscale_public_alb.template.cfn.yaml b/templates/ra_guac_autoscale_public_alb.template.cfn.yaml index e545e166..79d5ca1b 100644 --- a/templates/ra_guac_autoscale_public_alb.template.cfn.yaml +++ b/templates/ra_guac_autoscale_public_alb.template.cfn.yaml @@ -58,7 +58,7 @@ Mappings: Parameters: Location: 's3://app-chemistry/snippets/instance_type_map.snippet.cfn.yaml' Metadata: - Version: 0.3.6 + Version: 0.4.0 cfn-lint: config: ignore_checks: @@ -180,6 +180,11 @@ Parameters: PublicSubnetIDs: Description: A list of Public subnet IDs to attach to the Application Load Balancer Type: 'List' + RepoBranchPrefixUrl: + Description: URL prefix where the repo scripts can be retrieved + Default: https://raw.githubusercontent.com/plus3it/cfn/master + Type: String + AllowedPattern: '^https:/.*' ScaleDownDesiredCapacity: Default: '1' Description: (Optional) Desired number of instances during the Scale Down Scheduled Action; ignored if ScaleDownSchedule is unset @@ -563,7 +568,7 @@ Resources: group: root mode: '000700' owner: root - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/make-guac.sh + source: !Sub ${RepoBranchPrefixUrl}/scripts/make-guac.sh services: sysvinit: cfn-hup: diff --git a/templates/ra_rdcb_fileserver_ha.template.cfn.json b/templates/ra_rdcb_fileserver_ha.template.cfn.json index 17ba4177..1305412f 100644 --- a/templates/ra_rdcb_fileserver_ha.template.cfn.json +++ b/templates/ra_rdcb_fileserver_ha.template.cfn.json @@ -207,7 +207,7 @@ } } }, - "Version": "0.3.6" + "Version": "0.4.0" }, "Outputs": { "RdcbEc2InstanceId": { diff --git a/templates/ra_rdcb_fileserver_standalone.template.cfn.yaml b/templates/ra_rdcb_fileserver_standalone.template.cfn.yaml index ccf930c1..d4e7b5a5 100644 --- a/templates/ra_rdcb_fileserver_standalone.template.cfn.yaml +++ b/templates/ra_rdcb_fileserver_standalone.template.cfn.yaml @@ -82,7 +82,7 @@ Metadata: ParameterLabels: AmiNameSearchString: default: AMI Name Search Pattern - Version: 0.3.6 + Version: 0.4.0 cfn-lint: config: ignore_checks: @@ -109,9 +109,9 @@ Parameters: Description: Search pattern to match against an AMI Name Type: String CloudWatchAgentUrl: - AllowedPattern: '^$|^s3://.*\.msi$' + AllowedPattern: '^$|^https://.*\.msi$' Default: '' - Description: '(Optional) S3 URL to CloudWatch Agent MSI. Example: s3://amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi' + Description: '(Optional) S3 URL to CloudWatch Agent MSI. Example: https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi' Type: String DataVolumeSize: Default: '5' @@ -156,6 +156,13 @@ Parameters: ExtraSecurityGroupIds: Description: List of extra Security Group IDs to attach to the RDCB EC2 instance Type: 'List' + ForceCfnInitUpdate: + AllowedValues: + - A + - B + Default: A + Description: Toggles a cfn-init metadata update even if nothing else changes + Type: String InstanceType: AllowedValues: - t2.micro @@ -193,6 +200,11 @@ Parameters: AllowedPattern: '^$|.*[@].*' Description: (Optional) Email address to subscribe to notifications and alarms Type: String + RepoBranchPrefixUrl: + Description: URL prefix where the repo scripts can be retrieved + Default: https://raw.githubusercontent.com/plus3it/cfn/master + Type: String + AllowedPattern: '^https:/.*' SnapshotFrequency: Description: '(Optional) Specify an interval in minutes to configure snapshots of the EBS fileshare volume. Set an empty value "" to skip configuring snapshots. Default interval is 60 minutes.' Default: '60' @@ -265,10 +277,6 @@ Resources: Effect: Allow Resource: '*' Sid: AllowRestrictedSnapshotActions - - Action: - - 's3:GetObject' - Effect: Allow - Resource: 'arn:aws:s3:::amazoncloudwatch-agent/*' - Action: - 'ec2:CreateSnapshot' - 'ec2:DeleteSnapshot' @@ -392,22 +400,22 @@ Resources: 'AWS::CloudFormation::Init': cfnsetup: commands: - a-set-execution-policy: + 50-set-execution-policy: command: powershell.exe -command Set-ExecutionPolicy RemoteSigned -Force waitAfterCompletion: '0' - b-online-disks: + 51-online-disks: command: powershell.exe "foreach ($disk in (Get-CimInstance -ClassName Win32_DiskDrive)) { Set-Disk -Number $disk.Index -IsOffline $false }" ignoreErrors: 'true' waitAfterCompletion: '0' - c-initialize-disks: + 52-initialize-disks: command: powershell.exe C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeDisks.ps1 ignoreErrors: 'true' waitAfterCompletion: '0' - d-extend-disks: + 53-extend-disks: command: 'powershell.exe c:\cfn\scripts\extend-volumes.ps1 -verbose' ignoreErrors: 'true' waitAfterCompletion: '0' - e-unzip-pstools: + 54-unzip-pstools: command: powershell.exe c:\cfn\scripts\unzip-archive.ps1 c:\cfn\files\pstools.zip c:\cfn\files\pstools ignoreErrors: 'true' waitAfterCompletion: '0' @@ -431,12 +439,18 @@ Resources: - InitUpdate: !Sub - ${Command} --stack ${AWS::StackName} --region ${AWS::Region} - Command: !FindInMap [CfnUtilsMap, Init, Update] + 'c:\cfn\scripts\unzip-archive.ps1': + source: !Sub ${RepoBranchPrefixUrl}/scripts/unzip-archive.ps1 'c:\cfn\scripts\configure-ebsbackups.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/configure-ebsbackups.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/configure-ebsbackups.ps1 'c:\cfn\scripts\configure-fileshares.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/configure-fileshares.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/configure-fileshares.ps1 'c:\cfn\scripts\configure-rdcb.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/configure-rdcb.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/configure-rdcb.ps1 + 'c:\cfn\scripts\snap-by-group.ps1': + source: https://raw.githubusercontent.com/plus3it/WinEBSbackups/master/SnapByCgroup.ps1 + 'c:\cfn\scripts\snap-maintenance.ps1': + source: https://raw.githubusercontent.com/plus3it/WinEBSbackups/master/SnapMaint.ps1 'c:\cfn\scripts\extend-volumes.ps1': content: | [CmdLetBinding()] @@ -453,12 +467,6 @@ Resources: Write-Verbose "No change to disk $($disk.Number) partition 1" } } - 'c:\cfn\scripts\snap-by-group.ps1': - source: https://raw.githubusercontent.com/plus3it/WinEBSbackups/master/SnapByCgroup.ps1 - 'c:\cfn\scripts\snap-maintenance.ps1': - source: https://raw.githubusercontent.com/plus3it/WinEBSbackups/master/SnapMaint.ps1 - 'c:\cfn\scripts\unzip-archive.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/unzip-archive.ps1 services: windows: cfn-hup: @@ -482,11 +490,14 @@ Resources: - !Ref "AWS::NoValue" - configure-admins - configure-rdcb + - configure-rdcb-cleanup - reboot - finalize update: - ps-modules + - install-cloudwatch-agent - cfnsetup + - configure-rdcb-cleanup - finalize configure-admins: commands: @@ -546,6 +557,42 @@ Resources: - (${Command} --stack ${AWS::StackName} --region ${AWS::Region} && exit /b 1) - Command: !FindInMap [CfnUtilsMap, Signal, Failure] waitAfterCompletion: '0' + configure-rdcb-cleanup: + commands: + 20-rdcb-cleanup: + command: !Sub + - >- + ${PowershellCommand} -Command "${PsExecCommand} + -u \"${DomainNetbiosName}\$((Invoke-Expression ((Get-SSMParameterValue -Name '${SsmRdcbCredential}' -WithDecryption $true).Parameters ^| ? {$_.Name -eq '${SsmRdcbCredential}' }).Value).Username)\" + -p \"$((Invoke-Expression ((Get-SSMParameterValue -Name '${SsmRdcbCredential}' -WithDecryption $true).Parameters ^| ? {$_.Name -eq '${SsmRdcbCredential}'}).Value).Password)\" + ${PowershellCommand} c:\cfn\scripts\cleanup-rdcb-schedule.ps1 + -Verbose -ErrorAction Stop" || ${SignalFailure} + - PowershellCommand: !FindInMap [ShellCommandMap, powershell, command] + PsExecCommand: !FindInMap [ShellCommandMap, psexec, command] + SignalFailure: !Sub + - (${Command} --stack ${AWS::StackName} --region ${AWS::Region} && exit /b 1) + - Command: !FindInMap [CfnUtilsMap, Signal, Failure] + waitAfterCompletion: '0' + files: + 'c:\cfn\scripts\cleanup-rdcb.ps1': + source: !Sub ${RepoBranchPrefixUrl}/scripts/cleanup-rdcb.ps1 + 'c:\cfn\scripts\logit.cmd': + source: !Sub ${RepoBranchPrefixUrl}/scripts/logit.cmd + 'c:\cfn\scripts\cleanup-rdcb-schedule.ps1': + content: !Sub | + [CmdLetBinding()] + Param() + Import-Module P3Utils + $Credential = Invoke-Expression ((Get-SSMParameterValue -Name '${SsmRdcbCredential}' -WithDecryption $true).Parameters | ? {$_.Name -eq '${SsmRdcbCredential}' }).Value + New-RepeatingTask ` + -Name 'RDCB Cleanup' ` + -Command 'c:\cfn\scripts\logit.cmd' ` + -Arguments 'powershell.exe C:\cfn\scripts\cleanup-rdcb.ps1 -UpdPath D:\Shares\Profiles$ -DomainNetbiosName ${DomainNetbiosName} -Verbose -ErrorAction Stop >> c:\cfn\log\cleanup-rdcb.log 2>&1' ` + -User $Credential.Username ` + -SecurePassword ($Credential.Password | ConvertTo-SecureString -AsPlainText -Force) ` + -StartTime (Get-Date -Hour 20 -Minute 0 -Second 0).AddDays(1) ` + -RepetitionInterval (New-TimeSpan -Hours 24) ` + -Force configure-rdcb: commands: 20-configure-rdcb: @@ -574,20 +621,14 @@ Resources: waitAfterCompletion: '0' install-cloudwatch-agent: commands: - 10-install-cloudwatch-agent: + 10-configure-cloudwatch-agent: command: !Sub - >- ${PowershellCommand} -Command " Invoke-Command -ScriptBlock { $ErrorActionPreference = 'Stop'; Import-Module P3Utils; - $CloudWatchAgentUri = [System.Uri]'${CloudWatchAgentUrl}'; - $CloudWatchAgentScriptDir = 'c:\cfn\scripts\AmazonCloudWatchAgent'; - $CloudWatchAgentInstaller = Join-Path $CloudWatchAgentScriptDir $CloudWatchAgentUri.Segments[($CloudWatchAgentUri.Segments.Length-1)]; - $Null = New-Item $CloudWatchAgentScriptDir -Type Directory -Force; - Read-S3Object -BucketName $CloudWatchAgentUri.Host -Key ($CloudWatchAgentUri.Segments[1..($CloudWatchAgentUri.Segments.Length-1)] -Join '') -File $CloudWatchAgentInstaller -Region ${AWS::Region}; - $CloudWatchAgentConfig = $CloudWatchAgentScriptDir + '\aws-cloudwatch-agent-config.json'; - Invoke-RetryCommand -Command Start-Process -ArgList @{ FilePath='msiexec.exe'; ArgumentList = @('/i', $CloudWatchAgentInstaller, '/qn'); NoNewWindow = $true; PassThru = $true; Wait = $true } -CheckExpression '$Return.Result.ExitCode -eq 0' -InitialDelay 17 -MaxDelay 59 -Verbose; + $CloudWatchAgentConfig = 'c:\cfn\files\aws-cloudwatch-agent-config.json'; $CloudWatchAgentCtl = \"${!Env:ProgramFiles}\Amazon\AmazonCloudWatchAgent\amazon-cloudwatch-agent-ctl.ps1\"; & $CloudWatchAgentCtl -Action cond-restart; & $CloudWatchAgentCtl -a fetch-config -m ec2 -c file:$CloudWatchAgentConfig -s; @@ -599,7 +640,7 @@ Resources: - Command: !FindInMap [CfnUtilsMap, Signal, Failure] waitAfterCompletion: '0' files: - 'c:\cfn\scripts\AmazonCloudWatchAgent\aws-cloudwatch-agent-config.json': + 'c:\cfn\files\aws-cloudwatch-agent-config.json': content: !Sub - |- { @@ -624,6 +665,12 @@ Resources: "log_group_name": "${local_InstanceLogGroup}", "log_stream_name": "{instance_id}//c/cfn/log/cfn-init-cmd.log", "timestamp_format": "%H:%M:%S %y %b %-d" + }, + { + "file_path": "c:\\cfn\\log\\cleanup-rdcb.log", + "log_group_name": "${local_InstanceLogGroup}", + "log_stream_name": "{instance_id}//c/cfn/log/cleanup-rdcb.log", + "timestamp_format": "%H:%M:%S %y %b %-d" } ] }, @@ -671,6 +718,16 @@ Resources: } } - local_InstanceLogGroup: !If [InstallCloudWatchAgent, !Ref InstanceLogGroup, !Ref 'AWS::NoValue'] + packages: + msi: + cloudwatch-agent: !Ref CloudWatchAgentUrl + services: + windows: + AmazonCloudWatchAgent: + enabled: 'true' + ensureRunning: 'true' + files: + - 'c:\cfn\files\aws-cloudwatch-agent-config.json' install-roles: commands: 10-install-roles: @@ -697,9 +754,9 @@ Resources: ps-modules: files: 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psd1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psd1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psd1 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psm1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psm1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psm1 reboot: commands: 10-reboot: @@ -712,6 +769,7 @@ Resources: waitAfterCompletion: forever CfnUpdateTriggers: EbsVolumeSize: !Ref DataVolumeSize + ForceCfnInitUpdate: !Ref ForceCfnInitUpdate Properties: BlockDeviceMappings: - DeviceName: /dev/sda1 diff --git a/templates/ra_rdgw_autoscale_public_lb.template.cfn.yaml b/templates/ra_rdgw_autoscale_public_lb.template.cfn.yaml index 8654ba4d..f3085247 100644 --- a/templates/ra_rdgw_autoscale_public_lb.template.cfn.yaml +++ b/templates/ra_rdgw_autoscale_public_lb.template.cfn.yaml @@ -90,7 +90,7 @@ Metadata: default: AMI Name Search Pattern ScaleDownDesiredCapacity: default: Scale Down Desired Capacity - Version: 0.3.6 + Version: 0.4.0 cfn-lint: config: ignore_checks: @@ -210,6 +210,11 @@ Parameters: Description: Domain group of users authorized to use the RDGW MinLength: '1' Type: String + RepoBranchPrefixUrl: + Description: URL prefix where the repo scripts can be retrieved + Default: https://raw.githubusercontent.com/plus3it/cfn/master + Type: String + AllowedPattern: '^https:/.*' ScaleDownDesiredCapacity: Default: '1' Description: (Optional) Desired number of instances during the Scale Down Scheduled Action; ignored if ScaleDownSchedule is unset @@ -540,9 +545,9 @@ Resources: ps-modules: files: 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psd1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psd1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psd1 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psm1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psm1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psm1 setup: files: 'c:\cfn\cfn-hup.conf': @@ -561,7 +566,7 @@ Resources: - ${Command} --stack ${AWS::StackName} --region ${AWS::Region} - Command: !FindInMap [CfnUtilsMap, Init, Update] 'c:\cfn\scripts\configure-rdgw.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/configure-rdgw.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/configure-rdgw.ps1 services: windows: cfn-hup: diff --git a/templates/ra_rdsh_autoscale_internal_lb.template.cfn.yaml b/templates/ra_rdsh_autoscale_internal_lb.template.cfn.yaml index 7e12d960..f47211a4 100644 --- a/templates/ra_rdsh_autoscale_internal_lb.template.cfn.yaml +++ b/templates/ra_rdsh_autoscale_internal_lb.template.cfn.yaml @@ -89,7 +89,7 @@ Metadata: default: AMI Name Search Pattern ScaleDownDesiredCapacity: default: Scale Down Desired Capacity - Version: 0.3.6 + Version: 0.4.0 cfn-lint: config: ignore_checks: @@ -241,6 +241,11 @@ Parameters: Default: 'https://s3.amazonaws.com' Description: S3 endpoint URL hosting the bucket where the RDP certificate private key is stored Type: String + RepoBranchPrefixUrl: + Description: URL prefix where the repo scripts can be retrieved + Default: https://raw.githubusercontent.com/plus3it/cfn/master + Type: String + AllowedPattern: '^https:/.*' ScaleDownDesiredCapacity: Default: '1' Description: (Optional) Desired number of instances during the Scale Down Scheduled Action; ignored if ScaleDownSchedule is unset @@ -617,9 +622,9 @@ Resources: ps-modules: files: 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psd1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psd1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psd1 'C:\Program Files\WindowsPowerShell\Modules\P3Utils\P3Utils.psm1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/psmodules/P3Utils/P3Utils.psm1 + source: !Sub ${RepoBranchPrefixUrl}/psmodules/P3Utils/P3Utils.psm1 setup: commands: a-unzip-pstools: @@ -652,9 +657,9 @@ Resources: - ${Command} --stack ${AWS::StackName} --region ${AWS::Region} - Command: !FindInMap [CfnUtilsMap, Init, Update] 'c:\cfn\scripts\configure-rdsh.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/configure-rdsh.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/configure-rdsh.ps1 'c:\cfn\scripts\unzip-archive.ps1': - source: https://raw.githubusercontent.com/plus3it/cfn/master/scripts/unzip-archive.ps1 + source: !Sub ${RepoBranchPrefixUrl}/scripts/unzip-archive.ps1 services: windows: cfn-hup: