Skip to content

Commit

Permalink
changes to standards
Browse files Browse the repository at this point in the history
  • Loading branch information
KelvinTegelaar committed Jan 31, 2025
1 parent bdd3794 commit 2b4e7c4
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
function Convert-SingleStandardObject {

param(
[Parameter(Mandatory = $true)]
$Obj
)

# Ensure we have a PSCustomObject we can modify
$Obj = [pscustomobject]$Obj

# Extract action arrays
$AllActionValues = @()
if ($Obj.PSObject.Properties.Name -contains 'combinedActions') {
$AllActionValues = $Obj.combinedActions
$null = $Obj.PSObject.Properties.Remove('combinedActions')
$Obj.PSObject.Properties.Remove('combinedActions') | Out-Null
} elseif ($Obj.PSObject.Properties.Name -contains 'action') {
if ($Obj.action -and $Obj.action.value) {
$AllActionValues = $Obj.action.value
}
$null = $Obj.PSObject.Properties.Remove('action')
$Obj.PSObject.Properties.Remove('action') | Out-Null
}

# Convert actions to booleans
# Convert to booleans
$Obj | Add-Member -NotePropertyName 'remediate' -NotePropertyValue ($AllActionValues -contains 'Remediate') -Force
$Obj | Add-Member -NotePropertyName 'alert' -NotePropertyValue ($AllActionValues -contains 'warn') -Force
$Obj | Add-Member -NotePropertyName 'report' -NotePropertyValue ($AllActionValues -contains 'Report') -Force
Expand All @@ -31,7 +34,7 @@ function Convert-SingleStandardObject {
}
}
}
$null = $Obj.PSObject.Properties.Remove('standards')
$Obj.PSObject.Properties.Remove('standards') | Out-Null
}

return $Obj
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
function ConvertTo-CippStandardObject {

param(
[Parameter(Mandatory = $true)]
$StandardObject
)
# If it's an array of items, process each item
if ($StandardObject -is [System.Collections.IEnumerable] -and -not ($StandardObject -is [string])) {
$ProcessedItems = New-Object System.Collections.ArrayList
foreach ($Item in $StandardObject) {
$ProcessedItems.Add((Convert-SingleStandardObject $Item)) | Out-Null
}
return [System.Collections.ArrayList]$ProcessedItems
return $ProcessedItems
} else {
# Single object
return Convert-SingleStandardObject $StandardObject
}
}
264 changes: 264 additions & 0 deletions Modules/CIPPCore/Public/Standards/Get-CIPPStandards.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
function Get-CIPPStandards {
param(
[Parameter(Mandatory = $false)]
[string]$TenantFilter = 'allTenants',

[Parameter(Mandatory = $false)]
[switch]$ListAllTenants,

[Parameter(Mandatory = $false)]
$TemplateId = '*',

[Parameter(Mandatory = $false)]
$runManually = $false
)

# 1. Get all JSON-based templates from the "templates" table
$Table = Get-CippTable -tablename 'templates'
$Filter = "PartitionKey eq 'StandardsTemplateV2'"
$Templates = (Get-CIPPAzDataTableEntity @Table -Filter $Filter | Sort-Object TimeStamp).JSON |
ForEach-Object {
try {
# Fix old "Action" => "action"
$JSON = $_ -replace '"Action":', '"action":'
ConvertFrom-Json -InputObject $JSON -ErrorAction SilentlyContinue
} catch {}
} |
Where-Object {
$_.GUID -like $TemplateId -and $_.runManually -eq $runManually
}

# 2. Get tenant list, filter if needed
$AllTenantsList = Get-Tenants
if ($TenantFilter -ne 'allTenants') {
$AllTenantsList = $AllTenantsList | Where-Object {
$_.defaultDomainName -eq $TenantFilter -or $_.customerId -eq $TenantFilter
}
}

# 3. If -ListAllTenants, build standards for "AllTenants" only
if ($ListAllTenants.IsPresent) {
$AllTenantsTemplates = $Templates | Where-Object {
$_.tenantFilter.value -contains 'AllTenants'
}

$ComputedStandards = [ordered]@{}

foreach ($Template in $AllTenantsTemplates) {
$Standards = $Template.standards

foreach ($StandardName in $Standards.PSObject.Properties.Name) {
$Value = $Standards.$StandardName
$IsArray = $Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])

if ($IsArray) {
# e.g. IntuneTemplate with 2 items
foreach ($Item in $Value) {
$CurrentStandard = $Item.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

$Actions = $CurrentStandard.action.value
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
} else {
# single object
$CurrentStandard = $Value.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

$Actions = $CurrentStandard.action.value
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
}
}

# Output result for 'AllTenants'
foreach ($Standard in $ComputedStandards.Keys) {
$TempCopy = $ComputedStandards[$Standard].PSObject.Copy()

# Remove 'TemplateId' from final output
if ($TempCopy -is [System.Collections.IEnumerable] -and -not ($TempCopy -is [string])) {
foreach ($subItem in $TempCopy) {
$subItem.PSObject.Properties.Remove('TemplateId') | Out-Null
}
} else {
$TempCopy.PSObject.Properties.Remove('TemplateId') | Out-Null
}

$Normalized = ConvertTo-CippStandardObject $TempCopy

[pscustomobject]@{
Tenant = 'AllTenants'
Standard = $Standard
Settings = $Normalized
TemplateId = if ($ComputedStandards[$Standard] -is [System.Collections.IEnumerable] -and -not ($ComputedStandards[$Standard] -is [string])) {
# If multiple items from multiple templates, you may have multiple TemplateIds
$ComputedStandards[$Standard] | ForEach-Object { $_.TemplateId }
} else {
$ComputedStandards[$Standard].TemplateId
}
}
}
} else {
# 4. For each tenant, figure out which templates apply, merge them, and output.
foreach ($Tenant in $AllTenantsList) {
$TenantName = $Tenant.defaultDomainName

# Determine which templates apply to this tenant
$ApplicableTemplates = $Templates | ForEach-Object {
$template = $_
$tenantFilterValues = $template.tenantFilter | ForEach-Object { $_.value }
$excludedTenantValues = @()

if ($template.excludedTenants) {
if ($template.excludedTenants -is [System.Collections.IEnumerable] -and -not ($template.excludedTenants -is [string])) {
$excludedTenantValues = $template.excludedTenants | ForEach-Object { $_.value }
} else {
$excludedTenantValues = @($template.excludedTenants)
}
}

$AllTenantsApplicable = $false
$TenantSpecificApplicable = $false

if ($tenantFilterValues -contains 'AllTenants' -and -not ($excludedTenantValues -contains $TenantName)) {
$AllTenantsApplicable = $true
}
if ($tenantFilterValues -contains $TenantName) {
$TenantSpecificApplicable = $true
}

if ($AllTenantsApplicable -or $TenantSpecificApplicable) {
$template
}
}

# Separate them into AllTenant vs. TenantSpecific sets
$AllTenantTemplatesSet = $ApplicableTemplates | Where-Object {
$_.tenantFilter.value -contains 'AllTenants'
}
$TenantSpecificTemplatesSet = $ApplicableTemplates | Where-Object {
$_.tenantFilter.value -notcontains 'AllTenants'
}

$ComputedStandards = [ordered]@{}

# 4a. Merge the AllTenantTemplatesSet
foreach ($Template in $AllTenantTemplatesSet) {
$Standards = $Template.standards

foreach ($StandardName in $Standards.PSObject.Properties.Name) {
$Value = $Standards.$StandardName
$IsArray = $Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])

if ($IsArray) {
foreach ($Item in $Value) {
$CurrentStandard = $Item.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

$Actions = $CurrentStandard.action.value
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
} else {
$CurrentStandard = $Value.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

$Actions = $CurrentStandard.action.value
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
}
}

# 4b. Merge the TenantSpecificTemplatesSet
foreach ($Template in $TenantSpecificTemplatesSet) {
$Standards = $Template.standards

foreach ($StandardName in $Standards.PSObject.Properties.Name) {
$Value = $Standards.$StandardName
$IsArray = $Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])

if ($IsArray) {
foreach ($Item in $Value) {
$CurrentStandard = $Item.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

# Filter actions only 'Remediate','warn','Report'
$Actions = $CurrentStandard.action.value | Where-Object { $_ -in 'Remediate', 'warn', 'Report' }
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
} else {
$CurrentStandard = $Value.PSObject.Copy()
$CurrentStandard | Add-Member -NotePropertyName 'TemplateId' -NotePropertyValue $Template.GUID -Force

$Actions = $CurrentStandard.action.value | Where-Object { $_ -in 'Remediate', 'warn', 'Report' }
if ($Actions -contains 'Remediate' -or $Actions -contains 'warn' -or $Actions -contains 'Report') {
if (-not $ComputedStandards.Contains($StandardName)) {
$ComputedStandards[$StandardName] = $CurrentStandard
} else {
$MergedStandard = Merge-CippStandards -Existing $ComputedStandards[$StandardName] -New $CurrentStandard -StandardName $StandardName
$ComputedStandards[$StandardName] = $MergedStandard
}
}
}
}
}

# 4c. Output each final standard for this tenant
foreach ($Standard in $ComputedStandards.Keys) {
$TempCopy = $ComputedStandards[$Standard].PSObject.Copy()
# Remove local 'TemplateId' from final object(s)
if ($TempCopy -is [System.Collections.IEnumerable] -and -not ($TempCopy -is [string])) {
foreach ($subItem in $TempCopy) {
$subItem.PSObject.Properties.Remove('TemplateId') | Out-Null
}
} else {
$TempCopy.PSObject.Properties.Remove('TemplateId') | Out-Null
}

$Normalized = ConvertTo-CippStandardObject $TempCopy

[pscustomobject]@{
Tenant = $TenantName
Standard = $Standard
Settings = $Normalized
TemplateId = $ComputedStandards[$Standard].TemplateId
}
}
}
}
}

34 changes: 19 additions & 15 deletions Modules/CIPPCore/Public/Standards/Merge-CippStandards.ps1
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
function Merge-CippStandards {
param(
[Parameter(Mandatory = $true)]
[object]$Existing,
[Parameter(Mandatory = $true)]
[object]$New
[Parameter(Mandatory = $true)][object]$Existing,
[Parameter(Mandatory = $true)][object]$New,
[Parameter(Mandatory = $true)][string]$StandardName
)

if (-not $Existing) {
return $New
}
$ExistingIsArray = ($Existing -is [System.Collections.IEnumerable] -and -not ($Existing -is [string]))
$NewIsArray = ($New -is [System.Collections.IEnumerable] -and -not ($New -is [string]))
# If $Existing or $New is $null/empty, just return the other.
if (-not $Existing) { return $New }
if (-not $New) { return $Existing }

if (-not $ExistingIsArray) {
$Existing = @($Existing)
}
if (-not $NewIsArray) {
$New = @($New)
# If the standard name ends with 'Template', we treat them as arrays to merge.
if ($StandardName -like '*Template') {
$ExistingIsArray = $Existing -is [System.Collections.IEnumerable] -and -not ($Existing -is [string])
$NewIsArray = $New -is [System.Collections.IEnumerable] -and -not ($New -is [string])

# Make sure both are arrays
if (-not $ExistingIsArray) { $Existing = @($Existing) }
if (-not $NewIsArray) { $New = @($New) }

return $Existing + $New
} else {
# Single‐value standard: override the old with the new
return $New
}
return $Existing + $New
}

0 comments on commit 2b4e7c4

Please sign in to comment.