MANAGEMENT CONSENT: CAPTURE, PLAN AND REMOVE ONLY LAB ADDITIONS

PURPOSE AND PREREQUISITES
This is cleanup of the administrator's management client, not Lab.Read on the
agent. The allowed removal set is fixed to these three delegated Graph scopes:
  AgentIdentityBlueprint.Create
  AgentIdentityBlueprint.AddRemoveCreds.All
  AgentIdentityBlueprint.UpdateAuthProperties.All
Only scopes absent from the pre-lab baseline are removable. A pre-existing
scope with one of these names stays. Other scopes are always preserved.

Use an existing authorized management client and a separately planned cleanup
session. Capture/List/Plan require Application.Read.All and Directory.Read.All
with a supported operator role. Restore additionally requires already approved
DelegatedPermissionGrant.ReadWrite.All (or Directory.ReadWrite.All), and a role
supported by the grant update/delete operation, such as Cloud Application
Administrator or Application Administrator. A Global Administrator was used
in the recorded lab. Do not assign that role to the agent.

The helper never connects, consents or assigns roles. Arrange these prerequisite
authorizations before the lab. If they are not already available, stop before
granting the three lab scopes and arrange an authorized cleanup route under
your normal process. Any other new permissions require their own restoration
plan; this helper does not remove them automatically. Read-only OAuth scopes
and operator directory roles are separate requirements.

1. CAPTURE BEFORE ADDING THE THREE SCOPES
Run from the extracted folder with lab-config.json containing the tenant ID.
Use the management client that will perform the lab configuration, with its
already approved read permissions:

  $c = Get-Content .\lab-config.json -Raw | ConvertFrom-Json
  Connect-MgGraph -TenantId $c.TenantId -ContextScope Process -Scopes 'Application.Read.All','Directory.Read.All' -NoWelcome
  $managementClientAppId = (Get-MgContext).ClientId
  $managementClientAppId | Set-Content .\management-client-app-id.txt
  .\Manage-LabManagementConsent.ps1 -Mode Capture -ClientAppId $managementClientAppId
  $LASTEXITCODE

Expected: exit 0 and management-consent-before.json saved privately. Confirm
tenant/client selection and inspect the baseline. All pages must be read.
The snapshot includes only this client's delegated grants to Microsoft Graph,
including consentType, principalId, grant ID and exact original scopes.
Capture refuses to overwrite an existing baseline. Never recapture after the
temporary permissions were granted. Keep both files outside the publication.
If a baseline is missing, do not fabricate one from the current state.

2. LIST AFTER THE LAB
Keep an authorized cleanup session open. It may use a different pre-approved
management client; ClientAppId below always identifies the client being cleaned,
not necessarily the client authenticating the cleanup request.

  $managementClientAppId = (Get-Content .\management-client-app-id.txt -Raw).Trim()
  .\Manage-LabManagementConsent.ps1 -Mode List -ClientAppId $managementClientAppId

AllPrincipals is tenant-wide administrator consent; principalId must be null.
Principal is consent for a specific user; principalId must match that user's
object ID. The Entra Permissions > User consent tab cannot revoke these grants.
Graph/PowerShell is required for that branch. The helper supports either type.
Use the exact listed grant ID and expected user. Do not select by display name.

3. PREVIEW ONE EXACT GRANT
For per-user consent, replace the placeholders with IDs from the private list:

  $target = @{
    ClientAppId = $managementClientAppId
    GrantId = 'EXACT_GRANT_ID_FROM_LIST'
    ConsentType = 'Principal'
    PrincipalId = 'EXACT_USER_OBJECT_ID_FROM_LIST'
  }
  .\Manage-LabManagementConsent.ps1 -Mode Plan @target
  .\Manage-LabManagementConsent.ps1 -Mode Restore @target -WhatIf

For administrator consent use ConsentType = 'AllPrincipals' and omit PrincipalId.
Review the tenant/client/resource, grant, consent type and user, action,
removeScopes, keepScopes and preservedAdditionalScopes before proceeding.
Plan uses GET only. Restore -WhatIf also makes no Graph write, but requires the
existing cleanup authorization so that prerequisites are checked beforehand.

4. APPLY AND VERIFY
Coordinate a window without concurrent consent changes, then run:

  .\Manage-LabManagementConsent.ps1 -Mode Restore @target
  $LASTEXITCODE
  .\Manage-LabManagementConsent.ps1 -Mode List -ClientAppId $managementClientAppId

Restore asks for confirmation for that grant only and rereads it immediately
before writing. A changed record stops the operation. This reread is not an
atomic concurrency guarantee; do not make simultaneous consent changes.
PATCH sets the complete retained scope list, preserving all baseline scopes
and any unrelated current scopes. Existing missing baseline scopes stop the
operation; they are never silently regranted. A changed grant ID for the same
consent also stops the operation for manual review.

DELETE is allowed only for a grant absent from the baseline whose entire
current scope list consists of removable lab additions. Its absence is verified
with a subsequent 404. A new grant with unrelated scopes is PATCHed to keep
those scopes, never deleted wholesale. The helper never deletes a baseline
grant. Both PATCH and DELETE use the exact /oauth2PermissionGrants/{id} endpoint.

Readback must match the intended retained set or verified absence. Exit 1 means
failure; if a write was submitted before readback failed, inspect current state
before retrying. Exit 0 alone does not prove a change: Plan, -WhatIf, a cancelled
confirmation and an already-clean grant also return 0. Require the verified
readback message and independent List comparison. Repeat for every relevant
grant, including per-user and administrator records, rather than bulk revoking.

Review preservedAdditionalScopes separately. They are retained to avoid removing
unrelated authorization but do not establish exact restoration to the baseline.
Any extra permissions introduced outside the three-scope allowlist need separate
review and removal if appropriate. Keep the management application and existing
consent. Disconnect-MgGraph only ends the session; it does not revoke consent.

PROVENANCE
The original tenant cleanup was independently verified. This new public helper
was checked with local Graph fixtures, not by recreating tenant grants. Its
baseline and safety checks do not replace authorization to perform cleanup.

MICROSOFT REFERENCES
https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/manage-application-permissions
https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-oauth2permissiongrants?view=graph-rest-1.0
https://learn.microsoft.com/en-us/graph/api/oauth2permissiongrant-update?view=graph-rest-1.0
https://learn.microsoft.com/en-us/graph/api/oauth2permissiongrant-delete?view=graph-rest-1.0
