Created
August 5, 2024 15:04
-
-
Save MrWyss-MSFT/980f43ecaa74df26627b84b359e0ac15 to your computer and use it in GitHub Desktop.
Bulk wipe Intune devices based on a txt file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Wipes Intune Devices from a given computers.txt List. | |
| .DESCRIPTION | |
| Searches the DeviceID from a given computers.txt list in Intune. | |
| Found Devices will be wiped. There is standard output as well as a | |
| CMTrace compatible Logfile (Wipe-IntuneDevices-yyyy-m-dd.log) | |
| will be created in the ScriptRoot. | |
| Requires | |
| - Modules Microsoft.Graph.Intune and Microsoft.Graph.Authentication | |
| - Install-Module Microsoft.Graph.Authentication -Force | |
| - Access to MSGraph with Consent (DeviceManagementManagedDevices.PrivilegedOperations.All) | |
| - Tested with PowerShell 7.4 | |
| .EXAMPLE | |
| PS> .\Wipe-IntuneDevices.ps1 | |
| Default Validation Mode | |
| .EXAMPLE | |
| PS> .\Wipe-IntuneDevices.ps1 -Force | |
| With Force Mode devices will get wiped | |
| #> | |
| #Requires -Modules @{ ModuleName="Microsoft.Graph.Authentication"; ModuleVersion="2.20.0" } | |
| [CmdletBinding()] | |
| Param( | |
| [Parameter(Mandatory = $false)] | |
| [Switch] | |
| $Force | |
| ) | |
| Import-Module Microsoft.Graph.Authentication | |
| Connect-MgGraph -Scopes "Directory.Read.All, DeviceManagementServiceConfig.Read.All, DeviceManagementManagedDevices.PrivilegedOperations.All" -NoWelcome #TBC | |
| Write-Host "Connected to MSGraph with Scopes: " -NoNewline | |
| $Context = Get-MgContext | |
| #Connect-MSGraph -AdminConsent | out-null #Connect and grant permissions | |
| #region Variables | |
| $ComputerListFile = ".\computers.txt" | |
| $ScriptDirectory = Split-Path $MyInvocation.MyCommand.Path | |
| $LogFilePath = Join-Path $ScriptDirectory "Wipe-IntuneDevices-$(Get-Date -Format yyyy-M-dd).log" | |
| $LogComponent = "IntuneDeviceWipe" | |
| $PSDefaultParameterValues = @{ | |
| "Write-Log:Path" = $LogFilePath | |
| "Write-Log:Component" = $LogComponent | |
| "Write-Log:Type" = "Info" | |
| "Write-Log:ConsoleOutput" = $True | |
| } | |
| #endregion | |
| #region Functions | |
| 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 | |
| } | |
| function Get-IntuneDeviceID { | |
| [CmdletBinding()] | |
| param | |
| ( | |
| [Parameter( | |
| Position = 0, | |
| Mandatory = $true, | |
| ValueFromPipeline = $true, | |
| ValueFromPipelineByPropertyName = $true) | |
| ] | |
| [Alias('ComputerName')] | |
| [String[]] | |
| $ComputerList | |
| ) | |
| begin {} | |
| process { | |
| foreach ($ComputerName in $ComputerList) { | |
| $IntuneDeviceUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/?`$filter=deviceName eq '$ComputerName'&select=id, deviceName" | |
| $IntuneDevice = Invoke-MgGraphRequest -Uri $IntuneDeviceUrl -Method Get | |
| If ($IntuneDevice.'@odata.count' -gt 1) { | |
| Write-Log -Message "More than one Device found for $ComputerName in Intune, device will be excluded " -Type "Error" | |
| } | |
| If ($IntuneDevice.'@odata.count' -eq 0) { | |
| Write-Log -Message "No Device found for $ComputerName in Intune, device will be excluded " -Type "Error" | |
| } | |
| else { | |
| [pscustomobject]@{ComputerName = $IntuneDevice.value.deviceName; DeviceID = $IntuneDevice.value.id } | |
| } | |
| } | |
| } | |
| end { | |
| } | |
| } | |
| function Wipe-IntuneDevice { | |
| [CmdletBinding( | |
| SupportsShouldProcess, | |
| ConfirmImpact = 'High' | |
| )] | |
| param( | |
| [Parameter(Mandatory = $true, HelpMessage = "ComputerName, DeviceID List", ValueFromPipeline)] | |
| [pscustomobject[]]$ComputerNameDeviceIDList | |
| ) | |
| [int]$TotalCount = $Input.count | |
| [int]$Count = 0 | |
| Write-Log -Message "--- Start Wipe Intune Devices ---" | |
| Write-Log -Message "Number of Devices to Wipe: $TotalCount" -Type Warning | |
| $Input | ForEach-Object { | |
| $Count++ | |
| [int]$PercentComplete = ($Count / $TotalCount * 100) | |
| $DeviceText = "ComputerName: $($_.ComputerName) DeviceID: $($_.DeviceID)" | |
| Write-Progress -id 1 -ParentId 0 -Activity "Delete Devices " -Status "Process: $DeviceText ( $Count of $TotalCount )" -PercentComplete $PercentComplete | |
| #Write-Log -Message "Current $_ ($i/$($ComputerNameDeviceIDList.count))" | |
| if ($PSCmdlet.ShouldProcess("$DeviceText", 'WIPE')) { | |
| try { | |
| Write-Log -Message "Try to Wipe $DeviceText" -Type Warning | |
| if ($Force.IsPresent) { | |
| $IntuneDeviceUrl = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/$($_.DeviceID)/wipe" | |
| Invoke-MgGraphRequest -Uri $IntuneDeviceUrl -Method POST | |
| Write-Log -Message "Wiped : $DeviceText" -Type Warning | |
| } | |
| else { | |
| Write-Log -Message "Validation Mode (Wipe): $DeviceText" -Type Warning | |
| } | |
| } | |
| catch { | |
| #Write-Error ($_ | Out-String) | |
| Write-Log -Message ($_ | Out-String) -Type Error | |
| Write-Log -Message "Failed to Wipe $DeviceText" -Type Warning | |
| } | |
| } | |
| else { | |
| Write-Log -Message "Cancelled Wipe of $DeviceText" -Type Warning | |
| } | |
| } | |
| # Complete progress | |
| Write-Progress -id 1 -ParentId 0 -Activity "Delete Devices " -Completed | |
| Write-Log -Message "--- End Wipe Intune Devices ---" | |
| } | |
| #endregion | |
| #region Business Logic | |
| Write-Log -Message "Connected as $($Context.Account) MSGraph" -Type "Info" | |
| ## Read input file | |
| Write-Log -Message "----- Start Wipe-IntuneDevices Script -----" | |
| $ComputersList = Get-Content -Path $ComputerListFile | |
| #region Validate Input File | |
| ## Check if File is empty | |
| if ($ComputersList.count -eq 0) { | |
| Write-Log -Message "No ComputerNames found in File" -Type "Error" | |
| Write-Log -Message "Script will exit" -Type "Error" | |
| Write-Log -Message "----- End Wipe-IntuneDevices Script -----" -Type "Error" | |
| Exit | |
| } | |
| ## Check if there are emtpy Lines in File and print line number | |
| $lineNumber = 0 | |
| $EmptyOrWhitespaceLines = $ComputersList | ForEach-Object { | |
| $lineNumber++ | |
| if ($_ -match '^\s*$') { | |
| [PSCustomObject]@{ | |
| LineNumber = $lineNumber | |
| LineContent = $_ | |
| } | |
| } | |
| } | Where-Object { $_ -ne $null } | |
| if ($EmptyOrWhitespaceLines) { | |
| Write-Log -Message "Empty or whitespace lines found in file" -Type "Error" | |
| $EmptyOrWhitespaceLines | ForEach-Object { | |
| Write-Log -Message "Line $($_.LineNumber)" -Type "Error" | |
| } | |
| Write-Log -Message "Script will exit" -Type "Error" | |
| Write-Log -Message "----- End Wipe-IntuneDevices Script -----" -Type "Error" | |
| Exit | |
| } | |
| ## Check if duplicate ComputerNames in File | |
| $Duplicates = $ComputersList | Group-Object | Where-Object { $_.Count -gt 1 } | |
| if ($Duplicates) { | |
| Write-Log -Message "Duplicate ComputerNames found in File" -Type "Error" | |
| $Duplicates | ForEach-Object { | |
| Write-Log -Message "Duplicate ComputerName: $($_.Name)" -Type "Error" | |
| } | |
| Write-Log -Message "Script will exit" -Type "Error" | |
| Write-Log -Message "----- End Wipe-IntuneDevices Script -----" -Type "Error" | |
| Exit | |
| } | |
| ## Validate computer names | |
| $lineNumber = 0 | |
| $InvalidComputerNames = $ComputersList | ForEach-Object { | |
| $lineNumber++ | |
| if ($_ -match '^[0-9]+$' -or $_.Length -gt 15 -or $_ -match '[`~!@#$%^&*()=+_\[\]{}\\|;:.]') { | |
| [PSCustomObject]@{ | |
| LineNumber = $lineNumber | |
| ComputerName = $_ | |
| } | |
| } | |
| } | Where-Object { $_ -ne $null } | |
| if ($InvalidComputerNames) { | |
| Write-Log -Message "Invalid computer names found in file" -Type "Error" | |
| $InvalidComputerNames | ForEach-Object { | |
| Write-Log -Message "Line $($_.LineNumber): '$($_.ComputerName)'" -Type "Error" | |
| } | |
| Write-Log -Message "Script will exit" -Type "Error" | |
| Write-Log -Message "----- End Wipe-IntuneDevices Script -----" -Type "Error" | |
| Exit | |
| } | |
| #endregion | |
| Write-Log -Message "Number of Devices in File: $($ComputersList.count)" | |
| ## Search for Devices in Intune | |
| Write-Log -Message "Start Searching for Devices in Intune" | |
| $IntuneDevices = Get-IntuneDeviceID -ComputerList $ComputersList | |
| Write-Log -Message "Number of Devices to Wipe: $($IntuneDevices.count)" | |
| ## Wipe Devices | |
| Write-Log -Message "Start Searching for Devices in Intune" | |
| $IntuneDevices | Wipe-IntuneDevice | |
| Write-Log -Message "----- End Wipe-IntuneDevices Script -----" | |
| #endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment