Src/Private/Get-AbrSPOneDrive.ps1

function Get-AbrSPOneDrive {
    <#
    .SYNOPSIS
    Documents OneDrive for Business settings, provisioning, sync, sharing, and per-user drives.
    .DESCRIPTION
        Collects and reports on:
          - Provisioning (auto vs manual, default storage quota)
          - Storage quota (default quota, per-user quota management)
          - Sharing defaults (sharing level, default link type, link permissions)
          - Sync restrictions (domain-joined devices, allowed domains)
          - Known Folder Move (KFM) deployment guidance
          - Retention after user deletion (orphaned site retention period)
          - Per-user OneDrive summary (InfoLevel 2)
          - ACSC E8 and CIS compliance assessment
    .NOTES
        Version: 0.1.3
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting OneDrive for Business data for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'OneDrive'
    }

    process {
        #region Pre-compute ALL values before Section{} blocks (PScribo scope isolation)
        $AllowedDomainGuidsForSyncApp = @()
        $SyncDomainRestrictionEnabled = $false
        $OneDriveDefaultQuotaGB       = 0
        $BlockDownloadPolicy          = 'AllowFullAccess'
        $BlockDownloadLinksFileType   = 'None'
        $DisablePersonalListCreation  = $false
        $SPTenantData                 = $null

        if ($script:PnPAvailable) {
            try {
                $SPTenantData = Get-PnPTenant -ErrorAction Stop

                $AllowedDomainGuidsForSyncApp = @($SPTenantData.AllowedDomainGuidsForSyncApp)
                $SyncDomainRestrictionEnabled = ($AllowedDomainGuidsForSyncApp.Count -gt 0)
                $OneDriveDefaultQuotaGB       = [math]::Round($SPTenantData.OneDriveStorageQuota / 1024, 0)
                $BlockDownloadLinksFileType   = ConvertTo-SPEnumString $SPTenantData.BlockDownloadLinksFileType 'None'
                $BlockDownloadPolicy          = ConvertTo-SPEnumString $SPTenantData.ConditionalAccessPolicy    'AllowFullAccess'
                $DisablePersonalListCreation  = if ($null -ne $SPTenantData.DisablePersonalListCreation) { $SPTenantData.DisablePersonalListCreation } else { $false }
            } catch {
                Write-AbrDebugLog "Get-PnPTenant failed: $($_.Exception.Message)" 'WARN' 'ONEDRIVE'
            }
        }
        #endregion

        Section -Style Heading2 'OneDrive for Business' {
            Paragraph "The following section documents the OneDrive for Business configuration for tenant $TenantId, covering provisioning, storage quotas, sharing defaults, sync restrictions, and per-user drive activity."
            BlankLine

            if (-not $script:PnPAvailable) {
                Paragraph " [!] PnP.PowerShell is not available. OneDrive settings require a PnP connection."
            } else {
                if ($SPTenantData) {

                    #region Provisioning
                    Section -Style Heading3 'Provisioning' {
                        Paragraph "Controls how and when OneDrive for Business sites are created for users."
                        BlankLine
                        $ProvObj    = [System.Collections.ArrayList]::new()
                        $ProvMode   = if ($SPTenantData.OneDriveForGuestsEnabled -eq $true) { 'Auto-provisioned (users and guests)' } else { 'Auto-provisioned (users only)' }
                        $ProvInObj  = [ordered] @{
                            'Provisioning Mode'                   = $ProvMode
                            'Personal Site (OneDrive) Creation'   = if ($DisablePersonalListCreation) { 'Restricted to licensed users only' } else { 'Available to all licensed users' }
                            'OneDrive for Guests Enabled'         = if ($null -ne $SPTenantData.OneDriveForGuestsEnabled) { $SPTenantData.OneDriveForGuestsEnabled } else { $false }
                            'Notify Owners When Items Re-shared'  = if ($null -ne $SPTenantData.NotifyOwnersWhenItemsReshared) { $SPTenantData.NotifyOwnersWhenItemsReshared } else { $false }
                        }
                        $ProvObj.Add([pscustomobject](ConvertTo-HashToYN $ProvInObj)) | Out-Null
                        $ProvTableParams = @{ Name = "OneDrive Provisioning - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $ProvTableParams['Caption'] = "- $($ProvTableParams.Name)" }
                        $ProvObj | Table @ProvTableParams
                    }
                    BlankLine
                    #endregion

                    #region Storage Quota
                    Section -Style Heading3 'Storage Quota' {
                        Paragraph "Default and per-user storage quota settings for OneDrive for Business."
                        BlankLine
                        $QuotaObj   = [System.Collections.ArrayList]::new()
                        $QuotaInObj = [ordered] @{
                            'Default Quota per User (GB)'          = $OneDriveDefaultQuotaGB
                            'Quota Management'                     = if ($SPTenantData.OneDriveStorageQuota -gt 0) { 'Manually configured' } else { 'Microsoft managed (auto)' }
                            'Per-User Quota Override Allowed'      = 'Yes (via SharePoint Admin Center or PowerShell)'
                            'Max User Quota (tenant limit)'        = if ($OneDriveDefaultQuotaGB -gt 0) { "$OneDriveDefaultQuotaGB GB (default)" } else { 'Not configured' }
                        }
                        $QuotaObj.Add([pscustomobject]$QuotaInObj) | Out-Null
                        $QuotaTableParams = @{ Name = "OneDrive Storage Quota - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $QuotaTableParams['Caption'] = "- $($QuotaTableParams.Name)" }
                        $QuotaObj | Table @QuotaTableParams
                    }
                    BlankLine
                    #endregion

                    #region Sharing Defaults
                    Section -Style Heading3 'Sharing Defaults' {
                        Paragraph "Default sharing link type and permissions applied when users share OneDrive content."
                        BlankLine
                        $ShareDefObj   = [System.Collections.ArrayList]::new()
                        $ODSharingLevel = ConvertTo-SPEnumString $SPTenantData.OneDriveSharingCapability 'Unknown'
                        $DefLinkType    = ConvertTo-SPEnumString $SPTenantData.DefaultSharingLinkType    'Direct'
                        $DefLinkPerm    = ConvertTo-SPEnumString $SPTenantData.DefaultLinkPermission     'View'
                        $AnyoneExpiry   = if ($SPTenantData.RequireAnonymousLinksExpireInDays -gt 0) {
                                              "$($SPTenantData.RequireAnonymousLinksExpireInDays) days"
                                          } else { 'No expiry set' }

                        $ShareDefInObj = [ordered] @{
                            'OneDrive External Sharing Level' = $ODSharingLevel
                            'Default Sharing Link Type'       = $DefLinkType
                            'Default Link Permission'         = $DefLinkPerm
                            'Anyone Link Expiry'              = $AnyoneExpiry
                            'Guest Access Expiry (days)'      = if ($SPTenantData.ExternalUserExpireInDays -gt 0) { $SPTenantData.ExternalUserExpireInDays } else { 'Not configured' }
                            'Block Download (Unmanaged)'      = ($BlockDownloadLinksFileType -ne 'None')
                            'Unmanaged Device Policy'         = $BlockDownloadPolicy
                        }
                        $ShareDefObj.Add([pscustomobject](ConvertTo-HashToYN $ShareDefInObj)) | Out-Null

                        $null = (& {
                            if ($HealthCheck.SharePoint.OneDrive) {
                                $null = ($ShareDefObj | Where-Object { $_.'OneDrive External Sharing Level' -like '*Anyone*' } | Set-Style -Style Critical | Out-Null)
                            }
                        })

                        $ShareDefTableParams = @{ Name = "OneDrive Sharing Defaults - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $ShareDefTableParams['Caption'] = "- $($ShareDefTableParams.Name)" }
                        $ShareDefObj | Table @ShareDefTableParams
                    }
                    BlankLine
                    #endregion

                    #region Sync Restrictions
                    Section -Style Heading3 'Sync Restrictions' {
                        Paragraph "Controls which devices can sync OneDrive content using the OneDrive sync client."
                        BlankLine
                        $SyncObj   = [System.Collections.ArrayList]::new()
                        $SyncInObj = [ordered] @{
                            'Sync Domain Restriction Enabled'      = $SyncDomainRestrictionEnabled
                            'Allowed Domain GUIDs for Sync'        = if ($AllowedDomainGuidsForSyncApp.Count -gt 0) {
                                                                          $AllowedDomainGuidsForSyncApp -join ', '
                                                                      } else { 'No restriction (all devices can sync)' }
                            'Block Sync on Mac Clients'            = if ($null -ne $SPTenantData.BlockMacSync) { $SPTenantData.BlockMacSync } else { $false }
                            'Block External Sync'                  = if ($null -ne $SPTenantData.BlockSendLabelMismatchEmail) { $false } else { $false }
                            'Recommended Setting'                  = if ($SyncDomainRestrictionEnabled) { 'Compliant -- sync restricted to managed devices' } else { 'Review -- sync allowed from any device' }
                        }
                        $SyncObj.Add([pscustomobject](ConvertTo-HashToYN $SyncInObj)) | Out-Null

                        $null = (& {
                            if ($HealthCheck.SharePoint.OneDrive) {
                                $null = ($SyncObj | Where-Object { $_.'Sync Domain Restriction Enabled' -eq 'No' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $SyncTableParams = @{ Name = "OneDrive Sync Restrictions - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                        if ($Report.ShowTableCaptions) { $SyncTableParams['Caption'] = "- $($SyncTableParams.Name)" }
                        $SyncObj | Table @SyncTableParams
                    }
                    BlankLine
                    #endregion

                    #region Retention After User Deletion
                    Section -Style Heading3 'Retention After User Deletion' {
                        Paragraph "Controls how long a user's OneDrive is retained after their account is deleted or licence removed."
                        BlankLine
                        $RetObj     = [System.Collections.ArrayList]::new()
                        $RetDays    = if ($SPTenantData.OrphanedPersonalSitesRetentionPeriod -gt 0) {
                                          $SPTenantData.OrphanedPersonalSitesRetentionPeriod
                                      } else { 30 }   # Microsoft default is 30 days
                        $RetMonths  = [math]::Round($RetDays / 30, 1)
                        $retInObj   = [ordered] @{
                            'Retention Period (days)'             = $RetDays
                            'Retention Period (approx. months)'   = $RetMonths
                            'What happens at expiry'              = 'OneDrive content is permanently deleted. Assign a secondary owner before deletion to extend access.'
                            'How to extend access'               = 'Assign a manager or backup admin as secondary owner in SharePoint Admin Center before deleting the user account'
                            'Microsoft default'                   = '30 days'
                            'Recommended minimum'                 = '90 days (to allow HR/legal review)'
                            'Configure in'                        = 'SharePoint Admin Center > Settings > OneDrive retention'
                        }
                        $RetObj.Add([pscustomobject]$retInObj) | Out-Null

                        $null = (& {
                            if ($HealthCheck.SharePoint.OneDrive) {
                                $null = ($RetObj | Where-Object { $_.'Retention Period (days)' -lt 90 } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $RetTableParams = @{ Name = "OneDrive Retention After User Deletion - $TenantId"; List = $true; ColumnWidths = 35, 65 }
                        if ($Report.ShowTableCaptions) { $RetTableParams['Caption'] = "- $($RetTableParams.Name)" }
                        $RetObj | Table @RetTableParams
                    }
                    BlankLine
                    #endregion

                    #region Per-User OneDrive Summary (InfoLevel 2)
                    if ($InfoLevel.OneDrive -ge 2) {
                        Section -Style Heading3 'User Drive Summary' {
                            try {
                                Write-Host " - Retrieving OneDrive personal sites..."
                                $PersonalSites = @(Get-PnPTenantSite -IncludeOneDriveSites -ErrorAction Stop |
                                    Where-Object { $_.Url -like '*/personal/*' } |
                                    Sort-Object StorageUsageCurrent -Descending |
                                    Select-Object -First 100)

                                if ($PersonalSites.Count -gt 0) {
                                    $UserDriveObj = [System.Collections.ArrayList]::new()
                                    foreach ($Drive in $PersonalSites) {
                                        $UsedGB  = if ($Drive.StorageUsageCurrent -gt 0) { [math]::Round($Drive.StorageUsageCurrent / 1024, 2) } else { 0 }
                                        $QuotaGB = if ($Drive.StorageMaximumLevel  -gt 0) { [math]::Round($Drive.StorageMaximumLevel  / 1024, 0) } else { $OneDriveDefaultQuotaGB }
                                        $PctUsed = if ($QuotaGB -gt 0) { "$([math]::Round(($UsedGB / $QuotaGB) * 100, 1))%" } else { '--' }
                                        $SharingLvl = ConvertTo-SPEnumString $Drive.SharingCapability 'Unknown'

                                        $driveInObj = [ordered] @{
                                            'User / UPN'    = if ($Drive.Owner) { $Drive.Owner } else { $Drive.Url -replace '.*/personal/', '' -replace '_[^_]+$', '' }
                                            'Used (GB)'     = $UsedGB
                                            'Quota (GB)'    = $QuotaGB
                                            'Usage %'       = $PctUsed
                                            'Last Modified' = if ($Drive.LastContentModifiedDate -and $Drive.LastContentModifiedDate -ne [DateTime]::MinValue) {
                                                                  $Drive.LastContentModifiedDate.ToString('yyyy-MM-dd')
                                                              } else { 'Unknown' }
                                            'Sharing Level' = $SharingLvl
                                            'Locked'        = ($Drive.LockState -ne 'Unlock')
                                        }
                                        $UserDriveObj.Add([pscustomobject](ConvertTo-HashToYN $driveInObj)) | Out-Null
                                    }

                                    $null = (& {
                                        if ($HealthCheck.SharePoint.OneDrive) {
                                            $null = ($UserDriveObj | Where-Object {
                                                $p = $_.'Usage %' -replace '%',''; $p -ne '--' -and [double]$p -ge 90
                                            } | Set-Style -Style Critical | Out-Null)
                                            $null = ($UserDriveObj | Where-Object {
                                                $p = $_.'Usage %' -replace '%',''; $p -ne '--' -and [double]$p -ge 70 -and [double]$p -lt 90
                                            } | Set-Style -Style Warning | Out-Null)
                                        }
                                    })

                                    $titleSuffix = if ($PersonalSites.Count -ge 100) { ' (Top 100 by usage)' } else { '' }
                                    Paragraph "Total OneDrive drives found: $($PersonalSites.Count)$titleSuffix"
                                    BlankLine
                                    $UserDriveTableParams = @{ Name = "User Drive Summary$titleSuffix - $TenantId"; ColumnWidths = 30, 8, 8, 8, 12, 27, 7 }
                                    if ($Report.ShowTableCaptions) { $UserDriveTableParams['Caption'] = "- $($UserDriveTableParams.Name)" }
                                    $UserDriveObj | Table @UserDriveTableParams
                                    $script:ExcelSheets['OneDrive User Summary'] = $UserDriveObj
                                } else {
                                    Paragraph "No OneDrive personal sites found or insufficient permissions to enumerate drives."
                                }
                            } catch {
                                Write-AbrDebugLog "Per-user OneDrive retrieval failed: $($_.Exception.Message)" 'WARN' 'ONEDRIVE'
                                Paragraph " [!] Per-user OneDrive data could not be retrieved: $($_.Exception.Message)"
                            }
                        }
                        BlankLine
                    }
                    #endregion

                    # Add to Excel
                    $ODSummaryObj = [System.Collections.ArrayList]::new()
                    $ODSummaryObj.Add([pscustomobject][ordered]@{
                        'Default Quota (GB)'              = $OneDriveDefaultQuotaGB
                        'Sync Domain Restriction'         = if ($SyncDomainRestrictionEnabled) { 'Yes' } else { 'No' }
                        'Allowed Sync Domains'            = if ($AllowedDomainGuidsForSyncApp.Count -gt 0) { $AllowedDomainGuidsForSyncApp -join ', ' } else { 'None' }
                        'OD Sharing Level'                = ConvertTo-SPEnumString $SPTenantData.OneDriveSharingCapability 'Unknown'
                        'Default Link Type'               = ConvertTo-SPEnumString $SPTenantData.DefaultSharingLinkType    'Direct'
                        'Retention After Deletion (days)' = if ($SPTenantData.OrphanedPersonalSitesRetentionPeriod -gt 0) { $SPTenantData.OrphanedPersonalSitesRetentionPeriod } else { 30 }
                        'Guests Enabled'                  = if ($null -ne $SPTenantData.OneDriveForGuestsEnabled) { $SPTenantData.OneDriveForGuestsEnabled } else { $false }
                    }) | Out-Null
                    $script:ExcelSheets['OneDrive Settings'] = $ODSummaryObj
                }
            }

            #region ACSC E8 Assessment
            if ($script:IncludeACSCe8) {
                BlankLine
                Paragraph "ACSC Essential Eight Assessment -- OneDrive for Business"
                $E8Defs   = Get-AbrSPE8Checks -Section 'OneDrive'
                $E8Vars   = @{
                    AllowedDomainGuidsForSyncApp = $AllowedDomainGuidsForSyncApp
                    SyncDomainRestrictionEnabled = $SyncDomainRestrictionEnabled
                    OneDriveDefaultQuotaGB       = $OneDriveDefaultQuotaGB
                    BlockDownloadPolicy          = $BlockDownloadPolicy
                    BlockDownloadLinksFileType   = $BlockDownloadLinksFileType
                    ConditionalAccessPolicy      = $BlockDownloadPolicy
                }
                $E8Checks = Build-AbrSPComplianceChecks -Definitions $E8Defs -Framework 'E8' -CallerVariables $E8Vars
                New-AbrSPE8AssessmentTable -Checks $E8Checks -Name 'OneDrive' -TenantId $TenantId
                foreach ($row in $E8Checks) {
                    $null = $script:E8AllChecks.Add([pscustomobject](@{ Section = 'OneDrive' } + ($row | ConvertTo-HashTableSP)))
                }
            }
            #endregion

            #region CIS Baseline Assessment
            if ($script:IncludeCISBaseline) {
                BlankLine
                Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- OneDrive for Business"
                $CISDefs   = Get-AbrSPCISChecks -Section 'OneDrive'
                $CISVars   = @{
                    AllowedDomainGuidsForSyncApp = $AllowedDomainGuidsForSyncApp
                    SyncDomainRestrictionEnabled = $SyncDomainRestrictionEnabled
                    DisablePersonalListCreation  = $DisablePersonalListCreation
                }
                $CISChecks = Build-AbrSPComplianceChecks -Definitions $CISDefs -Framework 'CIS' -CallerVariables $CISVars
                New-AbrSPCISAssessmentTable -Checks $CISChecks -Name 'OneDrive' -TenantId $TenantId
                foreach ($row in $CISChecks) {
                    $null = $script:CISAllChecks.Add([pscustomobject](@{ Section = 'OneDrive' } + ($row | ConvertTo-HashTableSP)))
                }
            }
            #endregion
        }
    }

    end {
        Show-AbrDebugExecutionTime -End -TitleMessage 'OneDrive'
    }
}