MDM Local Management using SyncML Viewer

A month ago, I published a new version of the SyncML Viewer to support MMP-C. With this release I came to the idea of integrating some SyncML requests functionality. Sending local SyncML requests to the Windows OS and letting it process the OMA DM commands and get results back. After quite some research how to do that, I stumbled across Michael Niehaus blog Send MDM commands without an MDM service using PowerShell. I actually read it back then but wasn’t aware of it at the moment of research. But anyway, finally his idea is the basis of my development work for the new SyncML Viewer with Local MDM requests support. Big kudos 💪 to Michael for that! Thankfully 🙏 he released his module under CC by 4.0. This is what a great community and also open source is about. This means I could use and adapt his approach in my tool. I reviewed his approach to adaptation. Finally, I found the official “documentation” of the relevant API information in the Windows SDK: C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\mdmlocalmanagement.h. This is really nice, as C++ header files contain declarations and necessary information for using the functions or classes without providing their actual implementations. So basically, this is the exact instruction on how to interact with this API and this knowledge can be used not only in C++ as Michael demonstrated with his PowerShell approach, but also in C#, the language I use for SyncML Viewer.

Interesting fact about the mdmdlocalmanagement API is that it needs AllowEmbeddedMode to be set. The official IoT documentation says this is supported on IoT Core and Enterprise. But hey it doesn’t tell explicitly that it is not supported on other editions. The Policy CSP for example has an AllowEmbeddedMode setting which is supported on all Editions 😉🤷‍♂️

AllowEmbeddedMode CSP docs

Okay research done; I finally developed some functionality (with quite some issues to overcome 🫣 during development) for SyncML Viewer to interact with this “mdmlocalmanagement.dll” API (beginning with version 1.2.0). The result is a graphical user interface to do local MDM management now:

SyncML Viewer with SyncML Requests functionality

Some details of the inner workings

The tool is constructing all necessary prerequisites (SHA265 hash of SMBIOS UUID) for AllowEmbeddedMode and sets the value temporarily in registry: HKLM\SYSTEM\CurrentControlSet\Services\EmbeddedMode\Parameters\Flags. The flag is reverted after execution of a SyncML Request command to leave no change on the device. If you want to play around further, there is an option to set and clear it explicitly:

SyncML Viewer options to set and clear embedded mode

REMINDER: This tool and its functionality are intended to dig into the inner workings of the MDM stack. Especially when tampering with the device in a way like this (setting MDM settings) you can easily break the device. So be careful and use it on a dev or test devices preferably! Simple GET requests should not harm the device or leave any leftovers.

The basic flow of the API is 1. Allowing EmbeddedMode, 2. Register to MDM Local Management then 3. apply some local management SyncML and then optionally 4. Unregister. The register call will enroll and generate a new MDM enrollment (Local_Management) in registry, see the new enrollment GUID and the new enrollment type = 20 with a ProviderID named Local_Management:

The GUID on the left represents the enrollment ID of the mdmlocalmanagement.dll. Same way the MS DM Server gets registered during enrollment of a device but with a different enrollment type = 6.

I’ve chosen the default flow to trigger unregister by default at the end to have no leftovers on the device after playing around. There is an option to change this, demonstrated later in the article. The default I’ve chosen is especially useful if a request was made on a non-test device. I want to make sure regular devices are not broken in the end when using the tool. But keep in mind not every setting might be reversed after calling unregister. There are settings which are getting tattooed, so they will still be active even after calling unregister. So be careful where you play around with this!

Usage and further details

I hope the user interface is easy enough to use as soon as you have looked into the Configuration Service Provider reference documentation in the past (hint there is a key combo Ctrl+F1 to open the CSP docs). With the upper section in the SyncML Requests tab, you can build the request by using the dropdowns and input fields to construct an OMA-URI and the tool will build the SyncML request in the background automatically and shows it on the left after request:

SyncML Viewer and a generated SyncML Request with response

This is an easy way to try documented CSPs via OMA DM commands (I refer as SyncML requests here and in the tool). In the example above I used the given information from the CSP documentation below (watch out for the Copy buttons on the docs 👌):

CSP documentation about HardwareDeviceData OMA URI

The result for all the requests and responses are kept on the right textbox:

SyncML Viewer Request and Response details

If you prefer to do your own editing you can use the SyncML editor and write the SyncML xml yourself. The assisted part on the top will then be disabled and you have to get it correct by editing it directly:

SyncML Viewer with assisted SyncML request build from dropdowns and input fields

In the example above I’ve constructed the SyncML xml to set the LockScreenImageUrl and DesktopImageUrl via one request and two individual Replace commands. The final result looks like this:

SyncML Viewer LockScreen and DesktopImage replaced via SyncML command result

To demonstrate further the inner workings, I will use another example and set the AllowBuildPreview:

SyncML Viewer AllowBuildPreview example

In this case I’ve set the additional option under Advanced “Keep local MDM enrollment (SyncML Requests)“:

SyncML Viewer option to keep local MDM enrollment

This prevents the tool from calling Unregister API at the end of a SyncML request. Leaving the enrollment of the “mdmlocalmanagement.dll” in the registry and we can view our setting result:

SyncML Viewer local MDM enrollment values for AllowBuildPreview in registry

The GUID represents the enrollment ID of the mdmlocalmanagement.dll and due to the setting “Keep local MDM enrollment (SyncML Requests)” we prevented the call for unregister and everything we have set is kept in the registry.

Reminder: I’ve chosen the default to unregister by default to have no leftovers on the device after playing around. This is especially useful if a request was made on a non-test device. I want to make sure regular devices are not broken in the end when using the tool. But keep in mind not every setting might be reversed after calling unregister. There are settings which are getting tattooed, so they will still be active even after calling unregister. So be careful where you play around with this!

For more complex requests, where you have to build bigger data structures as input data, I’ve added a Data Editor as well for easy editing and formatting. The following example denies the RDP logon for the Users Group (SID = S-1-5-32-545) by utilizing the DenyRemoteDesktopServicesLogOn setting:

SyncML Viewer with complex SyncML Request using Data Editor window

The result is shown below in the registry:

SyncML Viewer with complex SyncML Request registry result

and here in the local Group Policy it is also reflected:

SyncML Viewer with complex SyncML Request local GPO setting result

If you like to play around a little more in a standalone manner, it might be of interest, there is an option to keep the Executer process after exiting SyncML Viewer. You have to uncheck the Option > Advanced > Cleanup working folder after exit:

SyncML Viewer option to disable Cleanup working folder after exit

This will result in the Executer process (SyncMLViewer.Executer.exe) staying side-by-side with the SyncML Viewer. Without this option the binary will be generated on-the-fly and verified for integrity out of the SyncML Viewer. The binary is then used for executing the requests and deleted after tool usage:

SyncML Viewer and its extracted SyncMLViewer.Executer.exe

The Executer can be used standalone and has some help. Check the command line help -? for correct usage:

Command line tool SyncMLViewer.Executer.exe help parameter

Further improvements

While the release focused on SyncML Requests I’ve added or extended here and there some functionality, check the Change log on GitHub for details. Here some of them which are good to mention:

Data Editor used for Base64 Decoding

When selecting a text, you can hit Ctrl+B to try decode the selection from base64 encoding. I’ll try to format it correctly if it is xml or json content as shown below. The Ctrl+B is also supported on the Data Editor window itself to call another instance and decode further embedded base64 content. Nested calling so to say. The result is still copied to the clipboard automatically as well:

SyncML Viewer with decoded base64 data using Data Editor window

Data Editor used for HTML Decoding

If you use the Ctrl+H key combination, you can invoke HTML decoding on any selected text. In the example below I decoded the encoded HTML text between the <Data></Data> tags and used the [Format Document] to format it nicly.

SyncML Viewer with decoded and formated HTML data using Data Editor window

Key combinations for most actions

I’ve added key combinations for most actions, have a look in the menus:

SyncML Viewer key combinations

Reset Sync trigger status

I’ve also added an Action > Advanced > Reset Sync trigger status. As it may happen that the SyncML Viewer UI doesn’t recognize an interrupted sync. So instead of closing the tool and re-open you can simply reset the status and the Sync buttons are enabled again 👌

SyncML Viewer Actions menu to reset Sync trigger status

Download and feedback

I’ll hope this will help research and dig into the MDM stack, driving understanding and maybe coming up with solutions for various problems. As always if there is feedback leave a comment or open an issue on GitHub.

The project is maintained on GitHub here:
https://github.com/okieselbach/SyncMLViewer

Here a direct link to the zip archived program:
https://github.com/okieselbach/SyncMLViewer/tree/master/SyncMLViewer/dist

Finally special thanks to Michael Niehaus for his initial research and as always Rudy Ooms for various feedback on the tool.