Remove-Entra-Group-Assignments.ps1

<#PSScriptInfo
.VERSION 1.0
.GUID d1f762a9-ad9d-47da-9f0c-8ec98b7ea1f6
.AUTHOR Alexander Marrero
.COMPANYNAME
.COPYRIGHT
.TAGS Intune EntraID MicrosoftGraph DeviceManagement Automation
.LICENSEURI https://opensource.org/licenses/MIT
.PROJECTURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES Microsoft.Graph.Authentication,Microsoft.Graph.Groups
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
Version 1.0:
- Initial release
- Removes direct group memberships from Entra ID device objects
- Skips dynamic groups and non-group directory objects
.PRIVATEDATA
#>


<#
.SYNOPSIS
Removes all direct Entra ID group memberships from a device object.

.DESCRIPTION
This script connects to Microsoft Graph and removes a specified device object
(from Entra ID / Azure AD) from all directly assigned group memberships.

The script performs the following operations:
- Retrieves all directory objects the device is a member of
- Filters results to only include Entra ID groups
- Identifies and skips dynamic membership groups (cannot be modified)
- Removes the device from all eligible (static) group memberships
- Skips non-group objects such as directory roles or administrative units

This is useful for:
- Device offboarding
- Cleanup of legacy or incorrect group assignments
- Preparing devices for reassignment or re-enrollment workflows
- Maintenance window orchestration scenarios (e.g., kiosk devices)

.PARAMETER ObjectID
The Entra ID (Azure AD) Object ID of the device.

This value is required and must be the GUID of the device object in Entra ID.

.EXAMPLE
.\Remove-EntraDeviceGroupAssignments.ps1 -ObjectID "381379cd-5c0e-403c-9af7-abbbd90214d9"

Removes the specified device from all directly assigned Entra ID groups.

.EXAMPLE
.\Remove-EntraDeviceGroupAssignments.ps1 -ObjectID (Get-MgDevice -Filter "displayName eq 'KIOSK-01'").Id

Resolves a device by name and removes all direct group memberships.

.NOTES
- Requires Microsoft Graph PowerShell SDK
- Requires the following permissions:
  • Device.ReadWrite.All
  • Group.ReadWrite.All
- Does NOT modify dynamic group memberships
- Safe to run multiple times (idempotent behavior)
- Intended for administrative and automation use cases

.LINK
https://learn.microsoft.com/en-us/graph/api/device-list-memberof
#>



[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$ObjectID
)

Begin {
    # Ensure required modules
    if (-not (Get-Module Microsoft.Graph.Authentication -ListAvailable)) {
        Write-Host "Installing Microsoft.Graph.Authentication"
        Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force
    }

    if (-not (Get-Module Microsoft.Graph.Groups -ListAvailable)) {
        Write-Host "Installing Microsoft.Graph.Groups"
        Install-Module Microsoft.Graph.Groups -Scope CurrentUser -Force
    }

    Import-Module Microsoft.Graph.Authentication
    Import-Module Microsoft.Graph.Groups

    # Connect to Graph
    Connect-MgGraph -Scopes "Device.ReadWrite.All","Group.ReadWrite.All"
    $context = Get-MgContext
    Write-Host "Connected to Tenant: $($context.TenantId)"
}

Process {

    if ([string]::IsNullOrWhiteSpace($ObjectID)) {
        Write-Host "ERROR: ObjectID is empty"
        return
    }

    try {
        $memberships = Get-MgDeviceMemberOf -DeviceId $ObjectID -All
    }
    catch {
        Write-Host "ERROR: Failed to retrieve memberships - $_"
        return
    }

    if (-not $memberships) {
        Write-Host "No group memberships found for device $ObjectID"
        return
    }

    foreach ($entry in $memberships) {

        # Only process Entra groups
        if ($entry.AdditionalProperties['@odata.type'] -ne '#microsoft.graph.group') {
            Write-Host "Skipping non-group object: $($entry.Id)"
            continue
        }

        # Get group details (to check if dynamic)
        try {
            $group = Get-MgGroup -GroupId $entry.Id -Property MembershipRule
        }
        catch {
            Write-Host "Skipping group (unable to retrieve details): $($entry.Id)"
            continue
        }

        # Skip dynamic groups
        if ($group.MembershipRule) {
            Write-Host "Skipping dynamic group: $($entry.Id)"
            continue
        }

        Write-Host -ForegroundColor Yellow "Removing device from group: $($entry.Id)"

        try {
            Remove-MgGroupMemberByRef `
                -GroupId $entry.Id `
                -DirectoryObjectId $ObjectID `
                -ErrorAction Stop

            Write-Host -ForegroundColor Green "SUCCESS: Removed from $($entry.Id)"
        }
        catch {
            Write-Host -ForegroundColor Red "FAILED: $($entry.Id) - $_"
        }
    }
}

End {
    Write-Host "Completed processing for device: $ObjectID"
    Disconnect-MgGraph
}