Remediation/SharePointAndOneDrive/SPO-ONEDRIVE.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
    Gives each OneDrive a decision, in particular those of inactive accounts, and prepares their copy (procedure SPO-ONEDRIVE).
 
.DESCRIPTION
    Reads oneDrive, users and the identity matrix. The copy goes through a third-party tool: the script gives the order, the volumes and
    the target account.
    decisions\SPO-ONEDRIVE.csv says for each OneDrive: Copy, Copy to the manager, Export or Drop.
    For the two decisions that go through a manager, the account goes in the Note column: the script names it administrator of the OneDrive.
    Giving access to a OneDrive exposes personal data: the script also requires the Decision maker column.
    decisions\SPO-ONEDRIVE-RETENTION.csv says if the retention of orphaned OneDrives of the source extends to that of the target.
    Limit: the pre-copy starts in the chosen tool, largest first.
#>

$c = Open-TcRemediation

$targetRetention = [int](Get-TcRemediationValue 'Target' 'sharePointSettings' 'OrphanedPersonalSitesRetentionPeriod' 0)

$retentionRows = [System.Collections.Generic.List[object]]::new()
$proposals = foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t
    if (-not (Test-TcCollectionComplete $t 'oneDrive')) { Write-TcRemediationAlert 'OneDrive collection incomplete: the list may be partial' }
    $activeOnes = @{}
    foreach ($u in Get-TcRemediationTable $t 'users') { if (ConvertTo-TcBoolean $u['Enabled']) { $activeOnes[([string]$u['UPN']).ToLowerInvariant()] = 1 } }
    $matrix = @{}
    foreach ($m in @(Get-TcRemediationIdentity $t | ForEach-Object { (Get-TcMember $_ 'rows') })) { $matrix[((Get-TcText $m 'Source UPN')).ToLowerInvariant()] = $m }
    $list = @(Get-TcRemediationTable $t 'oneDrive' | Where-Object { $_['Url'] } | Sort-Object { - [double]$_['Storage MB'] })
    $orphans = @($list | Where-Object { $_['Owner'] -and -not $activeOnes[([string]$_['Owner']).ToLowerInvariant()] })
    Write-TcRemediationNote ("{0} OneDrive(s), {1:N1} GB, {2} with no active owner" -f $list.Count, ((Get-TcMember (@($list | ForEach-Object { [double]$_['Storage MB'] }) | Measure-Object -Sum) 'Sum') / 1024), $orphans.Count)

    $retention = [int](Get-TcRemediationValue $t 'sharePointSettings' 'OrphanedPersonalSitesRetentionPeriod' 0)
    if ($retention -and $retention -lt $targetRetention) {
        $retentionRows.Add([ordered]@{ Tenant = $t; 'Current retention (days)' = $retention; 'Target retention (days)' = $targetRetention; Proposal = 'Extend' })
    }

    $rank = 0
    foreach ($o in $list) {
        $rank++
        $p = ([string]$o['Owner']).ToLowerInvariant()
        $m = $matrix[$p]
        $active = [bool]$activeOnes[$p]
        [ordered]@{ Tenant = $t; Order = $rank; OneDrive = $o['Url']; 'Owner' = $o['Owner']; 'Volume GB' = [math]::Round([double]$o['Storage MB'] / 1024, 2); 'Days inactive' = $o['Days inactive']
                    'Active account' = $(if ($active) { 'Yes' } else { 'No' }); Matrix = $(if ($m) { (Get-TcMember $m 'Decision') } else { 'Missing' }); 'Target account' = $(if ($m) { (Get-TcMember $m 'Target UPN') } else { '' })
                    Proposal = $(if ($active -and $m -and (Get-TcMember $m 'Decision') -in 'Create', 'Duplicate') { 'Copy' } else { '' }) }
    }
}
$decisions = @(Get-TcDecision -Name 'SPO-ONEDRIVE' -Proposal @($proposals) -Key 'Tenant', 'OneDrive' -Choice 'Copy', 'Copy to the manager', 'Export', 'Drop')
$chosenPolicies = @(Get-TcDecision -Name 'SPO-ONEDRIVE-RETENTION' -Proposal @($retentionRows) -Key 'Tenant' -Choice 'Extend', 'Keep')

foreach ($r in $chosenPolicies | Where-Object { (Get-TcMember $_ 'Decision') -eq 'Extend' }) {
    Add-TcCommandAction -Tenant (Get-TcMember $r 'Tenant') -Service Pnp -Command 'Set-PnPTenant' -Parameters @{ OrphanedPersonalSitesRetentionPeriod = $targetRetention } -Object 'OrphanedPersonalSitesRetentionPeriod' `
        -Description ("Extend the retention of orphaned OneDrives to {0} days" -f $targetRetention) `
        -Test {
            param($a)
            $p = Invoke-TcServiceCommand -Tenant $a.tenant -Service Pnp -Command 'Get-PnPTenant'
            $a.undo = @{ command = 'Set-PnPTenant'; parameters = @{ OrphanedPersonalSitesRetentionPeriod = [int](Get-TcMember $p 'OrphanedPersonalSitesRetentionPeriod') } }
            return [int](Get-TcMember $p 'OrphanedPersonalSitesRetentionPeriod') -ge $a.parameters.OrphanedPersonalSitesRetentionPeriod
        }
}

foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Decision') -in 'Copy to the manager', 'Export' }) {
    $manager = ((Get-TcText $d 'Note')).Trim()
    if ($manager -notmatch '^[^@\s]+@[^@\s]+\.[^@\s]+$') { Write-TcRemediationAlert ("{0}: the Note column must give the account of the manager" -f (Get-TcMember $d 'OneDrive')); continue }
    if (-not (Get-TcMember $d 'Decision maker')) { Write-TcRemediationAlert ("{0}: access not given, the Decision maker column must keep the trace of the customer approval" -f (Get-TcMember $d 'OneDrive')); continue }
    Add-TcCommandAction -Tenant (Get-TcMember $d 'Tenant') -Service Pnp -Command 'Set-PnPTenantSite' -Parameters @{ Identity = (Get-TcMember $d 'OneDrive'); Owners = @($manager) } -Object (Get-TcMember $d 'OneDrive') `
        -Description ("Give {0} access to the OneDrive {1}" -f $manager, (Get-TcMember $d 'OneDrive')) `
        -Undo @{ command = 'Remove-PnPSiteCollectionAdmin'; parameters = @{ Owners = @($manager) }; url = (Get-TcMember $d 'OneDrive') } `
        -Test {
            param($a)
            $who = $a.parameters.Owners[0]
            return [bool](@(Invoke-TcServiceCommand -Tenant $a.tenant -Service Pnp -Command 'Get-PnPSiteCollectionAdmin' -Url $a.parameters.Identity) | Where-Object { (Get-TcMember $_ 'Email') -eq $who -or (Get-TcMember $_ 'LoginName') -like "*|$who" })
        }
}

foreach ($t in $c.Tenants) {
    $byUrl = @{}
    foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t }) { $byUrl[$d.OneDrive] = (Get-TcMember $d 'Decision') }
    $copyRows = @($proposals | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t } | ForEach-Object {
        $l = [pscustomobject]$_
        $l | Add-Member -NotePropertyName 'Decision' -NotePropertyValue ([string]$byUrl[(Get-TcMember $_ 'OneDrive')]) -PassThru })
    $f = Export-TcRemediationReport -Name "onedrive-copy-plan-$t" -Rows $copyRows
    Add-TcManualAction -Tenant $t -Object 'OneDrive pre-copy' -Description "Start the pre-copy of the OneDrives in the chosen tool, largest first" `
        -Instruction @("List, order and target accounts: $f", 'Only the rows decided Copy or Copy to the manager go into the copy.')
}

Invoke-TcRemediationPlan -Verify {
    param($t)
    New-TcRemediationResult Info ("retention of orphaned OneDrives: {0} days" -f (Get-TcMember (Invoke-TcServiceCommand -Tenant $t -Service Pnp -Command 'Get-PnPTenant') 'OrphanedPersonalSitesRetentionPeriod'))
    $l = @($decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t })
    New-TcRemediationResult Info ("{0} OneDrive(s) decided" -f (Get-TcCount $l))
}
Close-TcRemediation