Joining an Azure VM to the domain is actually fairly easy. It’s no different from joining any other domain, as you will see in a second. However, it does get a bit more complicated if you expect to work with Group Policy or join a specific Organizational Unit (OU).
To join an AADDS domain, only a small number of things need to be in place. Most of these are obvious if you think about them, but here goes:
- Network connectivity
- AADDS is a managed service, but it is deployed into a virtual network, which means that your VMs that you want to join to that domain require network connectivity to the AADDS vnet. This can be because the VM is deployed to the same vnet, or because the vnets are peered. Vnet peering in this case also works across regions. A VM in a vnet in Europe North can join an AADDS domain deployed to Australia East. Depending on what it is that you’re deploying in North Europe and if it depends on AD authentication, it now heavily depends on a Single Point of Failure in Australia East.
- a Windows Azure VM
- an Azure AD (AAD) user that is a member of the AAD group AAD DC Administrators
- being a member of this group allows that user to join VMs to the domain
- password synchronization must be complete
- vnet DNS updated to point at AADDS IPs
The status of the password synchronization between AAD and AAADDS can be checked in the Azure Portal.
The DNS configuration of our vnet can be checked in the AADDS service portal and can also be updated from there by clicking on Configure.
Join AADDS ^
Now, if all those prerequisites are met, then we can log in to our Azure VM with a local administrator account.
There are a couple of ways to join our shiny new VM to AADDS, with some being more desirable than others. I will list the most common ones here, from not desirable to most desirable:
- manual domain join via GUI
- manual execution of PowerShell locally on the VM
- automation tool executing PowerShell on the VM
- Azure Resource Manager (ARM) template VM extension (or another tool that can apply extensions to VMs)
When I say desirable, I mean the most Azure native way of doing it. They all work, but with all things, it’s impossible to scale manual tasks reliably to anything beyond a single execution. Because of this, I will show you the PowerShell command to execute on a VM in order to join the domain and also an example ARM template snippet to join a VM resource to the domain.
Once logged in to the VM, we start an administrative PowerShell session.
$userName = 'firstname.lastname@example.org'
$userPassword = Read-Host -Prompt "Enter password" -AsSecureString
$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $userName, $userPassword
First, we need a PowerShell object with our user’s credentials that we can then reuse in a minute. The above snippet will do exactly that, but just make sure you change the username to a user who is a member of the AAD group we mentioned earlier.
The next step is the actual domain join, using PowerShell.
Add-Computer -ComputerName veryimportantvm -Credential $credentials -DomainName david-obrien.net -Verbose
The result should look like this.
If you get an error message, then like with any other domain join, you should take a look at the log file in c:\windows\debug\netsetup.log. There are two common errors:
- domain cannot be found
- make sure the DNS setting on your vnet is correct
- username or password incorrect
- make sure the password synchronization has succeeded
Once rebooted, we can now log in to the VM with AAD user accounts.
We can check the server manager now and also do a whoami to check who we are.
Join the domain using the Azure VM extension ^
Alternatively (and this is my recommended approach for when you are deploying VMs through ARM templates), here’s a snippet of an ARM template that you can use to automatically join your Azure VMs to the domain at deployment time without the need for a user to log in and execute the PowerShell snippet from above. For more information about using the snippet below, read the documentation on the domain join extension type here.
"User": "[concat(parameters('domainToJoin'), '\\', parameters('domainUsername'))]",