disable-duplicateAzureAdDevices.ps1
<#PSScriptInfo
.VERSION 1.0.2 .GUID a4167185-5eb8-4e61-93bc-3cf86394fc6b .AUTHOR Jos Lieben .COMPANYNAME Lieben Consultancy .COPYRIGHT .TAGS .LICENSEURI .PROJECTURI https://www.lieben.nu/ .ICONURI #> #Requires -Module @{ModuleName = 'MSOnline'; ModuleVersion = '1.1.183.57'} <# .DESCRIPTION Cleans up older duplicates of Azure Device Entries with the same hardware ID (Windows only) #> #try to set TLS to v1.2, Powershell defaults to v1.0 try{ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 Write-Verbose "Set TLS protocol version to prefer v1.2" }catch{ Write-Error "Failed to set TLS protocol to prefer v1.2, script may fail" -ErrorAction Continue Write-Error $_ -ErrorAction Continue } #get O365 Creds try{ $script:o365credentials = Get-Credential #you can use Get-AutomationPSCredential here Write-Output "O365 credentials loaded" }catch{ Write-Error "Failed to load credentials" Exit } try{ Connect-MsolService -Credential $script:o365credentials }catch{ Write-Error "Failed to connect to AzureAD" Exit } $script:token = $Null function update-MSApiToken{ Param( [String]$resource="https://graph.windows.net/" ) if($script:token -eq $Null -or $script:token.resource -ne $resource -or ((Get-Date).AddSeconds(-180)) -le ([timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($script:token.expires_on)))){ $userName = $script:o365credentials.UserName $pwd = $script:o365credentials.GetNetworkCredential().Password $uri = "https://login.microsoftonline.com/$tenantId/oauth2/token" $postBody = "resource=$([System.Web.HttpUtility]::UrlEncode($resource))&client_id=$([System.Web.HttpUtility]::UrlEncode("1950a258-227b-4e31-a9cf-717495945fc2"))&grant_type=password&username=$([System.Web.HttpUtility]::UrlEncode($userName))&password=$([System.Web.HttpUtility]::UrlEncode($pwd))&scope=openid" $script:token = ((Invoke-RestMethod -Uri $uri -Body $postBody -Method POST -ContentType 'application/x-www-form-urlencoded')) } } #automatically look up the tenant ID based on the standardized environment name $tenantId = (Invoke-RestMethod "https://login.windows.net/$($o365credentials.UserName.Split("@")[1])/.well-known/openid-configuration" -Method GET).userinfo_endpoint.Split("/")[3] Write-Output "Autodetected tenant ID $tenantID" $enabledWindowsDevices = @() try{ update-MSApiToken $devices = Invoke-RestMethod -Method GET -UseBasicParsing -Uri "https://graph.windows.net/$tenantId/devices?`$expand=registeredOwners&api-version=1.61-internal&`$top=100&`$filter=deviceOSType eq 'Windows' and accountEnabled eq true" -Headers @{"Authorization" = "Bearer $($script:token.access_token)"} -ContentType "application/json" $enabledWindowsDevices += $devices.value Write-Output "Initial fetch of $($devices.value.count) succeeded" }catch{ Write-Verbose "Failed to get a token or retrieve devices, aborting" Throw } while($devices.'odata.nextLink'){ Write-Verbose "Fetching next 100 devices..." $skipToken = $devices.'odata.nextLink'.SubString($devices.'odata.nextLink'.IndexOf("skiptoken=")+10) $devices = Invoke-RestMethod -Method GET -UseBasicParsing -Uri "https://graph.windows.net/$tenantId/devices?api-version=1.61-internal&`$expand=registeredOwners&`$filter=deviceOSType eq 'Windows' and accountEnabled eq true&`$skiptoken=$skipToken&`$top=100" -Headers @{"Authorization" = "Bearer $($script:token.access_token)"} -ContentType "application/json" $enabledWindowsDevices += $devices.value Write-Verbose "Got $($devices.value.Count) devices (total is now $($enabledWindowsDevices.Count))" } #get all enabled AzureAD devices Write-Output "$($enabledWindowsDevices.count) total enabled Windows devices in environment, building hashtable..." $hwIds = @{} $duplicates=@{} #create hashtable with all devices that have a Hardware ID foreach($device in $enabledWindowsDevices){ $physId = $Null foreach($deviceId in $device.DevicePhysicalIds){ if($deviceId.StartsWith("[HWID]")){ $physId = $deviceId.Split(":")[-1] } } if($physId){ if(!$hwIds.$physId){ $hwIds.$physId = @{} $hwIds.$physId.Devices = @() $hwIds.$physId.DeviceCount = 0 } $hwIds.$physId.DeviceCount++ $hwIds.$physId.Devices += $device } } S Write-Output "Hashtable created, detecting duplicates...." #select HW ID's that have multiple device entries $hwIds.Keys | ForEach-Object{ if($hwIds.$_.DeviceCount -gt 1){ $duplicates.$_ = $hwIds.$_.Devices } } Write-Output "$($duplicates.Keys.Count) duplicates detected, remediating...." #loop over the duplicate HW Id's $cleanedUp = 0 $totalDevices = 0 foreach($key in $duplicates.Keys){ $mostRecentlyActive = (Get-Date).AddYears(-100) foreach($device in $duplicates.$key){ $totalDevices++ #detect which device is the most recently active device if([DateTime]$device.ApproximateLastLogonTimestamp -gt $mostRecentlyActive){ $mostRecentlyActive = [DateTime]$device.ApproximateLastLogonTimestamp } } $mostRecentlyCreated = (Get-Date).AddYears(-100) foreach($device in $duplicates.$key){ #detect which device is the most recently registered device try{ $createdDateTime = [DateTime]($device.deviceSystemMetadata | where {$_.key -eq "CreationTime"}).value if($createdDateTime -gt $mostRecentlyCreated){ $mostRecentlyCreated = $createdDateTime } }catch{ $createdDateTime = (Get-Date).AddYears(-100) } } foreach($device in $duplicates.$key){ try{ $createdDateTime = [DateTime]($device.deviceSystemMetadata | where {$_.key -eq "CreationTime"}).value }catch{ $createdDateTime = $Null } if($createdDateTime -and $createdDateTime -lt $mostRecentlyCreated){ try{ Disable-MsolDevice -DeviceId $device.DeviceId -Force -Confirm:$False -ErrorAction Stop Write-Output "Disabled Stale device $($device.DisplayName) with last active date: $($device.ApproximateLastLogonTimestamp) and registered date $createdDateTime" $cleanedUp++ }catch{ Write-Output "Failed to disable Stale device $($device.DisplayName) with last active date: $($device.ApproximateLastLogonTimestamp) and registered date $createdDateTime" Write-Output $_.Exception } } } } Write-Output "Total unique hardware ID's with >1 device registration: $($duplicates.Keys.Count)" Write-Output "Total devices registered to these $($duplicates.Keys.Count) hardware ID's: $totalDevices" Write-Output "Devices cleaned up: $cleanedUp" |