Public/Remove-GkStaleGuest.ps1
|
function Remove-GkStaleGuest { <# .SYNOPSIS Disable (default) or delete stale guest accounts, with a guest-type safety check. .DESCRIPTION By default blocks sign-in (PATCH /users/{id} accountEnabled=false). With -Delete it instead deletes the account (DELETE /users/{id}); deletion is a 30-day soft-delete, recoverable from the directory's deleted items. Permanent purge is intentionally NOT offered here — do it deliberately. Safety: unless -Force is given, each account is verified to be a guest (userType eq 'Guest') before any change, and members are skipped with a warning — so piping the wrong objects in cannot accidentally disable/delete a member. State-changing: supports -WhatIf / -Confirm and prompts by default. Accepts users from the pipeline and yields a PSGraphKit.GuestRemovalResult per user; failures warn and continue. Requires User.ReadWrite.All (or Directory.ReadWrite.All) plus a supporting Entra role. .PARAMETER UserId One or more user object IDs or userPrincipalNames (accepts pipeline input, incl. by the UserPrincipalName / Id property). .PARAMETER Delete Delete the account (soft-delete) instead of only disabling it. .PARAMETER Force Skip the guest-type safety check (act on the user regardless of userType). .EXAMPLE Get-GkGuestInventory -StaleOnly -InactiveDays 180 | Remove-GkStaleGuest -WhatIf Preview disabling guests inactive 180+ days. .EXAMPLE Get-GkGuestInventory -StaleOnly -InactiveDays 365 | Remove-GkStaleGuest -Delete -Confirm:$false Soft-delete guests inactive 365+ days. .EXAMPLE Remove-GkStaleGuest -UserId guest_ext#EXT#@contoso.onmicrosoft.com Disable one guest (prompts for confirmation). .OUTPUTS PSGraphKit.GuestRemovalResult #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] [OutputType('PSGraphKit.GuestRemovalResult')] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('UserPrincipalName', 'Id')] [string[]] $UserId, [switch] $Delete, [switch] $Force ) begin { Test-GkConnection -FunctionName 'Remove-GkStaleGuest' | Out-Null } process { foreach ($uid in $UserId) { if ([string]::IsNullOrWhiteSpace($uid)) { continue } $enc = [uri]::EscapeDataString($uid) $action = if ($Delete) { 'Delete guest (soft-delete)' } else { 'Disable guest (accountEnabled = false)' } # Guest-type safety check (unless -Force). if (-not $Force) { try { $u = Invoke-GkGraphRequest -Raw -Uri "/users/$enc`?`$select=id,userType" -CallerFunction 'Remove-GkStaleGuest' $utype = [string](Get-GkDictValue $u 'userType') if ($utype -ne 'Guest') { Write-Warning "Skipping '$uid': not a guest (userType=$utype). Use -Force to override." [pscustomobject]@{ PSTypeName = 'PSGraphKit.GuestRemovalResult'; UserId = $uid; Action = 'Skipped'; Outcome = 'Skipped'; Error = "Not a guest (userType=$utype)" } continue } } catch { Write-Warning "Skipping '$uid': could not verify guest status. $($_.Exception.Message)" [pscustomobject]@{ PSTypeName = 'PSGraphKit.GuestRemovalResult'; UserId = $uid; Action = 'Skipped'; Outcome = 'Failed'; Error = $_.Exception.Message } continue } } if (-not $PSCmdlet.ShouldProcess($uid, $action)) { continue } $outcome = if ($Delete) { 'Deleted' } else { 'Disabled' } $errMsg = $null try { if ($Delete) { Invoke-GkGraphRequest -Method DELETE -Uri "/users/$enc" -CallerFunction 'Remove-GkStaleGuest' | Out-Null } else { Invoke-GkGraphRequest -Method PATCH -Uri "/users/$enc" -Body @{ accountEnabled = $false } -CallerFunction 'Remove-GkStaleGuest' | Out-Null } } catch { $outcome = 'Failed' $errMsg = $_.Exception.Message Write-Warning "Failed to $(if ($Delete) { 'delete' } else { 'disable' }) '$uid': $errMsg" } [pscustomobject]@{ PSTypeName = 'PSGraphKit.GuestRemovalResult' UserId = $uid Action = if ($Delete) { 'SoftDelete' } else { 'DisableAccount' } Outcome = $outcome Error = $errMsg } } } } |