public/Get-MsecSharePointTenantSetting.ps1
|
function Get-MsecSharePointTenantSetting { <# .SYNOPSIS The tenant-wide SharePoint and OneDrive settings that decide how far content can travel outside the organisation - one row per setting. .DESCRIPTION These are TENANT settings, not site properties, and they are the ceiling every site sits under. A site can be locked down and still sit in a tenant where anyone-links are on; reviewing sites one by one never surfaces that. ONE ROW PER SETTING, grouped into a Category, the same shape as Get-MsecTeamsPolicy. /admin/sharepoint/settings returns roughly thirty properties covering sync clients, storage quotas, time zones and newsfeeds; only the ones that bear on access and data movement are projected, and which ones is a judgement this command makes on your behalf, so it is written out in the source. -All returns every property for checking it. AN EMPTY LIST IS REPORTED AS '(none)' AND A MISSING VALUE AS '(not set)'. The distinction matters most for the domain lists: with SharingDomainRestrictionMode set to allowList, an EMPTY SharingAllowedDomainList means nobody outside can be invited at all, which is the opposite of what a blank cell suggests. Neither is rendered as blank, because a blank reads as "we did not look". READ-ONLY, LIKE THE REST OF msec. Changing any of this needs SharePointTenantSettings.ReadWrite.All, which New-MsecApp does not grant - use the SharePoint admin centre. .PARAMETER All Return every property Graph reports, not just the security-relevant projection. Unprojected settings come back with Category 'Other'. .EXAMPLE Connect-Msec -KeyVaultName kv-msec -TenantId <guid> -ClientId <guid> Get-MsecSharePointTenantSetting .EXAMPLE # The three that decide external reach. Get-MsecSharePointTenantSetting | Where-Object Category -eq 'Sharing' | Format-Table Setting, Value .OUTPUTS PSCustomObject per setting, PSTypeName 'MsecSharePointTenantSetting'. .NOTES Needs Connect-Msec and SharePointTenantSettings.Read.All on MICROSOFT GRAPH, which New-MsecApp -Workload SharePoint grants. Sites.Read.All does NOT cover this endpoint - that one reads sites, and these are tenant settings - and without the right permission Graph returns a bare 403 that names no permission at all. This is the only route msec has to these settings. The PnP equivalent, Get-PnPTenant, needs a token whose audience is the tenant's ADMIN HOST, and Get-AzAccessToken cannot mint one - see Connect-MsecSharePointOnline's notes. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [switch] $All ) Assert-MsecSession # The settings worth reporting, and why. Written out rather than derived, because "which of # these thirty properties is a security control" is exactly the judgement a reader needs to # be able to check and argue with. $projection = [ordered]@{ # How far content can travel, and to whom. sharingCapability = 'Sharing' # anyone-links, guests, or off sharingDomainRestrictionMode = 'Sharing' # none, allowList or blockList sharingAllowedDomainList = 'Sharing' sharingBlockedDomainList = 'Sharing' isResharingByExternalUsersEnabled = 'Sharing' # a guest passing access on isRequireAcceptingUserToMatchInvitedUserEnabled = 'Sharing' # or the link works for anyone who gets it # Legacy auth is the one most worth seeing: it bypasses Conditional Access entirely. isLegacyAuthProtocolsEnabled = 'Authentication' idleSessionSignOut = 'Authentication' # Content leaving onto a machine nobody manages. isUnmanagedSyncAppForTenantRestricted = 'Sync' allowedDomainGuidsForSyncApp = 'Sync' excludedFileExtensionsForSyncApp = 'Sync' # Where content can appear, and how long it lingers after someone leaves. isSiteCreationEnabled = 'Governance' isLoopEnabled = 'Governance' deletedUserPersonalSiteRetentionPeriodInDays = 'Governance' } try { $settings = Invoke-MsecGraphRequest -Path '/v1.0/admin/sharepoint/settings' } catch { $detail = $_.Exception.Message if ($detail -match '403|Forbidden|accessDenied') { throw "Forbidden reading the SharePoint tenant settings. The msec app needs 'SharePointTenantSettings.Read.All' on MICROSOFT GRAPH - Sites.Read.All does NOT cover this endpoint, and Graph's 403 names no permission. Run New-MsecApp -Workload SharePoint, then Connect-Msec again so the new grant is in the token. Original error: $detail" } throw "Could not read the SharePoint tenant settings: $detail" } # Graph returns the object itself, not a collection, so there is no .value to unwrap. $names = if ($All) { @($settings.PSObject.Properties.Name | Where-Object { $_ -notmatch '^@odata' }) } else { # Only what this tenant actually reports - the property set moves as Microsoft adds # settings, and asking for an absent one would emit a row of nulls that reads as # "configured off" rather than "not present in this tenant". @($projection.Keys | Where-Object { $_ -in $settings.PSObject.Properties.Name }) } foreach ($name in $names) { $raw = $settings.$name # One level of nesting is flattened rather than printed as a type name: # idleSessionSignOut is an object, and 'Setting = idleSessionSignOut, # Value = System.Management.Automation.PSCustomObject' tells a reader nothing. $isNested = $null -ne $raw -and $raw -isnot [string] -and $raw -isnot [ValueType] -and $raw -isnot [System.Collections.IEnumerable] $pairs = if ($isNested) { foreach ($child in $raw.PSObject.Properties) { @{ Name = "$name.$($child.Name)"; Value = $child.Value } } } else { , @{ Name = $name; Value = $raw } } foreach ($pair in $pairs) { $value = $pair.Value $rendered = if ($null -eq $value) { '(not set)' } elseif ($value -isnot [string] -and $value -is [System.Collections.IEnumerable]) { $items = @($value) # '(none)' rather than blank: with an allowList restriction mode an EMPTY # allowed-domain list means nobody outside can be invited, and a blank cell # reads as "not measured" instead. if ($items.Count) { ($items | ForEach-Object { "$_" }) -join '; ' } else { '(none)' } } else { [string] $value } [PSCustomObject]@{ PSTypeName = 'MsecSharePointTenantSetting' Category = if ($projection.Contains($name)) { $projection[$name] } else { 'Other' } Setting = $pair.Name Value = $rendered } } } } |