Private/LocationScope.ps1
|
<# .SYNOPSIS Location scope functions for DLP policy location handling. .DESCRIPTION This file contains functions for determining and managing location scopes in DLP policies, including detecting "All" vs specific targeting, extracting location details, and converting locations during cross-tenant migrations. .NOTES Internal module file - not exported to users. Functions are available to all module cmdlets. IMPORTANT: Microsoft stores location data differently than expected: - Location arrays like OneDriveLocation contain [{"Name":"All","Type":"Tenant"}] even for specific targeting - Actual targeting is determined by scoping properties like OneDriveSharedBy, ExchangeSender, etc. - This module uses the comprehensive logic to correctly identify "All" locations #> function Test-IsAllLocation { <# .SYNOPSIS Determines if a location is scoped to "All" (tenant-wide) vs specific users/groups/sites. .DESCRIPTION Microsoft stores location data differently than expected: - Location arrays like OneDriveLocation contain [{"Name":"All","Type":"Tenant"}] even for specific targeting - Actual targeting is determined by scoping properties like OneDriveSharedBy, ExchangeSender, etc. This function checks both the location array AND relevant scoping properties to accurately determine if a location is truly set to "All" or is targeting specific entities. .PARAMETER LocationArray The location array from Get-DlpCompliancePolicy (e.g., OneDriveLocation, ExchangeLocation). .PARAMETER ScopingProperties Additional properties that indicate specific targeting (e.g., OneDriveSharedBy, ExchangeSender). If any scoping properties are populated, the location is NOT "All". .PARAMETER AdaptiveScopeProperties Adaptive scope properties (e.g., ExchangeAdaptiveScopes, SharePointAdaptiveScopes). If any adaptive scopes are configured, the location is NOT "All". .OUTPUTS System.Boolean Returns $true if location is "All" (tenant-wide), $false if specific or not configured. .EXAMPLE $isAll = Test-IsAllLocation -LocationArray $policy.ExchangeLocation ` -ScopingProperties @($policy.ExchangeSender, $policy.ExchangeSenderMemberOf) ` -AdaptiveScopeProperties @($policy.ExchangeAdaptiveScopes) .NOTES This is the comprehensive version from Get-DLPPolicyConfiguration.ps1. It correctly handles all edge cases for location scope detection. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $false)] [object]$LocationArray, [Parameter(Mandatory = $false)] [object[]]$ScopingProperties = @(), [Parameter(Mandatory = $false)] [object[]]$AdaptiveScopeProperties = @() ) # Not configured if array is empty or null if (-not $LocationArray -or $LocationArray.Count -eq 0) { return $false } # Check if adaptive scopes are configured (always specific targeting) foreach ($adaptiveScope in $AdaptiveScopeProperties) { if ($adaptiveScope -and @($adaptiveScope).Count -gt 0) { Write-Verbose "Location has adaptive scopes - not 'All'" return $false # Has adaptive scopes = specific targeting } } # Check if scoping properties indicate specific targeting foreach ($scopingProp in $ScopingProperties) { if ($scopingProp -and @($scopingProp).Count -gt 0) { Write-Verbose "Location has scoping properties - not 'All'" return $false # Has specific scoping = not "All" } } # Check the location array structure $locationList = @($LocationArray) # If exactly one location and it's Type="Tenant" with Name="All", it's truly "All" if ($locationList.Count -eq 1) { $firstLocation = $locationList[0] # Handle object with Type property if ($firstLocation.PSObject.Properties['Type'] -and $firstLocation.Type.Value -eq 'Tenant' -and $firstLocation.Name -eq 'All') { Write-Verbose "Location is 'All' (Type=Tenant)" return $true } # Handle simple string "All" if ($firstLocation -eq 'All') { Write-Verbose "Location is 'All' (string)" return $true } } Write-Verbose "Location has multiple entries or specific sites/groups - not 'All'" return $false # Multiple locations or specific sites/groups } function Get-LocationScopeInfo { <# .SYNOPSIS Gets detailed scope information for a workload location. .DESCRIPTION Returns comprehensive information about a location's configuration including: - Whether the location is configured at all - Whether it's set to "All" (tenant-wide) - Whether it's specific targeting - Count of targeted entities - Formatted string of location identifiers .PARAMETER LocationArray The location array from Get-DlpCompliancePolicy (e.g., OneDriveLocation, ExchangeLocation). .PARAMETER ScopingProperties Additional properties that indicate specific targeting (e.g., OneDriveSharedBy, ExchangeSender). .PARAMETER AdaptiveScopeProperties Adaptive scope properties (e.g., ExchangeAdaptiveScopes, SharePointAdaptiveScopes). .PARAMETER LocationName The friendly name of the location (e.g., "Exchange", "OneDrive") for logging purposes. .OUTPUTS System.Collections.Hashtable Returns a hashtable with keys: - IsConfigured (bool): Location is configured - IsAll (bool): Location is set to "All" - IsSpecific (bool): Location has specific targeting - Count (int): Number of targeted entities - Locations (string): Formatted string of identifiers .EXAMPLE $exchangeScope = Get-LocationScopeInfo -LocationArray $policy.ExchangeLocation ` -LocationName "Exchange" ` -ScopingProperties @($policy.ExchangeSender, $policy.ExchangeSenderMemberOf) ` -AdaptiveScopeProperties @($policy.ExchangeAdaptiveScopes) if ($exchangeScope.IsAll) { Write-Host "Exchange location is set to All" } #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory = $false)] [object]$LocationArray, [Parameter(Mandatory = $false)] [object[]]$ScopingProperties = @(), [Parameter(Mandatory = $false)] [object[]]$AdaptiveScopeProperties = @(), [Parameter(Mandatory = $false)] [string]$LocationName = "Location" ) # Return not configured if location array is empty if (-not $LocationArray -or $LocationArray.Count -eq 0) { Write-Verbose "$LocationName is not configured" return @{ IsConfigured = $false IsAll = $false IsSpecific = $false Count = 0 Locations = "" } } # Determine if location is "All" or specific $isAll = Test-IsAllLocation -LocationArray $LocationArray ` -ScopingProperties $ScopingProperties ` -AdaptiveScopeProperties $AdaptiveScopeProperties $locationList = @($LocationArray) # Build location string based on type $locationString = "" if ($isAll) { $locationString = "All" } else { # Extract meaningful identifiers from scoping properties and adaptive scopes $identifiers = @() # Check scoping properties first (most specific) foreach ($scopingProp in $ScopingProperties) { if ($scopingProp -and @($scopingProp).Count -gt 0) { foreach ($item in @($scopingProp)) { if ($item -is [string]) { # Might be JSON string, try to extract email/name if ($item -match '"PrimarySmtpAddress":"([^"]+)"') { $identifiers += $matches[1] } elseif ($item -match '"DisplayName":"([^"]+)"') { $identifiers += $matches[1] } else { $identifiers += $item } } else { # Object with properties if ($item.PrimarySmtpAddress) { $identifiers += $item.PrimarySmtpAddress } elseif ($item.DisplayName) { $identifiers += $item.DisplayName } elseif ($item.Name) { $identifiers += $item.Name } } } } } # Check adaptive scopes foreach ($adaptiveScope in $AdaptiveScopeProperties) { if ($adaptiveScope -and @($adaptiveScope).Count -gt 0) { foreach ($item in @($adaptiveScope)) { if ($item.DisplayName) { $identifiers += $item.DisplayName } elseif ($item.Name) { $identifiers += $item.Name } else { $identifiers += $item.ToString() } } } } # Fallback to location array contents if ($identifiers.Count -eq 0) { foreach ($loc in $locationList) { if ($loc.DisplayName -and $loc.DisplayName -ne 'All') { $identifiers += $loc.DisplayName } elseif ($loc.Name -and $loc.Name -ne 'All') { $identifiers += $loc.Name } elseif ($loc -is [string] -and $loc -ne 'All') { $identifiers += $loc } } } $locationString = $identifiers -join "; " if ([string]::IsNullOrWhiteSpace($locationString)) { $locationString = "Specific (details in raw properties)" } } $result = @{ IsConfigured = $true IsAll = $isAll IsSpecific = -not $isAll Count = if ($isAll) { 1 } else { ([array]$identifiers).Count } Locations = $locationString } Write-Verbose "$LocationName scope: IsAll=$($result.IsAll), Count=$($result.Count)" return $result } function ConvertTo-AllLocation { <# .SYNOPSIS Converts specific location targeting to "All" for cross-tenant migration. .DESCRIPTION When migrating DLP policies across tenants, specific user/group/site targeting cannot be preserved because the identities don't exist in the destination tenant. This function converts specific targeting to "All" (tenant-wide) and clears all scoping properties and exceptions. .PARAMETER LocationType The type of location being converted (e.g., "Exchange", "SharePoint", "OneDrive", "Teams"). .OUTPUTS System.Collections.Hashtable Returns a hashtable with keys: - Location: Set to @("All") - ScopingProperties: Empty hashtable for all scoping properties - AdaptiveScopeProperties: Empty hashtable for adaptive scope properties - ExceptionProperties: Empty hashtable for all exception properties .EXAMPLE $allLocations = ConvertTo-AllLocation -LocationType "Exchange" # Returns configuration to set Exchange to "All" with no scoping .EXAMPLE $sharepointAll = ConvertTo-AllLocation -LocationType "SharePoint" # Returns configuration to set SharePoint to "All" with no exceptions .NOTES This function is primarily used during cross-tenant migration mode. It ensures policies can be recreated in destination tenant without referencing non-existent users, groups, or sites. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory = $true)] [ValidateSet('Exchange', 'SharePoint', 'OneDrive', 'Teams')] [string]$LocationType ) $result = @{ Location = @('All') ScopingProperties = @{} AdaptiveScopeProperties = @{} ExceptionProperties = @{} } # Location-specific scoping and exception properties switch ($LocationType) { 'Exchange' { $result.ScopingProperties = @{ ExchangeSender = @() ExchangeSenderMemberOf = @() } $result.AdaptiveScopeProperties = @{ ExchangeAdaptiveScopes = @() } $result.ExceptionProperties = @{ ExchangeLocationException = @() ExchangeSenderException = @() ExchangeSenderMemberOfException = @() } } 'SharePoint' { $result.ScopingProperties = @{} # SharePoint doesn't have sender-type scoping $result.AdaptiveScopeProperties = @{ SharePointAdaptiveScopes = @() } $result.ExceptionProperties = @{ SharePointLocationException = @() } } 'OneDrive' { $result.ScopingProperties = @{ OneDriveSharedBy = @() OneDriveSharedByMemberOf = @() } $result.AdaptiveScopeProperties = @{ OneDriveAdaptiveScopes = @() } $result.ExceptionProperties = @{ OneDriveLocationException = @() OneDriveSharedByException = @() OneDriveSharedByMemberOfException = @() } } 'Teams' { $result.ScopingProperties = @{ TeamsSender = @() TeamsSenderMemberOf = @() } $result.AdaptiveScopeProperties = @{ TeamsAdaptiveScopes = @() } $result.ExceptionProperties = @{ TeamsLocationException = @() TeamsSenderException = @() TeamsSenderMemberOfException = @() } } } Write-Verbose "Converted $LocationType location to 'All' (cleared all scoping and exceptions)" return $result } |