Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save MrWyss-MSFT/0ca51f35d175418dc556409c00ff3296 to your computer and use it in GitHub Desktop.

Select an option

Save MrWyss-MSFT/0ca51f35d175418dc556409c00ff3296 to your computer and use it in GitHub Desktop.
Delete-AutopilotDevices.ps1
param(
[Parameter(Mandatory = $false)]
[string]$CSVPath = "WindowsAutopilotDevices.csv", #TODO: Change back to full path
[Parameter(Mandatory = $false)]
[switch]$Force
)
#region Variables
$ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path
$LogFilePath = Join-Path $ScriptDirectory "Delete-AutopilotDevices-$(Get-Date -Format yyyy-M-dd).log"
$PSDefaultParameterValues = @{
"Write-Log:Path" = $LogFilePath
"Write-Log:Component" = "DeleteAutopilotDevices"
"Write-Log:Type" = "Info"
"Write-Log:ConsoleOutput" = $True
}
#endregion
#region Functions
function Invoke-CustomMgGraphRequest {
[cmdletbinding()]
param(
[Parameter()]
[switch]$All,
[Parameter(ValueFromRemainingArguments = $true)]
$RemainingArgs
)
$GraphRequestParamsArgs = @{}
for ($i = 0; $i -lt $RemainingArgs.count; $i += 2) {
$GraphRequestParamsArgs[($RemainingArgs[$i] -replace '^-+')] = $RemainingArgs[$i + 1]
}
$Result = Invoke-MgGraphRequest @GraphRequestParamsArgs
if ($All.IsPresent) {
#$Result.value
if ($Result.'@odata.nextLink') {
if ($Result.'@odata.nextLink' -match "skip=(.*)") {
[int]$skipValue = $Matches[1]
$totalValue = $Result.'@odata.count' - $Result.'@odata.count' % $skipValue + $skipValue
for ($i = $skipValue; $i -lt $totalValue; $i += $skipValue) {
Write-Progress -id 1 -ParentId 0 -PercentComplete ($i / $totalvalue * 100) -Status "$i of $totalvalue pages" -Activity "Retrieving next page"
$moreObj = Invoke-MgGraphRequest @GraphRequestParamsArgs
$moreObj.value
}
Write-Progress -id 1 -ParentId 0 -Activity "Retrieving next page" -Completed
}
if ($Result.'@odata.nextLink' -match "skiptoken=(.*)") {
#add $count=true to the uri if not present already
if ($GraphRequestParamsArgs.uri -notmatch "\?") {
$TotalCountUri = $GraphRequestParamsArgs.uri + "?`$count=true"
}
else {
$TotalCountUri = $GraphRequestParamsArgs.uri + "&`$count=true"
}
$TotalCountResult = Invoke-MgGraphRequest -uri $TotalCountUri -Method Get
$totalValue = $TotalCountResult.'@odata.count'
$allValues = @()
$currentUri = $GraphRequestParamsArgs.uri
$count = 0
# Loop through pages until there is no @odata.nextLink
do {
# Request the current page
$pageResult = Invoke-MgGraphRequest -uri $currentUri -Method Get
$allValues += $pageResult.value
$count += $pageResult.value.Count
# Update progress
Write-Progress -id 1 -ParentId 0 -PercentComplete ($count / $totalValue * 100) -Status "$count of $totalValue items" -Activity "Retrieving next page"
# Check for next link
$currentUri = $pageResult.'@odata.nextLink'
} while ($null -ne $currentUri)
# Complete progress
Write-Progress -id 1 -ParentId 0 -Activity "Retrieving next page" -Completed
# Output the total count and all values
Write-Output "Total items retrieved: $count"
$allValues
}
else {
Write-Error "No skiptoken or skip value found in nextLink"
}
}
else {
$Result.value
}
}
else {
$Result.value
}
}
#endregion
Function Write-Log {
<#
.SYNOPSIS
Writes CMTrace log file, customized version of https://janikvonrotz.ch/2017/10/26/powershell-logging-in-cmtrace-format/
#>
[CmdletBinding()]
Param(
[parameter(Mandatory = $true)]
[String]$Path,
[parameter(Mandatory = $true, ValueFromPipeline)]
[String]$Message,
[parameter(Mandatory = $true)]
[String]$Component,
[Parameter(Mandatory = $true)]
[ValidateSet("Info", "Warning", "Error")]
[String]$Type,
[Parameter(Mandatory = $false)]
[Switch]$ConsoleOutput
)
switch ($Type) {
"Info" { [int]$Type = 1 }
"Warning" { [int]$Type = 2 }
"Error" { [int]$Type = 3 }
}
if ($ConsoleOutput.IsPresent) {
switch ($Type) {
1 { $ForgroundColor = "White" }
2 { $ForgroundColor = "Yellow" }
3 { $ForgroundColor = "Red" }
}
$OutPut = "{0} : {1}" -f $(Get-Date -Format "MM-d-yyyy HH:mm:ss.ffffff"), $Message
write-host $OutPut -ForegroundColor $ForgroundColor
}
# Create a log entry
$Content = "<![LOG[$Message]LOG]!>" + `
"<time=`"$(Get-Date -Format "HH:mm:ss.ffffff")`" " + `
"date=`"$(Get-Date -Format "M-d-yyyy")`" " + `
"component=`"$Component`" " + `
"context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " + `
"type=`"$Type`" " + `
"thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " + `
"file=`"`">"
# Write the line to the log file
$Content | Out-File -FilePath $Path -Append -Encoding UTF8
}
#endregion
#region Main Code
Write-Log -Message "--- Start $DeleteAutopilotDevices ---"
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Directory.Read.All, DeviceManagementServiceConfig.Read.All, DeviceManagementServiceConfig.ReadWrite.All" -NoWelcome
# Check the current context
#Get-MgContext | Select-Object -Property Account, TenantId, Environment
# Import CSV
$DevicesToDelete = $null
try {
$DevicesToDelete = Import-Csv -Path $CSVPath
}
catch {
write-Log -Message "Error: $($_.Exception.Message)" -Type Error
Write-Log -Message "--- End $DeleteAutopilotDevices ---"
return
}
# Check if the CSV file is empty
if ($null -eq $DevicesToDelete) {
Write-Log -Message "No devices found in CSV file" -Type Error
Write-Log -Message "--- End $DeleteAutopilotDevices ---"
return
}
# Check if the CSV file contains the required columns
if ($null -eq $DevicesToDelete.'Serial Number') {
Write-Log -Message "CSV file does not contain the required column 'Serial Number'" -Type Error
Write-Log -Message "--- End $DeleteAutopilotDevices ---"
return
}
# Return number of devices to delete
Write-Log -Message "Number of devices to delete: $($DevicesToDelete.Count)"
# Delete devices from the CSV file based on Serial Number
Foreach ($ListDevice in $DevicesToDelete) {
$SerialNumber = $ListDevice.'Serial Number'
# Get device
$AutopilotDeviceIdentityUrl = "https://graph.microsoft.com/v1.0/deviceManagement/windowsAutopilotDeviceIdentities?`$filter=contains(serialNumber,'$SerialNumber')"
$AutopilotDevice = (Invoke-MgGraphRequest -uri $AutopilotDeviceIdentityUrl).value
# Delete device
If ($null -ne $AutopilotDevice -and $AutopilotDevice -ne "") {
Write-Log -Message "Found Autopilot registration for device with Serial Number $SerialNumber :"
$EntraDeviceID = $AutopilotDevice.azureActiveDirectoryDeviceId
$IntuneDeviceID = $AutopilotDevice.managedDeviceId
$AutopilotDeviceID = $AutopilotDevice.id
Write-log -Message " Autopilot Id: $AutopilotDeviceID"
Write-log -Message " Intune Device Id: $IntuneDeviceID"
Write-log -Message " Entra Device Id: $EntraDeviceID"
# Remove Autopilot registration
Write-Log -Message " Removing Autopilot registration for device with Serial Number $SerialNumber" -Type Warning
## Get Autopilot device infos first
## Device info already present in $AutopilotDevice
Write-log -Message " Enrollment State: $($AutopilotDevice.enrollmentState)"
Write-log -Message " Last Contact Date: $($AutopilotDevice.lastContactedDateTime)"
## Log Autopilot device
$AutopilotDeviceDeviceUrl = "https://graph.microsoft.com/v1.0/deviceManagement/windowsAutopilotDeviceIdentities/$AutopilotDeviceID"
$AutopilotDeviceJson = ($AutopilotDevice | ConvertTo-Json) -replace '\s+|\n', ' '
Write-Log -Message " Autopilot Device = [$AutopilotDeviceJson]" -ConsoleOutput:$false
## Delete Autopilot device
if ($Force.IsPresent) {
Invoke-MgGraphRequest -Uri $AutopilotDeviceDeviceUrl -Method DELETE
Write-Log -Message " Autopilot device with Serial Number $SerialNumber deleted" -Type Warning
}
else {
Write-Log -Message " Autopilot device with Serial Number $SerialNumber would have been deleted" -Type Warning
}
# Remove Intune device
Write-Log -Message " Removing associated Intune device with Serial Number $SerialNumber" -Type Warning
## Get Intune device infos first
$IntuneDeviceUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/$IntuneDeviceID"
$IntuneDevice = Invoke-MgGraphRequest -Uri $IntuneDeviceUrl -Method GET
Write-log -Message " Last Sync Date: $($IntuneDevice.lastSyncDateTime)"
Write-log -Message " User: $($IntuneDevice.userDisplayName)"
Write-log -Message " Device Name: $($IntuneDevice.deviceName)"
## Log Intune device
$IntuneDeviceJson = ($IntuneDevice | ConvertTo-Json) -replace '\s+|\n', ' '
Write-Log -Message " Intune Device = [$IntuneDeviceJson]" -ConsoleOutput:$false
## Delete Intune device
if ($Force.IsPresent) {
Invoke-MgGraphRequest -Uri $IntuneDeviceUrl -Method DELETE
Write-Log -Message " Intune device with Serial Number $SerialNumber deleted" -Type Warning
}
else {
Write-Log -Message " Intune device with Serial Number $SerialNumber would have been deleted" -Type Warning
}
# Remove Entra device
Write-Log -Message " Removing associated Entra device with Serial Number $SerialNumber" -Type Warning
## Get Entra device infos first
$EntraDeviceUrl = "https://graph.microsoft.com/v1.0/devices(deviceId='$EntraDeviceID')"
$EntraDevice = Invoke-MgGraphRequest -Uri $EntraDeviceUrl -Method GET
Write-log -Message " Approx. Last Sign In: $($EntraDevice.approximateLastSignInDateTime)"
Write-log -Message " Device Name: $($EntraDevice.displayName)"
## Log Entra device
$EntraDeviceJson = ($EntraDevice | ConvertTo-Json) -replace '\s+|\n', ' '
Write-Log -Message " Entra Device = [$EntraDeviceJson]" -ConsoleOutput:$false
## Delete Entra device
if ($Force.IsPresent) {
Invoke-MgGraphRequest -Uri $EntraDeviceUrl -Method DELETE
Write-Log -Message " Entra device with Serial Number $SerialNumber deleted" -Type Warning
}
else {
Write-Log -Message " Entra device with Serial Number $SerialNumber would have been deleted" -Type Warning
}
} Else {
Write-Log -Message " Device with Serial Number $SerialNumber not found" -Type Error
}
}
# Device not found
Write-Log -Message " "
Write-Log -Message "--- End $DeleteAutopilotDevices ---"
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment