Windows Autopilot

Requiring a network connection during OOBE

When deploying an organization-owned device, you typically want to put “guard rails” in place to make sure the user goes down the right path. (Really, that’s what Windows Autopilot is all about, simplifying the OOBE experience by getting rid of all the complexity that Microsoft introduced into OOBE over the years.) One of those “guard rails” would be to make sure the user always connects the device to a network for internet access; without internet access, you can’t get the Autopilot profile, you can’t authenticate with Azure AD, you can’t enroll in MDM, etc.

There is an MDM setting available in Windows 10 and above that helps with this, documented in the TenantLockdown CSP documentation:

OMA-URI: ./Vendor/MSFT/TenantLockdown/RequireNetworkInOOBE
Type: boolean
Value: true

Cool, so what’s the catch? Well, think about this a little: You first need to get the device enrolled in the MDM service before you can send down the policy that says you must connect to the network so that you are forced to enroll the device in the MDM service. The ordering of those operations aren’t right, the policy is received too late, for obvious reasons.

Interestingly though, this particular policy is “sticky.” Even if you reimage or reset the OS, it’s still there. So this isn’t your typical MDM policy where the policy value is stored in the registry. Where else can it be stored? Well, the only place that can possibly survive reimaging is the firmware of the device, i.e. UEFI. But how do you see what’s been defined in UEFI? I published a couple of blogs on that, with the most recent linked to the UEFIv2 module that I published to the PowerShell Gallery. Let’s run that on a device that has received the TenantLockdown policy to see what’s there:

See that entry a few lines down that says “FORCED_NETWORK_FLAG”? Seems like a pretty good candidate. If we dump out the value of that one, we can see that it’s just four bytes of zeroes. If we print it as a string, we don’t see anything intelligible, but we can dump it out as a byte array:

So that value of 1 for the first byte indicates that this is enabled. But to verify that the setting is indeed in effect, we can reset the OS and go back through OOBE with no network connection in place to see what happens.

Sure enough, I can’t get past this screen until I connect to a network.

Of course it’s just as easy to disable this using the same UEFIv2 module to erase that variable by setting the value to null (or you can just leave off the “-ByteArray” switch, since the default value is an empty string which has the same result):

After reimaging the computer with the network disconnected, we’re back to the normal behavior:

Interestingly, on Windows 11 at least doing a reset wasn’t sufficient — the setting was still in effect on the OS even though it wasn’t configured in the firmware. That’s a good thing overall I guess, just not quite what I expected to see. You can run “c:\windows\system32\sysprep\sysprep.exe /generalize /oobe /reboot” to pick up the change though.

If you want to set the value without using the MDM policy, that’s pretty easy too:

In an ideal world, this variable would be set for you by the OEM when the device is still in the factory. And to make it harder for someone to bypass this, having a firmware password configured at the same time would be good.

Note that having this enabled can interfere with “normal” imaging operations (e.g. break/fix) that use an unattend.xml — you might find the process stops in OOBE (which would otherwise be completely automated by the unattend.xml). Removing the variable (which you can do in Windows PE) is a reasonable workaround — you can always put the value back when you’re done.

Categories: Windows Autopilot

3 replies »

  1. This is a great document to share, always learn valuable knowledge from you, thank you for sharing.