React to MEM Logs using Event Hubs and Azure Functions

Example: Convert User-Driven provisioned Autopilot Devices to Shared Devices

I’ve got an interesting challenge from one of my customers.
Long story short, we have hybrid ad joined devices (for no really good reason, I know 😉 ), but only “User Driven Provisioning” via Windows Autopilot is available (at the moment).
But mobile devices get shared regularly for weekend tasks, the customer wants to allow every user to use the company portal on these devices.
The only way to accomplish this at the moment is to remove the primary user from the device in the MEM Admin Portal, because this will “convert” the device into a shared device.
So he wants me to automate this when a new device gets enrolled in intune via Windows Autopilot.

In the past, I’ve often used Azure Monitor with Alerts and Runbooks to perform tasks like this.
But since I’ve dug a little bit into Azure Functions in my last project, I decided to go with an alternative approach this time. (Because Azure Functions are so damn awesome and a shout-out to Laura Kokkarinen, her blog post helps me a lot: Link )

So lets start, that’s the plan:

  1. Logstreaming MEM operational logs and looking for a specific “event”.
  2. Redirect formatted output of a specific event to a new logstream
  3. The redirected event triggers Azure Functions via binding
  4. Azure Function calls Microsoft Graph with a token from the Managed Identity endpoint (that’s by far the coolest part)

Create the necessary Event Hubs

We create two Event Hubs in our new namespace, one for operational logs and one for the filtered enrollment events (we will get into that later).

Forward MEM Operational Log to Event Hub

Return to the MEM Admin Portal and configure log forwarding.

Note: Now is a good time to enroll a test device, so you have some log entries to play with.

Configure Azure Stream Analytics Job

Let’s take a look at the logstream. We navigate back to our event hub namespace and open our previously selected event hub.

We save the query as a stream analytics job.
We add output to our “Analytics Job”.
Don’t forget to start the job (unfortunately discovered after 2 hours of troubleshooting :-))
Note: Again, now is another good time to enroll a device, so we can validate if entries are received by our event hubs.

Create Azure Function and bind to Event Hub

We now create a new Azure Function App with your favorite runtime stack. I usually go with Powershell and my demo code is also written in Powershell (so if you want simple copy and paste –> select Powershell Core).
The rest of the settings are good by default, and the serverless plan is the most beaty one :-). Application Insights give us a historical view.

Next we create our first function in our new Function App and bind it with our “newenrolleddevice” event hub.
Click create and the portal brings us to our new function, where we go to the “Code + Test” section and enter the following code and click “Save”.

param($eventHubMessages, $TriggerMetadata)

# Write-Host "PowerShell event hub trigger function called for message array: $eventHubMessages"

$eventHubMessages | ForEach-Object {

    # get Intune device id
    $jsonOut = $_ | convertto-json
    Write-Host "Processing event: $jsonOut"

    $deviceID = $
    Write-Host "DeviceID: $deviceID"

    try {
        # request accesstoken from managed identity
        Write-Host "Trying to get authentication token from managed identity."
        $authToken = Receive-MyMsiGraphToken

        #Invoke REST call to Graph API
        Write-Host "Call Microsoft Graph to remove primary user from device."
        Remove-MyPrimaryUser -IntuneDeviceID $deviceID -AuthToken $authToken
    catch {
        Write-Error $_

As you might see, I use 2 helper functions in this example. “Receive-MyMsiGraphToken” and “Remove-MyPrimaryUser”, we add this function to the “profile.ps1”. The “profile.ps1” file loads every time the function does a cold start.

We append the following code to our “profile.ps1” file.
function Receive-MyMsiGraphToken {
    $Scope = ""
    $tokenAuthUri = $env:IDENTITY_ENDPOINT + "?resource=$Scope&api-version=2019-08-01"

    $splatt = @{
        Method = "Get"
        Uri = $tokenAuthUri
        UseBasicParsing = $true
        Headers = @{
    $response = Invoke-RestMethod @splatt
    $accessToken = $response.access_token

    if ($accessToken) {
        return $accessToken
    else {
        throw "Could not receive auth token for msgraph, maybe managed Identity is not enabled for this function"
function Remove-MyPrimaryUser {
    param (
    $splatt = @{
        Method = "DELETE"
        Uri = "'$IntuneDeviceID')/users/`$ref"
        UseBasicParsing = $true
        ContentType = "application/json"
        # ResponseHeadersVariable = "RES"
        Headers = @{
            'Authorization'= 'Bearer ' +  $AuthToken
    $result = (Invoke-RestMethod @splatt).value

    if ([string]::IsNullOrEmpty($result)) {
        return $true
    else {
        throw "Removing primary user from device ('$IntuneDeviceID') failed"

Add MS Graph permissions to the Azure Function App

Now we have everything in place, for our final part. We have to add some permissions to our Azure Function App.

We enable the managed identity for our function app and we copy the object ID to our clipboard because we need it in the next step.
# replace with your managed identity object ID
$miObjectID = "place your object id here"

# MS Graph app ID
$appId = "00000003-0000-0000-c000-000000000000"

# replace with the API permissions required by your app
$permissionsToAdd = @(


$app = Get-AzureADServicePrincipal -Filter "AppId eq '$appId'"

foreach ($permission in $permissionsToAdd) {
    $role = $app.AppRoles | Where-Object Value -Like $permission | Select-Object -First 1
    New-AzureADServiceAppRoleAssignment -Id $role.Id -ObjectId $miObjectID -PrincipalId $miObjectID -ResourceId $app.ObjectId

# Restart app after changing permission

Try it out

We reset and reenroll our test device/VM and take a look. After some time, we should see the message received by our “newenrolleddevice” logstream.

And some “success” messages in our function monitoring

Some final words

  • This is only an example, so please feel free to select different tiers and plans to meet your needs
  • Why redirecting into an new Event Hub? For demonstration purposes only, if you use this method in an environment with thousands of clients, you can easily reduce the number of times your function is invoked.
  • The possibilities are basically endless; tagging based on geolocation, joining groups based on properties, which are not supported at the moment, etc…

Azure Virtual Desktop and AzureAD joined VM

Since some time it is possible to join a Windows VM to Azure AD directly. Now this is also possible with Azure Virtual Desktop.

This Blogpost will show all my steps until I am possible to login to my Windows 10 System.


Create a host pool

First of all we need some basic informations such as pool name.

next to the basics we need to define: VM Size, VM Availability, Image type and the number of VMs.

General Settings

addition to that we can use an existing network or we are able to create a new one.

Network Settings

After these Settings we need to define which domain we want to join. Her we can now choose between Active Directory and Azure Active Directory.

I have chosen AzureAD.


During the host pool creation it is possible to create a assignment to a workspace. I have created tech-guy-workspace as a new one.

Roles and Permissions

With AzureAD joined devices we need to create a role assignment and a app group assignment. With each host pool one default app group will be created. In my test lab it is called “tech-guys-personal-pool-DAG”.

default app group

within this app group we are able to assign users

2nd task is to assign rbac role to at least the virtual machine to that we want to login. I prefer to assign that role to my resource group that I have that assignment for all future host as well.

there are 2 roles we need to consider about.

RBAC Roles

As it says the first role is useful when you want to login and want to have admin privileges on that machine. second group is only for your users that they are able to login without admin permission. In my lab I assigned my test user to “virtual machine user login” and my cloud only user “virtual machine administrator login” role.

To access host pool VMs, your local computer must be:

  • Azure AD-joined or hybrid Azure AD-joined to the same Azure AD tenant as the session host.
  • Running Windows 10 version 2004 or later, and also Azure AD-registered to the same Azure AD tenant as the session host.

Host pool access uses the Public Key User to User (PKU2U) protocol for authentication. To sign in to the VM, the session host and the local computer must have the PKU2U protocol enabled. For Windows 10 version 2004 or later machines, if the PKU2U protocol is disabled, enable it in the Windows registry as follows:

  1. Navigate to HKLM\SYSTEM\CurrentControlSet\Control\Lsa\pku2u.
  2. Set AllowOnlineID to 1

and here we go.

If you need to use an other client rather than the windows one, than you need enable the RDSTLS protocol. Just add a new custom RDP Property to the host pool, targetisaadjoined:i:1. Azure Virtual Desktop then uses this protocol instead of PKU2U.