Skip to content

Commit 061fd64

Browse files
authored
Merge pull request #1032 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 786f247 + 1a8b716 commit 061fd64

7 files changed

Lines changed: 147 additions & 8 deletions

File tree

Config/standards.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4930,7 +4930,7 @@
49304930
"name": "standards.SPFileRequests",
49314931
"cat": "SharePoint Standards",
49324932
"tag": [],
4933-
"helpText": "*Requires 'Sharing Level for OneDrive and SharePoint' to be set to Anyone* Enables or disables File Requests for SharePoint and OneDrive, allowing users to create secure upload-only links. Optionally sets the maximum number of days for the link to remain active before expiring.",
4933+
"helpText": "Enables or disables File Requests for SharePoint and OneDrive, allowing users to create secure upload-only links. Optionally sets the maximum number of days for the link to remain active before expiring.",
49344934
"docsDescription": "File Requests allow users to create secure upload-only share links where uploads are hidden from other people using the link. This creates a secure and private way for people to upload files to a folder. This feature is not enabled by default on new tenants and requires PowerShell configuration. This standard enables or disables this feature and optionally configures link expiration settings for both SharePoint and OneDrive.",
49354935
"executiveText": "Enables secure file upload functionality that allows external users to submit files directly to company folders without seeing other submissions or folder contents. This provides a professional and secure way to collect documents from clients, vendors, and partners while maintaining data privacy and security.",
49364936
"addedComponent": [
@@ -8085,8 +8085,12 @@
80858085
{
80868086
"type": "number",
80878087
"name": "standards.SPOVersionControl.ExpireVersionsAfterDays",
8088-
"label": "Expire Versions After Days (0 = never, when auto trim is off)",
8089-
"default": 0
8088+
"label": "Expire Versions After Days (0 = never, otherwise 30-36500, when auto trim is off)",
8089+
"default": 0,
8090+
"validators": {
8091+
"min": { "value": 0, "message": "Use 0 for never, or 30 or more days" },
8092+
"max": { "value": 36500, "message": "Maximum value is 36500" }
8093+
}
80908094
},
80918095
{
80928096
"type": "switch",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
function Get-CIPPSiteVersionCleanupStatus {
2+
<#
3+
.SYNOPSIS
4+
Get the progress of a file version batch delete (trim) job for a SharePoint site
5+
6+
.DESCRIPTION
7+
Queries the progress of the file version batch delete job for a SharePoint site via the
8+
CSOM GetFileVersionBatchDeleteJobProgress method on the Tenant object, using the same
9+
ProcessQuery channel as Start-CIPPSiteVersionCleanup. Reports the status of a cleanup
10+
previously started with Start-CIPPSiteVersionCleanup.
11+
12+
Unlike NewFileVersionBatchDeleteJob / RemoveFileVersionBatchDeleteJob (which return an
13+
SpoOperation object that the client serialises with a <Query SelectAllProperties="true">),
14+
GetFileVersionBatchDeleteJobProgress returns a plain String whose content is a JSON blob.
15+
It is therefore invoked as a bare <Method> inside <Actions> with no <Query> wrapper - asking
16+
for SelectAllProperties on a String fails server-side with "Cannot find stub for type
17+
System.String". The ProcessQuery response is an array whose only String element is the JSON
18+
progress payload, which this function parses and returns. (Confirmed against a captured
19+
Get-SPOSiteFileVersionBatchDeleteJobProgress request.)
20+
21+
.PARAMETER TenantFilter
22+
Tenant to query
23+
24+
.PARAMETER SiteUrl
25+
Full URL of the SharePoint site to query
26+
27+
.EXAMPLE
28+
Get-CIPPSiteVersionCleanupStatus -TenantFilter 'contoso.onmicrosoft.com' -SiteUrl 'https://contoso.sharepoint.com/sites/MySite'
29+
30+
.FUNCTIONALITY
31+
Internal
32+
33+
#>
34+
[CmdletBinding()]
35+
param(
36+
[Parameter(Mandatory = $true)]
37+
[string]$TenantFilter,
38+
[Parameter(Mandatory = $true)]
39+
[string]$SiteUrl
40+
)
41+
42+
$SharePointInfo = Get-SharePointAdminLink -Public $false -tenantFilter $TenantFilter
43+
$AdminUrl = $SharePointInfo.AdminUrl
44+
$EscapedSiteUrl = [System.Security.SecurityElement]::Escape($SiteUrl)
45+
46+
# CSOM pattern: Tenant Constructor -> GetFileVersionBatchDeleteJobProgress(siteUrl).
47+
# The method returns a String (JSON), so it is called directly in <Actions> with no <Query>.
48+
$XML = @"
49+
<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="SharePoint Online PowerShell (16.0.24908.0)" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="40" ObjectPathId="39" /><Method Name="GetFileVersionBatchDeleteJobProgress" Id="41" ObjectPathId="39"><Parameters><Parameter Type="String">$EscapedSiteUrl</Parameter></Parameters></Method></Actions><ObjectPaths><Constructor Id="39" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}" /></ObjectPaths></Request>
50+
"@
51+
52+
$AdditionalHeaders = @{
53+
'Accept' = 'application/json;odata=verbose'
54+
}
55+
56+
$Response = New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
57+
58+
# ProcessQuery returns a JSON array; if it came back as raw text, parse it first.
59+
if ($Response -is [string]) {
60+
$Response = $Response | ConvertFrom-Json
61+
}
62+
63+
# The first array element carries ErrorInfo for the whole request.
64+
$ErrorInfo = $Response | Where-Object { $_.PSObject.Properties.Name -contains 'ErrorInfo' } | Select-Object -First 1
65+
if ($ErrorInfo.ErrorInfo) {
66+
throw "SharePoint returned an error querying version cleanup status for $SiteUrl : $($ErrorInfo.ErrorInfo.ErrorMessage)"
67+
}
68+
69+
# GetFileVersionBatchDeleteJobProgress returns its payload as the only String element.
70+
$ProgressJson = $Response | Where-Object { $_ -is [string] } | Select-Object -First 1
71+
72+
if ([string]::IsNullOrWhiteSpace($ProgressJson)) {
73+
return [PSCustomObject]@{
74+
SiteUrl = $SiteUrl
75+
Status = 'NoJob'
76+
Message = 'No file version batch delete job found for this site.'
77+
}
78+
}
79+
80+
$Progress = $ProgressJson | ConvertFrom-Json
81+
Add-Member -InputObject $Progress -MemberType NoteProperty -Name 'SiteUrl' -Value $SiteUrl -Force
82+
return $Progress
83+
}

Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function New-CIPPCAPolicy {
112112
$JSONobj.conditions.users.excludeGuestsOrExternalUsers.externalTenants.PSObject.Properties.Remove('@odata.context')
113113
}
114114
if ($State -and $State -ne 'donotchange') {
115-
$JSONobj.state = $State
115+
$JSONobj | Add-Member -NotePropertyName 'state' -NotePropertyValue $State -Force
116116
}
117117
} catch {
118118
$ErrorMessage = Get-CippException -Exception $_
@@ -543,7 +543,7 @@ function New-CIPPCAPolicy {
543543
return $false
544544
} else {
545545
if ($State -eq 'donotchange') {
546-
$JSONobj.state = $CheckExisting.state
546+
$JSONobj | Add-Member -NotePropertyName 'state' -NotePropertyValue $CheckExisting.state -Force
547547
$RawJSON = ConvertTo-Json -InputObject $JSONobj -Depth 10 -Compress
548548
}
549549
# Preserve any exclusion groups named "Vacation Exclusion - <PolicyDisplayName>" from existing policy

Modules/CIPPCore/Public/Start-CIPPSiteVersionCleanup.ps1

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ function Start-CIPPSiteVersionCleanup {
7575
}
7676

7777
if ($PSCmdlet.ShouldProcess($SiteUrl, 'Start file version batch delete job')) {
78-
return New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
78+
$Response = New-GraphPostRequest -scope "$AdminUrl/.default" -tenantid $TenantFilter -Uri "$AdminUrl/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
79+
80+
# CSOM reports validation failures as HTTP 200 with a populated ErrorInfo on the first
81+
# array element. Surface it instead of returning a silent "success" the caller can't see.
82+
if ($Response -is [string]) {
83+
$Response = $Response | ConvertFrom-Json
84+
}
85+
$ErrorInfo = $Response | Where-Object { $_.PSObject.Properties.Name -contains 'ErrorInfo' } | Select-Object -First 1
86+
if ($ErrorInfo.ErrorInfo) {
87+
throw "SharePoint rejected the version cleanup job for $SiteUrl : $($ErrorInfo.ErrorInfo.ErrorMessage)"
88+
}
89+
90+
return $Response
7991
}
8092
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
function Invoke-ListSPOVersionCleanup {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
Sharepoint.Site.Read
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
$APIName = $Request.Params.CIPPEndpoint
12+
$Headers = $Request.Headers
13+
$TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter
14+
$SiteUrl = $Request.Query.SiteUrl ?? $Request.Body.SiteUrl
15+
16+
try {
17+
$Result = Get-CIPPSiteVersionCleanupStatus -TenantFilter $TenantFilter -SiteUrl $SiteUrl
18+
Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message "Retrieved version cleanup status for $SiteUrl" -sev Info
19+
$StatusCode = [HttpStatusCode]::OK
20+
} catch {
21+
$ErrorMessage = Get-CippException -Exception $_
22+
Write-LogMessage -API $APIName -tenant $TenantFilter -headers $Headers -message "Failed to retrieve version cleanup status for $SiteUrl : $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
23+
$Result = "Failed to retrieve version cleanup status: $($ErrorMessage.NormalizedError)"
24+
$StatusCode = [HttpStatusCode]::InternalServerError
25+
}
26+
27+
return [HttpResponseContext]@{
28+
StatusCode = $StatusCode
29+
Body = @{ Results = $Result }
30+
}
31+
}

Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardConditionalAccessTemplate.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function Invoke-CIPPStandardConditionalAccessTemplate {
109109
# This ensures drift detection compares against the desired state, not the original template state
110110
if ($Settings.state -and $Settings.state -ne 'donotchange') {
111111
Write-Information "Overriding template state from '$($Policy.state)' to '$($Settings.state)' for drift comparison"
112-
$Policy.state = $Settings.state
112+
$Policy | Add-Member -NotePropertyName 'state' -NotePropertyValue $Settings.state -Force
113113
}
114114

115115
$CheckExististing = $AllCAPolicies | Where-Object -Property displayName -EQ $Settings.TemplateList.label

Modules/CIPPStandards/Public/Standards/Invoke-CIPPStandardSPOVersionControl.ps1

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function Invoke-CIPPStandardSPOVersionControl {
1818
ADDEDCOMPONENT
1919
{"type":"switch","name":"standards.SPOVersionControl.EnableAutoTrim","label":"Enable Automatic Version Trimming (Microsoft managed)"}
2020
{"type":"number","name":"standards.SPOVersionControl.MajorVersionLimit","label":"Maximum Major Versions (when auto trim is off)","default":50}
21-
{"type":"number","name":"standards.SPOVersionControl.ExpireVersionsAfterDays","label":"Expire Versions After Days (0 = never, when auto trim is off)","default":0}
21+
{"type":"number","name":"standards.SPOVersionControl.ExpireVersionsAfterDays","label":"Expire Versions After Days (0 = never, otherwise 30-36500, when auto trim is off)","default":0,"validators":{"min":{"value":0,"message":"Use 0 for never, or 30 or more days"},"max":{"value":36500,"message":"Maximum value is 36500"}}}
2222
{"type":"switch","name":"standards.SPOVersionControl.ApplyToExistingSites","label":"Apply to all existing sites and document libraries"}
2323
IMPACT
2424
Medium Impact
@@ -53,6 +53,15 @@ function Invoke-CIPPStandardSPOVersionControl {
5353
$DesiredMajorVersionLimit = [int]($Settings.MajorVersionLimit ?? 50)
5454
$DesiredExpireVersionsAfterDays = [int]($Settings.ExpireVersionsAfterDays ?? 0)
5555

56+
# SharePoint only accepts 0 (never expire) or 30-36500 days for version expiration. Reject
57+
# anything in the 1-29 gap (or above the max) up front so we never send a value the tenant
58+
# will refuse. This is the same 30-day floor the version cleanup (trim) job enforces.
59+
if (-not $DesiredAutoTrim -and $DesiredExpireVersionsAfterDays -ne 0 -and
60+
($DesiredExpireVersionsAfterDays -lt 30 -or $DesiredExpireVersionsAfterDays -gt 36500)) {
61+
Write-LogMessage -API 'Standards' -tenant $Tenant -message "SPOVersionControl: ExpireVersionsAfterDays must be 0 (never) or between 30 and 36500 days. Received '$DesiredExpireVersionsAfterDays'. Skipping standard." -sev Error
62+
return
63+
}
64+
5665
try {
5766
$CurrentState = Get-CIPPSPOTenant -TenantFilter $Tenant | Select-Object -Property _ObjectIdentity_, TenantFilter, EnableAutoExpirationVersionTrim, MajorVersionLimit, ExpireVersionsAfterDays
5867
} catch {

0 commit comments

Comments
 (0)