Microsoft Workloads on AWS
Simplifying Active Directory domain join with AWS Systems Manager
A new version of this blog was recently published. The new blog post offers an updated solution to streamline your Active Directory credentials with AWS Secrets Manager. This replaces AWS Systems Manager Parameter Store and AWS Lambda function while maintaining the same Automation workflow.
In this blog post, I will present a solution for managing the Active Directory domain membership for a dynamic fleet of Amazon Elastic Compute Cloud (Amazon EC2) Windows instances using Automation, a capability of AWS Systems Manager.
Using Systems Manager Automation, you can dynamically automate domain join and unjoin activities with Microsoft Active Directory Domain Services (AD DS) for your Windows instances on Amazon EC2. With Automation, you can use runbooks to perform domain join and unjoin activities manually, automatically, or as event-driven. Thus, you no longer have to rely on legacy login scripts or third-party software to perform such operations, which cannot be easily scaled in the cloud.
With built-in multi-account and multi-Region support, Systems Manager Automation can ease the domain join and unjoin tasks for your Windows workloads at any size. Automation is flexible as it supports hybrid AD DS environments, self-managed AD DS running on Windows EC2 instances, and AWS Directory Service for Microsoft Active Directory (AWS Managed Microsoft AD). Finally, Systems Manager Automation runbooks can be the glue to the other event-driven AWS services. For example, Automation can be used together with Amazon EC2 Auto Scaling to support domain join or unjoin activities for your dynamic fleet of Windows EC2 instances.
Solution Overview
This blog post will demonstrate how to manually run Automation against a Windows EC2 instance to join an AWS Managed Microsoft AD domain. The steps outlined in this blog post are also applicable for AD environments in a hybrid cloud or a self-managed AD running on EC2 instances. To learn more about deploying and hosting AD on AWS, please visit our QuickStart guide or visit our documentation to learn more about AWS Managed Microsoft AD.
There are a couple of components to this solution:
- An AWS CloudFormation template to deploy the Automation runbook and set the parameters
- An AWS Lambda function within the CloudFormation template, which creates a secure string parameter in Parameter Store, a capability of AWS Systems Manager, to store the AD domain join user’s password.
- To create the Automation runbook manually, download the template and author a custom Automation runbook. Visit the AWS documentation to learn how to create runbooks with the Document Builder or how to create runbooks with the Editor.
- AD join/unjoin relevant data stored in Parameter Store
- Automation actions run PowerShell
NOTE: This guide assumes your DNS has been configured already for your Active Directory environment running in AWS. Configuring DNS at scale is beyond the scope of this blog. However, you can review existing guides to configure such an environment with either Amazon Route 53 Resolver endpoints or DHCP option sets in Amazon VPC.
To deploy the runbook and parameters automatically, download and save the AWS CloudFormation template from Github locally to your computer to create a new CloudFormation stack. Creating a new stack will simplify the deployment of the Automation runbook and create the appropriate parameters to perform the AD join/unjoin activities automatically. To learn more about CloudFormation stack creation, visit the AWS documentation.
A CloudFormation stack, presented In Figure 1, is created with the name SSM-Automation-Demo. The first input parameter in the stack requires you to enter a name for the Automation runbook. For this demo, I will use SSM-Automation-AD-Join, though you can enter any name you like as long as it meets the requirements referenced on the stack creation screen. The remaining four input parameters in the stack create Systems Manager parameters that are relevant to the AD domain join and unjoin activities. I will look at the Parameter Store in more detail later in the post.
Now, let’s take a look at the runbook.
The Automation runbook workflow
This custom runbook is the central piece of the AD domain join and unjoin workflow. Using this runbook, administrators can manually or automatically trigger domain join or unjoin for their Windows EC2 instance to and from an AD domain. This blog post will demonstrate the manual execution of the Automation.
First, let’s review the parameters that need to be specified.
Parameters
- AutomationAssumeRole – The service role gives the automation permission to perform actions on your behalf, for example, when configuring automatic domain join/unjoin activities with AWS Systems Manager State Manager.
- InstanceId – The Windows EC2 instance Id where your runbook will run the commands. The instance Id is persistent throughout the runbook.
- DomainJoinActivity – A dropdown option allowing you to select to either Join or Unjoin from an AD domain. Depending on which option you choose, the runbook will perform the appropriate follow-up steps.
Runbook steps
There are 9 steps in the Automation workflow. Below are descriptions of the key steps and how they factor into the AD domain join/unjoin activities.
- assertInstanceIsWindows– The first step checks if the EC2 instance is running Windows and will only continue if the platform is Windows. The
aws:assertAwsResourceProperty
action allows you to assert a specific resource state or event state for a specific Automation step. To learn more aboutaws:assertAwsResourceProperty
, follow the official documentation. - chooseDomainJoinActivity– This step is associated with the
aws:branch
action, which allows Automation to conditionally follow the steps relevant to join an AD domain or unjoin from an AD domain. It is similar in concept to a basic if-else statement in PowerShell, for example. To learn more aboutaws:branch
, follow the official documentation.
Below are the decision trees that explain the steps in sequence for both successful and failed domain joins (Figure 2) and unjoins (Figure 3).
Join and unjoin domain
There are two steps, joinDomain and unjoinDomain, that are associated with the aws:runCommand
action. Either of these steps can be run directly on respective EC2 instances. Since I am working on Windows Server explicitly, I want to run domain join and unjoin related commands using PowerShell with the aid of the AWS-RunPowerShellScript command document. To learn more about aws:runCommand
, follow the official documentation.
The PowerShell statements in both the joinDomain (Figure 4) and unjoinDomain (Figure 5) steps are wrapped in the try
and catch
blocks. To learn more, visit about_Try_Catch_Finally. If either step succeeds, the EC2 instance will be tagged to indicate the instance has been joined or unjoined to or from the AD domain, respectively. Reboots are only applied upon a successful domain join executing the rebootServer step upon the join activity. Otherwise, the runbook will stop the EC2 instance by running the stopServer step upon the unjoin activity. If either step fails, a Failed status is returned and outputted in the Systems Manager Automation console; the Automation also runs the failADEC2Tag step, which tags the EC2 instance to reflect the failure, and then runs the stopServer step to stop the EC2 instance. This allows you to troubleshoot the failure and try again. (For example, the domain controllers may not be responding or there is a typo with one of the parameters, etc.)
If ((Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'PartOfDomain') -eq $false) {
Try {
$targetOU = (Get-SSMParameterValue -Name 'defaultTargetOU' -ErrorAction Stop).Parameters[0].Value
$domainName = (Get-SSMParameterValue -Name 'domainName' -ErrorAction Stop).Parameters[0].Value
$domainJoinUserName = (Get-SSMParameterValue -Name 'domainJoinUserName' -ErrorAction Stop).Parameters[0].Value
$domainJoinPassword = (Get-SSMParameterValue -Name 'domainJoinPassword' -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value | ConvertTo-SecureString -AsPlainText -Force
} Catch [System.Exception] {
Write-Output " Failed to get SSM Parameter(s) $_"
}
$domainCredential = New-Object System.Management.Automation.PSCredential($domainJoinUserName, $domainJoinPassword)
Try {
Write-Output "Attempting to join $env:COMPUTERNAME to Active Directory domain: $domainName and moving $env:COMPUTERNAME to the following OU: $targetOU."
Add-Computer -ComputerName $env:COMPUTERNAME -DomainName $domainName -Credential $domainCredential -OUPath $targetOU -Restart:$false -ErrorAction Stop
} Catch [System.Exception] {
Write-Output "Failed to add computer to the domain $_"
Exit 1
}
} Else {
Write-Output "$env:COMPUTERNAME is already part of the Active Directory domain $domainName."
Exit 0
}
Figure 4: The PowerShell statement that implements the joinDomain step.
If ((Get-CimInstance -ClassName 'Win32_ComputerSystem' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'PartOfDomain') -eq $true) {
Try {
$domainName = (Get-SSMParameterValue -Name 'domainName' -ErrorAction Stop).Parameters[0].Value
$domainJoinUserName = (Get-SSMParameterValue -Name 'domainJoinUserName' -ErrorAction Stop).Parameters[0].Value
$domainJoinPassword = (Get-SSMParameterValue -Name 'domainJoinPassword' -WithDecryption:$true -ErrorAction Stop).Parameters[0].Value | ConvertTo-SecureString -AsPlainText -Force
} Catch [System.Exception] {
Write-Output "Failed to get SSM Parameter(s) $_"
}
$domainCredential = New-Object System.Management.Automation.PSCredential($domainJoinUserName, $domainJoinPassword)
If (-not (Get-WindowsFeature -Name 'RSAT-AD-Tools' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'Installed')) {
Write-Output 'Installing RSAT AD Tools to allow domain joining'
Try {
$Null = Add-WindowsFeature -Name 'RSAT-AD-Tools' -ErrorAction Stop
} Catch [System.Exception] {
Write-Output "Failed to install RSAT AD Tools $_"
Exit 1
}
}
$getADComputer = (Get-ADComputer -Identity $env:COMPUTERNAME -Credential $domainCredential)
$distinguishedName = $getADComputer.DistinguishedName
Try {
Remove-Computer -ComputerName $env:COMPUTERNAME -UnjoinDomainCredential $domainCredential -Verbose -Force -Restart:$false -ErrorAction Stop
Remove-ADComputer -Credential $domainCredential -Identity $distinguishedName -Server $domainName -Confirm:$False -Verbose -ErrorAction Stop
} Catch [System.Exception] {
Write-Output "Failed to remove $env:COMPUTERNAME from the $domainName domain and in a Windows Workgroup. $_"
Exit 1
}
} Else {
Write-Output "$env:COMPUTERNAME is not part of the Active Directory domain $domainName and already part of a Windows Workgroup."
Exit 0
}
Figure 5: The PowerShell statement that implements the unjoinDomain step.
Parameters to store AD domain configuration
The runbook uses parameters stored in Parameter Store. Parameter Store provides secure, hierarchical storage for managing configuration data. You can store configuration values as either plain text or as encrypted ciphertext. These values will be referenced as Systems Manager parameters in the runbook, specifically in the PowerShell script, by using the unique name that was specified when the parameters were created.
Pay close attention to the Get-SSMParameterValue cmdlet. This PowerShell cmdlet reads the parameter values and stores it in a variable. This is particularly useful for the AD credentials, specifically the AD domain user’s password, which is encrypted and decrypted with an AWS Key Management Service (AWS KMS) ID. Credentials are stored securely. They are not hard coded in the PowerShell code, and AD admins can rotate the password and update the parameters without modifying the code.
In particular, this solution uses 4 parameters:
- Fully qualified domain name (FQDN) of the AD domain name
- AD domain user’s username
- AD domain user’s password
- Specific Organizational Unit (OU) in AD where the computer account for the domain-joined instance will be created
Below are examples of the parameters and values required to complete the domain join and unjoin activities. The CloudFormation template will create the parameters automatically after filling in the input parameters as seen in Figure 1. To manually create these parameters, review our documentation to learn more.
(NOTE: the parameter names and values are cAsE-SeNsItIvE).
Name (do not change these values) | Type | Data type | Value (example values) | AWS KMS Key ID Required |
domainName | String | text | corp.example.com | No |
domainJoinUserName | String | text | CORP\Admin | No |
domainJoinPassword | SecureString | text | YOURPASSWORD | Yes |
defaultTargetOU | String | text | OU=Computers,OU=CORP,dc=corp,dc=example,dc=com | No |
Figure 6: Table reference of the parameter names and sample values.
Putting it all together
Now that the key steps in the runbook and the parameters needed are recognized, I will walk through an example of how to manually domain join an EC2 instance.
- Open the AWS Systems Manager console at https://console.thinkwithwp.com/systems-manager/.
- In the navigation pane, choose Automation, and then choose Execute automation (Figure 8).
- In the Automation document list, choose the SSM-Automation-AD-Join Choose the Owned by me tab to view the custom runbook.
- In the Document details section, verify that Document version is set to Default version at runtime. While I will choose the default version in this example, the system includes the following version options:
- Default version at runtime: Choose this option if the Automation runbook is updated periodically and a new default version is assigned.
- Latest version at runtime: Choose this option if the Automation runbook is updated periodically, and you want to run the version that was most recently updated.
- 1 (Default): Choose this option to run the first version of the document, which is the default (Figure 9).
- Choose Next.
- In the Execution Mode section, choose Simple execution.
- In the Input parameters section, specify the required inputs.
- Select an Instance ID from the interactive instance picker.
- In the DomainJoinActivity dropdown, select Join.
- Choose Execute (Figure 10).
NOTE: Simple execution targets a single EC2 instance. To deploy this runbook against multiple EC2 instances, you can choose Rate control in the Execution Mode section. In this mode, you can define your targets based on different parameters. For example, you can select InstanceId as the Parameter and Parameter Values as the Targets. From there, you can multi-select all of the EC2 instances you want to join to an AD domain. Refer to Figure 11 to see what this looks like and our documentation for more details on how to deploy runbooks for multiple targets.
- The Execution detail screen will show a green banner stating Execution has been initiated. From here, each individual step is run in the Executed steps.
- The overall status is available in the Executions status.
- Each step can be viewed by clicking on the Step ID link along with start and end time stamps
- The Step ID is unique to every execution.
- Each step indicates individual Success or Failed in the Status column (Figure 12).
- For example, to see the details of Step #3, I can click on the 469c0afd-6ab4-4b51-9e4d-ac5a336e92bf Step ID
- Since this step runs PowerShell locally on the Windows EC2 instance, the Outputs displays PowerShell output relevant to the Add-Computer cmdlet I would expect.
- To go back to the Execution detail screen, click on Back to execution detail (Figure 13).
- After all the execution steps have successfully completed, the Windows EC2 instance has been joined to the AD domain.
The process to join a Windows EC2 instance to an AD domain is easy using Automation. With Automation, you can review the steps as they are processed or follow-up from within the Systems Manager console at any time. If a failure occurs on any step, the entire process will stop, and the failed process can be reviewed by clicking on the Step ID.
Below are example screenshots of a failed domain join attempt due to an improper parameter configuration. In Figure 14, I can see that Step #3 failed and is clearly indicated with a status of Failed. Figure 15 shows the specific output of the failure; in this case, it is a specific PowerShell error related to the Add-Computer cmdlet.
Finally, to remove the EC2 instance from an AD domain, simply rerun the Automation runbook and select Unjoin in the DomainJoinActivity dropdown as seen in Figure 16. Similar to the domain join process, the unjoin process will run the Remove-Computer and Remove-ADComputer cmdlets to remove the instance from AD and clean up AD immediately. (Please reference Figure 5 for more details for the code.)
Cleanup
To delete the resources created by the CloudFormation template, go to the AWS CloudFormation console in the management account. Choose the stack you created, and then choose Delete.
Summary
There you have it! Using Systems Manager Automation, you can simplify the domain join and unjoin processes for your Amazon EC2 instances running Windows Server. In part two of this series, I will show you how to automate the domain join or unjoin process of a fleet of Windows EC2 instances.
AWS can help you assess how your company can get the most out of cloud. Join the millions of AWS customers that trust us to migrate and modernize their most important applications in the cloud. To learn more on modernizing Windows Server or SQL Server, visit Windows on AWS. Contact us to start your modernization journey today.