PC Buyback for Windows Autopilot devices – part 1

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.

 

create resource group.png

 

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.

 

PCBuyback function app.png

Step 3. Create a RemoveAutopilotDevice http trigger

In the Function app, create a trigger called RemoveAutopilotDevice

create removeautopilotdevice http trigger.png

In the newly created Http trigger, click on Code + Test

adding removeautopilotdevice http trigger.png

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

register this app.png

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.

image.png

Step 7. Create a secret

In production use Azure key vault, for your lab you can quickly create a secret to test this.

create secret.png

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

editing the http triggers.png

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"

}

 

test run serial.png

 

After running, we can see it reports that the device is true as it is a Windows Autopilot registered device.

devicetrue.png

Now we know it works, let’s test the other trigger.

And that too, works great!

 

removeautopilotdevice success.png

 

That’s it for part 1, see you in the next part where we’ll create the Win32 app and test it !

This entry was posted in Intune, PCBuyback, Windows AutoPilot. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.