Skip to content

Commit c2258f7

Browse files
authored
Merge pull request #360 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents becd6e1 + 89b53e1 commit c2258f7

File tree

115 files changed

+1274
-490
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+1274
-490
lines changed

ConversionTable.csv

Lines changed: 157 additions & 98 deletions
Large diffs are not rendered by default.

Modules/CIPPCore/Public/ConversionTable.csv

Lines changed: 157 additions & 98 deletions
Large diffs are not rendered by default.

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Identity/Administration/Users/Invoke-ExecBECRemediate.ps1

Lines changed: 170 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,54 +19,196 @@ function Invoke-ExecBECRemediate {
1919
$Username = $Request.Body.username
2020
Write-Host $TenantFilter
2121
Write-Host $SuspectUser
22+
2223
$Results = try {
24+
$AllResults = [System.Collections.Generic.List[object]]::new()
25+
26+
# Step 1: Reset Password
2327
$Step = 'Reset Password'
24-
Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers
28+
try {
29+
$PasswordResult = Set-CIPPResetPassword -UserID $Username -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers
30+
$AllResults.Add($PasswordResult)
31+
} catch {
32+
$AllResults.Add([pscustomobject]@{
33+
resultText = "Failed to reset password: $($_.Exception.Message)"
34+
state = 'error'
35+
})
36+
}
37+
38+
# Step 2: Disable Account
2539
$Step = 'Disable Account'
26-
Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers
40+
try {
41+
$DisableResult = Set-CIPPSignInState -userid $Username -AccountEnabled $false -tenantFilter $TenantFilter -APIName $APIName -Headers $Headers
42+
$AllResults.Add([pscustomobject]@{
43+
resultText = $DisableResult
44+
state = if ($DisableResult -like "*WARNING*") { 'warning' } else { 'success' }
45+
})
46+
} catch {
47+
$AllResults.Add([pscustomobject]@{
48+
resultText = "Failed to disable account: $($_.Exception.Message)"
49+
state = 'error'
50+
})
51+
}
52+
53+
# Step 3: Revoke Sessions
2754
$Step = 'Revoke Sessions'
28-
Revoke-CIPPSessions -userid $SuspectUser -username $Username -Headers $Headers -APIName $APIName -tenantFilter $TenantFilter
55+
try {
56+
$SessionResult = Revoke-CIPPSessions -userid $SuspectUser -username $Username -Headers $Headers -APIName $APIName -tenantFilter $TenantFilter
57+
$AllResults.Add([pscustomobject]@{
58+
resultText = $SessionResult
59+
state = if ($SessionResult -like "*Failed*") { 'error' } else { 'success' }
60+
})
61+
} catch {
62+
$AllResults.Add([pscustomobject]@{
63+
resultText = "Failed to revoke sessions: $($_.Exception.Message)"
64+
state = 'error'
65+
})
66+
}
67+
68+
# Step 4: Remove MFA methods
2969
$Step = 'Remove MFA methods'
30-
Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers
70+
try {
71+
$MFAResult = Remove-CIPPUserMFA -UserPrincipalName $Username -TenantFilter $TenantFilter -Headers $Headers
72+
$AllResults.Add([pscustomobject]@{
73+
resultText = $MFAResult
74+
state = if ($MFAResult -like "*No MFA methods*") { 'info' } elseif ($MFAResult -like "*Successfully*") { 'success' } else { 'error' }
75+
})
76+
} catch {
77+
$AllResults.Add([pscustomobject]@{
78+
resultText = "Failed to remove MFA methods: $($_.Exception.Message)"
79+
state = 'error'
80+
})
81+
}
82+
83+
# Step 5: Disable Inbox Rules
3184
$Step = 'Disable Inbox Rules'
32-
$Rules = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true }
33-
$RuleDisabled = 0
34-
$RuleFailed = 0
35-
if (($Rules | Measure-Object).Count -gt 0) {
36-
$Rules | Where-Object { $_.Name -ne 'Junk E-Mail Rule' -and $_.Name -notlike 'Microsoft.Exchange.OOF.*' } | ForEach-Object {
37-
try {
38-
Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $_.Identity -RuleName $_.Name -Disable -APIName $APIName -Headers $Headers
39-
$RuleDisabled++
40-
} catch {
41-
$_.Exception.Message
42-
$RuleFailed++
85+
try {
86+
Write-LogMessage -headers $Headers -API $APIName -message "Starting inbox rules processing for user: $Username" -Sev 'Info' -tenant $TenantFilter
87+
$Rules = New-ExoRequest -anchor $Username -tenantid $TenantFilter -cmdlet 'Get-InboxRule' -cmdParams @{Mailbox = $Username; IncludeHidden = $true }
88+
Write-LogMessage -headers $Headers -API $APIName -message "Retrieved $(($Rules | Measure-Object).Count) total rules for $Username" -Sev 'Info' -tenant $TenantFilter
89+
$RuleDisabled = 0
90+
$RuleFailed = 0
91+
$DelegateRulesSkipped = 0
92+
$RuleMessages = [System.Collections.Generic.List[string]]::new()
93+
94+
if (($Rules | Measure-Object).Count -eq 0) {
95+
# No rules exist at all
96+
$AllResults.Add([pscustomobject]@{
97+
resultText = "No Inbox Rules found for $Username."
98+
state = 'info'
99+
})
100+
} else {
101+
# Rules exist, filter and process them
102+
$ProcessableRules = $Rules | Where-Object {
103+
$_.Name -ne 'Junk E-Mail Rule' -and
104+
$_.Name -notlike 'Microsoft.Exchange.OOF.*'
105+
}
106+
107+
if (($ProcessableRules | Measure-Object).Count -eq 0) {
108+
# Rules exist but none are processable after filtering
109+
$SystemRulesCount = ($Rules | Measure-Object).Count - $DelegateRulesSkipped
110+
if ($SystemRulesCount -gt 0) {
111+
$AllResults.Add([pscustomobject]@{
112+
resultText = "Found $(($Rules | Measure-Object).Count) inbox rules for $Username, but none require disabling (only system rules found)."
113+
state = 'info'
114+
})
115+
}
116+
} else {
117+
# Process the filterable rules
118+
$ProcessableRules | ForEach-Object {
119+
$CurrentRule = $_
120+
Write-LogMessage -headers $Headers -API $APIName -message "Processing rule: Name='$($CurrentRule.Name)', Identity='$($CurrentRule.Identity)'" -Sev 'Info' -tenant $TenantFilter
121+
122+
try {
123+
Set-CIPPMailboxRule -Username $Username -TenantFilter $TenantFilter -RuleId $CurrentRule.Identity -RuleName $CurrentRule.Name -Disable -APIName $APIName -Headers $Headers
124+
125+
Write-LogMessage -headers $Headers -API $APIName -message "Successfully disabled rule: $($CurrentRule.Name)" -Sev 'Info' -tenant $TenantFilter
126+
$RuleDisabled++
127+
} catch {
128+
# Check if this is a system delegate rule, if so we can ignore the error
129+
if ($CurrentRule.Name -match '^Delegate Rule -\d+$') {
130+
Write-LogMessage -headers $Headers -API $APIName -message "Skipping delegate rule '$($CurrentRule.Name)' - unable to disable (expected behavior)" -Sev 'Info' -tenant $TenantFilter
131+
$DelegateRulesSkipped++
132+
} else {
133+
# Handle as normal error
134+
$ErrorMsg = "Could not disable rule '$($CurrentRule.Name)': $($_.Exception.Message)"
135+
Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter
136+
$RuleMessages.Add($ErrorMsg)
137+
$RuleFailed++
138+
}
139+
}
140+
}
141+
142+
# Report results
143+
if ($RuleDisabled -gt 0) {
144+
$AllResults.Add([pscustomobject]@{
145+
resultText = "Successfully disabled $RuleDisabled inbox rules for $Username"
146+
state = 'success'
147+
})
148+
} elseif ($DelegateRulesSkipped -gt 0 -and $RuleDisabled -eq 0 -and $RuleFailed -eq 0) {
149+
# Only system rules were found, report as no processable rules
150+
$AllResults.Add([pscustomobject]@{
151+
resultText = "No processable inbox rules found for $Username"
152+
state = 'info'
153+
})
154+
}
155+
156+
if ($RuleFailed -gt 0) {
157+
$AllResults.Add([pscustomobject]@{
158+
resultText = "Failed to process $RuleFailed inbox rules for $Username"
159+
state = 'warning'
160+
})
161+
162+
# Add individual rule failure messages as objects
163+
foreach ($RuleMessage in $RuleMessages) {
164+
$AllResults.Add([pscustomobject]@{
165+
resultText = $RuleMessage
166+
state = 'error'
167+
})
168+
}
169+
}
43170
}
44171
}
45-
}
46-
if ($RuleDisabled -gt 0) {
47-
"Disabled $RuleDisabled Inbox Rules for $Username"
48-
} else {
49-
"No Inbox Rules found for $Username. We have not disabled any rules."
50-
}
51172

52-
if ($RuleFailed -gt 0) {
53-
"Failed to disable $RuleFailed Inbox Rules for $Username"
173+
$TotalProcessed = $RuleDisabled + $RuleFailed + $DelegateRulesSkipped
174+
Write-LogMessage -headers $Headers -API $APIName -message "Completed inbox rules processing for $Username. Total rules: $(($Rules | Measure-Object).Count), Processed: $TotalProcessed, Disabled: $RuleDisabled, Failed: $RuleFailed, Delegate rules skipped: $DelegateRulesSkipped" -Sev 'Info' -tenant $TenantFilter
175+
176+
} catch {
177+
$ErrorMsg = "Failed to process inbox rules: $($_.Exception.Message)"
178+
Write-LogMessage -headers $Headers -API $APIName -message $ErrorMsg -Sev 'Error' -tenant $TenantFilter
179+
$AllResults.Add([pscustomobject]@{
180+
resultText = $ErrorMsg
181+
state = 'error'
182+
})
54183
}
184+
55185
$StatusCode = [HttpStatusCode]::OK
56-
Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' -LogData @($Results)
186+
Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username" -sev 'Info' -LogData @($AllResults)
187+
188+
# Return the results array
189+
$AllResults.ToArray()
57190

58191
} catch {
59192
$ErrorMessage = Get-CippException -Exception $_
60-
$Results = [pscustomobject]@{'Results' = "Failed to execute remediation. $($ErrorMessage.NormalizedError)" }
193+
$ErrorList = [System.Collections.Generic.List[object]]::new()
194+
$ErrorList.Add([pscustomobject]@{
195+
resultText = "Failed to execute remediation at step '$Step'. $($ErrorMessage.NormalizedError)"
196+
state = 'error'
197+
})
61198
Write-LogMessage -API 'BECRemediate' -tenant $TenantFilter -message "Executed Remediation for $Username failed at the $Step step" -sev 'Error' -LogData $ErrorMessage
62199
$StatusCode = [HttpStatusCode]::InternalServerError
200+
201+
# Return the error array
202+
$ErrorList.ToArray()
63203
}
64-
$Results = [pscustomobject]@{'Results' = @($Results) }
65204

66-
# Associate values to output bindings by calling 'Push-OutputBinding'.
205+
# Create the final response structure
206+
$ResponseBody = [pscustomobject]@{'Results' = @($Results) }
207+
208+
# Associate values to output bindings
67209
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
68210
StatusCode = $StatusCode
69-
Body = $Results
211+
Body = $ResponseBody
70212
})
71213

72214
}

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Teams-Sharepoint/Invoke-AddSite.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using namespace System.Net
22

3-
Function Invoke-AddSite {
3+
function Invoke-AddSite {
44
<#
55
.FUNCTIONALITY
66
Entrypoint
@@ -24,7 +24,7 @@ Function Invoke-AddSite {
2424
$StatusCode = [HttpStatusCode]::OK
2525
} catch {
2626
$StatusCode = [HttpStatusCode]::InternalServerError
27-
$Result = "Failed to create SharePoint Site: $($_.Exception.Message)"
27+
$Result = $_.Exception.Message
2828
}
2929

3030
# Associate values to output bindings by calling 'Push-OutputBinding'.

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Standards/Invoke-ListTenantAlignment.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function Invoke-ListTenantAlignment {
2525
standardId = $_.StandardId
2626
alignmentScore = $_.AlignmentScore
2727
LicenseMissingPercentage = $_.LicenseMissingPercentage
28+
combinedAlignmentScore = $_.CombinedScore
2829
latestDataCollection = $_.LatestDataCollection
2930
}
3031
}

Modules/CIPPCore/Public/Functions/Get-CIPPTenantAlignment.ps1

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ function Get-CIPPTenantAlignment {
179179
}
180180

181181
$IsCompliant = ($Value -eq $true)
182-
$IsLicenseMissing = ($Value -is [string] -and $Value -like "License Missing:*")
182+
$IsLicenseMissing = ($Value -is [string] -and $Value -like 'License Missing:*')
183183

184184
if ($IsReportingDisabled) {
185185
$ComplianceStatus = 'Reporting Disabled'
@@ -233,18 +233,19 @@ function Get-CIPPTenantAlignment {
233233
}
234234

235235
$Result = [PSCustomObject]@{
236-
TenantFilter = $TenantName
237-
StandardName = $Template.templateName
238-
StandardId = $Template.GUID
239-
AlignmentScore = $AlignmentPercentage
236+
TenantFilter = $TenantName
237+
StandardName = $Template.templateName
238+
StandardId = $Template.GUID
239+
AlignmentScore = $AlignmentPercentage
240240
LicenseMissingPercentage = $LicenseMissingPercentage
241-
CompliantStandards = $CompliantStandards
242-
NonCompliantStandards = $NonCompliantStandards
243-
LicenseMissingStandards = $LicenseMissingStandards
244-
TotalStandards = $AllCount
245-
ReportingDisabledCount = $ReportingDisabledStandardsCount
246-
LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null }
247-
ComparisonDetails = $ComparisonTable
241+
CombinedScore = $AlignmentPercentage + $LicenseMissingPercentage
242+
CompliantStandards = $CompliantStandards
243+
NonCompliantStandards = $NonCompliantStandards
244+
LicenseMissingStandards = $LicenseMissingStandards
245+
TotalStandards = $AllCount
246+
ReportingDisabledCount = $ReportingDisabledStandardsCount
247+
LatestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null }
248+
ComparisonDetails = $ComparisonTable
248249
}
249250

250251
$Results.Add($Result)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function Test-CIPPStandardLicense {
2+
<#
3+
.SYNOPSIS
4+
Tests if a tenant has the required license capabilities for a specific standard
5+
.DESCRIPTION
6+
This function checks if a tenant has the necessary license capabilities to run a specific standard.
7+
If the license is missing, it logs an error and sets the comparison field appropriately.
8+
.PARAMETER StandardName
9+
The name of the standard to check licensing for
10+
.PARAMETER TenantFilter
11+
The tenant to check licensing for
12+
.PARAMETER RequiredCapabilities
13+
Array of required capabilities for the standard
14+
.FUNCTIONALITY
15+
Internal
16+
.EXAMPLE
17+
Test-CIPPStandardLicense -StandardName "ConditionalAccessTemplate" -TenantFilter "contoso.onmicrosoft.com" -RequiredCapabilities @('AADPremiumService')
18+
.EXAMPLE
19+
Test-CIPPStandardLicense -StandardName "SafeLinksPolicy" -TenantFilter "contoso.onmicrosoft.com" -RequiredCapabilities @('DEFENDER_FOR_OFFICE_365_PLAN_1', 'DEFENDER_FOR_OFFICE_365_PLAN_2')
20+
#>
21+
[CmdletBinding()]
22+
param(
23+
[Parameter(Mandatory = $true)]
24+
[string]$StandardName,
25+
26+
[Parameter(Mandatory = $true)]
27+
[string]$TenantFilter,
28+
29+
[Parameter(Mandatory = $true)]
30+
[string[]]$RequiredCapabilities
31+
)
32+
33+
try {
34+
$TenantCapabilities = Get-CIPPTenantCapabilities -TenantFilter $TenantFilter
35+
36+
$Capabilities = foreach ($Capability in $RequiredCapabilities) {
37+
Write-Host "Checking capability: $Capability"
38+
if ($TenantCapabilities.$Capability -eq $true) {
39+
$Capability
40+
}
41+
}
42+
43+
if ($Capabilities.Count -le 0) {
44+
Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Tenant does not have the required capability to run standard $StandardName`: The tenant needs one of the following service plans: $($RequiredCapabilities -join ',')" -sev Error
45+
Set-CIPPStandardsCompareField -FieldName "standards.$StandardName" -FieldValue "License Missing: This tenant is not licensed for the following capabilities: $($RequiredCapabilities -join ',')" -Tenant $TenantFilter
46+
Write-Host "Tenant does not have the required capability to run standard $StandardName - $($RequiredCapabilities -join ','). Exiting"
47+
exit 0
48+
}
49+
Write-Host "Tenant has the required capabilities for standard $StandardName"
50+
} catch {
51+
Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Error checking license capabilities for standard $StandardName`: $($_.Exception.Message)" -sev Error
52+
Set-CIPPStandardsCompareField -FieldName "standards.$StandardName" -FieldValue "License Missing: Error checking license capabilities - $($_.Exception.Message)" -Tenant $TenantFilter
53+
exit 0
54+
}
55+
}

Modules/CIPPCore/Public/Get-CIPPTenantCapabilities.ps1

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ function Get-CIPPTenantCapabilities {
77
$Headers
88
)
99

10-
$Org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/organization' -tenantid $TenantFilter
11-
$Plans = $Org.assignedPlans | Where-Object { $_.capabilityStatus -eq 'Enabled' } | Sort-Object -Property service -Unique | Select-Object capabilityStatus, service
12-
10+
$Org = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/subscribedSkus' -tenantid $TenantFilter
11+
$Plans = $Org.servicePlans | Where-Object { $_.provisioningStatus -eq 'Success' } | Sort-Object -Property serviceplanName -Unique | Select-Object servicePlanName, provisioningStatus
1312
$Results = @{}
1413
foreach ($Plan in $Plans) {
15-
$Results."$($Plan.service)" = $Plan.capabilityStatus -eq 'Enabled'
14+
$Results."$($Plan.servicePlanName)" = $Plan.provisioningStatus -eq 'Success'
1615
}
1716
[PSCustomObject]$Results
1817
}

0 commit comments

Comments
 (0)