Introduction
Windows Autopilot allows you to quickly enroll devices using policies, apps and settings applicable to your tenant, and that’s great for deploying new Windows devices. Those devices can be registered as Windows Autopilot registered devices by the OEM (Original Equipment Manufacturer) or by a third party integrator or by the organization themselves by importing the Windows Autopilot hardware CSV into Intune.
After a device has reached end of life however, it needs to be disposed of or sold on. It would be nice to offer end users the ability to buy back their old Company hardware with minimum fuss, but at the same time to remove Intune management and remove the device from Windows Autopilot registration while installing a new copy of Windows 11 professional that the end user could use for their own personal use.
I created a Win32 app that does all this called PC Buyback. The app integrates with a back end Azure app using http triggers to do the magic. This post will cover the app features and code needed to implement it in your own tenant.
The features of the app are as follows:
- Easy to use
- Self-Service app available in Company Portal
- Removes company data and apps
- Reinstalls Windows
- Emails results to a company inbox
- Logging to Azure tables (optional)
Note: In this blog, the app uses http triggers that use certificate secrets, this is fine in a lab, but in production you should use Azure Key Vault instead as it’s more secure.
This mini series is broken down into the following parts:
- PC Buyback for Windows Autopilot devices – part 1 <— You are here
- PC Buyback for Windows Autopilot devices – part 2
- PC Buyback for Windows Autopilot devices – part 3
Step 1. Create resource group
In Entra, using an account that has permission to create Resource Groups in your subscription, create a resource group called PCBuyback, create it in the region that your tenant is located.
Step 2. Create a function app in the resource group
In the PCBuyback resource group, create a function app in the same region as the resource group you created above.
Step 3. Create a RemoveAutopilotDevice http trigger
In the Function app, create a trigger called RemoveAutopilotDevice
In the newly created Http trigger, click on Code + Test
and paste in the following code to overwrite the existing code…
# # Remove Autopilot device by serial number # Verify if the device is autopilot and delete it from intune # version: 0.1 windowsnoob.com 2024/03/31 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. $serialNumber = $Request.Query.serialNumber if (-not $serialNumber) { $serialNumber = $Request.Body.serialNumber } # define the following variables $ApplicationID = "" # create an application with permissions to delete devices in Azure $TenantDomainName = "" # your tenant name $AccessSecret = "" # this is the secret of the app you create in app registrations $GraphBody = @{ 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 $GraphBody #get the token $token = $ConnectGraph.access_token # to improve logging... $body = " `n" $response = "" $body = $body + "$(Get-Date)" + " Starting Azure function...`n" $body = $body + "$(Get-Date)" + " Connected to tenant: $TenantDomainName.`n" # now do things... if ($serialNumber) { $body = $body + "$(Get-Date)" + " You supplied serialNumber: '$serialNumber'" + ".`n" Try{ # Get Device Reference from Intune $body = $body + "$(Get-Date)" + " Get Device Reference from Intune" + ".`n" $Device = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$filter=contains(serialNumber,'$serialNumber')" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value If($Device){ $body = $body + "$(Get-Date)" + " Found number of devices similar to '$serialNumber': $($Device.count)" + "`n" Foreach($d in $Device){ If($d.serialNumber -eq $serialNumber){ $body = $body + "$(Get-Date)" + " Get deviceID from Intune: $($d.id)" + ".`n" Invoke-RestMethod -Method Delete -uri "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/$($d.id)" -Headers @{Authorization = "Bearer $token"} $body = $body + "$(Get-Date)" + " Successfully deleted Device $($d.serialNumber) from Intune. May take a few minutes to complete in Intune Portal." + ".`n" Write-Host $body $response = "Success" break } } } Else{ $body = $body + "$(Get-Date)" + " serialNumber: $serialNumber does not exist in Intune" + ".`n" $response += "Failed" Write-Host $body } } Catch{ $body = $body + "$(Get-Date)" + " $($Error)" + ".`n" $body = $body + "$(Get-Date)" + " Failed to get Device Reference from Intune." + ".`n" $response += "Failed" Write-Host $body } Try{ # Get Device Reference from Windows AutoPilot $body = $body + "$(Get-Date)" + " Get Device Reference from Windows AutoPilot" + ".`n" $Device = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/deviceManagement/windowsAutopilotDeviceIdentities?`$filter=contains(serialNumber,'$serialNumber')" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value If($Device){ $body = $body + "$(Get-Date)" + " Found number of devices similar to '$serialNumber': $($Device.count)" + "`n" Foreach($d in $Device){ If($($d.serialNumber) -eq $serialNumber){ $body = $body + "$(Get-Date)" + " Get deviceID from Windows Autopilot: $($d.id)" + ".`n" $body = $body + "$(Get-Date)" + " Get ManagedDeviceId from Windows Autopilot: $($d.managedDeviceId)" + ".`n" Invoke-RestMethod -Method Delete -uri "https://graph.microsoft.com/v1.0/deviceManagement/windowsAutopilotDeviceIdentities/$($d.id)" -Headers @{Authorization = "Bearer $token"} $body = $body + "$(Get-Date)" + " Successfully deleted Serial Number $($d.serialNumber) from Windows Autopilot device. May take a few minutes to complete in Intune Portal." + ".`n" $response = "Success" Write-Host $body break } } } Else{ $body = $body + "$(Get-Date)" + " Serial Number: $serialNumber does not exist in Windows Autopilot" + ".`n" $response += "Failed" Write-Host $body } } Catch{ $body = $body + "$(Get-Date)" + " $($Error)" + ".`n" $body = $body + "$(Get-Date)" + " Failed to get Serial Number: $serialNumber from Windows Autopilot." + ".`n" $response += "Failed" Write-Host $body } $body = $body + "$(Get-Date)" + " Exiting Azure function." } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $response })
Step 4. Create a GetAPDeviceFromIntune http trigger
In the Function app, create a another http trigger called GetAPDeviceFromIntune and paste in the following code for that trigger.
# Get Autopilot device from Intune # version: 0.1 windowsnoob.com 2024/03/31 using namespace System.Net param($Request, $TriggerMetadata) # define the following variables $ApplicationID = "" # create an application with permissions to delete devices in Azure $TenantDomainName = "" # your tenant name $AccessSecret = "" # this is the secret of the app you create in app registrations $serialNumber = $Request.Query.serialNumber if (-not $serialNumber) { $serialNumber = $Request.Body.serialNumber } $GraphBody = @{ 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 $GraphBody #get the token $token = $ConnectGraph.access_token Write-Host "Get AP Device from Intune" # to improve logging... $body = " `n" $body = $body + "$(Get-Date)" + " Starting Azure function...`n" $body = $body + "$(Get-Date)" + " Connected to tenant: $TenantDomainName.`n" # now do things... if ($serialNumber) { $body = $body + "$(Get-Date)" + " supplied serial number: '$serialNumber'" + ".`n" try { # Get Device Reference from Intune $body = $body + "$(Get-Date)" + " Get Device Reference from Intune" + ".`n" $DeviceReference = Invoke-RestMethod -Method Get -uri "https://graph.microsoft.com/v1.0/deviceManagement/windowsAutopilotDeviceIdentities?`$top=25&`$filter=contains(serialNumber,'$serialNumber')" -Headers @{Authorization = "Bearer $token"} | Select-Object -ExpandProperty Value #Write-Host $body Write-Host "Device Reference count: $($DeviceReference.Count)" if($DeviceReference.Count -gt 0) { $body = $body + "DeviceTrue" + ".`n" $body = $body + "$(Get-Date)" + " Serial number: $serialNumber is an autopilot machine" + ".`n" } Else { $body = $body + "DeviceFalse" + ".`n" $body = $body + "$(Get-Date)" + " Serial number: $serialNumber is not an autopilot machine" + ".`n" #Write-Host "Device doesn't Exists" } } Catch { $body = $body + "$(Get-Date)" + " $($Error)" + ".`n" $body = $body + "$(Get-Date)" + " Failed to get Device Reference from Intune." + ".`n" } } $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 5. Create an enterprise app
In Microsoft EntraId, create an enterprise app called PCBuyback
Step 6. assign permissions to the app
Once created, click on API permissions and using the + Add a permission button, add permissions as I’ve listed below. Note that these are Application permissions (type) for Microsoft Graph, once done, don’t forget to grant permission for your tenant.
Step 7. Create a secret
In production use Azure key vault, for your lab you can quickly create a secret to test this.
Copy the value somewhere safe as you’ll need it for the next step.
Step 8. Edit the variables in the two triggers
In the two http triggers you just created, edit the variables and paste in the Enterprise App ID and secret, and your tenant name like so
Once done, save the changes
Step 9. Verify the http triggers
Now that you’ve created the triggers and assigned permissions, you’ll need to verify that they do what they are supposed to do. Let’s start with the GetAPDeviceFromIntune. To test this, paste in the serial number of a Windows Autopilot registered device and click on Test/Run.
Replace the serial number below with the serial number of the computer you want to test with.
{
"serialNumber": "5366-8776-5502-4105-6320-3369-17"
}
After running, we can see it reports that the device is true as it is a Windows Autopilot registered device.
Now we know it works, let’s test the other trigger.
And that too, works great!
That’s it for part 1, see you in the next part where we’ll create the Win32 app and test it !