Skip to content

Commit becd6e1

Browse files
authored
Merge pull request #359 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents efdcad6 + 361a68f commit becd6e1

File tree

6 files changed

+377
-235
lines changed

6 files changed

+377
-235
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
function Get-CIPPAlertLowTenantAlignment {
2+
<#
3+
.SYNOPSIS
4+
Alert for low tenant alignment percentage
5+
.DESCRIPTION
6+
This alert checks tenant alignment scores against standards templates and alerts when the alignment percentage falls below the specified threshold.
7+
.PARAMETER TenantFilter
8+
The tenant to check alignment for
9+
.PARAMETER InputValue
10+
The minimum alignment percentage threshold (0-100). Default is 80.
11+
.FUNCTIONALITY
12+
Entrypoint
13+
.EXAMPLE
14+
Get-CIPPAlertLowTenantAlignment -TenantFilter "contoso.onmicrosoft.com" -InputValue 75
15+
#>
16+
[CmdletBinding()]
17+
param (
18+
[Parameter(Mandatory)]
19+
$TenantFilter,
20+
[Alias('input')]
21+
[ValidateRange(0, 100)]
22+
[int]$InputValue = 99
23+
)
24+
25+
try {
26+
# Get tenant alignment data using the new function
27+
$AlignmentData = Get-CIPPTenantAlignment -TenantFilter $TenantFilter
28+
29+
if (-not $AlignmentData) {
30+
Write-AlertMessage -tenant $TenantFilter -message "No alignment data found for tenant $TenantFilter. This may indicate no standards templates are configured or applied to this tenant."
31+
return
32+
}
33+
34+
$LowAlignmentAlerts = $AlignmentData | Where-Object { $_.AlignmentScore -lt $InputValue } | ForEach-Object {
35+
[PSCustomObject]@{
36+
TenantFilter = $_.TenantFilter
37+
StandardName = $_.StandardName
38+
StandardId = $_.StandardId
39+
AlignmentScore = $_.AlignmentScore
40+
LicenseMissingPercentage = $_.LicenseMissingPercentage
41+
LatestDataCollection = $_.LatestDataCollection
42+
}
43+
}
44+
45+
if ($LowAlignmentAlerts.Count -gt 0) {
46+
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $LowAlignmentAlerts
47+
}
48+
49+
} catch {
50+
Write-AlertMessage -tenant $TenantFilter -message "Could not get tenant alignment data for $TenantFilter`: $(Get-NormalizedError -message $_.Exception.message)"
51+
}
52+
}

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

Lines changed: 26 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -13,229 +13,31 @@ function Invoke-ListTenantAlignment {
1313
$APIName = $Request.Params.CIPPEndpoint
1414
$Headers = $Request.Headers
1515

16-
# Get all standard templates
17-
$TemplateTable = Get-CippTable -tablename 'templates'
18-
$TemplateFilter = "PartitionKey eq 'StandardsTemplateV2'"
19-
$Templates = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $TemplateFilter) | ForEach-Object {
20-
$JSON = $_.JSON -replace '"Action":', '"action":'
21-
try {
22-
$RowKey = $_.RowKey
23-
$Data = $JSON | ConvertFrom-Json -Depth 100 -ErrorAction SilentlyContinue
24-
} catch {
25-
Write-Host "$($RowKey) standard could not be loaded: $($_.Exception.Message)"
26-
return
27-
}
28-
if ($Data) {
29-
$Data | Add-Member -NotePropertyName 'GUID' -NotePropertyValue $_.GUID -Force
30-
$Data
31-
}
32-
}
33-
34-
# Get standards comparison data using the same pattern as ListStandardsCompare
35-
$StandardsTable = Get-CIPPTable -TableName 'CippStandardsReports'
36-
$Standards = Get-CIPPAzDataTableEntity @StandardsTable
37-
38-
# Build tenant standards data structure like in ListStandardsCompare
39-
$TenantStandards = @{}
40-
foreach ($Standard in $Standards) {
41-
$FieldName = $Standard.RowKey
42-
$FieldValue = $Standard.Value
43-
$Tenant = $Standard.PartitionKey
44-
45-
# Process field value like in ListStandardsCompare
46-
if ($FieldValue -is [System.Boolean]) {
47-
$FieldValue = [bool]$FieldValue
48-
} elseif ($FieldValue -like '*{*') {
49-
$FieldValue = ConvertFrom-Json -InputObject $FieldValue -ErrorAction SilentlyContinue
50-
} else {
51-
$FieldValue = [string]$FieldValue
52-
}
53-
54-
if (-not $TenantStandards.ContainsKey($Tenant)) {
55-
$TenantStandards[$Tenant] = @{}
56-
}
57-
$TenantStandards[$Tenant][$FieldName] = @{
58-
Value = $FieldValue
59-
LastRefresh = $Standard.TimeStamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
60-
}
61-
}
62-
63-
$Results = [System.Collections.Generic.List[object]]::new()
64-
65-
# Process each template against all tenants
66-
foreach ($Template in $Templates) {
67-
$TemplateStandards = $Template.standards
68-
if (-not $TemplateStandards) {
69-
continue
70-
}
71-
72-
# Check if template has tenant assignments (scope)
73-
$TemplateAssignedTenants = @()
74-
$AppliestoAllTenants = $false
75-
76-
if ($Template.tenantFilter -and $Template.tenantFilter.Count -gt 0) {
77-
# Extract tenant values from the tenantFilter array
78-
$TenantValues = $Template.tenantFilter | ForEach-Object { $_.value }
79-
80-
if ($TenantValues -contains 'AllTenants') {
81-
$AppliestoAllTenants = $true
82-
Write-Host "Template '$($Template.templateName)' applies to all tenants (AllTenants)"
83-
} else {
84-
$TemplateAssignedTenants = $TenantValues
85-
Write-Host "Template '$($Template.templateName)' is assigned to specific tenants: $($TemplateAssignedTenants -join ', ')"
86-
}
87-
} else {
88-
$AppliestoAllTenants = $true
89-
Write-Host "Template '$($Template.templateName)' applies to all tenants (no tenantFilter)"
90-
}
91-
92-
$AllStandards = [System.Collections.Generic.List[string]]::new()
93-
$ReportingEnabledStandards = [System.Collections.Generic.List[string]]::new()
94-
$ReportingDisabledStandards = [System.Collections.Generic.List[string]]::new()
95-
96-
foreach ($StandardKey in $TemplateStandards.PSObject.Properties.Name) {
97-
$StandardConfig = $TemplateStandards.$StandardKey
98-
$StandardId = "standards.$StandardKey"
99-
100-
101-
$Actions = @()
102-
if ($StandardConfig.action) {
103-
$Actions = $StandardConfig.action
104-
} elseif ($StandardConfig.Action) {
105-
$Actions = $StandardConfig.Action
106-
} elseif ($StandardConfig.PSObject.Properties['action']) {
107-
$Actions = $StandardConfig.PSObject.Properties['action'].Value
108-
}
109-
110-
$ReportingEnabled = $false
111-
if ($Actions -and $Actions.Count -gt 0) {
112-
$ReportingEnabled = ($Actions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0
113-
}
114-
115-
$AllStandards.Add($StandardId)
116-
117-
if ($ReportingEnabled) {
118-
$ReportingEnabledStandards.Add($StandardId)
119-
} else {
120-
$ReportingDisabledStandards.Add($StandardId)
121-
}
122-
123-
if ($StandardKey -eq 'IntuneTemplate' -and $StandardConfig -is [array]) {
124-
$AllStandards.Remove($StandardId)
125-
if ($ReportingEnabled) {
126-
$ReportingEnabledStandards.Remove($StandardId)
127-
} else {
128-
$ReportingDisabledStandards.Remove($StandardId)
129-
}
130-
131-
foreach ($IntuneTemplate in $StandardConfig) {
132-
if ($IntuneTemplate.TemplateList.value) {
133-
$IntuneStandardId = "standards.IntuneTemplate.$($IntuneTemplate.TemplateList.value)"
134-
135-
$IntuneActions = if ($IntuneTemplate.action) { $IntuneTemplate.action } else { @() }
136-
$IntuneReportingEnabled = ($IntuneActions | Where-Object { $_.value -and ($_.value.ToLower() -eq 'report' -or $_.value.ToLower() -eq 'remediate') }).Count -gt 0
137-
138-
$AllStandards.Add($IntuneStandardId)
139-
140-
if ($IntuneReportingEnabled) {
141-
$ReportingEnabledStandards.Add($IntuneStandardId)
142-
} else {
143-
$ReportingDisabledStandards.Add($IntuneStandardId)
144-
}
145-
}
146-
}
147-
}
148-
}
149-
150-
foreach ($TenantName in $TenantStandards.Keys) {
151-
if (-not $AppliestoAllTenants -and $TenantName -notin $TemplateAssignedTenants) {
152-
Write-Host "Skipping tenant '$TenantName' for template '$($Template.templateName)' - not in assigned tenant list"
153-
continue
154-
}
155-
$AllCount = $AllStandards.Count
156-
157-
$CompliantStandards = 0
158-
$NonCompliantStandards = 0
159-
$ReportingDisabledStandardsCount = 0
160-
$LatestDataCollection = $null
161-
$ComparisonTable = @()
162-
163-
foreach ($StandardKey in $AllStandards) {
164-
$IsReportingDisabled = $ReportingDisabledStandards -contains $StandardKey
165-
166-
if ($TenantStandards[$TenantName].ContainsKey($StandardKey)) {
167-
$StandardObject = $TenantStandards[$TenantName][$StandardKey]
168-
$Value = $StandardObject.Value
169-
170-
if ($StandardObject.LastRefresh) {
171-
$RefreshTime = [DateTime]::Parse($StandardObject.LastRefresh)
172-
if (-not $LatestDataCollection -or $RefreshTime -gt $LatestDataCollection) {
173-
$LatestDataCollection = $RefreshTime
174-
}
175-
}
176-
177-
$IsCompliant = ($Value -eq $true)
178-
179-
if ($IsReportingDisabled) {
180-
$ReportingDisabledStandardsCount++
181-
$ComplianceStatus = 'Reporting Disabled'
182-
} elseif ($IsCompliant) {
183-
$CompliantStandards++
184-
$ComplianceStatus = 'Compliant'
185-
} else {
186-
$NonCompliantStandards++
187-
$ComplianceStatus = 'Non-Compliant'
188-
}
189-
190-
$ComparisonTable += [PSCustomObject]@{
191-
StandardName = $StandardKey
192-
Compliant = $IsCompliant
193-
StandardValue = ($Value | ConvertTo-Json -Compress)
194-
ComplianceStatus = $ComplianceStatus
195-
ReportingDisabled = $IsReportingDisabled
196-
}
197-
} else {
198-
if ($IsReportingDisabled) {
199-
$ReportingDisabledStandardsCount++
200-
$ComplianceStatus = 'Reporting Disabled'
201-
} else {
202-
$NonCompliantStandards++
203-
$ComplianceStatus = 'Non-Compliant'
204-
}
205-
206-
$ComparisonTable += [PSCustomObject]@{
207-
StandardName = $StandardKey
208-
Compliant = $false
209-
StandardValue = 'NOT FOUND'
210-
ComplianceStatus = $ComplianceStatus
211-
ReportingDisabled = $IsReportingDisabled
212-
}
213-
}
214-
}
215-
216-
217-
$AlignmentPercentage = if (($AllCount - $ReportingDisabledStandardsCount) -gt 0) {
218-
[Math]::Round(($CompliantStandards / ($AllCount - $ReportingDisabledStandardsCount)) * 100)
219-
} else {
220-
0
221-
}
222-
223-
$TenantOnlyStandards = $TenantStandards[$TenantName].Keys | Where-Object { $_ -notin $AllStandards }
224-
225-
$Result = [PSCustomObject]@{
226-
tenantFilter = $TenantName
227-
standardName = $Template.templateName
228-
standardId = $Template.GUID
229-
alignmentScore = $AlignmentPercentage
230-
latestDataCollection = if ($LatestDataCollection) { $LatestDataCollection } else { $null }
231-
}
232-
233-
$Results.Add($Result)
234-
}
16+
try {
17+
# Use the new Get-CIPPTenantAlignment function to get alignment data
18+
$AlignmentData = Get-CIPPTenantAlignment
19+
20+
# Transform the data to match the expected API response format
21+
$Results = $AlignmentData | ForEach-Object {
22+
[PSCustomObject]@{
23+
tenantFilter = $_.TenantFilter
24+
standardName = $_.StandardName
25+
standardId = $_.StandardId
26+
alignmentScore = $_.AlignmentScore
27+
LicenseMissingPercentage = $_.LicenseMissingPercentage
28+
latestDataCollection = $_.LatestDataCollection
29+
}
30+
}
31+
32+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
33+
StatusCode = [HttpStatusCode]::OK
34+
Body = @($Results)
35+
})
36+
} catch {
37+
Write-LogMessage -API $APIName -message "Failed to get tenant alignment data: $($_.Exception.Message)" -sev Error
38+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
39+
StatusCode = [HttpStatusCode]::InternalServerError
40+
Body = @{ error = "Failed to get tenant alignment data: $($_.Exception.Message)" }
41+
})
23542
}
236-
237-
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
238-
StatusCode = [HttpStatusCode]::OK
239-
Body = @($Results)
240-
})
24143
}

0 commit comments

Comments
 (0)