Jump to content


Adding devices to an Azure AD group after Windows Autopilot is complete - part 1

Recommended Posts


I've come across various problems during Windows Autopilot causing OOBE to fail that could be solved if only we could decide the order of when things were installed, and to resolve this in a nice way we wanted to dynamically populate an Azure AD group that could be targeted with a device configuration profile. That would mean that we could target sensitive policies to devices after enrollment instead of during enrollment allowing for a smoother, less error prone experience.

Being able to apply a profile after Autopilot is finished requires knowing when Autopilot is actually complete, and I touched upon that subject in a previous blog post here. To expand upon that, we can run a scheduled task on login which runs a PowerShell script which in turn, only delivers the payload if certain things are in place such as.

  • C:\ProgramData\Microsoft\IntuneManagementExtension  was created within the last X hours
  • The logged on user is not defaultuser0

We could do this using a PowerShell script which runs as a scheduled task after login but that would require storing sensitive credentials on the client.

This blog post will show you the necessary steps taken to get to a stage where you can add devices to an Azure AD group using Azure Functions and Graph, and that is interesting because in conjunction with an app registration allows you to embed certificates or secrets within the function and thereby bypass the need for storing credentials in your PowerShell script which runs on the client.

There are other ways of doing this, but this is kind of neat. You need to do the following steps.

  • Create a resource group
  • Create an app registration
  • Create a client secret
  • Create a function app
  • Add a HTTP trigger
  • Get the application ID
  • Create an azure ad group
  • Add missing details
  • Configure API permissions
  • Test adding a client

So now you have an idea of what this blog post is about, let's get started.

Note: I've released an updated version of this concept which includes checking for device compliance here.

Step 1. Create a resource group

In Azure Active Directory, create an Azure Resource Group. To do that click on Create a Resource in https://portal.azure.comIn the page that appears, search for Resource Group. Select it and click on Create. Next, give it a useful name like Graph_Functions, and select the region applicable to you.

create a resource group.png

And click on Review + create and after being presented with the summary, click Create.

Step 2. Create an app registration

In Azure Active Directory, create an create an APP Registration called graph_functions by clicking on App registrations in the left pane and clicking on + New registration.

create app registration.png

fill in the user-facing display name and then click on Register.


app registration.png

The app registration is created.

app registration is complete.png

Step 3. Create a client secret

In the Graph_Function app registration you just created, click on Certificates & Secrets, choose the option to create a + New client secret

new client secret.png

Give it a name like graph_function_secret


Click Add

After adding the client secret make sure to copy the secret and keep it safe.

copy the client secret.png
copy the secret value and id, you will need them later.

Step 4. Create a function app

Next, select your previously created resource group called Graph_Functions and create a function app in the graph_functions resource group by clicking on +Add

add function app.png


Search for Function App and click Create. A wizard will appear, fill in your choices and select PowerShell core and your region.

create function app wizard.png


Create a new storage group or let the wizard create it's own, then click Review + Create. If it generates an error click on the error details, most likely the storage group name you tried to create is already taken. If so, pick another name.

storage account for function.png


Finally, click on Create to create the function app.

create the function app.png

Step 5. add a HTTP trigger

Select the function app created above and click on functions in the left pane. In the ribbon click on + Add.

functions add.png


Select HTTP trigger and click on Add.

http trigger add.png

Select Code + test and paste in the code below then save the results

# use this code in a http trigger as part of a function app
# for more details see https://www.windows-noob.com/forums/topic/21814-adding-devices-to-an-azure-ad-group-after-windows-autopilot-is-complete-part-1/
# Niall Brady, windows-noob.com 2020/12/18 v 0.3

using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.

$deviceId = $Request.Query.deviceId

if (-not $deviceId) { 
                    $deviceId = $Request.Body.deviceId
# define the following variables
$ApplicationID =    "" # this is the id of the app you created in app registrations
$TenantDomainName = "" # your tenant name, eg: windowsnoob.com
$AccessSecret =     "" # this is the secret of the app you create in app registrations
$GroupID = ""          # this is the ObjectID of the Azure AD group that we want to add devices to

$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
client_Id = $ApplicationID
Client_Secret = $AccessSecret
# make initial connection to Graph
$ConnectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantDomainName/oauth2/v2.0/token" -Method POST -Body $Body
#get the token
$token = $ConnectGraph.access_token
# to improve logging...
$a = Get-Date
$body = " `n"
$body = $body + "$a Starting Azure function...`n"
$body = $body + "$a Connected to tenant: $TenantDomainName.`n"
# now do things...

if ($deviceId) {
    $body = $body + "$a You supplied deviceId: '$deviceId'" + ".`n"
    # deviceID should be supplied to the function via the function url, 
    # if you want to hard code it for testing un rem next line and supply the correct deviceId
        #$deviceID = "3a4f4d9d-c648-4ca2-966f-6c6c10acff35"
        #$InputDevice = "MININT-U7CQUG7"
        #$Devices = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/devices?`$filter=startswith(displayName,'$InputDevice')" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value | %{    
    $Group = Invoke-RestMethod -Method Get        -uri "https://graph.microsoft.com/v1.0/groups?`$filter=Id eq '$GroupId'" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value
    $GroupName = $Group.displayName
    $body = $body +"$a Group.displayName: '$GroupName'" + ".`n"
    $GroupMembers = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/groups/$GroupID/members" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value
    $AddDevice = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/devices?`$filter=deviceId eq '$deviceId'" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value | %{ 
   if ($GroupMembers.ID -contains $_.id) {
       Write-Host -ForegroundColor Yellow "$($_.DisplayName) ($($_.ID)) is in the group" 
        $body = $body + "$a $($_.DisplayName) ($($_.ID)) is already in the '$GroupName' group, nothing to do.`n"
    } else {
        Write-Host -ForegroundColor Green "Adding $($_.DisplayName) ($($_.ID)) to the group"
        $body = $body +  "$a Adding $($_.DisplayName) ($($_.ID)) to the group with ObjectID $GroupID.`n"
        $BodyContent = @{
        } | ConvertTo-Json
        Invoke-RestMethod -Method POST -uri "https://graph.microsoft.com/v1.0/groups/$GroupID/members/`$ref" -Headers @{Authorization = "Bearer $token"; 'Content-Type' = 'application/json'} -Body $BodyContent
$body = $body +  "$a Exiting Azure function."

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
 StatusCode = [HttpStatusCode]::OK
 Body = $body


Step 6. Get the application ID

Go back to app registrations, select the app called graph_functions and copy this field.

  • Application (client) ID:

copy application iud.png

copy the Application (client) ID somewhere.

Step 7. Create an azure ad group

In Microsoft Endpoint Manager create an azure ad group called Autopilot Completed

autopilot completed.png

Take note and copy the Object ID of this Azure Ad group.

object id.png

Step 8. Add missing details

Now that you have the values you need (remember you copied the access secret earlier ?), go back into your function app and edit the code, fill in the values below for the following variables

  • $ApplicationID
  • $TenantDomainName
  • $AccessSecret
  • $GroupID

like I've done here... but obviously use your own values.

4 details added.png

Save the code after making your edits.

Step 9. configure API permissions

Next you need to configure API Permissions for the app registration, don't forget to 'grant admin consent after doing so', please select the following permissions from those available.

Click + Add a permission, click Microsoft Graph, select Application permissions

request api permissions.png

  • Device.Read.All                     Application                Read all devices                        Yes                Granted for windowsnoob.com
  • Group.ReadWrite.All             Application                Read and write all groups        Yes                Granted for windowsnoob.com

In your lab environment it's ok to Grant admin consent, in Production, think more carefully about how you want to approach it.

grant admin consent.png


and once done they'll look like so.

api permissions set.png


Step 10. Test adding a client

Now you are ready to test this. On a client, open a cmd prompt and type dsregcmd /status

look for the deviceID value highlighted below and copy it.

deviceid on client.png

Copy that value and on your http trigger function, use the following in your test window. Replace the deviceID listed below with one from your device.

"deviceID": "f6331344-f625-4259-87e7-73b62f6dbc4a"
device id added to input.png

Click Run and watch your function do it's thing 🙂

If you followed my guide exactly you'll see something like this.

code tested ok.png

And after a quick look in the members of your Azure Ad group, guess what, your client computer is present !

refresh to see the client computer.png

Awesome ! job done :):):)

Useful links

That's it for this part, please join me in Part 2 where we'll look into scripting things in an automated way on the client.



Share this post

Link to post
Share on other sites

what type of account are you using to create this ?

I just tried it now and i do see the option to create a new storage as you see here


Share this post

Link to post
Share on other sites


thanks for the above, I have implemented it and the test worked well with a device ID. However I’ve done a couple autopilot builds and the devices aren’t going into the group, when should this trigger? How often shout it run. Thanks in advance 

Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.