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' } } |