Public/Get-InforcerAlignmentScore.ps1
|
<# .SYNOPSIS Retrieves alignment scores from the Inforcer API (table or raw). .DESCRIPTION Format Table: flattened table with one row per alignment. Format Raw: raw API response. Optional -TenantId and -Tag (Table only) filters. .PARAMETER Format Table (default) or Raw. .PARAMETER TenantId Optional. Filter to this tenant. .PARAMETER Tag Optional. When Format is Table, filter to tenants with tag containing this value (case-insensitive). .PARAMETER OutputType Used when Format is Raw. PowerShellObject (default) or JsonObject. JSON uses Depth 100. .EXAMPLE Get-InforcerAlignmentScore .EXAMPLE Get-InforcerAlignmentScore -Format Raw -OutputType JsonObject .EXAMPLE Get-InforcerAlignmentScore -TenantId 482 -Tag Production .EXAMPLE Get-InforcerAlignmentScore -Format Table Table includes LastComparisonDateTime and uses fresh data from /beta/alignmentScores. .OUTPUTS PSObject or String .LINK Connect-Inforcer #> function Get-InforcerAlignmentScore { [CmdletBinding()] [OutputType([PSObject], [string])] param( [Parameter(Mandatory = $false)] [ValidateSet('Table', 'Raw')] [string]$Format = 'Table', [Parameter(Mandatory = $false)] [object]$TenantId, [Parameter(Mandatory = $false)] [string]$Tag, [Parameter(Mandatory = $false)] [ValidateSet('PowerShellObject', 'JsonObject')] [string]$OutputType = 'PowerShellObject' ) if (-not (Test-InforcerSession)) { Write-Error -Message 'Not connected yet. Please run Connect-Inforcer first.' -ErrorId 'NotConnected' -Category ConnectionError return } function FormatAlignmentScore($scoreVal) { if ($null -eq $scoreVal) { return $null } $scoreStr = $scoreVal.ToString().Replace(',', '.') $scoreNumeric = 0.0 if (-not [double]::TryParse($scoreStr, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$scoreNumeric)) { return $null } $rounded = [Math]::Round($scoreNumeric, 1) if ($rounded -eq [Math]::Floor($rounded)) { return [int][Math]::Floor($rounded).ToString() } return $rounded.ToString('F1', [System.Globalization.CultureInfo]::InvariantCulture).Replace('.', ',') } if ($Format -eq 'Raw') { Write-Verbose 'Retrieving alignment scores (raw)...' $response = Invoke-InforcerApiRequest -Endpoint '/beta/alignmentScores' -Method GET -OutputType $OutputType if ($null -eq $response) { return } if ($null -ne $TenantId) { try { $clientTenantId = Resolve-InforcerTenantId -TenantId $TenantId } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument return } Write-Verbose "Filtering alignment scores to tenant ID $clientTenantId..." $predicate = { param($p) $tidProp = $p.PSObject.Properties['tenantId'] if (-not $tidProp) { $tidProp = $p.PSObject.Properties['clientTenantId'] } if ($tidProp -and [int]$tidProp.Value -eq $clientTenantId) { return $true } $summariesProp = $p.PSObject.Properties['alignmentSummaries'] if ($summariesProp -and $summariesProp.Value -is [object[]]) { foreach ($s in $summariesProp.Value) { if ($s -is [PSObject]) { $aidProp = $s.PSObject.Properties['alignedBaselineTenantId'] if ($aidProp -and [int]$aidProp.Value -eq $clientTenantId) { return $true } } } } $false } if ($OutputType -eq 'JsonObject') { Filter-InforcerResponse -InputObject $response -FilterScript $predicate -OutputType JsonObject } else { $filtered = Filter-InforcerResponse -InputObject $response -FilterScript $predicate -OutputType PowerShellObject foreach ($item in (ConvertTo-InforcerArray $filtered)) { if ($item -is [PSObject]) { Add-InforcerPropertyAliases -InputObject $item -ObjectType AlignmentScore | Out-Null } } if ($filtered -is [array]) { $filtered | ForEach-Object { $_ } } else { $filtered } } return } if ($OutputType -eq 'JsonObject') { return $response } foreach ($item in (ConvertTo-InforcerArray $response)) { if ($item -is [PSObject]) { Add-InforcerPropertyAliases -InputObject $item -ObjectType AlignmentScore | Out-Null } } if ($response -is [array]) { $response | ForEach-Object { $_ } } else { $response } return } # Format Table: use /beta/alignmentScores for fresh data, optionally join tenant names from /beta/tenants Write-Verbose 'Retrieving alignment scores for table...' $alignmentResponse = Invoke-InforcerApiRequest -Endpoint '/beta/alignmentScores' -Method GET -OutputType PowerShellObject if ($null -eq $alignmentResponse) { return } $allAlignmentData = ConvertTo-InforcerArray $alignmentResponse # Detect flat format before deciding whether we need /beta/tenants $firstItem = $null if ($allAlignmentData.Count -gt 0) { $firstItem = $allAlignmentData[0] } $flatFormat = ($firstItem -is [PSObject]) -and $firstItem.PSObject.Properties['tenantId'] -and $firstItem.PSObject.Properties['score'] -and -not $firstItem.PSObject.Properties['alignmentSummaries'] # Only fetch tenants when needed: nested format, or -TenantId, or -Tag (for baseline-owner expansion or tag filtering) $needTenants = (-not $flatFormat) -or ($null -ne $TenantId) -or -not [string]::IsNullOrWhiteSpace($Tag) $allTenants = @() $tenantLookup = @{} if ($needTenants) { Write-Verbose 'Retrieving tenant information for alignment table...' $tenantResponse = Invoke-InforcerApiRequest -Endpoint '/beta/tenants' -Method GET -OutputType PowerShellObject if ($null -eq $tenantResponse) { return } $allTenants = ConvertTo-InforcerArray $tenantResponse foreach ($t in $allTenants) { if ($t -is [PSObject]) { Add-InforcerPropertyAliases -InputObject $t -ObjectType Tenant | Out-Null $idProp = $t.PSObject.Properties['clientTenantId'] if ($null -ne $idProp -and $null -ne $idProp.Value) { $id = [int]$idProp.Value $tenantLookup[$id] = $t } } } } # Filter tenants by TenantId and Tag (same as before) $tenants = @($allTenants | Where-Object { $_ -is [PSObject] }) if ($null -ne $TenantId) { try { $clientTenantId = Resolve-InforcerTenantId -TenantId $TenantId -TenantData $allTenants } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument return } Write-Verbose "Filtering to tenant ID: $clientTenantId" $tenants = @($tenants | Where-Object { $idProp = $_.PSObject.Properties['clientTenantId'] $idProp -and [int]$idProp.Value -eq $clientTenantId }) # If no tenant with this ID exists in the system, return nothing. if ($tenants.Count -eq 0) { Write-Verbose "No tenant found with ID $clientTenantId. Returning no results." return } } if (-not [string]::IsNullOrWhiteSpace($Tag)) { Write-Verbose "Filtering to tenants with tag containing: $Tag" $tenants = @($tenants | Where-Object { $tagsProp = $_.PSObject.Properties['tags'] if (-not $tagsProp -or $null -eq $tagsProp.Value) { return $false } $val = $tagsProp.Value if ($val -is [object[]]) { foreach ($x in $val) { if ($x -and $x.ToString().IndexOf($Tag, [StringComparison]::OrdinalIgnoreCase) -ge 0) { return $true } } } else { if ($val.ToString().IndexOf($Tag, [StringComparison]::OrdinalIgnoreCase) -ge 0) { return $true } } $false }) } Write-Verbose "Building alignment table from alignment scores ($($allAlignmentData.Count) source(s))..." # Helper: add to $targetIds any tenant that is aligned to a baseline owned by an ID in $baselineOwnerIds function Add-ChildTenantIdsFromAlignments { param([System.Collections.Hashtable]$targetIds, [array]$allTenants, [array]$baselineOwnerIds) foreach ($t in $allTenants) { if (-not ($t -is [PSObject])) { continue } $sumProp = $t.PSObject.Properties['alignmentSummaries'] if (-not $sumProp -or $null -eq $sumProp.Value) { continue } $sums = $sumProp.Value if ($sums -isnot [object[]]) { $sums = @($sums) } foreach ($s in $sums) { if (-not ($s -is [PSObject])) { continue } $abtProp = $s.PSObject.Properties['alignedBaselineTenantId'] if ($abtProp -and $null -ne $abtProp.Value) { $abt = 0 if ([int]::TryParse($abtProp.Value.ToString(), [ref]$abt) -and ($baselineOwnerIds -contains $abt)) { $childId = 0 $childProp = $t.PSObject.Properties['clientTenantId'] if ($childProp -and [int]::TryParse($childProp.Value.ToString(), [ref]$childId)) { $targetIds[$childId] = $true } } } } } } # Only filter by tenant when user explicitly passed -TenantId or -Tag; otherwise show all alignment rows $tenantIds = @{} if ($null -ne $TenantId -or -not [string]::IsNullOrWhiteSpace($Tag)) { foreach ($t in $tenants) { $idProp = $t.PSObject.Properties['clientTenantId'] if ($idProp -and $null -ne $idProp.Value) { $tid = 0 if ([int]::TryParse($idProp.Value.ToString(), [ref]$tid)) { $tenantIds[$tid] = $true } } } # If the requested tenant is a baseline owner, also include tenants aligned TO it if ($null -ne $TenantId -and $tenantIds.Count -gt 0) { $baselineOwnerIds = @($tenantIds.Keys) Add-ChildTenantIdsFromAlignments -targetIds $tenantIds -allTenants $allTenants -baselineOwnerIds $baselineOwnerIds } } # API can return flat array (tenantId, score, baselineGroupName...) or nested (clientTenantId, alignmentSummaries) $alignmentTable = [System.Collections.ArrayList]::new() if ($flatFormat) { foreach ($item in $allAlignmentData) { if (-not ($item -is [PSObject])) { continue } $tidProp = $item.PSObject.Properties['tenantId'] if (-not $tidProp) { $tidProp = $item.PSObject.Properties['clientTenantId'] } $targetTenantClientTenantId = 0 if ($tidProp -and $null -ne $tidProp.Value) { if (-not [int]::TryParse($tidProp.Value.ToString(), [ref]$targetTenantClientTenantId)) { continue } } if ($tenantIds.Count -gt 0 -and -not $tenantIds.ContainsKey($targetTenantClientTenantId)) { continue } $scoreVal = $item.PSObject.Properties['score'].Value $alignmentScoreFormatted = FormatAlignmentScore $scoreVal $row = [PSCustomObject]@{ BaselineName = $item.PSObject.Properties['baselineGroupName'].Value BaselineId = $item.PSObject.Properties['baselineGroupId'].Value AlignmentScore = $alignmentScoreFormatted AlignedThreshold = $null SemiAlignedThreshold = $null LastAlignmentDateTime = $null LastComparisonDateTime = $item.PSObject.Properties['lastComparisonDateTime'].Value BaselineOwnerTenantFriendlyName = $null BaselineOwnerTenantMsTenantId = $null BaselineOwnerTenantId = $null TargetTenantFriendlyName = ($item.PSObject.Properties['tenantFriendlyName'].Value -as [string]) TargetTenantMsTenantId = $null TargetTenantClientTenantId = $targetTenantClientTenantId AlignedBaselineId = $item.PSObject.Properties['baselineGroupId'].Value } $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantFriendlyName', 'TargetTenantFriendlyName')) $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantMsTenantId', 'TargetTenantMsTenantId')) $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantClientTenantId', 'TargetTenantClientTenantId')) [void]$alignmentTable.Add($row) } } else { foreach ($tenant in $allAlignmentData) { if (-not ($tenant -is [PSObject])) { continue } $cidProp = $tenant.PSObject.Properties['clientTenantId'] $targetTenantClientTenantId = 0 if ($null -ne $cidProp -and $null -ne $cidProp.Value) { if (-not [int]::TryParse($cidProp.Value.ToString(), [ref]$targetTenantClientTenantId)) { continue } } if ($tenantIds.Count -gt 0 -and -not $tenantIds.ContainsKey($targetTenantClientTenantId)) { continue } $summariesProp = $tenant.PSObject.Properties['alignmentSummaries'] if (-not $summariesProp -or $null -eq $summariesProp.Value) { continue } $summaries = ConvertTo-InforcerArray $summariesProp.Value if ($summaries.Count -eq 0) { continue } $targetTenantFriendlyName = $tenant.PSObject.Properties['tenantFriendlyName'].Value -as [string] if (-not $targetTenantFriendlyName) { $targetTenantFriendlyName = '' } $targetTenantMsTenantId = $tenant.PSObject.Properties['msTenantId'].Value -as [string] foreach ($alignment in $summaries) { if (-not ($alignment -is [PSObject])) { continue } $baselineOwnerTenantId = 0 $aidProp = $alignment.PSObject.Properties['alignedBaselineTenantId'] if ($null -ne $aidProp -and $null -ne $aidProp.Value) { $tmp = 0 if ([int]::TryParse($aidProp.Value.ToString(), [ref]$tmp)) { $baselineOwnerTenantId = $tmp } } $baselineOwnerFriendlyName = "Unknown (ID: $baselineOwnerTenantId)" $baselineOwnerMsTenantId = $null if ($tenantLookup.ContainsKey($baselineOwnerTenantId)) { $ownerTenant = $tenantLookup[$baselineOwnerTenantId] $fn = $ownerTenant.PSObject.Properties['tenantFriendlyName'].Value $baselineOwnerFriendlyName = if ($fn) { $fn.ToString() } else { "Unknown (ID: $baselineOwnerTenantId)" } $baselineOwnerMsTenantId = $ownerTenant.PSObject.Properties['msTenantId'].Value -as [string] } $scoreProp = $alignment.PSObject.Properties['alignmentScore'] $alignmentScoreFormatted = FormatAlignmentScore $(if ($scoreProp -and $null -ne $scoreProp.Value) { $scoreProp.Value } else { $null }) $row = [PSCustomObject]@{ BaselineName = $alignment.PSObject.Properties['alignedBaselineName'].Value BaselineId = $alignment.PSObject.Properties['alignedBaselineId'].Value AlignmentScore = $alignmentScoreFormatted AlignedThreshold = $alignment.PSObject.Properties['alignedThreshold'].Value SemiAlignedThreshold = $alignment.PSObject.Properties['semiAlignedThreshold'].Value LastAlignmentDateTime = $alignment.PSObject.Properties['lastAlignmentDateTime'].Value LastComparisonDateTime = $alignment.PSObject.Properties['lastComparisonDateTime'].Value BaselineOwnerTenantFriendlyName = $baselineOwnerFriendlyName BaselineOwnerTenantMsTenantId = $baselineOwnerMsTenantId BaselineOwnerTenantId = $baselineOwnerTenantId TargetTenantFriendlyName = $targetTenantFriendlyName TargetTenantMsTenantId = $targetTenantMsTenantId TargetTenantClientTenantId = $targetTenantClientTenantId AlignedBaselineId = $alignment.PSObject.Properties['alignedBaselineId'].Value } $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantFriendlyName', 'TargetTenantFriendlyName')) $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantMsTenantId', 'TargetTenantMsTenantId')) $row.PSObject.Properties.Add([System.Management.Automation.PSAliasProperty]::new('TenantClientTenantId', 'TargetTenantClientTenantId')) [void]$alignmentTable.Add($row) } } } Write-Verbose "Generated alignment table with $($alignmentTable.Count) row(s)." if ($OutputType -eq 'JsonObject') { $json = $alignmentTable | ConvertTo-Json -Depth 100 Write-Output $json return } $alignmentTable | ForEach-Object { $_ } } |