AWSTemplateFormatVersion: '2010-09-09' Description: >- (0003) - This template deploys two Windows Server Failover Clustering (WSFC) and SQL Server 2012 AlwaysOn Availability Group nodes. This template is intended to be installed into an existing VPC that was built using the sample reference architecture titled: "Implementing Active Directory Domain Services in the AWS Cloud" **WARNING** This template creates Amazon EC2 Windows instance and related resources. You will be billed for the AWS resources used if you create a stack from this template. (qs-1tsaami1i) Metadata: cfn-lint: config: ignore_checks: - W2001 - W4002 - W9002 - W9003 - W9006 - E9007 - E9010 Parameters: KeyPairName: Description: Public/private key pairs allow you to securely connect to your instance after it launches Type: AWS::EC2::KeyPair::KeyName WSFCNode1InstanceType: Description: Amazon EC2 instance type for the 1st WSFC Node Type: String Default: r3.2xlarge AllowedValues: - r3.xlarge - r3.2xlarge - r3.4xlarge ConstraintDescription: Only EBS Optimized instance types r3.xlarge, r3.2xlarge, r3.4xlarge allowed WSFCNode2InstanceType: Description: Amazon EC2 instance type for the 1st WSFC Node Type: String Default: r3.2xlarge AllowedValues: - r3.xlarge - r3.2xlarge - r3.4xlarge ConstraintDescription: Only EBS Optimized instance types r3.xlarge, r3.2xlarge, r3.4xlarge allowed DomainDNSName: Description: Fully qualified domain name (FQDN) of the forest root domain e.g. corp.example.com Type: String Default: example.com MinLength: '3' MaxLength: '25' AllowedPattern: '[a-zA-Z0-9\-]+\..+' DomainNetBIOSName: Description: NetBIOS name of the domain (upto 15 characters) for users of earlier versions of Windows e.g. CORP Type: String Default: example MinLength: '1' MaxLength: '15' AllowedPattern: '[a-zA-Z0-9\-]+' WSFCNode1NetBIOSName: Description: NetBIOS name of the 1st WSFC Node (up to 15 characters) Type: String Default: WSFCNode1 MinLength: '1' MaxLength: '15' AllowedPattern: '[a-zA-Z0-9\-]+' WSFCNode2NetBIOSName: Description: NetBIOS name of the 2nd WSFC Node (up to 15 characters) Type: String Default: WSFCNode2 MinLength: '1' MaxLength: '15' AllowedPattern: '[a-zA-Z0-9\-]+' ADServerNetBIOSName1: Description: NetBIOS name of the existing Domain Controller in AZ1 Type: String Default: DC1 MinLength: '1' MaxLength: '15' AllowedPattern: '[a-zA-Z0-9\-]+' ADServerNetBIOSName2: Description: NetBIOS name of the existing Domain Controller in AZ2 Type: String Default: DC2 MinLength: '1' MaxLength: '15' AllowedPattern: '[a-zA-Z0-9\-]+' DomainAdminUser: Description: User name for the account that will be added as Domain Administrator. This is separate from the default "Administrator" account Type: String Default: StackAdmin MinLength: '5' MaxLength: '25' AllowedPattern: '[a-zA-Z0-9]*' DomainAdminPassword: Description: Password for the domain admin user. Must be at least 8 characters containing letters, numbers and symbols Type: String MinLength: '8' MaxLength: '32' AllowedPattern: (?=^.{6,255}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* NoEcho: true SQLServiceAccount: Description: User name for the SQL Server Service Account. This Account is a Domain User. Type: String Default: sqlsa MinLength: '5' MaxLength: '25' AllowedPattern: '[a-zA-Z0-9]*' SQLServiceAccountPassword: Description: Password for the SQL Service account. Must be at least 8 characters containing letters, numbers and symbols Type: String MinLength: '8' MaxLength: '32' AllowedPattern: (?=^.{6,255}$)((?=.*\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))^.* NoEcho: true ADServer1PrivateIp: Description: Fixed private IP for the first existing Active Directory server located in AZ1 Type: String Default: 10.0.0.10 ADServer2PrivateIp: Description: Fixed private IP for the second existing Active Directory serverr located in AZ2 Type: String Default: 10.0.64.10 WSFCNode1PrivateIp: Description: Primary private IP for the 1st WSFC Node located in AZ1 Type: String Default: 10.0.0.100 WSFCNode1PrivateIp2: Description: Secondary private IP for WSFC cluster on 1st WSFC Node Type: String Default: 10.0.0.101 WSFCNode1PrivateIp3: Description: Third private IP for Availability Group Listner on 1st WSFC Node Type: String Default: 10.0.0.102 WSFCNode2PrivateIp: Description: Primary private IP for the 2nd WSFC Node located in AZ2 Type: String Default: 10.0.64.100 WSFCNode2PrivateIp2: Description: Secondary private IP for WSFC cluster on 2nd WSFC Node Type: String Default: 10.0.64.101 WSFCNode2PrivateIp3: Description: Third private IP for Availability Group Listner on 2nd WSFC Node Type: String Default: 10.0.64.102 DomainMemberSGID: Description: ID of the Domain Member Security Group (e.g., sg-7f16e910) Type: AWS::EC2::SecurityGroup::Id VPC: Description: ID of the VPC (e.g., vpc-0343606e) Type: AWS::EC2::VPC::Id PrivateSubnet1Id: Description: ID of the subnet you want to provision the first WSFC node into (e.g., subnet-a0246dcd) Type: AWS::EC2::Subnet::Id PrivateSubnet2Id: Description: ID of the subnet you want to provision the second WSFC node into (e.g., subnet-e3246d8e) Type: AWS::EC2::Subnet::Id PrivSub1CIDR: Description: CIDR Block for Private Subnet located in AZ1 Type: String Default: 10.0.0.0/19 AllowedPattern: '[a-zA-Z0-9]+\..+' PrivSub2CIDR: Description: CIDR Block for for Private Subnet located in AZ2 Type: String Default: 10.0.64.0/19 AllowedPattern: '[a-zA-Z0-9]+\..+' SQLServerVersion: Description: Version of SQL Server to install on WSFC Nodes. Options include either "2014" or "2012" Type: String AllowedValues: - '2014' - '2012' Default: '2014' VPCCIDR: Description: CIDR Block used by the VPC Type: String Default: 10.0.0.0/16 AllowedPattern: '[a-zA-Z0-9]+\..+' Mappings: AWSAMIRegionMap: AMI: WS2012R2: Windows_Server-2012-R2_RTM-English-64Bit-Base-2019.07.12 ap-northeast-1: WS2012R2: ami-06823103be2218b98 ap-northeast-2: WS2012R2: ami-050e65d9f2ec90145 ap-northeast-3: WS2012R2: ami-04dfed75117825fec ap-south-1: WS2012R2: ami-045e1f06f29929467 ap-southeast-1: WS2012R2: ami-0c322369af7718803 ap-southeast-2: WS2012R2: ami-0813db0de4ddab990 ca-central-1: WS2012R2: ami-0850dfaa3ee6f6233 eu-central-1: WS2012R2: ami-024652d0a3df40e74 eu-west-1: WS2012R2: ami-0d2f69fcc5f00c97a eu-west-2: WS2012R2: ami-0998a91bb1756752d eu-west-3: WS2012R2: ami-0d6e54e3504cc1615 sa-east-1: WS2012R2: ami-044d56b6baa621d7d us-east-1: WS2012R2: ami-094a644f1fb9e4ce3 us-east-2: WS2012R2: ami-0a1a54d8690206089 us-west-1: WS2012R2: ami-094dcbdb1aa24c8da us-west-2: WS2012R2: ami-0f8967b5f815400c0 Resources: WSFCNode1WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WSFCNode1 Properties: Handle: !Ref 'WSFCNode1WaitHandle' Timeout: '5400' WSFCNode1WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WSFCNode2WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: WSFCNode2 Properties: Handle: !Ref 'WSFCNode2WaitHandle' Timeout: '5400' WSFCNode2WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WSFCNode1: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: configSets: config: - CFNsetup - rename - join - createSqlAccount - installsql - finalize CFNsetup: files: c:\cfn\cfn-hup.conf: content: !Join - '' - - "[main]\n" - stack= - !Ref 'AWS::StackId' - "\n" - region= - !Ref 'AWS::Region' - "\n" c:\cfn\hooks.d\cfn-auto-reloader.conf: content: !Join - '' - - "[cfn-auto-reloader-hook]\n" - "triggers=post.update\n" - "path=Resources.WSFCNode1.Metadata.AWS::CloudFormation::Init\n" - 'action=cfn-init.exe -v -s ' - !Ref 'AWS::StackId' - ' -r WSFCNode1' - ' --region ' - !Ref 'AWS::Region' - "\n" services: windows: cfn-hup: enabled: true ensureRunning: true files: - c:\cfn\cfn-hup.conf - c:\cfn\hooks.d\cfn-auto-reloader.conf commands: a-set-execution-policy: command: !Join - '' - - powershell.exe -command Set-ExecutionPolicy RemoteSigned -Force waitAfterCompletion: '0' rename: commands: 1-execute-powershell-script-RenameComputer: command: !Join - '' - - 'powershell.exe -Command Rename-Computer -NewName ' - !Ref 'WSFCNode1NetBIOSName' - ' -Restart' waitAfterCompletion: forever join: commands: a-set-dns-servers: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Get-NetAdapter | Set-DnsClientServerAddress -ServerAddresses ' - !Ref 'ADServer1PrivateIp' - ',' - !Ref 'ADServer2PrivateIp' - '"' waitAfterCompletion: '30' b-join-domain: command: !Join - '' - - 'powershell.exe ' - -Command " - 'Add-Computer -DomainName ' - !Ref 'DomainDNSName' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - -Restart" waitAfterCompletion: forever createSqlAccount: commands: 1-create-sqlacct: command: !Join - '' - - 'powershell.exe -Command ' - '"Invoke-Command -Scriptblock{ New-ADUser ' - '-Name ' - !Ref 'SQLServiceAccount' - ' ' - '-UserPrincipalName ' - !Ref 'SQLServiceAccount' - '@' - !Ref 'DomainDNSName' - ' ' - '-AccountPassword (ConvertTo-SecureString ' - !Ref 'SQLServiceAccountPassword' - ' -AsPlainText -Force) ' - '-Enabled $true ' - '-PasswordNeverExpires $true -EA 0} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' installsql: files: C:\cfn\scripts\WSFC.ps1: content: !Join - '' - - Install-WindowsFeature failover-clustering -IncludeManagementTools - "\n" c:\cfn\scripts\DownloadSQLEE.ps1: source: https://s3.amazonaws.com/aws-quickstart/quickstart-microsoft-sql/scripts/DownloadSQLEE-Legacy.ps1 c:\cfn\scripts\OpenWSFCPorts.bat: source: https://s3.amazonaws.com/aws-quickstart/quickstart-microsoft-sql/scripts/OpenWSFCPorts.bat C:\cfn\scripts\AddUserToGroup.ps1: content: !Join - '' - - Param( - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$ServerName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$GroupName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$DomainNetBIOSName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$UserName' - "\n" - ) - $de = [ADSI]"WinNT://$ServerName/$GroupName,group" - "\n" - $de.psbase.Invoke("Add",([ADSI]"WinNT://$DomainNetBIOSName/$UserName").path) - "\n" C:\Users\Default\Desktop\InstallSQLEE.bat: content: !Join - '' - - powershell.exe -command Install-WindowsFeature NET-Framework-Core - "\n" - powershell.exe -command "dir \\ - !Ref 'ADServerNetBIOSName1' - \sqlinstall\*.iso | Mount-DiskImage" - "\n" - powershell.exe -command "Get-Volume | ?{$_.DriveType -eq 'CD-ROM'} | select -ExpandProperty DriveLetter" > %temp%\driveletter.txt - "\n" - SET /p driveletter=<%temp%\driveletter.txt - "\n" - '%driveletter%:\SETUP.EXE ' - '/QS ' - '/Action=Install ' - '/Features=SQLEngine,Replication,FullText,Conn,BOL,ADV_SSMS ' - '/INSTANCENAME=MSSQLSERVER ' - /SQLSVCACCOUNT=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'SQLServiceAccount' - '" ' - /SQLSVCPASSWORD=" - !Ref 'SQLServiceAccountPassword' - '" ' - /AGTSVCACCOUNT=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'SQLServiceAccount' - '" ' - /AGTSVCPASSWORD=" - !Ref 'SQLServiceAccountPassword' - '" ' - /SQLSYSADMINACCOUNTS=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - '" ' - '/SQLUSERDBDIR="D:\MSSQL\DATA" ' - '/SQLUSERDBLOGDIR="E:\MSSQL\LOG" ' - '/SQLBACKUPDIR="f:\MSSQL\Backup" ' - '/SQLTEMPDBDIR="f:\MSSQL\TempDB" ' - '/SQLTEMPDBLOGDIR="f:\MSSQL\TempDB" ' - /IACCEPTSQLSERVERLICENSETERMS - "\n" - C:\PROGRA~1\MICROS~1\CLIENT~1\ODBC\110\Tools\Binn\SQLCMD.EXE -i c:\cfn\scripts\MaxDOP.sql c:\cfn\scripts\MaxDOP.sql: source: https://s3.amazonaws.com/aws-quickstart/quickstart-microsoft-sql/scripts/MaxDOP.sql commands: a-execute-powershell-script-WSFC: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\WSFC.ps1' waitAfterCompletion: '0' b-create-folder: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock {New-Item -ItemType directory -Path c:\ -Name sqlinstall} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' c-create-share: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock { New-SmbShare -Name sqlinstall -Path c:\sqlinstall -FullAccess everyone } -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' d-execute-powershell-DownloadSQLEE: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\DownloadSQLEE.ps1 -SQLServerVersion ' - !Ref 'SQLServerVersion' - ' -DestServer ' - !Ref 'ADServerNetBIOSName1' - ' -DestShare sqlinstall' waitAfterCompletion: '0' e-open-WSFC-ports: command: C:\cfn\scripts\OpenWSFCPorts.bat f-execute-powershell-script-AddUserToGroup: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\AddUserToGroup.ps1 -UserName ' - !Ref 'DomainAdminUser' - ' -ServerName ' - !Ref 'WSFCNode1NetBIOSName' - ' -DomainNetBIOSName ' - !Ref 'DomainNetBIOSName' - ' -GroupName "Administrators"' - "\n" waitAfterCompletion: '0' g-execute-powershell-script-AddUserToGroup: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\AddUserToGroup.ps1 -UserName ' - !Ref 'SQLServiceAccount' - ' -ServerName ' - !Ref 'WSFCNode1NetBIOSName' - ' -DomainNetBIOSName ' - !Ref 'DomainNetBIOSName' - ' -GroupName "Administrators"' - "\n" waitAfterCompletion: '0' h-enable-autologon: command: !Join - '' - - 'powershell.exe -Command ' - '"New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name AutoAdminLogon -Value 1' - ; - 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name DefaultUserName -Value ' - !Ref 'DomainAdminUser' - '@' - !Ref 'DomainDNSName' - ' | out-null' - ; - 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name DefaultPassword -Value ' - !Ref 'DomainAdminPassword' - '"' waitAfterCompletion: '0' i-set-startup-script: command: !Join - '' - - 'powershell.exe -Command ' - '"New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce -Name InstallSQL -Value C:\Users\Default\Desktop\InstallSQLEE.bat' - '"' waitAfterCompletion: '0' j-reboot: command: !Join - '' - - powershell.exe -Command Restart-Computer -Force waitAfterCompletion: forever k-force-ad-replication: command: !Join - '' - - 'powershell.exe -Command ' - '"Invoke-Command -Scriptblock{ repadmin /syncall /A /e /P } -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' l-cleanup-registry: command: !Join - '' - - 'powershell.exe -Command ' - '"Remove-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name AutoAdminLogon' - ; - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName - ; - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword - '"' waitAfterCompletion: '900' finalize: commands: 1-signal-success: command: !Join - '' - - cfn-signal.exe -e 0 " - !Ref 'WSFCNode1WaitHandle' - '"' Properties: ImageId: !FindInMap - AWSAMIRegionMap - !Ref 'AWS::Region' - WS2012R2 InstanceType: !Ref 'WSFCNode1InstanceType' EbsOptimized: true NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: "0" SubnetId: !Ref 'PrivateSubnet1Id' PrivateIpAddresses: - Primary: true PrivateIpAddress: !Ref 'WSFCNode1PrivateIp' - Primary: false PrivateIpAddress: !Ref 'WSFCNode1PrivateIp2' - Primary: false PrivateIpAddress: !Ref 'WSFCNode1PrivateIp3' GroupSet: - !Ref 'DomainMemberSGID' - !Ref 'WSFCSecurityGroup' - !Ref 'WSFCClientSecurityGroup' Tags: - Key: Name Value: !Ref 'WSFCNode1NetBIOSName' BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeSize: 100 VolumeType: gp2 - DeviceName: /dev/xvdca VirtualName: ephemeral0 Volumes: - VolumeId: !Ref 'WSFCNode1Volume1' Device: /dev/xvdb - VolumeId: !Ref 'WSFCNode1Volume2' Device: /dev/xvdc - VolumeId: !Ref 'WSFCNode1Volume3' Device: /dev/xvdd KeyName: !Ref 'KeyPairName' UserData: !Base64 Fn::Join: - '' - - "<script>\n" - 'cfn-init.exe -v -c config -s ' - !Ref 'AWS::StackId' - ' -r WSFCNode1 ' - ' --region ' - !Ref 'AWS::Region' - "\n" - </script> WSFCNode2: Type: AWS::EC2::Instance DependsOn: WSFCNode1WaitCondition Metadata: AWS::CloudFormation::Init: configSets: config: - CFNsetup - rename - join - installsql - configsql - finalize CFNsetup: files: c:\cfn\cfn-hup.conf: content: !Join - '' - - "[main]\n" - stack= - !Ref 'AWS::StackId' - "\n" - region= - !Ref 'AWS::Region' - "\n" c:\cfn\hooks.d\cfn-auto-reloader.conf: content: !Join - '' - - "[cfn-auto-reloader-hook]\n" - "triggers=post.update\n" - "path=Resources.WSFCNode2.Metadata.AWS::CloudFormation::Init\n" - 'action=cfn-init.exe -v -s ' - !Ref 'AWS::StackId' - ' -r WSFCNode2' - ' --region ' - !Ref 'AWS::Region' - "\n" c:\cfn\scripts\MaxDOP.sql: source: https://s3.amazonaws.com/aws-quickstart/quickstart-microsoft-sql/scripts/MaxDOP.sql services: windows: cfn-hup: enabled: true ensureRunning: true files: - c:\cfn\cfn-hup.conf - c:\cfn\hooks.d\cfn-auto-reloader.conf commands: a-set-execution-policy: command: !Join - '' - - powershell.exe -command Set-ExecutionPolicy RemoteSigned -Force waitAfterCompletion: '0' rename: commands: 1-execute-powershell-script-RenameComputer: command: !Join - '' - - 'powershell.exe -Command Rename-Computer -NewName ' - !Ref 'WSFCNode2NetBIOSName' - ' -Restart' waitAfterCompletion: forever join: commands: a-set-dns-servers: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Get-NetAdapter | Set-DnsClientServerAddress -ServerAddresses ' - !Ref 'ADServer2PrivateIp' - ',' - !Ref 'ADServer1PrivateIp' - '"' waitAfterCompletion: '30' b-join-domain: command: !Join - '' - - 'powershell.exe ' - -Command " - 'Add-Computer -DomainName ' - !Ref 'DomainDNSName' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - -Restart" waitAfterCompletion: forever installsql: files: C:\cfn\scripts\WSFC.ps1: content: !Join - '' - - Install-WindowsFeature failover-clustering -IncludeManagementTools - "\n" c:\cfn\scripts\OpenWSFCPorts.bat: source: https://s3.amazonaws.com/aws-quickstart/quickstart-microsoft-sql/scripts/OpenWSFCPorts.bat C:\cfn\scripts\AddUserToGroup.ps1: content: !Join - '' - - Param( - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$ServerName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$GroupName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$DomainNetBIOSName,' - "\n" - '[Parameter(Mandatory=$True)]' - "\n" - '[string]$UserName' - "\n" - ) - $de = [ADSI]"WinNT://$ServerName/$GroupName,group" - "\n" - $de.psbase.Invoke("Add",([ADSI]"WinNT://$DomainNetBIOSName/$UserName").path) - "\n" C:\Users\Default\Desktop\InstallSQLEE.bat: content: !Join - '' - - powershell.exe -command Install-WindowsFeature NET-Framework-Core - "\n" - powershell.exe -command "dir \\ - !Ref 'ADServerNetBIOSName1' - \sqlinstall\*.iso | Mount-DiskImage" - "\n" - powershell.exe -command "Get-Volume | ?{$_.DriveType -eq 'CD-ROM'} | select -ExpandProperty DriveLetter" > %temp%\driveletter.txt - "\n" - SET /p driveletter=<%temp%\driveletter.txt - "\n" - '%driveletter%:\SETUP.EXE ' - '/QS ' - '/Action=Install ' - '/Features=SQLEngine,Replication,FullText,Conn,BOL,ADV_SSMS ' - '/INSTANCENAME=MSSQLSERVER ' - /SQLSVCACCOUNT=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'SQLServiceAccount' - '" ' - /SQLSVCPASSWORD=" - !Ref 'SQLServiceAccountPassword' - '" ' - /AGTSVCACCOUNT=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'SQLServiceAccount' - '" ' - /AGTSVCPASSWORD=" - !Ref 'SQLServiceAccountPassword' - '" ' - /SQLSYSADMINACCOUNTS=" - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - '" ' - '/SQLUSERDBDIR="D:\MSSQL\DATA" ' - '/SQLUSERDBLOGDIR="E:\MSSQL\LOG" ' - '/SQLBACKUPDIR="f:\MSSQL\Backup" ' - '/SQLTEMPDBDIR="f:\MSSQL\TempDB" ' - '/SQLTEMPDBLOGDIR="f:\MSSQL\TempDB" ' - /IACCEPTSQLSERVERLICENSETERMS - "\n" - C:\PROGRA~1\MICROS~1\CLIENT~1\ODBC\110\Tools\Binn\SQLCMD.EXE -i c:\cfn\scripts\MaxDOP.sql commands: a-execute-powershell-script-WSFC: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\WSFC.ps1' waitAfterCompletion: '0' b-open-WSFC-ports: command: C:\cfn\scripts\OpenWSFCPorts.bat c-execute-powershell-script-AddUserToGroup: command: !Join - '' - - 'powershell.exe ' - -ExecutionPolicy - ' RemoteSigned' - ' C:\cfn\scripts\AddUserToGroup.ps1 -UserName ' - !Ref 'SQLServiceAccount' - ' -ServerName ' - !Ref 'WSFCNode2NetBIOSName' - ' -DomainNetBIOSName ' - !Ref 'DomainNetBIOSName' - ' -GroupName "Administrators"' - "\n" waitAfterCompletion: '0' d-enable-autologon: command: !Join - '' - - 'powershell.exe -Command ' - '"New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name AutoAdminLogon -Value 1' - ; - 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name DefaultUserName -Value ' - !Ref 'DomainAdminUser' - '@' - !Ref 'DomainDNSName' - ; - 'New-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name DefaultPassword -Value ' - !Ref 'DomainAdminPassword' - '"' waitAfterCompletion: '0' e-set-startup-script: command: !Join - '' - - 'powershell.exe -Command ' - '"New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce -Name InstallSQL -Value C:\Users\Default\Desktop\InstallSQLEE.bat' - '"' waitAfterCompletion: '0' f-reboot: command: !Join - '' - - powershell.exe -Command Restart-Computer -Force waitAfterCompletion: forever configsql: files: c:\cfn\scripts\InstallWsfc.ps1: content: !Join - '' - - 'New-Cluster -Name WSFCluster1 -Node ' - !Ref 'WSFCNode1NetBIOSName' - ',' - !Ref 'WSFCNode2NetBIOSName' - ' -StaticAddress ' - !Ref 'WSFCNode1PrivateIp2' - ',' - !Ref 'WSFCNode2PrivateIp2' - "\n" commands: a-set-startup-script: command: !Join - '' - - 'powershell.exe -Command ' - '"New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce -Name InstallWSFC -Value ''powershell.exe -ExecutionPolicy RemoteSigned -Command c:\cfn\scripts\InstallWsfc.ps1''' - '"' waitAfterCompletion: '1500' b-reboot: command: !Join - '' - - powershell.exe -Command Restart-Computer -Force waitAfterCompletion: forever c-force-ad-replication: command: !Join - '' - - 'powershell.exe -Command ' - '"Invoke-Command -Scriptblock{ repadmin /syncall /A /e /P } -ComputerName ' - !Ref 'ADServerNetBIOSName2' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '300' d-create-folder: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock {New-Item -ItemType directory -Path c:\ -Name witness} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' e-create-folder2: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock {New-Item -ItemType directory -Path c:\ -Name replica} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' f-create-share: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock { New-SmbShare -Name witness -Path c:\witness -FullAccess everyone } -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' g-create-share2: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock { New-SmbShare -Name replica -Path c:\replica -FullAccess everyone } -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' h-set-share-permissions: command: !Join - '' - - 'powershell.exe -Command ' - '"' - Invoke-Command -ScriptBlock { - ' $acl = Get-Acl c:\witness;' - ' $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(''' - !Ref 'DomainNetBIOSName' - \WSFCluster1$','FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'); - ' $acl.AddAccessRule($rule);' - ' Set-Acl c:\witness $acl' - '} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' i-set-share-permissions2: command: !Join - '' - - 'powershell.exe -Command ' - '"' - Invoke-Command -ScriptBlock { - ' $acl = Get-Acl c:\replica;' - ' $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(''' - !Ref 'DomainNetBIOSName' - \ - !Ref 'SQLServiceAccount' - ''',''FullControl'', ''ContainerInherit, ObjectInherit'', ''None'', ''Allow'');' - ' $acl.AddAccessRule($rule);' - ' Set-Acl c:\replica $acl' - '} -ComputerName ' - !Ref 'ADServerNetBIOSName1' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' j-set-wsfc-cluster-quorum: command: !Join - '' - - 'powershell.exe -Command ' - '"Invoke-Command -Scriptblock{ ' - Set-ClusterQuorum -NodeAndFileShareMajority \\ - !Ref 'ADServerNetBIOSName1' - \witness - ' } -ComputerName ' - !Ref 'WSFCNode2NetBIOSName' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' k-enable-always-on-node1: command: !Join - '' - - 'powershell.exe -Command ' - '"' - 'Invoke-Command -ScriptBlock {Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned;Enable-SqlAlwaysOn -ServerInstance ' - !Ref 'WSFCNode1NetBIOSName' - ' -Force} -ComputerName ' - !Ref 'WSFCNode1NetBIOSName' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' l-enable-always-on-node2: command: !Join - '' - - 'powershell.exe -Command ' - '"Invoke-Command -Scriptblock{ ' - 'Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned;Enable-SqlAlwaysOn -ServerInstance ' - !Ref 'WSFCNode2NetBIOSName' - ' -Force' - ' } -ComputerName ' - !Ref 'WSFCNode2NetBIOSName' - ' -Credential ' - (New-Object System.Management.Automation.PSCredential(' - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' - ''',' - '(ConvertTo-SecureString ' - !Ref 'DomainAdminPassword' - ' -AsPlainText -Force))) ' - '"' waitAfterCompletion: '0' finalize: commands: a-cleanup-registry: command: !Join - '' - - 'powershell.exe -Command ' - '"Remove-ItemProperty -Path ''HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'' -Name AutoAdminLogon' - ; - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName - ; - Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword - '"' waitAfterCompletion: '0' b-signal-success: command: !Join - '' - - cfn-signal.exe -e 0 " - !Ref 'WSFCNode2WaitHandle' - '"' Properties: ImageId: !FindInMap - AWSAMIRegionMap - !Ref 'AWS::Region' - WS2012R2 InstanceType: !Ref 'WSFCNode2InstanceType' EbsOptimized: true NetworkInterfaces: - DeleteOnTermination: true DeviceIndex: "0" SubnetId: !Ref 'PrivateSubnet2Id' PrivateIpAddresses: - Primary: true PrivateIpAddress: !Ref 'WSFCNode2PrivateIp' - Primary: false PrivateIpAddress: !Ref 'WSFCNode2PrivateIp2' - Primary: false PrivateIpAddress: !Ref 'WSFCNode2PrivateIp3' GroupSet: - !Ref 'DomainMemberSGID' - !Ref 'WSFCSecurityGroup' - !Ref 'WSFCClientSecurityGroup' Tags: - Key: Name Value: !Ref 'WSFCNode2NetBIOSName' BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeSize: 100 VolumeType: gp2 Encrypted: true - DeviceName: /dev/xvdca VirtualName: ephemeral0 Volumes: - VolumeId: !Ref 'WSFCNode2Volume1' Device: /dev/xvdb - VolumeId: !Ref 'WSFCNode2Volume2' Device: /dev/xvdc - VolumeId: !Ref 'WSFCNode2Volume3' Device: /dev/xvdd KeyName: !Ref 'KeyPairName' UserData: !Base64 Fn::Join: - '' - - "<script>\n" - 'cfn-init.exe -v -c config -s ' - !Ref 'AWS::StackId' - ' -r WSFCNode2 ' - ' --region ' - !Ref 'AWS::Region' - "\n" - </script> WSFCNode1Volume1: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 0 - !GetAZs '' WSFCNode1Volume2: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 0 - !GetAZs '' WSFCNode1Volume3: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 0 - !GetAZs '' WSFCNode2Volume1: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 1 - !GetAZs '' WSFCNode2Volume2: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 1 - !GetAZs '' WSFCNode2Volume3: Type: AWS::EC2::Volume Properties: Size: 500 VolumeType: gp2 Encrypted: true AvailabilityZone: !Select - 1 - !GetAZs '' WSFCSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable the WSFC and SQL AlwaysOn Availability Group communications VpcId: !Ref 'VPC' SecurityGroupIngress: - IpProtocol: icmp FromPort: -1 ToPort: -1 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: icmp FromPort: -1 ToPort: -1 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 135 ToPort: 135 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 135 ToPort: 135 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 137 ToPort: 137 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 137 ToPort: 137 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 445 ToPort: 445 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 445 ToPort: 445 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1433 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1433 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 3343 ToPort: 3343 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 3343 ToPort: 3343 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 5022 ToPort: 5022 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 5022 ToPort: 5022 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 5985 ToPort: 5985 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 5985 ToPort: 5985 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 49152 ToPort: 65535 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 49152 ToPort: 65535 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: udp FromPort: 137 ToPort: 137 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: udp FromPort: 137 ToPort: 137 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: udp FromPort: 3343 ToPort: 3343 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: udp FromPort: 3343 ToPort: 3343 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: udp FromPort: 49152 ToPort: 65535 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: udp FromPort: 49152 ToPort: 65535 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1434 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1434 CidrIp: !Ref 'PrivSub2CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1434 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1434 CidrIp: !Ref 'PrivSub2CIDR' WSFCClientSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable SQL Client Connections from instances inside the VPC VpcId: !Ref 'VPC' SecurityGroupIngress: - IpProtocol: tcp FromPort: 1433 ToPort: 1433 CidrIp: !Ref 'PrivSub1CIDR' - IpProtocol: tcp FromPort: 1433 ToPort: 1433 CidrIp: !Ref 'PrivSub2CIDR' Outputs: DomainAdmin: Value: !Join - '' - - !Ref 'DomainNetBIOSName' - \ - !Ref 'DomainAdminUser' Description: Domain administrator account LocalAdmin: Value: Administrator Description: Please retrieve Administrator password of the instance WSFCNode1NetBIOSName: Value: !Ref 'WSFCNode1NetBIOSName' Description: NetBIOS name of the 1st WSFC Node WSFCNode2NetBIOSName: Value: !Ref 'WSFCNode2NetBIOSName' Description: NetBIOS name of the 2nd WSFC Node