Geeking Out

Geeking out: Offline domain join

Way back in 2009, with the release of Windows Server 2008 R2 and Windows 7, a new feature was added called “offline domain join” (ODJ for short). Initially, the feature wasn’t understood very well (imagine that) and people expected it would perform magic, enabling you to easily join all of your devices into Active Directory even if they aren’t on the corporate network. And while that’s not far from the truth, you do have to think about how it works:

  1. Run a utility, or use the equivalent API call, to create a computer account in Active Directory. This creates a “blob” (yes, even the official documentation calls it a blob) that represents that specific computer.
  2. Copy that blob (which effectively contains the ID and password for that computer) using some means to the computer where you want to use it.
  3. Inject that blob into Windows and reboot the computer, at which point it is joined to Active Directory.

So how does that work? Well, as you can probably guess, computer accounts in Active Directory work pretty much the same as user accounts in that each one has a password that is used to access the domain. So, it makes sense that the ODJ blob would contain the computer account name (COMPUTERNAME$) as well as the initial random password generated for that computer account. And if you look one some of the open-source tools that have reverse-engineered the ODJ blob file contents, you can see that. Here’s one example from

./djoin djoin-sample.txt
        Domain Info Version: 1 (0xcccccccc00081001)
        Size: 3a8 bytes

        Machine Information:
                Computer Name: testyalemu
                Computer Password: mZPf:TVv?REPP<;'Io-@5awA*R+MdStv:6f&+%dVQ2t&n'fUs27G3(gW)hD[I?\@qg_OH(fM`K;2P:r[K=2&Ir+95 ^Se';<nv>[&N*:K8qQi:r+j3yAvHHV

        Domain Policy Information:
                Domain Name: AD
                DNS Name:
                Forest Name:
                Domain GUID: f106154f-d6ca-8348-8c7f-af10e39afc44
                SID: S-1-5-21--1601293236-400107208-1135612523

        Domain Controller Information:
                Domain Controller Name: \\
                Domain Controller Address: \\
                Domain Controller Address Type: 0x1
                Domain GUID: f106154f-d6ca-8348-8c7f-af10e39afc44
                Domain DNS Name:
                Domain Forest Name:
                Flags: 0xe00013fd
                Domain Site Name: Default-First-Site-Name
                Computer Site Name: Default-First-Site-Name

        Options: 0x0

So, definitely treat that file with care — it does contain sensitive information. That computer password would typically get reset as soon as the computer has connectivity to AD, at which point the blob isn’t of much use any more. But until that happens, the first computer that uses it becomes that computer.

People initially thought they could specify /REUSE (or the API equivalent) when creating a blob and then be able to use that blob on multiple machines. Nope, it doesn’t work that way: If the computer account you specify already exists and you don’t specify /REUSE, the DJOIN request will fail with error 2224 (account already exists). If you specify /REUSE, the computer account will be updated in AD with a new password which will then be included in the generated blob. Want to break the domain trust for an existing computer? Run DJOIN.EXE with the /REUSE switch and specify the name of the computer you want to render unusable. It won’t take long before the original machine starts to complain.

But back to the original use case. Assuming you can build a mechanism to get the blob to the new computer, you need a transport mechanism. Maybe that mechanism is a USB key, in which case it is truly “offline.” But more likely than not, it’s going to be on the internet, so you need a (secure) way to get the blob to it.

Autopilot to the rescue

The Windows Autopilot Hybrid Azure AD Join scenario was the first “large scale” implementation of an ODJ transport service: the Windows 10 OS would signal to the MDM service that “I need to do an offline domain join” and the MDM service then responds back with an ODJ blob. The implementation was rather challenging though:

  • You couldn’t take advantage of the full capabilities of the offline domain join process (more on that in a minute).
  • You couldn’t specify what computer name you wanted to use (at least not directly — the MDM service gets to pick the computer name that it uses for the blob).
  • You can’t get an ODJ blob response quickly enough, it can take 15+ minutes (an architectural issue with the MDM service itself, not with the Autopilot implementation that just makes the request and waits for the MDM service to respond).
  • Getting the device joined is only the first challenge. You also need to be able to sign in (requiring line of sight to an AD domain controller) and you may need the background Hybrid Azure AD device registration process to complete (for anything that needs an Azure AD user token).

But those implementation issues can be overcome.

What else can ODJ blobs do?

It’s worth digging a little deeper into the ODJ capabilities. If you look at the name of the feature in the documentation, it says “DirectAccess Offline Domain Join.” But why is it tied directly to DirectAccess? Because DirectAccess solved some of the problems I previously listed. It enabled you to sign in and it enabled the background Hybrid Azure AD device registration to happen quickly in the background. It did this by enabling communication with on-premises resources (AD and any other servers that you designated, e.g. your management servers) transparently. But you had to get the DirectAccess configuration “bootstrapped” onto the device. To do that bootstrapping, the ODJ process has a few extra capabilities beyond just getting the computer into AD:

  • It can generate a certificate for the computer from your Active Directory Certificate Services (ADCS) PKI environment. You just specify the name of the certificate template defined in ADCS that it should use to generate a computer-specific certificate.
  • It can include any trusted root certificates that you have defined in Active Directory group policy. This is mostly important so that the generated certificate above is trusted, but it could also be useful for any other custom certificate authorities you need to use.
  • It can include offline versions of one or more computer-targeted group policy objects, automatically applying those policies before the device even has connectivity to AD. You just specify the name of the GPOs and it bundles those into the ODJ blob.

If you look at the DJOIN.EXE documentation, it mentions the command line parameters needed to get these:

/rootcacertsOptionally include root Certification Authority certificates.
/certtemplate <name>Optional name of the machine certificate template.Includes root Certification Authority certificates.
/policynames <name(s)>Optional semicolon-separated list of policy names. Each name is the displayName of the Group Policy object (GPO).

And if you are a “true developer” and only use API calls, the NetCreateProvisioningPackage function (which isn’t related to what we think of as a provisioning package today — just think “create ODJ blob”) does the exact same thing as DJOIN.EXE, but from C++ code. (It’s also possible to call it from C#, but it is messy to do.)

So if you look at this, you can see that none of this is really DirectAccess specific, but it certainly enables the full configuration of DirectAccess. I can create an ODJ blob like so:

Compare that to a simple ODJ without those, and you can see the size difference:

So 5KB for a simple ODJ blob, and a potentially much bigger one when you include certs and policies, 174KB in this case. (Funny story: I modified the ODJ Connector code myself — even though I was technically a program manager, not a developer — about three years ago for the purposes of seeing if I could get this working end to end with Microsoft’s MDM service. The connector code was fairly simple to modify to accept additional parameters for certs and GPOs. But the MDM service couldn’t handle an ODJ blob that was that large; it could only handle 16KB maximum. I never managed to get anyone to fix that.)

But “big” is pretty relative when you’re talking about Windows, so if you’ve got a way you can transport a 5KB file, you probably wouldn’t have any issues (normally) with a bigger one either. So, if you’ve got any machine certificate requirements (maybe for Wi-fi, Always On VPN, DirectAccess, etc.) or critical GPO requirements, anything that “must be” applied to the machine even before it’s on the corporate network, the ODJ blob can take care of that. It can have one computer cert, a number of trusted roots, and a number of GPOs baked into it.

An alternative implementation

When we implemented the Active Directory join process as part of the Tanium Provision feature, we implemented an offline domain join process. As part of the deployment process, the deployment scripts make a request to an ODJ service that we provide to generate the ODJ blob; that service does the needed work to return the resulting blob, with any needed certs and GPOs included in it, back to be injected into Windows to join the device to AD.

So imagine a scenario where the computer is on the internet, and it needs to be provisioned (maybe because it’s a brand new computer that needs the corporate image loaded onto it, or because it isn’t behaving or has been infected and needs to be re-imaged). We need a secure way to retrieve the image, drivers, scripts, ODJ blob, etc. I showed the basic process for doing that in my previous post, where we leverage the Azure AD App Proxy to authenticate access to our on-premises provisioning services. Here’s a slightly extended version of that video (speeding up the long, boring stare-at-progress part — no editing, just 20x speed), showing the subsequent user sign-on and validation that DirectAccess is indeed configured and being used to access on-premises resources:

Overall, it looks pretty straight-forward. But at the same time, it addresses the challenges that I had previously encountered:

  • The computer name is generated locally on the computer using flexible rules that the IT admin can define. For example, if you want a name like A-%Model:4%-%Serial:3%-%RAND:3%, or even manually type in the name to use, you can do that. That generated name is then provided to the ODJ service, saying “create a computer account with this name.”
  • The resulting blob contains a computer certificate (matching the computer name), trusted root certs, and the GPOs needed to set up DirectAccess, so the machine has connectivity to the corporate network immediately even when on the internet.
  • The computer can quickly complete the first part of the Hybrid Azure AD Join process (updating its certificate in AD so that the computer object can be synced to AAD) since it has network connectivity. (You still may need to “supercharge” the AAD Connect process to get the complete Hybrid Azure AD Join process to complete within 30 minutes.)
  • The blob is returned almost immediately by the web service, so there’s no delay in the process.

Now I just need to do the same thing with Always On VPN. That’s a blog for another day…