internal/functions/Test-EasyPIMConfigurationValidity.ps1
function Test-EasyPIMConfigurationValidity { <# .SYNOPSIS Validates EasyPIM configuration and detects common mismatches. .DESCRIPTION Performs comprehensive validation of EasyPIM configuration objects to detect common field name mismatches, missing required properties, and other configuration issues that could cause runtime failures. .PARAMETER Config The configuration object to validate. .PARAMETER AutoCorrect If specified, attempts to automatically correct common issues. .OUTPUTS ValidationResult object containing any issues found and corrected configuration. .EXAMPLE $result = Test-EasyPIMConfigurationValidity -Config $config -AutoCorrect if ($result.HasIssues) { Write-Warning "Configuration issues found: $($result.Issues.Count)" $config = $result.CorrectedConfig } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [PSCustomObject]$Config, [Parameter(Mandatory = $false)] [switch]$AutoCorrect ) $validationResult = [PSCustomObject]@{ HasIssues = $false Issues = @() Corrections = @() CorrectedConfig = $Config ValidationSummary = @{ ApproverFieldMismatches = 0 MissingRequiredFields = 0 TemplateReferences = 0 AutoCorrections = 0 } } Write-Verbose "🔍 Starting EasyPIM configuration validation..." # Deep clone the config for corrections if ($AutoCorrect) { $validationResult.CorrectedConfig = $Config | ConvertTo-Json -Depth 20 | ConvertFrom-Json } # Validation Rule 1: Check PolicyTemplates for Approvers field mismatches if ($Config.PSObject.Properties['PolicyTemplates']) { Write-Verbose "📋 Validating PolicyTemplates..." foreach ($templateName in $Config.PolicyTemplates.PSObject.Properties.Name) { $template = $Config.PolicyTemplates.$templateName if ($template.PSObject.Properties['Approvers'] -and $template.Approvers) { $approverIssues = Test-ApproversFormat -Approvers $template.Approvers -Context "PolicyTemplates.$templateName" if ($approverIssues.HasIssues) { $validationResult.HasIssues = $true $validationResult.Issues += $approverIssues.Issues $validationResult.ValidationSummary.ApproverFieldMismatches += $approverIssues.Issues.Count if ($AutoCorrect -and $approverIssues.CorrectedApprovers) { $validationResult.CorrectedConfig.PolicyTemplates.$templateName.Approvers = $approverIssues.CorrectedApprovers $validationResult.Corrections += "Auto-corrected Approvers format in PolicyTemplates.$templateName" $validationResult.ValidationSummary.AutoCorrections++ } } } } } # Validation Rule 2: Check EntraRoles policies for Approvers mismatches if ($Config.PSObject.Properties['EntraRoles'] -and $Config.EntraRoles.PSObject.Properties['Policies']) { Write-Verbose "📋 Validating EntraRoles policies..." foreach ($roleName in $Config.EntraRoles.Policies.PSObject.Properties.Name) { $rolePolicy = $Config.EntraRoles.Policies.$roleName if ($rolePolicy.PSObject.Properties['Approvers'] -and $rolePolicy.Approvers) { $approverIssues = Test-ApproversFormat -Approvers $rolePolicy.Approvers -Context "EntraRoles.Policies.$roleName" if ($approverIssues.HasIssues) { $validationResult.HasIssues = $true $validationResult.Issues += $approverIssues.Issues $validationResult.ValidationSummary.ApproverFieldMismatches += $approverIssues.Issues.Count if ($AutoCorrect -and $approverIssues.CorrectedApprovers) { $validationResult.CorrectedConfig.EntraRoles.Policies.$roleName.Approvers = $approverIssues.CorrectedApprovers $validationResult.Corrections += "Auto-corrected Approvers format in EntraRoles.Policies.$roleName" $validationResult.ValidationSummary.AutoCorrections++ } } } # Check for ApprovalRequired=true but missing Approvers if ($rolePolicy.PSObject.Properties['ApprovalRequired'] -and $rolePolicy.ApprovalRequired -eq $true) { if (-not $rolePolicy.PSObject.Properties['Approvers'] -or -not $rolePolicy.Approvers -or $rolePolicy.Approvers.Count -eq 0) { if (-not $rolePolicy.PSObject.Properties['Template'] -or -not $rolePolicy.Template) { $issue = [PSCustomObject]@{ Severity = "Error" Category = "MissingApprovers" Context = "EntraRoles.Policies.$roleName" Message = "ApprovalRequired is true but no Approvers defined and no Template specified" Suggestion = "Add Approvers array or use a Template with Approvers defined" } $validationResult.HasIssues = $true $validationResult.Issues += $issue $validationResult.ValidationSummary.MissingRequiredFields++ } } } } } # Validation Rule 3: Check AzureRoles policies for Approvers mismatches if ($Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies']) { Write-Verbose "📋 Validating AzureRoles policies..." foreach ($roleName in $Config.AzureRoles.Policies.PSObject.Properties.Name) { $rolePolicy = $Config.AzureRoles.Policies.$roleName if ($rolePolicy.PSObject.Properties['Approvers'] -and $rolePolicy.Approvers) { $approverIssues = Test-ApproversFormat -Approvers $rolePolicy.Approvers -Context "AzureRoles.Policies.$roleName" if ($approverIssues.HasIssues) { $validationResult.HasIssues = $true $validationResult.Issues += $approverIssues.Issues $validationResult.ValidationSummary.ApproverFieldMismatches += $approverIssues.Issues.Count if ($AutoCorrect -and $approverIssues.CorrectedApprovers) { $validationResult.CorrectedConfig.AzureRoles.Policies.$roleName.Approvers = $approverIssues.CorrectedApprovers $validationResult.Corrections += "Auto-corrected Approvers format in AzureRoles.Policies.$roleName" $validationResult.ValidationSummary.AutoCorrections++ } } } # Check for ApprovalRequired=true but missing Approvers if ($rolePolicy.PSObject.Properties['ApprovalRequired'] -and $rolePolicy.ApprovalRequired -eq $true) { if (-not $rolePolicy.PSObject.Properties['Approvers'] -or -not $rolePolicy.Approvers -or $rolePolicy.Approvers.Count -eq 0) { if (-not $rolePolicy.PSObject.Properties['Template'] -or -not $rolePolicy.Template) { $issue = [PSCustomObject]@{ Severity = "Error" Category = "MissingApprovers" Context = "AzureRoles.Policies.$roleName" Message = "ApprovalRequired is true but no Approvers defined and no Template specified" Suggestion = "Add Approvers array or use a Template with Approvers defined" } $validationResult.HasIssues = $true $validationResult.Issues += $issue $validationResult.ValidationSummary.MissingRequiredFields++ } } } } } # Validation Rule 4: Check Template references exist $templateNames = if ($Config.PSObject.Properties['PolicyTemplates']) { $Config.PolicyTemplates.PSObject.Properties.Name } else { @() } # Check EntraRoles template references if ($Config.PSObject.Properties['EntraRoles'] -and $Config.EntraRoles.PSObject.Properties['Policies']) { foreach ($roleName in $Config.EntraRoles.Policies.PSObject.Properties.Name) { $rolePolicy = $Config.EntraRoles.Policies.$roleName if ($rolePolicy.PSObject.Properties['Template'] -and $rolePolicy.Template) { if ($rolePolicy.Template -notin $templateNames) { $issue = [PSCustomObject]@{ Severity = "Error" Category = "InvalidTemplateReference" Context = "EntraRoles.Policies.$roleName" Message = "Template '$($rolePolicy.Template)' not found in PolicyTemplates" Suggestion = "Check spelling or add the template to PolicyTemplates section" } $validationResult.HasIssues = $true $validationResult.Issues += $issue $validationResult.ValidationSummary.TemplateReferences++ } } } } # Check AzureRoles template references if ($Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies']) { foreach ($roleName in $Config.AzureRoles.Policies.PSObject.Properties.Name) { $rolePolicy = $Config.AzureRoles.Policies.$roleName if ($rolePolicy.PSObject.Properties['Template'] -and $rolePolicy.Template) { if ($rolePolicy.Template -notin $templateNames) { $issue = [PSCustomObject]@{ Severity = "Error" Category = "InvalidTemplateReference" Context = "AzureRoles.Policies.$roleName" Message = "Template '$($rolePolicy.Template)' not found in PolicyTemplates" Suggestion = "Check spelling or add the template to PolicyTemplates section" } $validationResult.HasIssues = $true $validationResult.Issues += $issue $validationResult.ValidationSummary.TemplateReferences++ } } } } Write-Verbose "✅ Configuration validation completed" Write-Verbose " Issues found: $($validationResult.Issues.Count)" Write-Verbose " Auto-corrections: $($validationResult.ValidationSummary.AutoCorrections)" return $validationResult } function Test-ApproversFormat { <# .SYNOPSIS Validates Approvers array format and detects common field name mismatches. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] $Approvers, [Parameter(Mandatory = $true)] [string]$Context ) $result = [PSCustomObject]@{ HasIssues = $false Issues = @() CorrectedApprovers = $null } if (-not $Approvers -or $Approvers.Count -eq 0) { return $result } $correctedApprovers = @() $needsCorrection = $false for ($i = 0; $i -lt $Approvers.Count; $i++) { $approver = $Approvers[$i] $approverContext = "$Context.Approvers[$i]" $correctedApprover = @{} $approverNeedsCorrection = $false # Check for common field name mismatches (case-sensitive) $fieldMappings = @{ 'id' = 'Id' # lowercase id -> Id 'description' = 'Name' # description -> Name 'desc' = 'Name' # desc -> Name 'displayName' = 'Name' # displayName -> Name 'name' = 'Name' # lowercase name -> Name (but not uppercase Name) } # Process each property in the approver $hasId = $false $hasName = $false foreach ($prop in $approver.PSObject.Properties) { $propName = $prop.Name $propValue = $prop.Value # Handle case-insensitive matching for ID -> Id (but keep exact matches) if ($propName -eq 'ID' -and $propName -ne 'Id') { $correctedApprover['Id'] = $propValue $approverNeedsCorrection = $true $hasId = $true $issue = [PSCustomObject]@{ Severity = "Warning" Category = "FieldNameMismatch" Context = $approverContext Message = "Field '$propName' should be 'Id'" Suggestion = "Change '$propName' to 'Id' in Approvers configuration" OriginalField = $propName CorrectedField = 'Id' } $result.HasIssues = $true $result.Issues += $issue } elseif ($propName -in @('id', 'description', 'desc', 'displayName', 'name') -and $propName -notin @('Id', 'Name')) { # Only correct if it's actually wrong (not already correct case) $correctName = switch ($propName) { 'id' { 'Id' } 'description' { 'Name' } 'desc' { 'Name' } 'displayName' { 'Name' } 'name' { 'Name' } } $correctedApprover[$correctName] = $propValue $approverNeedsCorrection = $true if ($correctName -eq 'Id') { $hasId = $true } if ($correctName -eq 'Name') { $hasName = $true } $issue = [PSCustomObject]@{ Severity = "Warning" Category = "FieldNameMismatch" Context = $approverContext Message = "Field '$propName' should be '$correctName'" Suggestion = "Change '$propName' to '$correctName' in Approvers configuration" OriginalField = $propName CorrectedField = $correctName } $result.HasIssues = $true $result.Issues += $issue } else { # Keep the property as-is (it's already correct or unknown) $correctedApprover[$propName] = $propValue if ($propName -eq 'Id') { $hasId = $true } if ($propName -eq 'Name') { $hasName = $true } } } # Check for missing required fields if (-not $hasId) { $issue = [PSCustomObject]@{ Severity = "Error" Category = "MissingRequiredField" Context = $approverContext Message = "Missing required field 'Id'" Suggestion = "Add 'Id' field with the Object ID of the approver" } $result.HasIssues = $true $result.Issues += $issue } if (-not $hasName) { $issue = [PSCustomObject]@{ Severity = "Warning" Category = "MissingRecommendedField" Context = $approverContext Message = "Missing recommended field 'Name'" Suggestion = "Add 'Name' field with a descriptive name for the approver" } $result.HasIssues = $true $result.Issues += $issue } if ($approverNeedsCorrection) { $needsCorrection = $true } $correctedApprovers += [PSCustomObject]$correctedApprover } if ($needsCorrection) { $result.CorrectedApprovers = $correctedApprovers } return $result } |