Remediation/SharePointAndOneDrive/SPO-EXTERNAL.ps1
|
# UNTESTED: this script has never been run on a real tenant. Read it, try it on a test tenant first, and use it at your own risk. <# .SYNOPSIS Reinvites to the target the external users validated by site owners (procedure SPO-EXTERNAL). .DESCRIPTION Reads spoExternalUsers: the address is in Accepted as, because the Email column is empty in the snapshot. An invitation can send an email to a real person: the decision is taken row by row in decisions\SPO-EXTERNAL.csv: Invite with email : Microsoft sends the invitation; Invite without email : the guest account is created, with no message; Do not invite : nothing is done. Nothing is sent without a decision. An address already in the target, or in an internal domain, is proposed as Do not invite: one guest per person. The Note column can give the address of the site to redirect the guest to. An action applies to the target. Limit: access to the site, library or folder stays to be given back by the owner, then confirmed. The snapshot does not say which sites each external user could reach. #> $c = Open-TcRemediation function Get-TargetAccountValue { param([string]$Address) $f = $Address -replace "'", "''" return (@(Invoke-TcGraph -Tenant 'Target' -Uri ("v1.0/users?`$filter=mail eq '{0}'&`$select=id,mail,userType" -f $f) -All) | Select-Object -First 1) } $inTargetTenant = @{} foreach ($u in Get-TcRemediationTable 'Target' 'users') { foreach ($x in @($u['Email'], $u['UPN'])) { if ($x) { $inTargetTenant[([string]$x).ToLowerInvariant()] = [string]$u['Type'] } } } $internalDomains = @(foreach ($t in (Get-TcProjectRole)) { Get-TcRemediationTable $t 'domains' | ForEach-Object { ([string]$_['Domain']).ToLowerInvariant() } }) $redirect = 'https://{0}.sharepoint.com' -f ((Get-TcMember (Get-TcRemediationTenant 'Target') 'InitialDomain') -replace '\.onmicrosoft\.com$', '') $proposals = foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t if (-not (Test-TcCollectionComplete $t 'sharePointExternalUsers')) { Write-TcRemediationAlert 'External user collection incomplete: the list may be partial' } $list = @(Get-TcRemediationTable $t 'sharePointExternalUsers') $noAddress = @($list | Where-Object { -not ($_['Accepted as'] -or $_['Email']) }) Write-TcRemediationNote ("{0} external user(s) to validate, {1} with no address" -f $list.Count, $noAddress.Count) foreach ($x in $noAddress) { Write-TcRemediationAlert ("External user with no address: {0}" -f $x['Name']) } $presentItems = 0 foreach ($x in $list | Where-Object { $_['Accepted as'] -or $_['Email'] }) { $address = ([string]$(if ($x['Accepted as']) { $x['Accepted as'] } else { $x['Email'] })).ToLowerInvariant() $domain = ($address -split '@')[-1] $state = if ($inTargetTenant[$address]) { 'Already in the target ({0})' -f $inTargetTenant[$address] } elseif ($domain -in $internalDomains) { 'Internal domain' } else { '' } if ($state) { $presentItems++ } [ordered]@{ Tenant = $t; Address = $address; Name = $x['Name']; 'Created' = $x['Created']; 'State' = $state; Proposal = $(if ($state) { 'Do not invite' } else { '' }) } } Write-TcRemediationNote ("{0} address(es) already in the target or in an internal domain" -f $presentItems) } $decisions = @(Get-TcDecision -Name 'SPO-EXTERNAL' -Proposal @($proposals) -Key 'Tenant', 'Address' -Choice 'Invite with email', 'Invite without email', 'Do not invite') $invited = @($decisions | Where-Object { (Get-TcMember $_ 'Decision') -in 'Invite with email', 'Invite without email' }) foreach ($d in $invited) { if ((Get-TcMember $d 'State')) { Write-TcRemediationAlert ("{0}: {1}, invitation ignored to avoid a duplicate" -f (Get-TcMember $d 'Address'), (Get-TcMember $d 'State')); continue } $withEmail = (Get-TcMember $d 'Decision') -eq 'Invite with email' Add-TcBlockAction -Tenant 'Target' -Service Graph -Object (Get-TcMember $d 'Address') -Description ("Invite {0} to the target, {1}" -f (Get-TcMember $d 'Address'), $(if ($withEmail) { 'with email' } else { 'without email' })) ` -Data @{ Address = (Get-TcMember $d 'Address'); Name = (Get-TcMember $d 'Name'); Email = $withEmail; Redirect = $(if ((Get-TcMember $d 'Note') -match '^https://') { (Get-TcText $d 'Note') } else { $redirect }) } ` -Test { param($a) [bool](Get-TargetAccountValue $a.data.Address) } ` -Block { param($a) $x = $a.data $r = Invoke-TcGraph -Tenant $a.tenant -Method POST -Uri 'v1.0/invitations' -Body @{ invitedUserEmailAddress = (Get-TcMember $x 'Address'); invitedUserDisplayName = (Get-TcMember $x 'Name'); inviteRedirectUrl = (Get-TcMember $x 'Redirect'); sendInvitationMessage = [bool](Get-TcMember $x 'Email') } $a.undo = @{ type = 'Graph'; tenant = $a.tenant; method = 'DELETE'; uri = "v1.0/users/$((Get-TcMember (Get-TcMember $r 'invitedUser') 'id'))" } } } if ($invited.Count) { Add-TcManualAction -Tenant 'Target' -Object 'Guest access' -Description "Give the site access back and have the owners confirm" ` -Instruction @('SharePoint site of the target > Settings > Site permissions: give the guest back the site, library or folder.', 'The owner confirms that the access works. The sharing level of the site must allow guests (SPO-SHARING).') } Invoke-TcRemediationPlan -Verify { param($t) $l = @($invited | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t -and -not (Get-TcMember $_ 'State') }) if (-not $l.Count) { return } $missing = @($l | Where-Object { -not (Get-TargetAccountValue (Get-TcMember $_ 'Address')) }) if ($missing.Count) { New-TcRemediationResult Blocking ("{0} guest(s) out of {1} missing from the target" -f $missing.Count, $l.Count) } else { New-TcRemediationResult OK ("{0} guest(s) present in the target" -f $l.Count) } } Close-TcRemediation |