|
| 1 | +function Invoke-ExecRemoveSPOExternalUser { |
| 2 | + <# |
| 3 | + .FUNCTIONALITY |
| 4 | + Entrypoint |
| 5 | + .ROLE |
| 6 | + Sharepoint.Site.ReadWrite |
| 7 | + .DESCRIPTION |
| 8 | + Fully removes an external user's guest access: deletes their Entra guest account (when |
| 9 | + one exists) AND removes them from every SharePoint site they hold membership on, in one |
| 10 | + pass - so no orphaned accounts or lingering site access are left behind. The inert |
| 11 | + SharePoint external-store entry cannot be deleted (Microsoft deprecated |
| 12 | + RemoveExternalUsers) and ages out on its own; sharing links the user received are |
| 13 | + revoked separately via the Sharing Report. |
| 14 | + #> |
| 15 | + [CmdletBinding()] |
| 16 | + param($Request, $TriggerMetadata) |
| 17 | + |
| 18 | + $APIName = $Request.Params.CIPPEndpoint |
| 19 | + $Headers = $Request.Headers |
| 20 | + $TenantFilter = $Request.Body.tenantFilter |
| 21 | + $EntraUserId = $Request.Body.EntraUserId |
| 22 | + $LoginName = $Request.Body.LoginName |
| 23 | + $SiteUrls = @($Request.Body.SiteUrls) | Where-Object { $_ } |
| 24 | + $DisplayName = $Request.Body.DisplayName ?? $EntraUserId ?? $LoginName |
| 25 | + |
| 26 | + try { |
| 27 | + if (-not $EntraUserId -and $SiteUrls.Count -eq 0) { |
| 28 | + throw 'This entry has no Entra guest account and no known site memberships. The remaining SharePoint store entry cannot be removed (Microsoft deprecated the API) and ages out on its own; revoke any sharing links they hold via the Sharing Report.' |
| 29 | + } |
| 30 | + |
| 31 | + $Messages = [System.Collections.Generic.List[string]]::new() |
| 32 | + $Errors = [System.Collections.Generic.List[string]]::new() |
| 33 | + |
| 34 | + # 1. Strip the SharePoint footprint first (needs the login; falls back to the Entra UPN). |
| 35 | + if ($SiteUrls.Count -gt 0) { |
| 36 | + $RemovalLogin = $LoginName |
| 37 | + if (-not $RemovalLogin -and $EntraUserId) { |
| 38 | + try { |
| 39 | + $RemovalLogin = (New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/users/$($EntraUserId)?`$select=userPrincipalName" -tenantid $TenantFilter -AsApp $true).userPrincipalName |
| 40 | + } catch { |
| 41 | + $Errors.Add("Could not resolve the user's login for site removal: $($_.Exception.Message)") |
| 42 | + } |
| 43 | + } |
| 44 | + if ($RemovalLogin) { |
| 45 | + $Removal = Remove-CIPPSPOSiteUser -TenantFilter $TenantFilter -SiteUrls $SiteUrls -LoginName $RemovalLogin |
| 46 | + if ($Removal.Succeeded.Count -gt 0) { |
| 47 | + $Messages.Add("Removed from $($Removal.Succeeded.Count) site(s): $($Removal.Succeeded -join ', ').") |
| 48 | + } |
| 49 | + if ($Removal.Failed.Count -gt 0) { |
| 50 | + $Errors.Add("Site removal failed on: $($Removal.Failed -join '; ')") |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + # 2. Delete the Entra guest account so the user cannot sign in anywhere. |
| 56 | + if ($EntraUserId) { |
| 57 | + try { |
| 58 | + $null = New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/users/$EntraUserId" -tenantid $TenantFilter -type DELETE -body '' -asapp $true |
| 59 | + $Messages.Add('Deleted the Entra guest account, blocking their sign-in.') |
| 60 | + } catch { |
| 61 | + $Errors.Add("Deleting the Entra guest account failed: $($_.Exception.Message)") |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + if ($Messages.Count -eq 0) { |
| 66 | + throw ($Errors -join ' ') |
| 67 | + } |
| 68 | + $Results = "Removed guest access for $($DisplayName): $($Messages -join ' ')" |
| 69 | + if ($Errors.Count -gt 0) { |
| 70 | + $Results += " Issues: $($Errors -join '; ')" |
| 71 | + } |
| 72 | + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Results -sev Info |
| 73 | + $StatusCode = [HttpStatusCode]::OK |
| 74 | + } catch { |
| 75 | + $ErrorMessage = Get-CippException -Exception $_ |
| 76 | + $Results = "Failed to remove guest access for $($DisplayName): $($ErrorMessage.NormalizedError)" |
| 77 | + Write-LogMessage -Headers $Headers -API $APIName -tenant $TenantFilter -message $Results -sev Error -LogData $ErrorMessage |
| 78 | + $StatusCode = [HttpStatusCode]::BadRequest |
| 79 | + } |
| 80 | + |
| 81 | + return ([HttpResponseContext]@{ |
| 82 | + StatusCode = $StatusCode |
| 83 | + Body = @{ 'Results' = $Results } |
| 84 | + }) |
| 85 | +} |
0 commit comments