AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  This template creates the following:
    1.	(1) VPC w/ (2) public subnets in (2) AZs
    2.	(1) Amazon EC2 Instance Role w/ profile
    3.	(1) Domain Member SG
    4.	(1) Domain Controller SG
    5.	(1) Amazon EC2 Instances (t3.medium, 45Gb (C:) GP3)
    6.	(1) Amazon EC2 Instances (t3.large, 45Gb (C:) GP3)
    7.  (1) AWS Secrets Manager Secret containing the AD Administrator account password
    8.  (2) AWS SSM Automation documents to configure the Amazon EC2 Instances
Parameters:
  AMI:
    Default: /aws/service/ami-windows-latest/TPM-Windows_Server-2022-English-Full-Base
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
  OnpremDomainName:
    AllowedPattern: ^([a-zA-Z0-9]+[\\.-])+([a-zA-Z0-9])+$
    Default: corp.example.com
    Description: Fully qualified domain name (FQDN) of the On-Premises domain e.g. corp.example.com
    MaxLength: "255"
    MinLength: "2"
    Type: String
  OnpremNetBIOSName:
    AllowedPattern: ^[^\\/:*?"<>|.]+[^\\/:*?"<>|]*$
    Default: CORP
    Description: NetBIOS name of the On-Premises domain (up to 15 characters) e.g. CORP
    MaxLength: "15"
    MinLength: "1"
    Type: String
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Parameters
        Parameters:
          - AMI
          - OnpremDomainName
          - OnpremNetBIOSName
    ParameterLabels:
      AMI:
        default: SSM Parameter Value for Latest AMI ID
      OnpremDomainName:
        default: On-Premises Domain DNS Name
      OnpremNetBIOSName:
        default: On-Premises Domain NetBIOS Name
Resources:
  VPC:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W60
            reason: This is for testing a blog post not production use
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/24
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: RDS-Self-AD-VPC
  VPCPublicSubnet1Subnet:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W33
            reason: This is by design to keep template simple and cost effective
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Select
        - 0
        - Fn::Cidr:
            - !GetAtt VPC.CidrBlock
            - 2
            - 6
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: RDS-Self-AD-Subnet1
      VpcId: !Ref VPC
  VPCPublicSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: RDS-Self-AD-Subnet1
      VpcId: !Ref VPC
  VPCPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VPCPublicSubnet1RouteTable
      SubnetId:
        Ref: VPCPublicSubnet1Subnet
  VPCPublicSubnet1DefaultRoute:
    Type: AWS::EC2::Route
    DependsOn: VPCVPCGW
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VPCIGW
      RouteTableId:
        Ref: VPCPublicSubnet1RouteTable
  VPCPublicSubnet2Subnet:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W33
            reason: This is by design to keep template simple and cost effective
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Select
        - 1
        - Fn::Cidr:
            - !GetAtt VPC.CidrBlock
            - 2
            - 6
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: RDS-Self-AD-Subnet2
      VpcId: !Ref VPC
  VPCPublicSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: RDS-Self-AD-Subnet2
      VpcId: !Ref VPC
  VPCPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VPCPublicSubnet2RouteTable
      SubnetId:
        Ref: VPCPublicSubnet2Subnet
  VPCPublicSubnet2DefaultRoute:
    DependsOn: VPCVPCGW
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VPCIGW
      RouteTableId:
        Ref: VPCPublicSubnet2RouteTable
  VPCIGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: RDS-Self-AD-VPC-IGW
  VPCVPCGW:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId:
        Ref: VPCIGW
      VpcId: !Ref VPC
  InstanceRole:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W11
            reason: This is by design as they are read permissions only
          - id: W28
            reason: This is for testing a blog post not production use
          - id: W76
            reason: This is for testing a blog post not production use
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
        - !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy
      Path: /
      Policies:
        - PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Action: cloudformation:SignalResource
                Effect: Allow
                Resource: !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*
              - Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Effect: Allow
                Resource:
                  - !Ref OnPremAdministratorSecret
              - Action: ssm:SendCommand
                Effect: Allow
                Resource:
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:*:document/AWS-RunRemoteScript
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:*:document/AWS-RunPowerShellScript
              - Action: ssm:SendCommand
                Condition:
                  StringEquals:
                    "ssm:ResourceTag/aws:cloudformation:stack-name": !Ref AWS::StackName
                Effect: Allow
                Resource: !Sub arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/*
              - Action: ssm:StartAutomationExecution
                Effect: Allow
                Resource:
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${MgmtInstanceSSMAuto}:$DEFAULT
                  - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${OnPremDomainControllerSsmAuto}:$DEFAULT
              - Action:
                  - ec2:DescribeInstances
                  - ssm:DescribeInstanceInformation
                  - ssm:GetAutomationExecution
                  - ssm:ListCommands
                  - ssm:ListCommandInvocations
                Effect: Allow
                Resource: "*"
          PolicyName: !Sub Inline-Policy-${AWS::StackName}
      RoleName: !Sub RDS-Self-AD-InstanceRole-${AWS::StackName}
      Tags:
        - Key: StackName
          Value:
            Ref: AWS::StackName
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub RDS-Self-AD-InstanceRole-${AWS::StackName}
      Path: /
      Roles:
        - Ref: InstanceRole
  OnPremAdministratorSecret:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W77
            reason: This is for testing a blog post not production use, so it is uses the AWS provided keys
    Type: AWS::SecretsManager::Secret
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      Name: !Sub OnPremAdministratorSecret-${AWS::StackName}
      Description: Administrator Password for Onpremises AD
      GenerateSecretString:
        SecretStringTemplate: '{"username": "Administrator"}'
        GenerateStringKey: "password"
        PasswordLength: 30
        ExcludeCharacters: '"@/\'
  DomainMemberSG:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W5
            reason: This is for testing a blog post not production use
          - id: W9
            reason: This is for testing a blog post not production use
          - id: W28
            reason: This is for testing a blog post not production use
          - id: W40
            reason: This is for testing a blog post not production use
          - id: W42
            reason: This is for testing a blog post not production use and only allows inbound from within the VPC
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub Domain-Members-SG-${AWS::StackName}
      GroupName: !Sub Domain-Members-SG-${AWS::StackName}
      SecurityGroupEgress:
        - Description: All Outbound Traffic
          FromPort: -1
          IpProtocol: "-1"
          CidrIp: 0.0.0.0/0
          ToPort: -1
      SecurityGroupIngress:
        - Description: All Local VPC Traffic
          FromPort: -1
          IpProtocol: "-1"
          CidrIp: 10.0.0.0/24
          ToPort: -1
        - Description: RDP Access
          FromPort: 3389
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 3389
      Tags:
        - Key: Name
          Value: !Sub Domain-Members-SG-${AWS::StackName}
      VpcId: !Ref VPC
  MgmtInstanceSSMAuto:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      Content:
        schemaVersion: "0.3"
        description: Deploy MGMT Instance with SSM Automation
        parameters:
          AdminSecret:
            description: AWS Secrets Parameter Name for the Administrator account.
            type: String
          DomainDNSName:
            description: Fully qualified domain name (FQDN) of the forest root domain e.g. corp.example.com
            type: String
          DomainNetBIOSName:
            description: NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows e.g. CORP
            type: String
          DcIP01:
            description: IP of DNS server that can resolve domain (Must be accessible)
            type: String
          ServerNetBIOSName:
            description: NetBIOS name of the Management Instance server (up to 15 characters)
            type: String
          StackName:
            description: Stack Name Input for cfn resource signal
            type: String
          VPCCIDR:
            description: CIDR Block for the VPC
            type: String
        mainSteps:
          - name: mgmtInstanceId
            action: aws:executeAwsApi
            onFailure: step:signalfailure
            inputs:
              Service: ec2
              Api: DescribeInstances
              Filters:
                - Name: tag:Name
                  Values:
                    - "{{ServerNetBIOSName}}"
                - Name: tag:aws:cloudformation:stack-name
                  Values:
                    - "{{StackName}}"
                - Name: instance-state-name
                  Values:
                    - "running"
            outputs:
              - Name: InstanceId
                Selector: $.Reservations[0].Instances[0].InstanceId
                Type: String
            nextStep: intializeInstance
          - name: intializeInstance
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{mgmtInstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

                  $RegistryItems = @(
                      @{
                          Name         = 'SchUseStrongCrypto'
                          Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319'
                          Value        = '1'
                          PropertyType = 'DWORD'
                      },
                      @{
                          Name         = 'SchUseStrongCrypto'
                          Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319'
                          Value        = '1'
                          PropertyType = 'DWORD'
                      },
                      @{
                        Name         = 'HideFirstRunExperience'
                        Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge'
                        Value        = '1'
                        PropertyType = 'DWORD'
                      }
                  )

                  Foreach ($RegistryItem in $RegistryItems) {
                      $KeyPresent = Test-Path -Path $RegistryItem.Path -ErrorAction SilentlyContinue
                      If (-not $KeyPresent) {
                          Try {
                              $Null = New-Item -Path $RegistryItem.Path -Force -ErrorAction Stop
                          } Catch [System.Exception] {
                              Write-Output "Failed to create Registry named $($RegistryItem.Path) $_"
                              Exit 1
                          }
                      }
                      Try {
                          $Null = New-ItemProperty @RegistryItem -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create Registry key named $($RegistryItem.Name) at $($RegistryItem.Path) $_"
                          Exit 1
                      }
                  }

                  $Modules = @(
                      @{
                          Name = 'ActiveDirectoryDsc'
                          Version = '6.2.0'
                      },
                      @{
                          Name = 'ComputerManagementDsc'
                          Version = '9.0.0'
                      },
                      @{
                          Name = 'DnsServerDsc'
                          Version = '3.0.0'
                      },
                      @{
                          Name = 'NetworkingDsc'
                          Version = '9.0.0'
                      },
                      @{
                          Name = 'PSReadline'
                          Version = '2.2.6'
                      }
                  )

                  Write-Output 'Creating Temp Directory'
                  Try {
                      $Null = New-Item -Path 'C:\Temp\Module-AD' -ItemType 'Directory' -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to create Temp directory $_"
                      Exit 1
                  }

                  Write-Output 'Installing NuGet Package Provider'
                  Try {
                      $Null = Install-PackageProvider -Name 'NuGet' -MinimumVersion '2.8.5' -Force -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to install NuGet Package Provider $_"
                      Exit 1
                  }

                  Write-Output 'Setting PSGallery Respository to trusted'
                  Try {
                      Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted' -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to set PSGallery Respository to trusted $_"
                      Exit 1
                  }

                  Write-Output 'Installing the needed Powershell DSC modules'
                  Foreach ($Module in $Modules) {
                      Try {
                          Install-Module -Name $Module.Name -RequiredVersion $Module.Version -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to Import Modules $_"
                          Exit 1
                      }
                  }

                  Write-Output 'Installing SQL Server Management Studio'
                  (New-Object -TypeName 'System.Net.WebClient').DownloadFile('https://aka.ms/ssmsfullsetup', 'C:\Temp\SSMS-Setup.exe')
                  $ArgumentList = '/Quiet'
                  Try {
                      $Process = Start-Process -FilePath 'C:\Temp\SSMS-Setup.exe' -ArgumentList $ArgumentList -NoNewWindow -PassThru -Wait -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to install SQL Server Management Studio $_"
                      #Exit 1
                  }
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
            nextStep: configureInstance
          - name: configureInstance
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{mgmtInstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Get-SecretInfo {
                      [CmdletBinding()]
                      Param (
                          [Parameter(Mandatory = $True)][String]$Domain,
                          [Parameter(Mandatory = $True)][String]$SecretArn
                      )

                      Write-Output "Getting $SecretArn Secret"
                      Try {
                          $SecretContent = Get-SECSecretValue -SecretId $SecretArn -ErrorAction Stop | Select-Object -ExpandProperty 'SecretString' | ConvertFrom-Json -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to get $SecretArn Secret $_"
                          Exit 1
                      }

                      Write-Output 'Creating PSCredential object from Secret'
                      $Username = $SecretContent.username
                      $UserPassword = ConvertTo-SecureString ($SecretContent.password) -AsPlainText -Force
                      $DomainCredentials = New-Object -TypeName 'System.Management.Automation.PSCredential' ("$Domain\$Username", $UserPassword)
                      $Credentials = New-Object -TypeName 'System.Management.Automation.PSCredential' ($Username, $UserPassword)

                      $Output = [PSCustomObject][Ordered]@{
                          'Credentials'       = $Credentials
                          'DomainCredentials' = $DomainCredentials
                          'Username'          = $Username
                          'UserPassword'      = $UserPassword
                      }

                      Return $Output
                  }

                  Function Invoke-PreConfig {
                      Write-Output 'Temporarily disabling Windows Firewall'
                      Try {
                          Get-NetFirewallProfile -ErrorAction Stop | Set-NetFirewallProfile -Enabled False -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to disable Windows Firewall $_"
                          Exit 1
                      }

                      Write-Output 'Creating file directory for DSC public cert'
                      Try {
                          $Null = New-Item -Path 'C:\Temp\publickeys' -ItemType 'Directory' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create publickeys file directory $_"
                          Exit 1
                      }

                      Write-Output 'Creating certificate to encrypt credentials in MOF file'
                      Try {
                          $cert = New-SelfSignedCertificate -Type 'DocumentEncryptionCertLegacyCsp' -DnsName 'AWSQSDscEncryptCert' -HashAlgorithm 'SHA256' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create self signed cert $_"
                          Exit 1
                      }

                      Write-Output 'Exporting the self signed public key certificate'
                      Try {
                          $Null = $cert | Export-Certificate -FilePath 'C:\Temp\publickeys\AWSQSDscPublicKey.cer' -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to copy self signed cert to publickeys directory $_"
                          Exit 1
                      }
                  }

                  Function Invoke-LcmConfig {
                      Write-Output 'Getting the DSC cert thumbprint to secure the MOF file'
                      Try {
                          $DscCertThumbprint = Get-ChildItem -Path 'cert:\LocalMachine\My' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get DSC cert thumbprint $_"
                          Exit 1
                      } 

                      [DSCLocalConfigurationManager()]
                      Configuration LCMConfig
                      {
                          Node 'localhost' {
                              Settings {
                                  RefreshMode                    = 'Push'
                                  ConfigurationModeFrequencyMins = 15
                                  ActionAfterReboot              = 'StopConfiguration'
                                  RebootNodeIfNeeded             = $false
                                  ConfigurationMode              = 'ApplyAndAutoCorrect'
                                  CertificateId                  = $DscCertThumbprint
                              }
                          }
                      }

                      Write-Output 'Generating MOF file for LCM'
                      $Null = LCMConfig -OutputPath 'C:\Temp\LCMConfig'
                          
                      Write-Output 'Sets LCM configuration to MOF generated in previous command'
                      Try {
                          Set-DscLocalConfigurationManager -Path 'C:\Temp\LCMConfig' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to set LCM configuration $_"
                          Exit 1
                      } 
                  }

                  Function Get-EniConfig {
                      Write-Output 'Getting network configuration'
                      Try {
                          $NetIpConfig = Get-NetIPConfiguration -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to get network configuration $_"
                          Exit 1
                      }

                      Write-Output 'Grabbing the current gateway address in order to static IP correctly'
                      $GatewayAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4DefaultGateway' | Select-Object -ExpandProperty 'NextHop'

                      Write-Output 'Formatting IP address in format needed for IPAdress DSC resource'
                      $IpAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'IpAddress'
                      $Prefix = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'PrefixLength'
                      $IpAddr = 'IP/CIDR' -replace 'IP', $IpAddress -replace 'CIDR', $Prefix

                      Write-Output 'Getting MAC address'
                      Try {
                          $MacAddress = Get-NetAdapter -ErrorAction Stop | Select-Object -ExpandProperty 'MacAddress'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get MAC address $_"
                          Exit 1
                      }

                      $Output = [PSCustomObject][Ordered]@{
                          'GatewayAddress' = $GatewayAddress
                          'IpAddress'      = $IpAddr
                          'DnsIpAddress'   = $IpAddress
                          'MacAddress'     = $MacAddress
                      }
                      Return $Output
                  }

                  Function Set-DscConfiguration {
                      [CmdletBinding()]
                      param(
                          [Parameter(Mandatory = $true)][PSCredential]$DaCredentials,
                          [Parameter(Mandatory = $true)][string]$DomainDNSName,
                          [Parameter(Mandatory = $true)][string]$DomainNetBIOSName,
                          [Parameter(Mandatory = $true)][string]$DcIP01,
                          [Parameter(Mandatory = $true)][string]$GatewayAddress,
                          [Parameter(Mandatory = $true)][string]$InstanceIP,
                          [Parameter(Mandatory = $true)][string]$InstanceNetBIOSName,
                          [Parameter(Mandatory = $true)][string]$MacAddress
                      )

                      $VPCDNS = '169.254.169.253'

                      Write-Output 'Getting the DSC encryption thumbprint to secure the MOF file'
                      Try {
                          $DscCertThumbprint = Get-ChildItem -Path 'cert:\LocalMachine\My' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get DSC cert thumbprint $_"
                          Exit 1
                      }

                      Write-Output 'Creating configuration data block that has the certificate information for DSC configuration processing'
                      $ConfigurationData = @{
                          AllNodes = @(
                              @{
                                  NodeName             = '*'
                                  CertificateFile      = 'C:\Temp\publickeys\AWSQSDscPublicKey.cer'
                                  Thumbprint           = $DscCertThumbprint
                                  PSDscAllowDomainUser = $true
                                  PsDscAllowPlainTextPassword = $false
                              },
                              @{
                                  NodeName = 'localhost'
                              }
                          )
                      }

                      Configuration ConfigInstance {
                          Import-DscResource -ModuleName 'PSDesiredStateConfiguration', 'NetworkingDsc', 'ComputerManagementDsc', 'DnsServerDsc', 'ActiveDirectoryDsc'
                          Node LocalHost {
                              NetAdapterName RenameNetAdapterPrimary {
                                  NewName    = 'Primary'
                                  MacAddress = $MacAddress
                              }
                              NetIPInterface DisableDhcp {
                                  Dhcp           = 'Disabled'
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[NetAdapterName]RenameNetAdapterPrimary'
                              }
                              IPAddress SetIP {
                                  IPAddress      = $InstanceIP
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[NetIPInterface]DisableDhcp'
                              }
                              DefaultGatewayAddress SetDefaultGateway {
                                  Address        = $GatewayAddress
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[IPAddress]SetIP'
                              }
                              DnsServerAddress DnsServerAddress {
                                  Address        = $DcIP01, '169.254.169.253'
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[DefaultGatewayAddress]SetDefaultGateway'
                              }
                              DnsConnectionSuffix DnsConnectionSuffix {
                                  InterfaceAlias                 = 'Primary'
                                  ConnectionSpecificSuffix       = $DomainDNSName
                                  RegisterThisConnectionsAddress = $True
                                  UseSuffixWhenRegistering       = $False
                                  DependsOn                      = '[DnsServerAddress]DnsServerAddress'
                              }
                              IEEnhancedSecurityConfiguration 'DisableForAdministrators' {
                                  Role    = 'Administrators'
                                  Enabled = $false
                              }
                              IEEnhancedSecurityConfiguration 'DisableForUsers' {
                                  Role    = 'Users'
                                  Enabled = $false
                              }
                              WindowsFeature DnsTools {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-DNS-Server'
                                  DependsOn = '[DnsConnectionSuffix]DnsConnectionSuffix'
                              }
                              WindowsFeature RSAT-AD-Tools {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-AD-Tools'
                                  DependsOn = '[WindowsFeature]DnsTools'
                              }
                              WindowsFeature RSAT-ADDS {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-ADDS'
                                  DependsOn = '[WindowsFeature]RSAT-AD-Tools'
                              }
                              WindowsFeature GPMC {
                                  Ensure    = 'Present'
                                  Name      = 'GPMC'
                                  DependsOn = '[WindowsFeature]RSAT-ADDS'
                              }
                              Computer JoinDomain {
                                  Name       = $InstanceNetBIOSName
                                  DomainName = $DomainDnsName
                                  Credential = $DaCredentials
                                  DependsOn  = '[WindowsFeature]GPMC'
                              }
                          }
                      }
                      Write-Output 'Generating MOF file'
                      $Null = ConfigInstance -OutputPath 'C:\Temp\ConfigInstance' -ConfigurationData $ConfigurationData
                  }

                  $Secret = Get-SecretInfo -Domain '{{DomainNetBIOSName}}' -SecretArn '{{AdminSecret}}'
                  Invoke-PreConfig
                  Invoke-LcmConfig
                  $EniConfig = Get-EniConfig
                  Set-DscConfiguration -DaCredentials $Secret.DomainCredentials -DomainDNSName '{{DomainDNSName}}' -DomainNetBIOSName '{{DomainNetBIOSName}}' -DcIP01 '{{DcIP01}}'-GatewayAddress $EniConfig.GatewayAddress -InstanceIP $EniConfig.IpAddress -InstanceNetBIOSName '{{ServerNetBIOSName}}' -MacAddress $EniConfig.MacAddress
            nextStep: runMgmtMof
          - name: runMgmtMof
            action: aws:runCommand
            onFailure: step:signalfailure
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{mgmtInstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Invoke-DscStatusCheck {
                      $LCMState = Get-DscLocalConfigurationManager -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'LCMState'
                      If ($LCMState -eq 'PendingConfiguration' -Or $LCMState -eq 'PendingReboot') {
                          Exit 3010
                      } Else {
                          Write-Output 'DSC Config Completed'
                      }
                  }

                  Start-DscConfiguration 'C:\Temp\ConfigInstance' -Wait -Verbose -Force
                  Invoke-DscStatusCheck
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
            nextStep: PostConfig
          - name: PostConfig
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{mgmtInstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Invoke-Cleanup {
                      [CmdletBinding()]
                      Param (
                          [Parameter(Mandatory = $true)][String]$VPCCIDR
                      )

                      Write-Output 'Setting Windows Firewall WinRM Public rule to allow VPC CIDR traffic'
                      Try {
                          Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP-PUBLIC' -RemoteAddress $VPCCIDR
                      } Catch [System.Exception] {
                          Write-Output "Failed allow WinRM Traffic from VPC CIDR $_"
                      }

                      Write-Output 'Removing DSC Configuration'
                      Try {    
                          Remove-DscConfigurationDocument -Stage 'Current' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed build DSC Configuration $_"
                      }

                      Write-Output 'Re-enabling Windows Firewall'
                      Try {
                          Get-NetFirewallProfile -ErrorAction Stop | Set-NetFirewallProfile -Enabled 'True' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed re-enable firewall $_"
                      }

                      Write-Output 'Removing build files'
                      Try {
                          Remove-Item -Path 'C:\Temp' -Recurse -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed remove build files $_"
                      }

                      Write-Output 'Removing self signed cert'
                      Try {
                          $SelfSignedThumb = Get-ChildItem -Path 'cert:\LocalMachine\My\' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                          Remove-Item -Path "cert:\LocalMachine\My\$SelfSignedThumb" -DeleteKey -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed remove self signed cert $_"
                      }
                  }

                  Invoke-Cleanup -VPCCIDR '{{VPCCIDR}}'
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
          - name: CFNSignalEnd
            action: aws:branch
            inputs:
              Choices:
                - NextStep: signalsuccess
                  Not:
                    Variable: "{{StackName}}"
                    StringEquals: ""
                - NextStep: sleepend
                  Variable: "{{StackName}}"
                  StringEquals: ""
          - name: signalsuccess
            action: aws:executeAwsApi
            isEnd: True
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: MgmtInstance
              StackName: "{{StackName}}"
              Status: SUCCESS
              UniqueId: "{{mgmtInstanceId.InstanceId}}"
          - name: sleepend
            action: aws:sleep
            isEnd: True
            inputs:
              Duration: PT1S
          - name: signalfailure
            action: aws:executeAwsApi
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: MgmtInstance
              StackName: "{{StackName}}"
              Status: FAILURE
              UniqueId: "{{mgmtInstanceId.InstanceId}}"
      Tags:
        - Key: StackName
          Value: !Ref AWS::StackName
  MgmtInstance:
    Type: AWS::EC2::Instance
    CreationPolicy:
      ResourceSignal:
        Timeout: PT30M
        Count: 1
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 45
            VolumeType: gp3
            Encrypted: true
            KmsKeyId: alias/aws/ebs
            DeleteOnTermination: true
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref AMI
      InstanceType: t3.large
      SecurityGroupIds:
        - !Ref DomainMemberSG
      SubnetId: !Ref VPCPublicSubnet1Subnet
      Tags:
        - Key: Name
          Value: ONPREM-MGMT01
        - Key: Domain
          Value: !Ref OnpremDomainName
      UserData:
        Fn::Base64:
          Fn::Sub:
            - |
              <powershell>
              $Params = @{
                  AdminSecret          = '${OnPremAdministratorSecret}'
                  DomainDNSName        = '${OnpremDomainName}'
                  DomainNetBIOSName    = '${OnpremNetBIOSName}'
                  DcIP01               = '${DcIP01}'
                  ServerNetBIOSName    = 'ONPREM-MGMT01'
                  StackName            = '${AWS::StackName}'
                  VPCCIDR              = '${VPCCIDR}'
              }
              Start-SSMAutomationExecution -DocumentName '${MgmtInstanceSSMAuto}' -Parameter $Params
              </powershell>
            - DcIP01: !GetAtt OnPremDomainController.PrivateIp
              VPCCIDR: !GetAtt VPC.CidrBlock
  OnPremDomainControllerSG:
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W5
            reason: This is for testing a blog post not production use
          - id: W9
            reason: This is for testing a blog post not production use
          - id: W27
            reason: This is for testing a blog post not production use
          - id: W28
            reason: This is for testing a blog post not production use
          - id: W40
            reason: This is for testing a blog post not production use
          - id: W42
            reason: This is for testing a blog post not production use and only allows inbound from within the VPC
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub Domain-Controllers-SG-${AWS::StackName}
      GroupName: !Sub Domain-Controllers-SG-${AWS::StackName}
      SecurityGroupEgress:
        - Description: All Outbound Traffic
          FromPort: -1
          IpProtocol: "-1"
          CidrIp: 0.0.0.0/0
          ToPort: -1
      SecurityGroupIngress:
        - Description: DNS
          FromPort: 53
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 53
        - Description: DNS
          FromPort: 53
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 53
        - Description: Kerberos
          FromPort: 88
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 88
        - Description: Kerberos
          FromPort: 88
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 88
        - Description: Windows Time
          FromPort: 123
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 123
        - Description: RPC Port
          FromPort: 135
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 135
        - Description: Netlogon
          FromPort: 138
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 138
        - Description: LDAP
          FromPort: 389
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 389
        - Description: LDAP
          FromPort: 389
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 389
        - Description: SMB
          FromPort: 445
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 445
        - Description: SMB
          FromPort: 445
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 445
        - Description: Kerberos Set & Change Password
          FromPort: 464
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 464
        - Description: Kerberos Set & Change Password
          FromPort: 464
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 464
        - Description: LDAP over SSL
          FromPort: 636
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 636
        - Description: LDAP Global Catalog
          FromPort: 3268
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 3268
        - Description: LDAP Global Catalog over SSL
          FromPort: 3269
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 3269
        - Description: RDP Access
          FromPort: 3389
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 3389
        - Description: WinRM
          FromPort: 5985
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 5985
        - Description: SOAP ADWS
          FromPort: 9389
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 9389
        - Description: Random RPC
          FromPort: 49152
          IpProtocol: tcp
          CidrIp: 10.0.0.0/24
          ToPort: 65535
        - Description: Random RPC
          FromPort: 49152
          IpProtocol: udp
          CidrIp: 10.0.0.0/24
          ToPort: 65535
      Tags:
        - Key: Name
          Value: !Sub Domain-Controllers-SG-${AWS::StackName}
      VpcId: !Ref VPC
  OnPremDomainControllerSsmAuto:
    Type: AWS::SSM::Document
    Properties:
      Content:
        schemaVersion: "0.3"
        description: Deploy AD with SSM Automation
        parameters:
          ADAdminSecParamName:
            description: AWS Secrets Parameter Name that has Password and Username for the built-in administrator
            type: String
          ADServer1NetBIOSName:
            description: NetBIOS name of the first Active Directory Domain Controller (up to 15 characters)
            type: String
          DomainDNSName:
            description: Fully qualified domain name (FQDN) of the forest root domain e.g. onprem.local
            type: String
          DomainNetBIOSName:
            description: NetBIOS name of the domain (up to 15 characters) for users of earlier versions of Windows e.g. EXAMPLE
            type: String
          StackName:
            description: Stack Name Input for cfn resource signal
            type: String
          VPCCIDR:
            description: CIDR Block for the VPC
            type: String
        mainSteps:
          - name: dc1InstanceId
            action: aws:executeAwsApi
            onFailure: step:signalfailure
            inputs:
              Service: ec2
              Api: DescribeInstances
              Filters:
                - Name: tag:Name
                  Values:
                    - "{{ADServer1NetBIOSName}}"
                - Name: tag:aws:cloudformation:stack-name
                  Values:
                    - "{{StackName}}"
                - Name: instance-state-name
                  Values:
                    - running
            outputs:
              - Name: InstanceId
                Selector: $.Reservations[0].Instances[0].InstanceId
                Type: String
            nextStep: intializeInstance
          - name: intializeInstance
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{dc1InstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

                  $RegistryItems = @(
                      @{
                          Name         = 'SchUseStrongCrypto'
                          Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319'
                          Value        = '1'
                          PropertyType = 'DWORD'
                      },
                      @{
                          Name         = 'SchUseStrongCrypto'
                          Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319'
                          Value        = '1'
                          PropertyType = 'DWORD'
                      },
                      @{
                        Name         = 'HideFirstRunExperience'
                        Path         = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge'
                        Value        = '1'
                        PropertyType = 'DWORD'
                      }
                  )

                  Foreach ($RegistryItem in $RegistryItems) {
                      $KeyPresent = Test-Path -Path $RegistryItem.Path -ErrorAction SilentlyContinue
                      If (-not $KeyPresent) {
                          Try {
                              $Null = New-Item -Path $RegistryItem.Path -Force -ErrorAction Stop
                          } Catch [System.Exception] {
                              Write-Output "Failed to create Registry named $($RegistryItem.Path) $_"
                              Exit 1
                          }
                      }
                      Try {
                          $Null = New-ItemProperty @RegistryItem -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create Registry key named $($RegistryItem.Name) at $($RegistryItem.Path) $_"
                          Exit 1
                      }
                  }

                  $Modules = @(
                      @{
                          Name = 'ActiveDirectoryDsc'
                          Version = '6.2.0'
                      },
                      @{
                          Name = 'ComputerManagementDsc'
                          Version = '9.0.0'
                      },
                      @{
                          Name = 'DnsServerDsc'
                          Version = '3.0.0'
                      },
                      @{
                          Name = 'NetworkingDsc'
                          Version = '9.0.0'
                      },
                      @{
                          Name = 'PSReadline'
                          Version = '2.2.6'
                      }
                  )

                  Write-Output 'Creating Temp Directory'
                  Try {
                      $Null = New-Item -Path 'C:\Temp\Module-AD' -ItemType 'Directory' -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to create Temp directory $_"
                      Exit 1
                  }

                  Write-Output 'Installing NuGet Package Provider'
                  Try {
                      $Null = Install-PackageProvider -Name 'NuGet' -MinimumVersion '2.8.5' -Force -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to install NuGet Package Provider $_"
                      Exit 1
                  }

                  Write-Output 'Setting PSGallery Respository to trusted'
                  Try {
                      Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted' -ErrorAction Stop
                  } Catch [System.Exception] {
                      Write-Output "Failed to set PSGallery Respository to trusted $_"
                      Exit 1
                  }

                  Write-Output 'Installing the needed Powershell DSC modules'
                  Foreach ($Module in $Modules) {
                      Try {
                          Install-Module -Name $Module.Name -RequiredVersion $Module.Version -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to Import Modules $_"
                          Exit 1
                      }
                  }
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
            nextStep: configureInstance
          - name: configureInstance
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{dc1InstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Get-SecretInfo {
                      [CmdletBinding()]
                      Param (
                          [Parameter(Mandatory = $True)][String]$Domain,
                          [Parameter(Mandatory = $True)][String]$SecretArn
                      )

                      Write-Output "Getting $SecretArn Secret"
                      Try {
                          $SecretContent = Get-SECSecretValue -SecretId $SecretArn -ErrorAction Stop | Select-Object -ExpandProperty 'SecretString' | ConvertFrom-Json -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to get $SecretArn Secret $_"
                          Exit 1
                      }

                      Write-Output 'Creating PSCredential object from Secret'
                      $Username = $SecretContent.username
                      $UserPassword = ConvertTo-SecureString ($SecretContent.password) -AsPlainText -Force
                      $DomainCredentials = New-Object -TypeName 'System.Management.Automation.PSCredential' ("$Domain\$Username", $UserPassword)
                      $Credentials = New-Object -TypeName 'System.Management.Automation.PSCredential' ($Username, $UserPassword)

                      $Output = [PSCustomObject][Ordered]@{
                          'Credentials'       = $Credentials
                          'DomainCredentials' = $DomainCredentials
                          'Username'          = $Username
                          'UserPassword'      = $UserPassword
                      }

                      Return $Output
                  }

                  Function Invoke-PreConfig {
                      Write-Output 'Temporarily disabling Windows Firewall'
                      Try {
                          Get-NetFirewallProfile -ErrorAction Stop | Set-NetFirewallProfile -Enabled False -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to disable Windows Firewall $_"
                          Exit 1
                      }

                      Write-Output 'Creating file directory for DSC public cert'
                      Try {
                          $Null = New-Item -Path 'C:\Temp\publickeys' -ItemType 'Directory' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create publickeys file directory $_"
                          Exit 1
                      }

                      Write-Output 'Creating certificate to encrypt credentials in MOF file'
                      Try {
                          $cert = New-SelfSignedCertificate -Type 'DocumentEncryptionCertLegacyCsp' -DnsName 'AWSQSDscEncryptCert' -HashAlgorithm 'SHA256' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to create self signed cert $_"
                          Exit 1
                      }

                      Write-Output 'Exporting the self signed public key certificate'
                      Try {
                          $Null = $cert | Export-Certificate -FilePath 'C:\Temp\publickeys\AWSQSDscPublicKey.cer' -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to copy self signed cert to publickeys directory $_"
                          Exit 1
                      }
                  }

                  Function Invoke-LcmConfig {
                      Write-Output 'Getting the DSC cert thumbprint to secure the MOF file'
                      Try {
                          $DscCertThumbprint = Get-ChildItem -Path 'cert:\LocalMachine\My' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get DSC cert thumbprint $_"
                          Exit 1
                      } 

                      [DSCLocalConfigurationManager()]
                      Configuration LCMConfig
                      {
                          Node 'localhost' {
                              Settings {
                                  RefreshMode                    = 'Push'
                                  ConfigurationModeFrequencyMins = 15
                                  ActionAfterReboot              = 'StopConfiguration'
                                  RebootNodeIfNeeded             = $false
                                  ConfigurationMode              = 'ApplyAndAutoCorrect'
                                  CertificateId                  = $DscCertThumbprint
                              }
                          }
                      }

                      Write-Output 'Generating MOF file for LCM'
                      $Null = LCMConfig -OutputPath 'C:\Temp\LCMConfig'

                      Write-Output 'Sets LCM configuration to MOF generated in previous command'
                      Try {
                          Set-DscLocalConfigurationManager -Path 'C:\Temp\LCMConfig' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to set LCM configuration $_"
                          Exit 1
                      } 
                  }

                  Function Get-EniConfig {
                      Write-Output 'Getting network configuration'
                      Try {
                          $NetIpConfig = Get-NetIPConfiguration -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to get network configuration $_"
                          Exit 1
                      }

                      Write-Output 'Grabbing the current gateway address in order to static IP correctly'
                      $GatewayAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4DefaultGateway' | Select-Object -ExpandProperty 'NextHop'

                      Write-Output 'Formatting IP address in format needed for IPAdress DSC resource'
                      $IpAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'IpAddress'
                      $Prefix = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'PrefixLength'
                      $IpAddr = 'IP/CIDR' -replace 'IP', $IpAddress -replace 'CIDR', $Prefix

                      Write-Output 'Getting MAC address'
                      Try {
                          $MacAddress = Get-NetAdapter -ErrorAction Stop | Select-Object -ExpandProperty 'MacAddress'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get MAC address $_"
                          Exit 1
                      }

                      $Output = [PSCustomObject][Ordered]@{
                          'GatewayAddress' = $GatewayAddress
                          'IpAddress'      = $IpAddr
                          'DnsIpAddress'   = $IpAddress
                          'MacAddress'     = $MacAddress
                      }
                      Return $Output
                  }

                  Function Set-DscConfiguration {
                      [CmdletBinding()]
                      param(
                          [Parameter(Mandatory = $true)][PSCredential]$DaCredentials,
                          [Parameter(Mandatory = $true)][string]$DomainDNSName,
                          [Parameter(Mandatory = $true)][string]$DomainNetBIOSName,
                          [Parameter(Mandatory = $true)][string]$GatewayAddress,
                          [Parameter(Mandatory = $true)][string]$InstanceIP,
                          [Parameter(Mandatory = $true)][string]$InstanceNetBIOSName,
                          [Parameter(Mandatory = $true)][PSCredential]$LaCredentials,
                          [Parameter(Mandatory = $true)][string]$MacAddress,
                          [Parameter(Mandatory = $true)][PSCredential]$RestoreModeCredentials,
                          [Parameter(Mandatory = $true)][string]$SiteName,
                          [Parameter(Mandatory = $true)][string]$VPCCIDR
                      )

                      $VPCDNS = '169.254.169.253'

                      Write-Output 'Getting the DSC encryption thumbprint to secure the MOF file'
                      Try {
                          $DscCertThumbprint = Get-ChildItem -Path 'cert:\LocalMachine\My' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get DSC cert thumbprint $_"
                          Exit 1
                      }

                      Write-Output 'Creating configuration data block that has the certificate information for DSC configuration processing'
                      $ConfigurationData = @{
                          AllNodes = @(
                              @{
                                  NodeName             = '*'
                                  #CertificateFile      = 'C:\Temp\publickeys\AWSQSDscPublicKey.cer'
                                  #Thumbprint           = $DscCertThumbprint
                                  PSDscAllowDomainUser = $true
                                  PsDscAllowPlainTextPassword = $true
                              },
                              @{
                                  NodeName = 'localhost'
                              }
                          )
                      }

                      Configuration ConfigInstance {
                          Import-DscResource -ModuleName 'PSDesiredStateConfiguration', 'NetworkingDsc', 'ComputerManagementDsc', 'DnsServerDsc', 'ActiveDirectoryDsc'
                          Node LocalHost {
                              NetAdapterName RenameNetAdapterPrimary {
                                  NewName    = 'Primary'
                                  MacAddress = $MacAddress
                              }
                              NetIPInterface DisableDhcp {
                                  Dhcp           = 'Disabled'
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[NetAdapterName]RenameNetAdapterPrimary'
                              }
                              IPAddress SetIP {
                                  IPAddress      = $InstanceIP
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[NetIPInterface]DisableDhcp'
                              }
                              DefaultGatewayAddress SetDefaultGateway {
                                  Address        = $GatewayAddress
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[IPAddress]SetIP'
                              }
                              DnsServerAddress DnsServerAddress {
                                  Address        = '127.0.0.1', '169.254.169.253'
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                                  DependsOn      = '[DefaultGatewayAddress]SetDefaultGateway'
                              }
                              DnsConnectionSuffix DnsConnectionSuffix {
                                  InterfaceAlias                 = 'Primary'
                                  ConnectionSpecificSuffix       = $DomainDNSName
                                  RegisterThisConnectionsAddress = $True
                                  UseSuffixWhenRegistering       = $False
                                  DependsOn                      = '[DnsServerAddress]DnsServerAddress'
                              }
                              IEEnhancedSecurityConfiguration 'DisableForAdministrators' {
                                  Role    = 'Administrators'
                                  Enabled = $false
                              }
                              IEEnhancedSecurityConfiguration 'DisableForUsers' {
                                  Role    = 'Users'
                                  Enabled = $false
                              }
                              WindowsFeature DnsTools {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-DNS-Server'
                                  DependsOn = '[DnsConnectionSuffix]DnsConnectionSuffix'
                              }
                              WindowsFeature RSAT-AD-Tools {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-AD-Tools'
                                  DependsOn = '[WindowsFeature]DnsTools'
                              }
                              WindowsFeature RSAT-ADDS {
                                  Ensure    = 'Present'
                                  Name      = 'RSAT-ADDS'
                                  DependsOn = '[WindowsFeature]RSAT-AD-Tools'
                              }
                              WindowsFeature GPMC {
                                  Ensure    = 'Present'
                                  Name      = 'GPMC'
                                  DependsOn = '[WindowsFeature]RSAT-ADDS'
                              }
                              WindowsFeature DNS {
                                  Ensure    = 'Present'
                                  Name      = 'DNS'
                                  DependsOn = '[WindowsFeature]GPMC'
                              }
                              WindowsFeature AD-Domain-Services {
                                  Ensure    = 'Present'
                                  Name      = 'AD-Domain-Services'
                                  DependsOn = '[WindowsFeature]DNS'
                              }
                              Service ActiveDirectoryWebServices {
                                  Name        = 'ADWS'
                                  StartupType = 'Automatic'
                                  State       = 'Running'
                                  DependsOn   = '[WindowsFeature]AD-Domain-Services'
                              }
                              Computer Rename {
                                  Name      = $InstanceNetBIOSName
                                  DependsOn = '[WindowsFeature]AD-Domain-Services'
                              }
                              User AdministratorPassword {
                                  UserName  = 'Administrator'
                                  Password  = $LaCredentials
                                  DependsOn = '[Computer]Rename'
                              }
                              ADDomain PrimaryDC {
                                  DomainName                    = $DomainDnsName
                                  DomainNetBIOSName             = $DomainNetBIOSName
                                  Credential                    = $DaCredentials
                                  SafemodeAdministratorPassword = $RestoreModeCredentials
                                  DatabasePath                  = 'C:\NTDS'
                                  LogPath                       = 'C:\NTDS'
                                  SysvolPath                    = 'C:\SYSVOL'
                                  DependsOn                     = '[User]AdministratorPassword'
                              }
                              WaitForADDomain WaitForPrimaryDC {
                                  DomainName  = $DomainDnsName
                                  WaitTimeout = 600
                                  DependsOn   = '[ADDomain]PrimaryDC'
                              }
                              ADReplicationSite RegionSite {
                                  Name                       = $SiteName
                                  RenameDefaultFirstSiteName = $true
                                  DependsOn                  = '[WaitForADDomain]WaitForPrimaryDC', '[Service]ActiveDirectoryWebServices'
                              }
                              ADReplicationSubnet VPCCIDR {
                                  Name      = $VPCCIDR
                                  Site      = $SiteName
                                  DependsOn = '[ADReplicationSite]RegionSite'
                              }
                              DnsServerForwarder ForwardtoVPCDNS {
                                  IsSingleInstance = 'Yes'
                                  IPAddresses      = $VPCDNS
                                  DependsOn        = '[WaitForADDomain]WaitForPrimaryDC'
                              }
                              ADOptionalFeature RecycleBin {
                                  FeatureName                       = 'Recycle Bin Feature'
                                  EnterpriseAdministratorCredential = $DaCredentials
                                  ForestFQDN                        = $DomainDnsName
                                  DependsOn                         = '[WaitForADDomain]WaitForPrimaryDC'
                              }
                              ADKDSKey KdsKey {
                                  Ensure                   = 'Present'
                                  EffectiveTime            = ((Get-Date).addhours(-10))
                                  AllowUnsafeEffectiveTime = $True
                                  DependsOn                = '[WaitForADDomain]WaitForPrimaryDC'
                              }
                          }
                      }
                      Write-Output 'Generating MOF file'
                      $Null = ConfigInstance -OutputPath 'C:\Temp\ConfigInstance' -ConfigurationData $ConfigurationData
                  }

                  $DaSecret = Get-SecretInfo -Domain '{{DomainNetBIOSName}}' -SecretArn '{{ADAdminSecParamName}}'
                  Invoke-PreConfig
                  Invoke-LcmConfig
                  $EniConfig = Get-EniConfig
                  Set-DscConfiguration -DaCredentials $DaSecret.DomainCredentials -DomainDNSName '{{DomainDNSName}}' -DomainNetBIOSName '{{DomainNetBIOSName}}' -GatewayAddress $EniConfig.GatewayAddress -InstanceIP $EniConfig.IpAddress -InstanceNetBIOSName '{{ADServer1NetBIOSName}}' -LaCredentials $DaSecret.Credentials -MacAddress $EniConfig.MacAddress -RestoreModeCredentials $DaSecret.Credentials -SiteName '{{global:REGION}}' -VPCCIDR '{{VPCCIDR}}'
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
            nextStep: runDc1Mof
          - name: runDc1Mof
            action: aws:runCommand
            onFailure: step:signalfailure
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{dc1InstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Invoke-DscStatusCheck {
                      $LCMState = Get-DscLocalConfigurationManager -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'LCMState'
                      If ($LCMState -eq 'PendingConfiguration' -Or $LCMState -eq 'PendingReboot') {
                          Exit 3010
                      } Else {
                          Write-Output 'DSC Config Completed'
                      }
                  }
                  Start-DscConfiguration 'C:\Temp\ConfigInstance' -Wait -Verbose -Force
                  Invoke-DscStatusCheck
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
            nextStep: DnsConfig
          - name: DnsConfig
            action: aws:runCommand
            onFailure: step:signalfailure
            inputs:
              DocumentName: AWS-RunPowerShellScript
              InstanceIds:
                - "{{dc1InstanceId.InstanceId}}"
              Parameters:
                commands: |-
                  Function Set-DnsDscConfiguration {
                      [CmdletBinding()]
                      param(
                          [Parameter(Mandatory = $true)][string]$ADServer1PrivateIP,
                          [Parameter(Mandatory = $false)][string]$VPCCIDR
                      )

                      $AClass = 0..8
                      $BClass = 9..16
                      $CClass = 17..24
                      $DClass = 25..32
                      $IP = $VPCCIDR.Split('/')[0]
                      [System.Collections.ArrayList]$IPArray = $IP -Split "\."
                      $Range = $VPCCIDR.Split('/')[1]
                      If ($AClass -contains $Range) {
                          [System.Array]$Number = $IPArray[0] 
                      } Elseif ($BClass -contains $Range) {
                          [System.Array]$Number = $IPArray[0, 1]
                      } Elseif ($CClass -contains $Range) {
                          [System.Array]$Number = $IPArray[0, 1, 2] 
                      } Elseif ($DClass -contains $Range) {
                          [System.Array]$Number = $IPArray[0, 1, 2, 3] 
                      } 
                      [System.Array]::Reverse($Number)
                      $IpRev = $Number -Join "."
                      $ZoneName = $IpRev + '.in-addr.arpa'

                      Write-Output 'Getting the DSC encryption thumbprint to secure the MOF file'
                      Try {
                          $DscCertThumbprint = Get-ChildItem -Path 'cert:\LocalMachine\My' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get DSC cert thumbprint $_"
                          Exit 1
                      }
                      
                      Write-Output 'Creating configuration data block that has the certificate information for DSC configuration processing'
                      $ConfigurationData = @{
                          AllNodes = @(
                              @{
                                  NodeName             = '*'
                                  #CertificateFile      = 'C:\Temp\publickeys\AWSQSDscPublicKey.cer'
                                  #Thumbprint           = $DscCertThumbprint
                                  PSDscAllowDomainUser = $true
                                  PsDscAllowPlainTextPassword = $true
                              },
                              @{
                                  NodeName = 'localhost'
                              }
                          )
                      }

                      Configuration ConfigInstance {
                          Import-DscResource -ModuleName 'PSDesiredStateConfiguration', 'NetworkingDsc', 'ComputerManagementDsc', 'DnsServerDsc', 'ActiveDirectoryDsc'
                          Node LocalHost {
                              DnsServerAddress DnsServerAddress {
                                  Address        = $ADServer1PrivateIP, '127.0.0.1'
                                  InterfaceAlias = 'Primary'
                                  AddressFamily  = 'IPv4'
                              }
                              DnsConnectionSuffix DnsConnectionSuffix {
                                  InterfaceAlias                 = 'Primary'
                                  ConnectionSpecificSuffix       = (Get-ADDomain | Select-Object -ExpandProperty 'DNSRoot')
                                  RegisterThisConnectionsAddress = $True
                                  UseSuffixWhenRegistering       = $False
                                  DependsOn                      = '[DnsServerAddress]DnsServerAddress'
                              }
                              DnsServerADZone CreateReverseLookupZone {
                                  Ensure           = 'Present'
                                  Name             = $ZoneName
                                  DynamicUpdate    = 'Secure'
                                  ReplicationScope = 'Forest'
                                  DependsOn        = '[DnsConnectionSuffix]DnsConnectionSuffix'
                              }
                              DnsServerScavenging SetServerScavenging {
                                  DnsServer          = 'localhost'
                                  ScavengingState    = $true
                                  ScavengingInterval = '7.00:00:00'
                                  RefreshInterval    = '7.00:00:00'
                                  NoRefreshInterval  = '7.00:00:00'
                                  DependsOn          = '[DnsServerADZone]CreateReverseLookupZone'
                              }
                          }
                      }
                      Write-Output 'Generating MOF file'
                      $Null = ConfigInstance -OutputPath 'C:\Temp\ConfigInstance' -ConfigurationData $ConfigurationData
                  }

                  Function Invoke-Cleanup {
                      [CmdletBinding()]
                      Param (
                          [Parameter(Mandatory = $true)][String]$VPCCIDR
                      )

                      Write-Output 'Setting Windows Firewall WinRM Public rule to allow VPC CIDR traffic'
                      Try {
                          Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP-PUBLIC' -RemoteAddress $VPCCIDR
                      } Catch [System.Exception] {
                          Write-Output "Failed allow WinRM Traffic from VPC CIDR $_"
                      }

                      Write-Output 'Removing DSC Configuration'
                      Try {    
                          Remove-DscConfigurationDocument -Stage 'Current' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed build DSC Configuration $_"
                      }

                      Write-Output 'Re-enabling Windows Firewall'
                      Try {
                          Get-NetFirewallProfile -ErrorAction Stop | Set-NetFirewallProfile -Enabled 'True' -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed re-enable firewall $_"
                      }

                      Write-Output 'Removing build files'
                      Try {
                          Remove-Item -Path 'C:\Temp' -Recurse -Force -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed remove build files $_"
                      }

                      Write-Output 'Removing self signed cert'
                      Try {
                          $SelfSignedThumb = Get-ChildItem -Path 'cert:\LocalMachine\My\' -ErrorAction Stop | Where-Object { $_.Subject -eq 'CN=AWSQSDscEncryptCert' } | Select-Object -ExpandProperty 'Thumbprint'
                          Remove-Item -Path "cert:\LocalMachine\My\$SelfSignedThumb" -DeleteKey -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed remove self signed cert $_"
                      }
                  }

                  Function Get-EniConfig {
                      Write-Output 'Getting network configuration'
                      Try {
                          $NetIpConfig = Get-NetIPConfiguration -ErrorAction Stop
                      } Catch [System.Exception] {
                          Write-Output "Failed to get network configuration $_"
                          Exit 1
                      }

                      Write-Output 'Grabbing the current gateway address in order to static IP correctly'
                      $GatewayAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4DefaultGateway' | Select-Object -ExpandProperty 'NextHop'

                      Write-Output 'Formatting IP address in format needed for IPAdress DSC resource'
                      $IpAddress = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'IpAddress'
                      $Prefix = $NetIpConfig | Select-Object -ExpandProperty 'IPv4Address' | Select-Object -ExpandProperty 'PrefixLength'
                      $IpAddr = 'IP/CIDR' -replace 'IP', $IpAddress -replace 'CIDR', $Prefix

                      Write-Output 'Getting MAC address'
                      Try {
                          $MacAddress = Get-NetAdapter -ErrorAction Stop | Select-Object -ExpandProperty 'MacAddress'
                      } Catch [System.Exception] {
                          Write-Output "Failed to get MAC address $_"
                          Exit 1
                      }

                      $Output = [PSCustomObject][Ordered]@{
                          'GatewayAddress' = $GatewayAddress
                          'IpAddress'      = $IpAddr
                          'DnsIpAddress'   = $IpAddress
                          'MacAddress'     = $MacAddress
                      }
                      Return $Output
                  }

                  $EniConfig = Get-EniConfig
                  Set-DnsDscConfiguration -ADServer1PrivateIP $EniConfig.DnsIpAddress -VPCCIDR '{{VPCCIDR}}'
                  Start-DscConfiguration 'C:\Temp\ConfigInstance' -Wait -Verbose -Force
                  Invoke-Cleanup -VPCCIDR '{{VPCCIDR}}'
              CloudWatchOutputConfig:
                CloudWatchOutputEnabled: true
                CloudWatchLogGroupName: !Sub /aws/RDS-Self-AD/${AWS::StackName}
          - name: CFNSignalEnd
            action: aws:branch
            inputs:
              Choices:
                - NextStep: signalsuccess
                  Not:
                    Variable: "{{StackName}}"
                    StringEquals: ""
                - NextStep: sleepend
                  Variable: "{{StackName}}"
                  StringEquals: ""
          - name: signalsuccess
            action: aws:executeAwsApi
            isEnd: true
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: OnPremDomainController
              StackName: "{{StackName}}"
              Status: SUCCESS
              UniqueId: "{{dc1InstanceId.InstanceId}}"
          - name: sleepend
            action: aws:sleep
            isEnd: true
            inputs:
              Duration: PT1S
          - name: signalfailure
            action: aws:executeAwsApi
            inputs:
              Service: cloudformation
              Api: SignalResource
              LogicalResourceId: OnPremDomainController
              StackName: "{{StackName}}"
              Status: FAILURE
              UniqueId: "{{dc1InstanceId.InstanceId}}"
      DocumentType: Automation
      Tags:
        - Key: StackName
          Value:
            Ref: AWS::StackName
  OnPremDomainController:
    Type: AWS::EC2::Instance
    CreationPolicy:
      ResourceSignal:
        Count: 1
        Timeout: PT60M
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            KmsKeyId: alias/aws/ebs
            VolumeSize: 45
            VolumeType: gp3
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref AMI
      InstanceType: t3.medium
      SecurityGroupIds:
        - Ref: OnPremDomainControllerSG
      SubnetId: !Ref VPCPublicSubnet1Subnet
      Tags:
        - Key: Domain
          Value: !Ref OnpremDomainName
        - Key: Name
          Value: ONPREM-DC01
      UserData:
        Fn::Base64:
          Fn::Sub:
            - |
              <powershell>
              $Params = @{
                  ADAdminSecParamName     = '${OnPremAdministratorSecret}'
                  ADServer1NetBIOSName    = 'ONPREM-DC01'
                  DomainDNSName           = '${OnpremDomainName}'
                  DomainNetBIOSName       = '${OnpremNetBIOSName}'
                  StackName               = '${AWS::StackName}'
                  VPCCIDR                 = '${VPCCIDR}'
              }
              Start-SSMAutomationExecution -DocumentName '${OnPremDomainControllerSsmAuto}' -Parameter $Params
              </powershell>
            - VPCCIDR: !GetAtt VPC.CidrBlock
  OnPremDomainControllerIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: "-1"
      Description: Security Group Rule between Domain Controllers
      FromPort: -1
      GroupId:
        Ref: OnPremDomainControllerSG
      SourceSecurityGroupId:
        Ref: OnPremDomainControllerSG
      ToPort: -1
Outputs:
  OnPremAdministratorSecretArn:
    Description: Administrator Account Secret ARN
    Value: !Ref OnPremAdministratorSecret
  DomainControllerInstanceID:
    Description: Domain Controller Instance ID
    Value: !Ref OnPremDomainController
  DomainControllerInstancePrivateIP:
    Description: Domain Controller Instance Private IP
    Value: !GetAtt OnPremDomainController.PrivateIp
  ManagementInstanceID:
    Description: Management Instance ID
    Value: !Ref MgmtInstance
  ManagementInstancePrivateIP:
    Description: Management Instance Private IP
    Value: !GetAtt MgmtInstance.PrivateIp
  DomainMemberSGID:
    Description: Domain Member Security Group ID
    Value: !Ref DomainMemberSG
  DomainControllerSGID:
    Description: Domain Controller Security Group ID
    Value: !Ref OnPremDomainControllerSG
