Intune

  • Removing Duplicate Intune Devices

    Today I was notified we have some duplicate devices in our Intune environment. As it turns out, devices are not always reset correctly before reimaging and this means a few devices turn up multiple times. That’s not ideal.

    Now I could go and manually search for these devices and delete them but frankly, I have better things to do and so do my coworkers. So what better way to tackle this than with a script!

    Now of course, the first thing to do is check online. After all, what is the use of writing something from scratch if someone else has done it already? Before I could fire up my browser a coworker had already sent me a link to WPNinjas post on this topic. Now their code is a few years old and the Microsoft.Graph.Intune module uses the old disabled Microsoft Intune Powershell application and so the Connect-MsGraph command no longer works.

    Error message presented when trying to log in with the Connect-MsGrpah command
    Connect-MsGraph error message

    Now I could go and register a new app, add the Update-MSGraphEnvironment command to the script and run it that way, but i’d rather not use the old commands so I decided to just take their script as inspiration and rewrite the code to use the newer Microsoft.Graph.DeviceManagement module and authenticate using the Connect-MgGraph command for which the app registration is already in our environment

    This is what I cobbled together. Starting with the Begin block. I always add the Set-StrictMode -Version 3.0 to my scripts, as this tightens up the coding standards we’re held to by powershell and I feel like this decreases the chance of having to debug unexpected behaviour later.

    [CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')]
    Param()
    Begin {
    	Set-StrictMode -Version 3.0
    	Write-Host "Starting Remove-DuplicateIntuneDevices"
    	Write-Verbose "Checking if Microsoft.Graph.Intune module is installed"
    	try {
    		Import-Module Microsoft.Graph.DeviceManagement -ErrorAction Stop
    	}
    	catch {
    		Write-Error "Module Microsoft.Graph.Intune not found. Please install module."
    		exit 1
    	}
    
    	Write-Verbose "Connect to Intune"
    	try{
    		Connect-MgGraph -NoWelcome -Scopes 'DeviceManagementManagedDevices.ReadWrite.All' -ErrorAction stop
    	}
    	catch{
    		Write-Error "Not authenticated. Please authenticate and connect to intune with an account with sufficient privileges."
    		exit 1
    	}
    }

    I also added [CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='High')] so I could use -WhatIf and -Confirm as I feel like that is just good practice. Also, even though it is technically not necessary to add the Import-Module, it enables us to confirm the module is available on the system the script is run on.

    Next is the meat of the script, the Process block. It’s not too complicated and I have added comments in the code to explain the steps.

    • Get all managed devices (in our environment Windows and Apple laptop/desktop devices)
    • Place all devices in groups, (ignoring those without known Serial Number) based on a unique identifier (Serial Number)
    • Filter out the groups that have more than one (1) entry
    • For each group with more than one (1) entry:
      • Sort the group based on the Last Sync Time and ignore the newest synced (as this is the one we want to keep)
      • loop through the remaining devices and delete them from intune (with warnings as this is the high-risk operation)
    Process {
    	#Get all intune devices
    	$devices = Get-MgDeviceManagementManagedDevice -All
    	Write-Verbose "Found $($devices.Count) devices."
    
    	#Place devices in groups
    	$deviceGroups = $devices | Where-Object { -not [String]::IsNullOrWhiteSpace($_.serialNumber) -and ($_.serialNumber -ne "Defaultstring")} | Group-Object -Property serialNumber
    	
    	#filter out groups with more than one entry
    	$duplicatedDevices = $deviceGroups | Where-Object {$_.Count -gt 1 }
    	if ($null -ne $duplicatedDevices){
    		Write-Verbose "Found $($duplicatedDevices.Values.Count) devices with duplicated entries"
    		Write-Host "Processing removal of $($duplicatedDevices.Values.Count) duplicate devices"
    	}
    	
    	$DuplicatesDeleted = 0
    	
    	foreach($duplicatedDevice in $duplicatedDevices){
    		#find devices to delete, skip the first entry of the group as that is the most recently synced device
    		$devicesToDelete = $duplicatedDevice.Group | Sort-Object -Property lastSyncDateTime -Descending | Select-Object -Skip 1
    		Write-Verbose "Selected duplicate device: $($duplicatedDevice.DeviceName)"
    		
    		foreach($device in $devicesToDelete){
    			if($PSCmdlet.ShouldProcess("$($device.SerialNumber), $($device.LastSyncDateTime)", "Remove device from intune")){
    				Write-Verbose "Removing $($device.deviceName) $($device.lastSyncDateTime)"
    				try {
    					Remove-MgDeviceManagementManagedDevice -managedDeviceId $device.id
    					Write-Verbose "Device $($device.deviceName) $($device.lastSyncDateTime) deleted"
    					$DuplicatesDeleted++
    				}
    				catch {
    					Write-Error "Could not delete device: $($device.deviceName) $($device.lastSyncDateTime)"
    				}
    			} 
    			else{
    				Write-Verbose "Device $($device.deviceName), last synced on $($device.lastSyncDateTime) set to be deleted if script is run"
    			}
    		}
    	}

    The ‘risky’ parts are surrounded in ShouldProcess and try...catch statements to allow for the -WhatIf and -Confirm switches and handle any unexpected errors relatively neatly. Lastly, the End block. This just reports the amount of deleted devices and closes the connection.

    End {
    	Disconnect-MgGraph | Out-Null
    	Write-Host "Done. $DuplicatesDeleted devices deleted."
    }

    You can find the script at github.com/MiauwMaster/ConfigChronicles/tree/main/Scripts/Remove-DuplicateIntuneDevices
    Feel free to use it as-is or as inspiration for your own script. I’m sure there is tweaking that can be done.