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.

Will it run? How to run your favorite legacy OS in a Windows Azure VM

Introduction

Old OSs are very popular, at least judging by the number of, for want of a better word, “legacy” servers I find in customer data centers. The old workhorses Windows Server 2003/2003 R2, Windows 2000 and even good old Windows NT pop up in disturbing numbers. With them, the question “Will Windows X, run in a Windows Azure VM?” often follows. To be able to answer that question we first need to talk about “run” vs. “supported”. A lot of OSs can be made to run in a Windows Azure VM, but that does not mean that Microsoft will support that OS. Unfortunately a lot of people are only interested in the “run” part of the equation, ignoring any problems of supportability down the road. Support is very straight forward; the oldest supported Windows OS running in a Windows Azure VM is Windows Server 2008 R2 x64, as stated in KB2721672: Microsoft server software support for Windows Azure Virtual Machines. So if you want support the buck stops there. But what can we make to run if we throw supportability to the wind…

It’s still Hyper-V

All the physical servers in a Windows Azure data center run a modified version of Windows Server with the Hyper-V role installed. So basically all the capabilities of Hyper-V should be available in Windows Azure, unless they have been specifically blocked. The About Virtual Machines and Guest Operating Systems page lists all the guest operating systems that are supported on Hyper-V, so lets assume that we can run all of those. I’m not going to test all the OSs on that list, but rather focus on some specific golden oldies (all 32-bit editions):

  • Windows 2000
  • Windows XP
  • Windows Server 2003

There is also a lot of rumor on the Internet concerning support for 32-bit (x86) VMs in Windows Azure. Although all the images in the gallery and the fact that the earliest supported server OS (Windows Server 2008 R2) is only available in 64-bit (x64), this is never actually stated anywhere officially. (At least not that I have found.)

Another area where there is a lot of talk is concerning client operating systems in Windows Azure VMs. In this case Microsoft is very clear; client OSs are not supported and it is a violation of the license to install and run one in a Windows Azure VM. For the sake of scientific discovery I will ignore that issue for this post.

So lets see what’s what…

Basic requirements

I’m going to work from these premises during my testing:

  • The legacy OS needs to be on the supported list for Hyper-V
  • Hyper-V Integration Components need to be available
    Either on the vmguest.iso image, already in the OS or available for download.
  • Remote Desktop for Windows VMs and SSH for Linux VMs must be available
  • Only VHD images are supported in Windows Azure, not VHDX
  • None of the selected legacy OSs will support being generalized in a way that Windows Azure can use, so we can only upload OS disks, not images. (Windows Azure uses the setup technology introduced in Windows Vista, codenamed Panther, which legacy OSs don’t.)

Basic steps

Here is what we need to do (at least):

  1. Use a Hyper-V host running Windows Server 2012 R2
    This probably not a requirement, but the latest and greatest is always nice.
  2. Install a VM with the legacy OS, must select Generation 1 and the VHD disk format.
  3. Once OS install completes, install the Hyper-V integration components
  4. Update the OS through e.g. Microsoft Update
    Having the latest updates will maximize the chance of success
  5. If you want remote PowerShell or WinRM make sure to install those optional updates.
  6. Enable Remote Desktop and make sure the Remote Desktop exception is enabled in the Windows Firewall if it is enabled.
  7. If you are using WinRM technology configure that, preferably with HTTPS instead of HTTP, and enable the appropriate firewall exceptions.
  8. If you are actually trying to run a production server with a legacy OS in Windows Azure (something you should not do), remove any special drivers or low level software that might break the VM. You can always install this alter.
  9. Shut down the VM on your Hyper-V Server
  10. Upload the VHD to a Windows Azure storage account (Add-AzureVHD)
  11. Register the new blob as an OS image (Add-AzureDisk)
  12. Start the VM in Azure and log on (hopefully).

The results

Using the above method I was able to obtain these results:

OS Runs Notes
Windows 2000 Yes
  • Professional, Server and Advanced Server all share the same code base and all work
  • Windows Server 2012 removed Windows 2000 support from the Integration components so you have to use integrations components from Windows Server 2008 R2.
  • Performance was not great on my VM…
Windows XP x86 Yes
Windows Server 2003 x86 Yes Windows Server 2003 R2 is pretty much the same code base so it should run fine too. And in fact it does.

I assume that Windows Server 2003/2003R2 and XP x64 will also work.

Feel free to try some golden oldies yourself and use the comments for your results. Good luck.