Skip to content

Commit 9d71e07

Browse files
Dev to hf (#2140)
2 parents ee27a31 + 2da1872 commit 9d71e07

10 files changed

Lines changed: 815 additions & 8 deletions

File tree

Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/OneDrive Root Permissions/Push-DBCacheOneDriveRootPermissionsBatch.ps1

Lines changed: 563 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
function Push-StoreOneDriveRootPermissions {
2+
<#
3+
.SYNOPSIS
4+
Post-execution function that aggregates per-batch OneDrive root permission rows and writes the cache.
5+
6+
.DESCRIPTION
7+
Collects the Sites arrays returned by every Push-DBCacheOneDriveRootPermissionsBatch activity,
8+
flattens them into a single row set, and writes OneDriveRootPermissions once via Add-CIPPDbItem.
9+
10+
Completeness guard: if ActualCount -ne ExpectedSiteCount the function throws and does not
11+
call Add-CIPPDbItem (prevents replace-mode wipe on partial orchestrator failure). When counts
12+
match (including 0 for empty tenants handled by the orchestrator parent), a single full-replace
13+
write is performed.
14+
15+
Merge-on-Skip: when batch collection returns Skipped for a site, loads existing
16+
OneDriveRootPermissions via New-CIPPDbRequest and replaces the Skipped row with the prior
17+
Full row (matched by siteId) before writing. Transient SPO/Graph failures therefore do not
18+
wipe previously collected grant data. Skipped rows with no prior Full row are written as-is.
19+
DB read is skipped when no Skipped rows exist in the run.
20+
21+
Logs Skipped count from collection and how many were preserved from prior Full cache.
22+
23+
Cache row schema (one per personal site):
24+
id/siteId, siteUrl, siteDisplayName, ownerPrincipalName, ownerObjectId, ownerDisplayName,
25+
driveId, driveWebUrl, libraryId, libraryHasUniquePermissions, collectionStatus,
26+
collectionError, hasNonStandardAccess (nullable boolean), permissionsJson (string),
27+
grantCount, collectedAt.
28+
29+
permissionsJson is a pre-serialized JSON string of grant objects — consumers must
30+
ConvertFrom-Json before querying grants. Grant identity for dedup:
31+
{permissionSource}_{principalId}_{roleDefinitionId}_{permissionId}
32+
33+
Consumer notes:
34+
- Rows in the cache reflect merge-on-Skip: a site that failed collection this run may still
35+
show collectionStatus Full with prior permissionsJson if a previous Full row existed
36+
- hasNonStandardAccess: use -eq $true / -eq $false; $null means Skipped with no prior Full
37+
row to merge. Never use truthy checks
38+
- DriveRootLink with named recipients produces one grant per person; count sharing links
39+
by distinct permissionId within permissionsJson, not by grant row count (same permissionId
40+
may appear on multiple recipient grants)
41+
42+
.FUNCTIONALITY
43+
Entrypoint
44+
#>
45+
[CmdletBinding()]
46+
param($Item)
47+
48+
$TenantFilter = $Item.Parameters.TenantFilter
49+
$ExpectedSiteCount = [int]$Item.Parameters.ExpectedSiteCount
50+
51+
try {
52+
$AllRows = [System.Collections.Generic.List[object]]::new()
53+
foreach ($BatchResult in @($Item.Results)) {
54+
$Sites = if ($BatchResult.Sites) { @($BatchResult.Sites) } else { @() }
55+
foreach ($Row in $Sites) {
56+
if ($Row) { $AllRows.Add($Row) }
57+
}
58+
}
59+
60+
$ActualCount = $AllRows.Count
61+
if ($ActualCount -ne $ExpectedSiteCount) {
62+
throw "OneDrive root permissions completeness check failed for $TenantFilter : expected $ExpectedSiteCount site rows, got $ActualCount"
63+
}
64+
65+
$SkippedCount = @($AllRows | Where-Object { $_.collectionStatus -eq 'Skipped' }).Count
66+
$MergedCount = 0
67+
if ($SkippedCount -gt 0) {
68+
$ExistingBySiteId = @{}
69+
foreach ($Existing in @(New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'OneDriveRootPermissions')) {
70+
$Key = [string]($Existing.siteId ?? $Existing.id)
71+
if ($Key -and $Existing.collectionStatus -eq 'Full') {
72+
$ExistingBySiteId[$Key] = $Existing
73+
}
74+
}
75+
for ($i = 0; $i -lt $AllRows.Count; $i++) {
76+
$Row = $AllRows[$i]
77+
if ($Row.collectionStatus -ne 'Skipped') { continue }
78+
$Key = [string]($Row.siteId ?? $Row.id)
79+
if ($Key -and $ExistingBySiteId.ContainsKey($Key)) {
80+
$AllRows[$i] = $ExistingBySiteId[$Key]
81+
$MergedCount++
82+
}
83+
}
84+
}
85+
86+
$RemainingSkippedCount = $SkippedCount - $MergedCount
87+
if ($SkippedCount -gt 0) {
88+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "OneDrive root permissions: $SkippedCount of $ActualCount sites returned Skipped from collection; preserved $MergedCount from prior Full cache; $RemainingSkippedCount written as Skipped" -sev Warning
89+
}
90+
91+
Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveRootPermissions' -Data @($AllRows) -AddCount
92+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Cached $ActualCount OneDrive root permission site rows ($MergedCount merge-on-Skip) across $(@($Item.Results).Count) batches" -sev Info
93+
return
94+
95+
} catch {
96+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to store OneDrive root permissions: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_)
97+
throw
98+
}
99+
}

Modules/CIPPCore/Public/GraphHelper/Get-GraphToken.ps1

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,15 @@ function Get-GraphToken {
6767
$AppCache = Get-CIPPAzDataTableEntity @ConfigTable -Filter $Filter
6868
#force auth update is appId is not the same as the one in the environment variable.
6969
if ($AppCache.ApplicationId -and $env:ApplicationID -ne $AppCache.ApplicationId) {
70-
Write-Host "Setting environment variable ApplicationID to $($AppCache.ApplicationId)"
71-
$CIPPAuth = Get-CIPPAuthentication
70+
$CIPPAuth = Get-CIPPAuthentication # reload creds from KV (source of truth)
71+
if ($env:ApplicationID -and $env:ApplicationID -ne $AppCache.ApplicationId) {
72+
# KV and AppCache genuinely diverged — reconcile the marker to KV so we
73+
# don't reload on every subsequent token call.
74+
Write-Host "AppCache ApplicationId ($($AppCache.ApplicationId)) differs from KV ($env:ApplicationID); reconciling AppCache."
75+
$null = Add-CIPPAzDataTableEntity @ConfigTable -Entity @{
76+
PartitionKey = 'AppCache'; RowKey = 'AppCache'; ApplicationId = "$env:ApplicationID"
77+
} -Force
78+
}
7279
}
7380
$refreshToken = $env:RefreshToken
7481
#Get list of tenants that have 'directTenant' set to true

Modules/CIPPCore/Public/MCP/Get-CippMcpToolList.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ function Resolve-CippMcpNode {
155155
}
156156

157157
if ($Node -is [System.Collections.IEnumerable]) {
158-
return @($Node | ForEach-Object { Resolve-CippMcpNode -Node $_ -Spec $Spec -Depth ($Depth + 1) -Seen $Seen })
158+
$Resolved = @($Node | ForEach-Object { Resolve-CippMcpNode -Node $_ -Spec $Spec -Depth ($Depth + 1) -Seen $Seen })
159+
return , $Resolved
159160
}
160161

161162
return $Node

Modules/CIPPCore/Public/Webhooks/Invoke-CIPPPartnerWebhookProcessing.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ function Invoke-CippPartnerWebhookProcessing {
66

77
try {
88
if ($Data.AuditUri) {
9-
$AuditLog = New-GraphGetRequest -uri $Data.AuditUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default'
9+
$ParsedAuditUri = $null
10+
if ([System.Uri]::TryCreate([string]$Data.AuditUri, [System.UriKind]::Absolute, [ref]$ParsedAuditUri) -and
11+
$ParsedAuditUri.Scheme -eq 'https' -and
12+
$ParsedAuditUri.Host -eq 'api.partnercenter.microsoft.com') {
13+
$AuditLog = New-GraphGetRequest -uri $ParsedAuditUri.AbsoluteUri -tenantid $env:TenantID -NoAuthCheck $true -scope 'https://api.partnercenter.microsoft.com/.default'
14+
} else {
15+
Write-LogMessage -API 'Webhooks' -message "Partner Center webhook rejected: AuditUri is not a Partner Center API URL ($($Data.AuditUri))" -Sev 'Alert'
16+
return
17+
}
1018
}
1119

1220
switch ($Data.EventName) {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
function Set-CIPPDBCacheOneDriveRootPermissions {
2+
<#
3+
.SYNOPSIS
4+
Caches OneDrive root permissions for every personal site in a tenant.
5+
6+
.DESCRIPTION
7+
Self-contained cache writer: enumerates personal sites via paginated Graph getAllSites,
8+
fans out batched collection activities (20 sites each), and aggregates results in
9+
Push-StoreOneDriveRootPermissions (PostExecution).
10+
11+
One row per OneDrive site in OneDriveRootPermissions with permissionsJson (compressed grant
12+
array string), nullable hasNonStandardAccess, and collectionStatus (Full | Skipped).
13+
14+
Does not read OneDriveUsage or other CIPPDB caches. Requires SharePoint/OneDrive license
15+
(Test-CIPPStandardLicense -Preset SharePoint); unlicensed tenants exit before enumeration.
16+
Scheduling via CIPPDBCacheTypes.json is deferred — manual test:
17+
Invoke-ExecCIPPDBCache?TenantFilter={tenant}&Name=OneDriveRootPermissions
18+
19+
Site enumeration dedupes getAllSites results by siteId before batching. Empty tenants write
20+
an empty cache directly (PostExecution is skipped when there are zero batches).
21+
22+
PostExecution passes ExpectedSiteCount; Push-StoreOneDriveRootPermissions throws without writing
23+
if flattened row count does not match (prevents partial replace-mode wipe). Skipped site rows
24+
are merged with prior Full cache data when available (merge-on-Skip) so transient failures
25+
do not overwrite good grant data.
26+
27+
Owner resolution uses drive.owner (not Owners group or OneDriveUsage). permissionsJson grant
28+
paths: SiteAdmin, SiteRoleGroup, WebRoleAssignment, LibraryRoleAssignment, DriveRootGrant,
29+
DriveRootLink. Groups are stored as principals without Entra expansion.
30+
31+
Consumer limitations: grant paths != effective access; unprovisioned OneDrives absent;
32+
Skipped sites without a prior Full row have empty permissionsJson; merge-on-Skip may
33+
restore prior Full data after transient failures; count DriveRootLink entries by distinct
34+
permissionId (named recipients produce multiple grant rows per link); child folder/file
35+
sharing is out of scope (SharePointSharingLinks cache).
36+
37+
.PARAMETER TenantFilter
38+
Tenant to cache OneDrive root permissions for
39+
40+
.PARAMETER QueueId
41+
Optional queue ID for progress tracking
42+
#>
43+
[CmdletBinding()]
44+
param(
45+
[Parameter(Mandatory = $true)]
46+
[string]$TenantFilter,
47+
[string]$QueueId
48+
)
49+
50+
$BatchSize = 20
51+
52+
try {
53+
$LicenseCheck = Test-CIPPStandardLicense -StandardName 'OneDriveRootPermissionsCache' -TenantFilter $TenantFilter -Preset SharePoint -SkipLog
54+
if ($LicenseCheck -eq $false) {
55+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Tenant does not have SharePoint/OneDrive license, skipping OneDrive root permissions cache' -sev Debug
56+
return
57+
}
58+
59+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'Starting OneDrive root permissions collection' -sev Debug
60+
61+
$RawSites = @(New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/sites/getAllSites?`$filter=isPersonalSite eq true&`$select=id,webUrl,displayName" -tenantid $TenantFilter -asapp $true)
62+
63+
$SiteById = @{}
64+
foreach ($Site in $RawSites) {
65+
if ($Site.id) { $SiteById[$Site.id] = $Site }
66+
}
67+
$Sites = @($SiteById.Values)
68+
$ExpectedSiteCount = $Sites.Count
69+
70+
if ($ExpectedSiteCount -eq 0) {
71+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message 'No personal sites found; writing empty OneDriveRootPermissions cache' -sev Debug
72+
Add-CIPPDbItem -TenantFilter $TenantFilter -Type 'OneDriveRootPermissions' -Data @() -AddCount
73+
return
74+
}
75+
76+
$Batches = [System.Collections.Generic.List[object]]::new()
77+
$TotalBatches = [Math]::Ceiling($Sites.Count / $BatchSize)
78+
for ($i = 0; $i -lt $Sites.Count; $i += $BatchSize) {
79+
$BatchSites = $Sites[$i..[Math]::Min($i + $BatchSize - 1, $Sites.Count - 1)]
80+
$BatchNumber = [Math]::Floor($i / $BatchSize) + 1
81+
$SiteSeeds = foreach ($Site in $BatchSites) {
82+
[PSCustomObject]@{
83+
id = $Site.id
84+
webUrl = $Site.webUrl
85+
displayName = $Site.displayName
86+
}
87+
}
88+
$BatchItem = [PSCustomObject]@{
89+
FunctionName = 'DBCacheOneDriveRootPermissionsBatch'
90+
TenantFilter = $TenantFilter
91+
QueueName = "OneDrive Root Permissions Batch $BatchNumber/$TotalBatches - $TenantFilter"
92+
BatchNumber = $BatchNumber
93+
TotalBatches = $TotalBatches
94+
Sites = @($SiteSeeds)
95+
}
96+
if ($QueueId) {
97+
$BatchItem | Add-Member -NotePropertyName 'QueueId' -NotePropertyValue $QueueId -Force
98+
}
99+
[void]$Batches.Add($BatchItem)
100+
}
101+
102+
if ($QueueId -and $Batches.Count -gt 0) {
103+
try {
104+
Update-CippQueueEntry -RowKey $QueueId -TotalTasks $Batches.Count -IncrementTotalTasks
105+
} catch {
106+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Could not update queue $QueueId with OneDrive root permission batch tasks: $($_.Exception.Message)" -sev Warning
107+
}
108+
}
109+
110+
$InputObject = [PSCustomObject]@{
111+
Batch = @($Batches)
112+
OrchestratorName = "OneDriveRootPermissions_$TenantFilter"
113+
PostExecution = @{
114+
FunctionName = 'StoreOneDriveRootPermissions'
115+
Parameters = @{
116+
TenantFilter = $TenantFilter
117+
ExpectedSiteCount = $ExpectedSiteCount
118+
}
119+
}
120+
}
121+
122+
$null = Start-CIPPOrchestrator -InputObject $InputObject
123+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Started OneDrive root permissions collection across $ExpectedSiteCount sites in $($Batches.Count) batches" -sev Debug
124+
125+
} catch {
126+
Write-LogMessage -API 'CIPPDBCache' -tenant $TenantFilter -message "Failed to start OneDrive root permissions collection: $($_.Exception.Message)" -sev Error -LogData (Get-CippException -Exception $_)
127+
throw
128+
}
129+
}

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecRemoveSnooze.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function Invoke-ExecRemoveSnooze {
33
.FUNCTIONALITY
44
Entrypoint,AnyTenant
55
.ROLE
6-
CIPP.Alert.ReadWrite
6+
CIPP.AlertSnooze.ReadWrite
77
#>
88
[CmdletBinding()]
99
param($Request, $TriggerMetadata)

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ExecSnoozeAlert.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function Invoke-ExecSnoozeAlert {
33
.FUNCTIONALITY
44
Entrypoint,AnyTenant
55
.ROLE
6-
CIPP.Alert.ReadWrite
6+
CIPP.AlertSnooze.ReadWrite
77
#>
88
[CmdletBinding()]
99
param($Request, $TriggerMetadata)

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/CIPP/Core/Invoke-ListSnoozedAlerts.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function Invoke-ListSnoozedAlerts {
33
.FUNCTIONALITY
44
Entrypoint,AnyTenant
55
.ROLE
6-
CIPP.Alert.Read
6+
CIPP.AlertSnooze.Read
77
.DESCRIPTION
88
Lists alerts that have been snoozed (temporarily suppressed), filterable by cmdlet name. Returns snooze duration and scope details.
99
#>

version_latest.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
10.6.1
1+
10.6.2

0 commit comments

Comments
 (0)