Debugging unattended domain join on Windows Azure VMs

Introduction

One great thing about Windows Azure PowerShell is the ability to join a VM to an Active Directory domain during provisioning, this ability is not available in the portal. Joining a domain during Windows setup is nothing new, and it is accomplished by using the normal Windows unattended setup mechanism; namely unattend.xml. In unattend.xml you can specify the following information about the domain you want to join and the account that has permissions to perform the join operation in the directory (excerpt has been edited for readability):

<component name=”Microsoft-Windows-UnattendedJoin” processorArchitecture=”amd64″>
<Identification>
<Credentials>
<Domain><NetBIOS domain name></Domain>
<Username><NT type (samaccountname) username></Username>
<Password><password in encrypted form></Password>
</Credentials>
<JoinDomain><FQDN of domain to join></JoinDomain>
<MachineObjectOU><DN of OU to place machine account in></MachineObjectOU>
</Identification>
</component>

You usually don’t create the unattend.xml file manually, but rather use a tool like Windows System Image Manager from the Windows Assessment and Deployment Kit (ADK).

I recently had an issue where none of my new VMs would join the domain during provisioning, leaving them all in a workgroup. That led me to compile the following during my debugging.

Add-WindowsProvisioningConfig

The cmdlet that enabled a Windows Azure VM to join a domain is called Add-WindowsProvisioningConfig. If used with the –WindowsDomain parameter it lets you specify these additional parameters:

Parameter Info
JoinDomain FQDN of domain to join
Domain NetBIOS name of account with permission to join computers to domain specified in the JoinDomain parameter. This is usually always the NetBIOS name of the domain from JoinDomin, but could be from another domain in the forest or a trusted domain.
DomainUserName NT-style username (samaccountname) of account with permissions to join the domain specified in JoinDomain
DomainPassword Password of the account with permissions to join the domain specified in JoinDomain
MachineObjectOU DN of OU where the computer account of the VM should be placed

This info is loaded into the unattend.xml file used to setup the new VM. After that the unattended setup process continues  as with any regular Windows install.

Logs

If domain join does not work there are several logs that should be examined.

Operation logs in the portal

Under Management Services in the Windows Azure portal you find the Operation Logs.

image

The provisioning of a new VM is an AddRole operation, first one with Status Started and then another one with Status Succeeded. By examining the details of these you can see what is passed to the back end. This is particularly useful if you are using scripts which pass variables to the domain join parameters. Here you see exactly what is passed. Note that the password is omitted from the logs, that is not an error.

Windows Setup logs

After the portal has done its job the rest of the provisioning is left to the normal Windows unattended setup process and is handled entirely within the VM. That means that any normal troubleshooting techniques for unattended Windows setup applies. The working folder of Windows Setup is C:WindowsPanther. Here you will find the unattend.xml file used during setup (if is has not been deleted by setup itself, Azure does not do this), and the logs for the entire setup process:

File Info
unattend.xml The answer file for unattended Windows setup
setuperr.log Any major errors encountered during setup
setupact.log All setup activity

The first thing you should do is check unattend.xml and verify that it contains the correct info.

Domain join during unattended setup is done with the djoin.exe executable, so to debug domin join we need to search for that in setupact.log. Also searching for the words Warning or Error could reveal useful information. This is an excerpt from the setupact.log with the error I encountered:

2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Begin
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Loading input parameters…
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: AccountData = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: UnsecureJoin = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: MachinePassword = [secret not logged]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: JoinDomain = [corp.mydomain.com]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: JoinWorkgroup = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Domain = [corp]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Username = [Administrator]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Password = [secret not logged]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: MachineObjectOU = [CN=Computers,DC=corp,DC=mydomain,DC=com]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: DebugJoin = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: DebugJoinOnlyOnThisError = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: TimeoutPeriodInMinutes = [NULL]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Checking that auto start services have started.
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Calling DsGetDcName for corp.mydomain.com…
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: DsGetDcName returned [dc2.corp.mydomain.com]
2014-03-11 14:09:45, Info     [DJOIN.EXE] Unattended Join: Constructed domain parameter [corp.mydomain.comDC2.corp.mydomain.com]
2014-03-11 14:09:45, Warning     [DJOIN.EXE] Unattended Join: NetJoinDomain attempt failed: 0x2, will retry in 10 seconds…
2014-03-11 22:09:56, Warning     [DJOIN.EXE] Unattended Join: NetJoinDomain attempt failed: 0x2, will retry in 10 seconds…

As you can see from this case, the NetJoinDomain function fails and is eventually abandoned. You can also find these errors in the System log:

ID: 4097
Source: NetJoin
Info: The machine SERVER1 attempted to join the domain corp.mydomain.comDC2.corp.mydomain.com but failed. The error code was 2.

To find out more about this we need to look at another log; C:WindowsdebugNetSetup.log. This file contains information for alle domain join operations performed on a machine, not just the ones during setup. Here is a section from mine:

03/11/2014 22:24:44:407 NetpDoDomainJoin
03/11/2014 22:24:44:407 NetpMachineValidToJoin: ‘SERVER1’
03/11/2014 22:24:44:407     OS Version: 6.2
03/11/2014 22:24:44:407     Build number: 9200 (9200.win8_gdr.130531-1504)
03/11/2014 22:24:44:407     SKU: Windows Server 2012 Datacenter
03/11/2014 22:24:44:407     Architecture: 64-bit (AMD64)
03/11/2014 22:24:44:407 NetpDomainJoinLicensingCheck: ulLicenseValue=1, Status: 0x0
03/11/2014 22:24:44:407 NetpGetLsaPrimaryDomain: status: 0x0
03/11/2014 22:24:44:407 NetpMachineValidToJoin: status: 0x0
03/11/2014 22:24:44:407 NetpJoinDomain
03/11/2014 22:24:44:407     Machine: SERVER1
03/11/2014 22:24:44:407     Domain: corp.mydomain.comDC2.corp.mydomain.com
03/11/2014 22:24:44:407     MachineAccountOU: CN=Computers,DC=corp,DC=mydomain,DC=com
03/11/2014 22:24:44:407     Account: corpAdministrator
03/11/2014 22:24:44:407     Options: 0x23
03/11/2014 22:24:44:407 NetpLoadParameters: loading registry parameters…
03/11/2014 22:24:44:407 NetpLoadParameters: DNSNameResolutionRequired not found, defaulting to ‘1’ 0x2
03/11/2014 22:24:44:407 NetpLoadParameters: DomainCompatibilityMode not found, defaulting to ‘0’ 0x2
03/11/2014 22:24:44:407 NetpLoadParameters: status: 0x2
03/11/2014 22:24:44:407 NetpDisableIDNEncoding: no domain dns available – IDN encoding will NOT be disabled
03/11/2014 22:24:44:407 NetpJoinDomainOnDs: NetpDisableIDNEncoding returned: 0x0
03/11/2014 22:24:44:407 NetpJoinDomainOnDs: status of connecting to dc ‘\DC2.corp.mydomain.com’: 0x0
03/11/2014 22:24:44:407 NetpJoinDomainOnDs: Passed DC ‘DC2.corp.mydomain.com’ verified as DNS name ‘\DC2.corp.mydomain.com’
03/11/2014 22:24:44:407 NetpLoadParameters: loading registry parameters…
03/11/2014 22:24:44:407 NetpLoadParameters: DNSNameResolutionRequired not found, defaulting to ‘1’ 0x2
03/11/2014 22:24:44:407 NetpLoadParameters: DomainCompatibilityMode not found, defaulting to ‘0’ 0x2
03/11/2014 22:24:44:407 NetpLoadParameters: status: 0x2
03/11/2014 22:24:44:407 NetpDsGetDcName: status of verifying DNS A record name resolution for ‘DC2.corp.mydomain.com’: 0x0
03/11/2014 22:24:44:407 NetpProvisionComputerAccount:
03/11/2014 22:24:44:407     lpDomain: corp.mydomain.com
03/11/2014 22:24:44:407     lpMachineName: SERVER1
03/11/2014 22:24:44:407     lpMachineAccountOU: CN=Computers,DC=corp,DC=mydomain,DC=com
03/11/2014 22:24:44:407     lpDcName: DC2.corp.mydomain.com
03/11/2014 22:24:44:407     lpDnsHostName: (NULL)
03/11/2014 22:24:44:407     lpMachinePassword: (null)
03/11/2014 22:24:44:407     lpAccount: corpAdministrator
03/11/2014 22:24:44:407     lpPassword: (non-null)
03/11/2014 22:24:44:407     dwJoinOptions: 0x23
03/11/2014 22:24:44:407     dwOptions: 0x40000003
03/11/2014 22:24:44:407 NetpLdapBind: Verified minimum encryption strength on DC2.corp.mydomain.com: 0x0
03/11/2014 22:24:44:407 NetpLdapGetLsaPrimaryDomain: reading domain data
03/11/2014 22:24:44:407 NetpGetNCData: Reading NC data
03/11/2014 22:24:44:407 NetpGetDomainData: Lookup domain data for: DC=corp,DC=mydomain,DC=com
03/11/2014 22:24:44:407 NetpGetDomainData: Lookup crossref data for: CN=Partitions,CN=Configuration,DC=corp,DC=mydomain,DC=com
03/11/2014 22:24:44:423 NetpLdapGetLsaPrimaryDomain: result of retrieving domain data: 0x0
03/11/2014 22:24:44:423 NetpCheckForDomainSIDCollision: returning 0x0(0).
03/11/2014 22:24:44:423 NetpGetDnsHostName: PrimaryDnsSuffix defaulted to DNS domain name: corp.mydomain.com
03/11/2014 22:24:44:423 NetpGetComputerObjectDn: Cracking DNS domain name corp.mydomain.com/ into Netbios on \DC2.corp.mydomain.com
03/11/2014 22:24:44:423 NetpGetComputerObjectDn: Crack results:     name = corp
03/11/2014 22:24:44:423 NetpGetComputerObjectDn: Cracking account name corpSERVER1$ on \DC2.corp.mydomain.com
03/11/2014 22:24:44:423 NetpGetComputerObjectDn: Crack results:     Account does not exist
03/11/2014 22:24:44:423 NetpGetComputerObjectDn: Specified path ‘CN=Computers,DC=corp,DC=mydomain,DC=com’ is not an OU
03/11/2014 22:24:44:423 NetpCreateComputerObjectInDs: NetpGetComputerObjectDn failed: 0x2
03/11/2014 22:24:44:423 NetpProvisionComputerAccount: LDAP creation failed: 0x2
03/11/2014 22:24:44:423 NetpProvisionComputerAccount: Cannot retry downlevel, specifying OU is not supported
03/11/2014 22:24:44:423 ldap_unbind status: 0x0
03/11/2014 22:24:44:423 NetpJoinCreatePackagePart: status:0x2.
03/11/2014 22:24:44:423 NetpAddProvisioningPackagePart: status:0x2.
03/11/2014 22:24:44:423 NetpJoinDomainOnDs: Function exits with status of: 0x2
03/11/2014 22:24:44:423 NetpJoinDomainOnDs: status of disconnecting from ‘\DC2.corp.mydomain.com’: 0x0
03/11/2014 22:24:44:423 NetpJoinDomainOnDs: NetpResetIDNEncoding on ‘(null)’: 0x0
03/11/2014 22:24:44:423 NetpDoDomainJoin: status: 0x2

I have highlighted my particular error. As you can see I specified the DN of the computers container as the location of the new computer object. Since Computers is in fact a container and not an OU, the NetpCreateComputerObjectInDs function fails. After I specified a bona fide OU in my Add-WindowsAzureProvisioningConfig cmdlet the VM was successfully joined to my domain. If you actually want the computer object to be placed in the Computers container just omit the MachineObjectOU parameter from Add-WindowsAzureProvisioningConfig. Since the Computers container is the default location for new computer objects, unless redirected by the admin or overridden, your VMs account will end up there.

2 thoughts on “Debugging unattended domain join on Windows Azure VMs”

  1. At least in the latest version of Azure PowerShell (0.9.8), the cmdlette Add-WindowsProvisioningConfig does not exist.

    In Azure Service Management, the cmdlette is Add-AzureProvisioningConfig. In Azure Resource Manager, I have not been able to find a similar cmdlette.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.