Private/ConvertFrom-InforcerV1Response.ps1
|
$script:InforcerV1MetadataKeys = @('continuationToken', 'userCount', 'groupCount', 'memberCount', 'failedPolicyTypes') $script:InforcerV1CollectionKeys = @('tenants', 'policies', 'users', 'groups', 'members', 'roles', 'baselines', 'alignmentScores', 'assessments', 'reportRuns', 'outputs', 'reportTypes', 'auditEvents', 'eventTypes') # Rename: v1 name -> canonical (beta) name. Nested: v1 property name -> map applied to its value(s). # Flatten: v1 object property -> its child properties lifted onto the parent under canonical names. $script:InforcerV1Map = @{ Tenant = @{ Rename = @{ name = 'tenantFriendlyName'; dnsName = 'tenantDnsName' }; Nested = @{ alignmentSummaries = 'AlignmentSummary' } } AlignmentSummary = @{ Rename = @{ lastAlignmentTimestamp = 'lastAlignmentDateTime' } } Policy = @{ Rename = @{ policyTypeName = 'inforcerPolicyTypeName' } } Baseline = @{ Flatten = @{ baselineTenant = [ordered]@{ msTenantId = 'baselineMsTenantId'; name = 'baselineTenantFriendlyName'; dnsName = 'baselineTenantDnsName' } } Nested = @{ members = 'BaselineMember'; items = 'BaselineItem' } } BaselineMember = @{ Rename = @{ name = 'tenantFriendlyName'; dnsName = 'tenantDnsName' } } BaselineItem = @{ Rename = @{ product = 'policyCategoryProduct'; primaryGroup = 'policyCategoryPrimaryGroup'; secondaryGroup = 'policyCategorySecondaryGroup' } } AlignmentScore = @{ Rename = @{ tenantName = 'tenantFriendlyName'; alignedBaselineId = 'baselineGroupId'; alignedBaselineName = 'baselineGroupName'; alignmentScore = 'score'; lastAlignmentTimestamp = 'lastComparisonDateTime' } } AlignmentMetrics = @{ Rename = @{ matched = 'matchedPolicies'; deviated = 'deviatedPolicies'; missingFromTenant = 'recommendedPoliciesFromBaseline'; additionalInTenant = 'customerOnlyPolicies' } } ComparedPolicy = @{ Rename = @{ displayName = 'policyName'; policyTypeName = 'inforcerPolicyTypeName'; tags = 'policyTags'; tenantPolicies = 'matchingSubjectPolicies'; variables = 'matchingVariables' } Nested = @{ tenantPolicies = 'TenantPolicyMatch'; variables = 'Variable'; acceptedDeviations = 'AcceptedDeviation'; similarPolicies = 'SimilarPolicy' } } TenantPolicyMatch = @{ Rename = @{ tags = 'policyTags' }; Nested = @{ policyDataDiff = 'PolicyDiff'; policyAssignmentsDiff = 'PolicyDiff'; acceptedDeviations = 'AcceptedDeviation' } } PolicyDiff = @{ Rename = @{ tenantValue = 'subjectValue' } } Variable = @{ Rename = @{ name = 'variableName'; tenantValue = 'subjectValue' } } AcceptedDeviation = @{ Rename = @{ acceptedTimestamp = 'acceptedDateTime'; reason = 'acceptedReason' }; Nested = @{ policyDataDiff = 'PolicyDiff'; policyAssignmentsDiff = 'PolicyDiff' } } SimilarPolicy = @{ Rename = @{ name = 'policyName'; categoryName = 'policyCategoryName' }; Nested = @{ policyDataDiff = 'PolicyDiff'; policyAssignmentsDiff = 'PolicyDiff' } } User = @{ Rename = @{ groupCount = 'groups'; roleCount = 'roles' } } AssessmentRun = @{ Rename = @{ assessmentId = 'assessment' } } ReportRun = @{ Rename = @{ reportRunId = 'runId' } } ReportOutput = @{ Rename = @{ msTenantId = 'tenantId' } } } $script:InforcerV1AlignmentBuckets = [ordered]@{ matched = 'matchedPolicies' matchedWithAcceptedDeviations = 'matchedWithAcceptedDeviations' deviated = 'deviatedUnaccepted' missingFromTenant = 'missingFromSubjectUnaccepted' additionalInTenant = 'additionalInSubjectUnaccepted' } function ConvertFrom-InforcerV1Response { <# .SYNOPSIS Maps v1 API objects to the module's canonical (beta) property names (Private helper). .DESCRIPTION Pure function: returns new objects, never calls the API. Renamed v1 keys are removed (no duplicate keys); v1-only properties are kept; property order is preserved. When a v1 object already carries the canonical name, that value wins and the v1 duplicate is dropped. Types without renames (Group, GroupMember, Role, SecureScore, Assessment, ReportType, AuditEvent, AuditEventType) are returned unchanged. .PARAMETER InputObject One or more v1 objects (collection items, or a single-object response). .PARAMETER ObjectType The kind of object, selecting the mapping. #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [AllowNull()] [AllowEmptyCollection()] [object[]]$InputObject, [Parameter(Mandatory = $true)] [ValidateSet('Tenant', 'Policy', 'Baseline', 'AlignmentScore', 'AlignmentDetails', 'User', 'Group', 'GroupMember', 'Role', 'SecureScore', 'Assessment', 'AssessmentRun', 'ReportType', 'ReportRun', 'ReportOutput', 'AuditEvent', 'AuditEventType')] [string]$ObjectType ) process { foreach ($item in $InputObject) { if ($null -eq $item) { continue } if ($ObjectType -eq 'AlignmentDetails') { ConvertFrom-InforcerV1AlignmentDetail $item } elseif ($script:InforcerV1Map.ContainsKey($ObjectType)) { ConvertFrom-InforcerV1Object $item $ObjectType } else { $item } } } } function ConvertFrom-InforcerV1Object { param($Object, [string]$MapName) if ($Object -isnot [System.Management.Automation.PSCustomObject]) { return $Object } $map = $script:InforcerV1Map[$MapName] $rename = $map.Rename; $nested = $map.Nested; $flatten = $map.Flatten $sourceNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($p in $Object.PSObject.Properties) { [void]$sourceNames.Add($p.Name) } $out = [ordered]@{} foreach ($p in $Object.PSObject.Properties) { $value = $p.Value if ($nested -and $nested.ContainsKey($p.Name)) { $value = ConvertFrom-InforcerV1Value $value $nested[$p.Name] } if ($flatten -and $flatten.ContainsKey($p.Name)) { if ($value -is [System.Management.Automation.PSCustomObject]) { $childMap = $flatten[$p.Name] foreach ($c in $value.PSObject.Properties) { $target = if ($childMap.Contains($c.Name)) { $childMap[$c.Name] } else { "$($p.Name)$($c.Name.Substring(0,1).ToUpperInvariant())$($c.Name.Substring(1))" } if (-not $sourceNames.Contains($target) -and -not $out.Contains($target)) { $out[$target] = $c.Value } } } else { foreach ($target in $flatten[$p.Name].Values) { if (-not $sourceNames.Contains($target) -and -not $out.Contains($target)) { $out[$target] = $null } } } continue } $name = $p.Name if ($rename -and $rename.ContainsKey($name)) { $target = $rename[$name] # The canonical name already exists on the source object: keep that one, drop the v1 duplicate. if ($sourceNames.Contains($target) -and -not $rename.ContainsKey($target)) { continue } $name = $target } if (-not $out.Contains($name)) { $out[$name] = $value } } [PSCustomObject]$out } function ConvertFrom-InforcerV1Value { param($Value, [string]$MapName) if ($null -eq $Value) { return $null } if ($Value -is [array]) { $list = [System.Collections.Generic.List[object]]::new($Value.Count) foreach ($v in $Value) { $list.Add((ConvertFrom-InforcerV1Object $v $MapName)) } return , $list.ToArray() } ConvertFrom-InforcerV1Object $Value $MapName } function ConvertFrom-InforcerV1AlignmentDetail { param($Object) if ($Object -isnot [System.Management.Automation.PSCustomObject]) { return $Object } $consumed = @{ alignmentScore = 'alignmentScore'; baselineDataTimestamp = 'baselineDataTimestamp'; tenantDataTimestamp = 'subjectDataTimestamp'; policies = $null } $alignment = [ordered]@{} foreach ($key in 'alignmentScore', 'baselineDataTimestamp', 'tenantDataTimestamp') { $prop = $Object.PSObject.Properties[$key] if ($prop) { $alignment[$consumed[$key]] = $prop.Value } } $policies = $Object.PSObject.Properties['policies'].Value if ($policies -is [System.Management.Automation.PSCustomObject]) { foreach ($bucket in $policies.PSObject.Properties) { $target = if ($script:InforcerV1AlignmentBuckets.Contains($bucket.Name)) { $script:InforcerV1AlignmentBuckets[$bucket.Name] } else { $bucket.Name } $alignment[$target] = ConvertFrom-InforcerV1Value $bucket.Value 'ComparedPolicy' } } $out = [ordered]@{} foreach ($p in $Object.PSObject.Properties) { if ($consumed.ContainsKey($p.Name)) { $out['alignment'] = [PSCustomObject]$alignment continue } if ($out.Contains($p.Name)) { continue } $out[$p.Name] = $p.Value if ($p.Name -eq 'metrics') { $out[$p.Name] = ConvertFrom-InforcerV1Value $p.Value 'AlignmentMetrics' } } [PSCustomObject]$out } function Get-InforcerV1CollectionName { <# .SYNOPSIS Returns the name of the single collection property of a v1 list response, or $null for a single object (Private helper). .DESCRIPTION v1 list responses are an object with exactly one collection plus optional metadata keys (continuationToken, userCount, groupCount, memberCount, failedPolicyTypes). Single-object responses are returned bare and never count as a collection, even when they hold arrays. #> param($Response) if ($Response -isnot [System.Management.Automation.PSCustomObject]) { return $null } $c = $null foreach ($p in $Response.PSObject.Properties) { if ($p.Name -in $script:InforcerV1MetadataKeys) { continue } if ($c) { return $null } $c = $p } if (-not $c) { return $null } if ($c.Value -is [array] -or ($null -eq $c.Value -and $c.Name -in $script:InforcerV1CollectionKeys)) { return $c.Name } $null } |