Windows 11

Send MDM commands without an MDM service using PowerShell

Sometimes you run into something completely by accident; other times, it’s more of a building process based on work that you’ve done in the past. In this case, it’s a combination of the two. While researching some MDM-related “stuff,” I came across a DLL named “mdmlocalmanagement.dll” and the file name got my interest, so I looked at it in more detail, first by looking at the entry points:

That looked super-simple, especially since there is an “mdmlocalmanagement.h” include file in the Windows 10/11 SDK that defines those functions. And there’s also some code on GitHub that calls them, published by Microsoft. Sure, that code talks about IoT and Azure, but it was working trying it out anyway. Interestingly, the first call, RegisterDeviceWithLocalManagement, failed indicating that it wasn’t supported. But with a little more poking around (IDA is a cool tool), I could tell that it was checking a registry value, HKLM\System\CurrentControlSet\Services\EmbeddedMode\Parameters\Flags, and comparing that against a value returned by a GetSMBIOSUniqueIdHash function. OK, interesting, but the default value of that Flags value was 0, and it wasn’t obvious how that hash function was hashing the SMBIOS UUID (although in retrospect, I should have guessed).

Fortunately, some searching on “Embedded Mode” finds some documentation that explains how to create a provisioning package that enables Embedded Mode on a device. So I created a PPKG file using Windows Configuration Designer and installed it on my device.

It’s a pretty simple package, and after installing it I could see that the “Flags” registry value I was looking at earlier was updated:

It’s now a 32-byte binary string. And what checksum is 32-bits long? SHA-256. So, I put together some simple PowerShell code that converts the machine’s SMBIOS UUID value to a byte array and then generated a SHA-256 hash of those bytes and compared it to the registry value (basically a variation of the logic that I had previously used when messing around with Windows Autopilot hardware hashes), and it matched perfectly. So, I could then replace the PPKG file with a simple PowerShell script that does the same thing:

OK, so let’s try the API calls again and see if they work. Getting a little closer, as this time the call failed with an 80040154 return code, which points to issues starting a COM server. A quick ProcMon trace confirmed what it was looking for, and that there wasn’t a 32-bit version of that. (In case you’re wondering, Visual Studio Code runs the 32-bit PowerShell engine by default.) So we need to run the 64-bit version.

With the 64-bit PowerShell, there was a new error 80010106. More searching for that error pointed to more COM stuff, this time single-threaded apartment (STA) vs. multi-threaded apartment (MTA) issues. So I launched PowerShell.exe with the “-MTA” switch and the error went away. So my sample code could then register itself for local MDM management and send OMA-URI SyncML requests to the local MDM stack, which would process them and then send back a SyncML response indicating whether or not it was successful. A couple of hours of experimentation later (building on an MDM server that I wrote a while back in Node.js where I had to be intimately familiar with this whole process), and I could successfully send my own MDM requests and watch them be applied to the machine. Cool.

So all that was left was to turn that mess of C++ code into some C# code, then wrap that in PowerShell so that I can do interesting things like this:

But that’s just retrieving information via MDM CSPs. What about configuring settings? Sure, that works fine too:

There’s a little trickiness in that, as you need to know the format of the data (the PowerShell module defaults to “int”, but it needs to be “chr” or character data for some values) and the type/representation of it (normally “text/pain”). You can also specify different comands (e.g. “Replace” vs. “Exec” vs. “Add”). I tried to put in reasonable defaults, but check the cmdlet help for more details. And if you really want to try a more advanced scenario, you can formulate the SyncML request entirely on your own:

The module that I put together is published on the PowerShell Gallery, so you can just “Install-Module LocalMDM” yourself to try it out.

It’s worth pointing out a few things:

  • Microsoft’s documentation says that Embedded Mode is supported on Windows 10 IoT Core and Windows 10 IoT Enterprise. From my testing, it works fine on Windows 10/11 Pro, Education, and Enterprise SKUs. So probably anything you do with this module is going to be unsupported — use at your own risk.
  • There is a second cmdlet named “Unregister-LocalMDM” that will “undo” the local MDM registration that happens the first time you use the “Send-LocalMDMRequest” cmdlet. (If you get an error “2147549183”, which is 8000FFFF, you may need to exit out of PowerShell and get back in again, not sure what’s up with that as that doesn’t appear to happen via native code.)
  • In my testing, doing the “unregister” will remove any (most?) policies that were configured via the “Send-LocalMDMRequest” cmdlet, similar to what would happen if you unenrolled from an MDM service.

I always wanted a tool that could send random MDM policies to a device to see what happens. Now I have one 🙂

So why does this MDM local management setup exist? My suspicion is internal Microsoft politics: Instead of managing IoT devices using a typical MDM service, the Azure IoT folks wanted to have their own service that was more “lightweight” on the client. Yes, it’s a little weird, but at least they are using most of the MDM client components (CSPs and the like).

1 reply »