Skip to content

Commit 57359c7

Browse files
Custom Scripts Test System (KelvinTegelaar#1815)
First version of custom scripts, its working for tests, manual runs (while editing the script) and task scheduler runs. whitelist commands and types. also a small fix for the user tasks orchestration that fails when passed a single object for the tenant group/filter UI: KelvinTegelaar/CIPP#5339
2 parents 053946d + eaa6894 commit 57359c7

14 files changed

Lines changed: 770 additions & 9 deletions

Modules/CIPPCore/Public/Add-CippTestResult.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ function Add-CippTestResult {
5656
[Parameter(Mandatory = $false)]
5757
[string]$ResultMarkdown,
5858

59+
[Parameter(Mandatory = $false)]
60+
[string]$ResultDataJson,
61+
5962
[Parameter(Mandatory = $false)]
6063
[string]$Risk,
6164

@@ -83,6 +86,7 @@ function Add-CippTestResult {
8386
RowKey = $TestId
8487
Status = $Status
8588
ResultMarkdown = $ResultMarkdown ?? ''
89+
ResultDataJson = $ResultDataJson ?? ''
8690
Risk = $Risk ?? ''
8791
Name = $Name ?? ''
8892
Pillar = $Pillar ?? ''

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-AddTestReport.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function Invoke-AddTestReport {
2323
$ReportId = New-Guid
2424
$IdentityTests = $Body.IdentityTests ? ($Body.IdentityTests | ConvertTo-Json -Compress) : '[]'
2525
$DevicesTests = $Body.DevicesTests ? ($Body.DevicesTests | ConvertTo-Json -Compress) : '[]'
26+
$CustomTests = $Body.CustomTests ? ($Body.CustomTests | ConvertTo-Json -Compress) : '[]'
2627

2728
# Create report object
2829
$Report = [PSCustomObject]@{
@@ -33,6 +34,7 @@ function Invoke-AddTestReport {
3334
version = '1.0'
3435
IdentityTests = [string]$IdentityTests
3536
DevicesTests = [string]$DevicesTests
37+
CustomTests = [string]$CustomTests
3638
CreatedAt = [string](Get-Date).ToString('o')
3739
}
3840

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListAvailableTests.ps1

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ function Invoke-ListAvailableTests {
1414
try {
1515
# Get all test folders
1616
$TestFolders = Get-ChildItem 'Modules\CIPPCore\Public\Tests' -Directory
17+
$CustomTestsTable = Get-CippTable -tablename 'CustomPowershellScripts'
18+
$Filter = "PartitionKey eq 'CustomScript'"
19+
$AllScripts = Get-CIPPAzDataTableEntity @CustomTestsTable -Filter $Filter
20+
# Group by ScriptGuid and get latest version of each
21+
$LatestCustomScripts = $AllScripts |
22+
Group-Object -Property ScriptGuid |
23+
ForEach-Object {
24+
$_.Group | Sort-Object -Property Version -Descending | Select-Object -First 1
25+
}
26+
1727

1828
# Build identity tests array
1929
$IdentityTests = foreach ($TestFolder in $TestFolders) {
@@ -66,9 +76,34 @@ function Invoke-ListAvailableTests {
6676
}
6777
}
6878

79+
# Build custom tests array from latest custom scripts
80+
$CustomTestsList = foreach ($CustomTest in @($LatestCustomScripts)) {
81+
$ScriptGuid = $CustomTest.ScriptGuid
82+
if ([string]::IsNullOrWhiteSpace($ScriptGuid)) {
83+
continue
84+
}
85+
86+
$TestId = "CustomScript-$ScriptGuid"
87+
$TestName = if ([string]::IsNullOrWhiteSpace($CustomTest.ScriptName)) { $TestId } else { $CustomTest.ScriptName }
88+
89+
[PSCustomObject]@{
90+
id = $TestId
91+
name = $TestName
92+
category = 'Custom'
93+
testFolder = 'Custom'
94+
scriptGuid = $ScriptGuid
95+
description = $CustomTest.Description ?? ''
96+
risk = $CustomTest.Risk ?? 'Medium'
97+
enabled = [bool]$CustomTest.Enabled
98+
alertOnFailure = [bool]$CustomTest.AlertOnFailure
99+
version = $CustomTest.Version
100+
}
101+
}
102+
69103
$Body = [PSCustomObject]@{
70104
IdentityTests = $IdentityTests
71105
DevicesTests = $DevicesTests
106+
CustomTests = @($CustomTestsList)
72107
}
73108
$StatusCode = [HttpStatusCode]::OK
74109
} catch {

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Invoke-ListTests.ps1

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,34 @@ function Invoke-ListTests {
2626

2727
$IdentityTotal = 0
2828
$DevicesTotal = 0
29+
$CustomTotal = 0
2930
$IdentityTests = @()
3031
$DevicesTests = @()
32+
$CustomTests = @()
33+
34+
$NormalizeTestIds = {
35+
param($Value)
36+
37+
if ($null -eq $Value) {
38+
return @()
39+
}
40+
41+
if ($Value -is [string]) {
42+
return @($Value)
43+
}
44+
45+
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
46+
return @($Value | ForEach-Object {
47+
if ($_ -is [pscustomobject] -and $_.PSObject.Properties['id']) {
48+
[string]$_.id
49+
} else {
50+
[string]$_
51+
}
52+
})
53+
}
54+
55+
return @([string]$Value)
56+
}
3157

3258
if ($ReportId) {
3359
$ReportJsonFiles = Get-ChildItem 'Modules\CIPPCore\Public\Tests\*\report.json' -ErrorAction SilentlyContinue
@@ -39,13 +65,17 @@ function Invoke-ListTests {
3965
try {
4066
$ReportContent = Get-Content $MatchingReport.FullName -Raw | ConvertFrom-Json
4167
if ($ReportContent.IdentityTests) {
42-
$IdentityTests = $ReportContent.IdentityTests
68+
$IdentityTests = & $NormalizeTestIds $ReportContent.IdentityTests
4369
$IdentityTotal = @($IdentityTests).Count
4470
}
4571
if ($ReportContent.DevicesTests) {
46-
$DevicesTests = $ReportContent.DevicesTests
72+
$DevicesTests = & $NormalizeTestIds $ReportContent.DevicesTests
4773
$DevicesTotal = @($DevicesTests).Count
4874
}
75+
if ($ReportContent.CustomTests) {
76+
$CustomTests = & $NormalizeTestIds $ReportContent.CustomTests
77+
$CustomTotal = @($CustomTests).Count
78+
}
4979
$ReportFound = $true
5080
} catch {
5181
Write-LogMessage -API $APIName -tenant $TenantFilter -message "Error reading report.json: $($_.Exception.Message)" -sev Warning
@@ -60,14 +90,19 @@ function Invoke-ListTests {
6090

6191
if ($ReportTemplate) {
6292
if ($ReportTemplate.identityTests) {
63-
$IdentityTests = $ReportTemplate.identityTests | ConvertFrom-Json
93+
$IdentityTests = & $NormalizeTestIds ($ReportTemplate.identityTests | ConvertFrom-Json)
6494
$IdentityTotal = @($IdentityTests).Count
6595
}
6696

6797
if ($ReportTemplate.DevicesTests) {
68-
$DevicesTests = $ReportTemplate.DevicesTests | ConvertFrom-Json
98+
$DevicesTests = & $NormalizeTestIds ($ReportTemplate.DevicesTests | ConvertFrom-Json)
6999
$DevicesTotal = @($DevicesTests).Count
70100
}
101+
102+
if ($ReportTemplate.CustomTests) {
103+
$CustomTests = & $NormalizeTestIds ($ReportTemplate.CustomTests | ConvertFrom-Json)
104+
$CustomTotal = @($CustomTests).Count
105+
}
71106
$ReportFound = $true
72107
} else {
73108
Write-LogMessage -API $APIName -tenant $TenantFilter -message "Report template '$ReportId' not found" -sev Warning
@@ -76,11 +111,13 @@ function Invoke-ListTests {
76111

77112
# Filter tests if report was found
78113
if ($ReportFound) {
79-
$AllReportTests = $IdentityTests + $DevicesTests
114+
$AllReportTests = @($IdentityTests) + @($DevicesTests) + @($CustomTests)
80115
# Use HashSet for O(1) lookup performance
81-
$TestLookup = [System.Collections.Generic.HashSet[string]]::new()
116+
$TestLookup = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
82117
foreach ($test in $AllReportTests) {
83-
[void]$TestLookup.Add($test)
118+
if (-not [string]::IsNullOrWhiteSpace($test)) {
119+
[void]$TestLookup.Add([string]$test)
120+
}
84121
}
85122
$FilteredTests = $TestResultsData.TestResults | Where-Object { $TestLookup.Contains($_.RowKey) }
86123
$TestResultsData.TestResults = @($FilteredTests)
@@ -90,10 +127,38 @@ function Invoke-ListTests {
90127
} else {
91128
$IdentityTotal = @($TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Identity' }).Count
92129
$DevicesTotal = @($TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Devices' }).Count
130+
$CustomTotal = @($TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Custom' }).Count
93131
}
94132

95133
$IdentityResults = $TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Identity' }
96134
$DeviceResults = $TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Devices' }
135+
$CustomResultsForCounts = $TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Custom' }
136+
137+
# Build lookup of custom script metadata (latest version per ScriptGuid)
138+
$CustomScriptMetadataLookup = @{}
139+
$CustomResults = @($TestResultsData.TestResults | Where-Object { $_.TestType -eq 'Custom' })
140+
if ($CustomResults.Count -gt 0) {
141+
$CustomScriptsTable = Get-CippTable -tablename 'CustomPowershellScripts'
142+
$CustomScripts = @(Get-CIPPAzDataTableEntity @CustomScriptsTable -Filter "PartitionKey eq 'CustomScript'")
143+
144+
if ($CustomScripts.Count -gt 0) {
145+
$LatestCustomScripts = $CustomScripts |
146+
Group-Object -Property ScriptGuid |
147+
ForEach-Object {
148+
$_.Group | Sort-Object -Property Version -Descending | Select-Object -First 1
149+
}
150+
151+
foreach ($Script in @($LatestCustomScripts)) {
152+
if (-not [string]::IsNullOrWhiteSpace($Script.ScriptGuid)) {
153+
$CustomScriptMetadataLookup[$Script.ScriptGuid] = [PSCustomObject]@{
154+
Description = $Script.Description ?? ''
155+
ReturnType = $Script.ReturnType ?? 'JSON'
156+
MarkdownTemplate = $Script.MarkdownTemplate ?? ''
157+
}
158+
}
159+
}
160+
}
161+
}
97162

98163
# Add descriptions from markdown files to each test result
99164
foreach ($TestResult in $TestResultsData.TestResults) {
@@ -110,6 +175,16 @@ function Invoke-ListTests {
110175
#Test
111176
}
112177
}
178+
179+
if ($TestResult.TestType -eq 'Custom') {
180+
$ScriptGuid = ($TestResult.RowKey -replace '^CustomScript-', '')
181+
if (-not [string]::IsNullOrWhiteSpace($ScriptGuid) -and $CustomScriptMetadataLookup.ContainsKey($ScriptGuid)) {
182+
$CustomMetadata = $CustomScriptMetadataLookup[$ScriptGuid]
183+
$TestResult | Add-Member -NotePropertyName 'Description' -NotePropertyValue ($CustomMetadata.Description) -Force
184+
$TestResult | Add-Member -NotePropertyName 'ReturnType' -NotePropertyValue ($CustomMetadata.ReturnType) -Force
185+
$TestResult | Add-Member -NotePropertyName 'MarkdownTemplate' -NotePropertyValue ($CustomMetadata.MarkdownTemplate) -Force
186+
}
187+
}
113188
}
114189

115190
$TestCounts = @{
@@ -127,6 +202,13 @@ function Invoke-ListTests {
127202
Skipped = @($DeviceResults | Where-Object { $_.Status -eq 'Skipped' }).Count
128203
Total = $DevicesTotal
129204
}
205+
Custom = @{
206+
Passed = @($CustomResultsForCounts | Where-Object { $_.Status -eq 'Passed' }).Count
207+
Failed = @($CustomResultsForCounts | Where-Object { $_.Status -eq 'Failed' }).Count
208+
Investigate = @($CustomResultsForCounts | Where-Object { $_.Status -eq 'Investigate' }).Count
209+
Skipped = @($CustomResultsForCounts | Where-Object { $_.Status -eq 'Skipped' }).Count
210+
Total = $CustomTotal
211+
}
130212
}
131213

132214
$TestResultsData | Add-Member -NotePropertyName 'TestCounts' -NotePropertyValue $TestCounts -Force

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Administration/Alerts/Invoke-ListAlertsQueue.ps1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ function Invoke-ListAlertsQueue {
9292
$TenantsForDisplay = @($TenantForDisplay)
9393
}
9494

95+
$TaskParameters = $Task.Parameters | ConvertFrom-Json -Depth 10 -ErrorAction SilentlyContinue
96+
$ScriptName = $TaskParameters.InputValue.ScriptGuid.label ?? $null
97+
9598
$TaskEntry = [PSCustomObject]@{
9699
RowKey = $Task.RowKey
97100
PartitionKey = $Task.PartitionKey
@@ -104,6 +107,7 @@ function Invoke-ListAlertsQueue {
104107
RepeatsEvery = $Task.Recurrence
105108
AlertComment = $Task.AlertComment
106109
RawAlert = $Task
110+
ScriptName = $ScriptName
107111
}
108112

109113
if ($AllowedTenants -notcontains 'AllTenants') {

0 commit comments

Comments
 (0)