Public/Get-InforcerAlignmentDetails.ps1
|
<# .SYNOPSIS Retrieves alignment scores or alignment details from the Inforcer API. Required API scope(s): Baselines.Read + AlignmentScores.Read + Tenants.Read + tenants.policies.Read (API key); Baselines + Align + Tenants permission (API client, unconfirmed). .DESCRIPTION Without -BaselineId: retrieves alignment score summaries (table or raw). With -BaselineId: retrieves detailed alignment data including metrics, per-policy matches, deviations, diffs, variables, and tags. When -TenantId is also specified, queries a single tenant. When only -BaselineId is specified, queries the first member tenant (baseline policies are identical across members). -BaselineId accepts a GUID or a friendly baseline name (resolved via the baselines API). With -BaselineId the metrics summary is written to the host, not the pipeline; the pipeline gets one object per policy (use -Format Raw for the metrics as an object). API-key login: table rows identify the tenant by TargetTenantClientTenantId. Client-credentials login: numeric tenant ids do not exist; table rows carry TargetTenantMsTenantId instead. Piped tenants (Get-InforcerTenant | Get-InforcerAlignmentDetails): scores, baselines and tenant records are fetched once for the whole pipeline, each row carries TenantId, no row repeats, and -OutputType JsonObject returns one JSON array. With client credentials the table is always a JSON array, also for one row. .PARAMETER Format Table (default) or Raw. .PARAMETER TenantId Optional. Filter to this tenant. When used with -BaselineId, queries only this tenant. Accepts the Microsoft Tenant ID (GUID), the tenant name, or (API-key login only) the numeric Client Tenant ID. .PARAMETER BaselineId Optional. Baseline GUID or friendly name. When provided, retrieves detailed alignment data. Without -TenantId, queries the first member tenant of the baseline. .PARAMETER Tag Optional. When Format is Table (without -BaselineId), filter to tenants with tag containing this value (case-insensitive). No match returns no rows and a warning. .PARAMETER OutputType PowerShellObject (default) or JsonObject. JSON uses Depth 100. .EXAMPLE Get-InforcerAlignmentDetails .EXAMPLE Get-InforcerAlignmentDetails -Format Raw -OutputType JsonObject .EXAMPLE Get-InforcerAlignmentDetails -TenantId 'Contoso' -Tag Production .EXAMPLE Get-InforcerAlignmentDetails -TenantId 'Contoso' -BaselineId "Provision M365" Retrieves detailed alignment data using the tenant name and the baseline friendly name (both logins). .EXAMPLE Get-InforcerAlignmentDetails -TenantId '00000000-0000-0000-0000-000000000001' -BaselineId "00000000-0000-0000-0000-000000000004" -Format Raw Retrieves the raw alignment detail response by Microsoft Tenant ID and baseline GUID. .EXAMPLE Get-InforcerAlignmentDetails -BaselineId "Inforcer Blueprint Baseline - Tier 2 - Enhanced" Retrieves alignment details for the first member tenant of the baseline. .OUTPUTS PSObject or String .LINK https://github.com/royklo/InforcerCommunity/blob/main/docs/CMDLET-REFERENCE.md#get-inforceralignmentdetails .LINK Connect-Inforcer #> function Get-InforcerAlignmentDetails { [CmdletBinding()] [OutputType([PSObject], [string])] param( [Parameter(Mandatory = $false)] [ValidateSet('Table', 'Raw')] [string]$Format = 'Table', [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias('ClientTenantId', 'MsTenantId')] [object]$TenantId, [Parameter(Mandatory = $false)] [string]$BaselineId, [Parameter(Mandatory = $false)] [string]$Tag, [Parameter(Mandatory = $false)] [ValidateSet('PowerShellObject', 'JsonObject')] [string]$OutputType = 'PowerShellObject' ) begin { $requestedTenantIds = [System.Collections.Generic.List[object]]::new() } process { $requestedTenantIds.Add($TenantId) } end { $piped = $PSCmdlet.MyInvocation.ExpectingInput if ($piped -and $requestedTenantIds.Count -eq 0) { return } $requested = $requestedTenantIds.ToArray() if (-not (Test-InforcerSession)) { Write-Error -Message 'Not connected yet. Please run Connect-Inforcer first.' -ErrorId 'NotConnected' -Category ConnectionError return } $isV1 = $script:InforcerSession.ApiVersion -eq 'v1' # The id the current API keys tenants by: beta [int] clientTenantId, v1 [string] msTenantId. function Get-TenantKey($Object) { $name = if ($isV1) { 'msTenantId' } else { 'clientTenantId' } $value = $Object.PSObject.Properties[$name].Value if ($null -eq $value) { return $null } if ($isV1) { return "$value" } $n = 0 if ([int]::TryParse($value.ToString(), [ref]$n)) { return $n } $null } function Add-PipedTenantId($Object, $Id) { if ($piped -and $null -ne $Id -and $Object -is [PSObject] -and -not $Object.PSObject.Properties['TenantId']) { $Object.PSObject.Properties.Add([System.Management.Automation.PSNoteProperty]::new('TenantId', $Id)) } } # Fetched at most once per call, whatever the number of piped tenants. $allBaselines = $null # --- Alignment Details mode: -BaselineId provided --- if (-not [string]::IsNullOrWhiteSpace($BaselineId)) { try { $guidTest = [guid]::Empty if (-not [guid]::TryParse($BaselineId.Trim(), [ref]$guidTest)) { $allBaselines = @(Get-InforcerBaselineList -Refresh) $baselineGuid = Resolve-InforcerBaselineId -BaselineId $BaselineId -BaselineData $allBaselines } else { $baselineGuid = $BaselineId.Trim() } } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidBaselineId' -Category InvalidArgument return } # Build list of tenants to query $tenantsToQuery = [System.Collections.Generic.List[object]]::new() $queuedIds = @{} foreach ($requestedTenantId in $requested) { if ($null -ne $requestedTenantId) { # Single tenant mode — resolve ID and friendly name try { $resolvedTenantId = Resolve-InforcerTenantId -TenantId $requestedTenantId -ErrorAction Stop } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument if ($piped) { continue } return } if ($queuedIds.ContainsKey($resolvedTenantId)) { continue } $tenantFriendlyName = "Tenant $resolvedTenantId" foreach ($t in @(Get-InforcerTenantList)) { if ($t -is [PSObject] -and (Get-TenantKey $t) -eq $resolvedTenantId) { $fnProp = $t.PSObject.Properties['tenantFriendlyName'] if ($fnProp -and $fnProp.Value) { $tenantFriendlyName = $fnProp.Value.ToString() } break } } $queuedIds[$resolvedTenantId] = $true [void]$tenantsToQuery.Add(@{ Id = $resolvedTenantId; Name = $tenantFriendlyName }) } else { # All members mode — find baseline members if ($null -eq $allBaselines) { $allBaselines = @(Get-InforcerBaselineList -Refresh) } $baselineObj = $null foreach ($bl in $allBaselines) { if (-not ($bl -is [PSObject])) { continue } $idProp = $bl.PSObject.Properties['id'] if ($idProp -and $null -ne $idProp.Value -and $idProp.Value.ToString() -eq $baselineGuid) { $baselineObj = $bl break } } if ($null -eq $baselineObj) { Write-Error -Message "Baseline not found: $baselineGuid" -ErrorId 'BaselineNotFound' -Category ObjectNotFound return } $membersProp = $baselineObj.PSObject.Properties['members'] if (-not $membersProp -or $null -eq $membersProp.Value -or @($membersProp.Value).Count -eq 0) { Write-Warning 'No member tenants found for this baseline.' return } # Pick first member only — baseline policies are the same regardless of member $memberEntry = $null foreach ($member in @($membersProp.Value)) { if (-not ($member -is [PSObject])) { continue } $mId = Get-TenantKey $member if ($null -eq $mId) { continue } $mNameProp = $member.PSObject.Properties['tenantFriendlyName'] $mName = if ($mNameProp -and $null -ne $mNameProp.Value) { $mNameProp.Value.ToString() } else { "Tenant $mId" } $memberEntry = @{ Id = $mId; Name = $mName } break } if ($null -eq $memberEntry) { Write-Warning 'No member tenants found for this baseline.' return } Write-Verbose "Using member tenant $($memberEntry.Name) ($($memberEntry.Id)) to retrieve baseline policies." if ($queuedIds.ContainsKey($memberEntry.Id)) { continue } $queuedIds[$memberEntry.Id] = $true [void]$tenantsToQuery.Add($memberEntry) } } # Status map for policy arrays $arrayStatusMap = @{ 'matchedPolicies' = 'Aligned' 'matchedWithAcceptedDeviations' = 'Accepted Deviation' 'deviatedUnaccepted' = 'Unaccepted Deviation' 'missingFromSubjectUnaccepted' = 'Recommended From Baseline' 'additionalInSubjectUnaccepted' = 'Existing Customer Policy' } # Piped JSON: one array for all tenants, built from the objects. $pipedJson = $piped -and $OutputType -eq 'JsonObject' $requestOutputType = if ($pipedJson) { 'PowerShellObject' } else { $OutputType } $jsonItems = [System.Collections.Generic.List[object]]::new() # Query each tenant foreach ($tenantEntry in $tenantsToQuery) { $tId = $tenantEntry.Id $tName = $tenantEntry.Name Write-Verbose "Retrieving alignment details for tenant $tId ($tName), baseline $baselineGuid..." $detailEndpoint = "/tenants/$tId/alignmentDetails?customBaselineId=$baselineGuid" $response = Invoke-InforcerApiRequest -Endpoint $detailEndpoint -Method GET -OutputType $requestOutputType -PreserveStructure:$pipedJson -ObjectType AlignmentDetails -ErrorAction SilentlyContinue -ErrorVariable apiErr if ($null -eq $response) { if ($apiErr -and $apiErr.Count -gt 0) { Write-Error -ErrorRecord $apiErr[-1] } else { Write-Warning "No alignment data returned for tenant '$tName' ($tId)." } continue } if ($pipedJson) { Add-PipedTenantId $response $tId $jsonItems.Add($response) continue } if ($OutputType -eq 'JsonObject') { $response continue } if ($Format -eq 'Raw') { if ($response -is [PSObject]) { $null = Add-InforcerPropertyAliases -InputObject $response -ObjectType AlignmentDetail } Add-PipedTenantId $response $tId $response continue } # Format Table: flatten into metrics summary + per-policy rows $alignment = $null $metrics = $null $completedAt = $null if ($response -is [PSObject]) { $alignmentProp = $response.PSObject.Properties['alignment'] if ($alignmentProp -and $null -ne $alignmentProp.Value) { $alignment = $alignmentProp.Value } $metricsProp = $response.PSObject.Properties['metrics'] if ($metricsProp -and $null -ne $metricsProp.Value) { $metrics = $metricsProp.Value } $completedAtProp = $response.PSObject.Properties['completedAt'] if ($completedAtProp -and $null -ne $completedAtProp.Value) { $completedAt = $completedAtProp.Value } } if ($null -eq $alignment) { Write-Warning "No alignment data returned for tenant $tName ($tId)." continue } $alignmentScoreVal = $alignment.PSObject.Properties['alignmentScore'].Value $alignmentScoreFormatted = if ($null -ne $alignmentScoreVal) { [Math]::Round([double]$alignmentScoreVal, 2) } else { $null } $totalPolicies = if ($metrics) { $metrics.PSObject.Properties['totalPolicies'].Value } else { $null } $alignedCount = if ($metrics) { $metrics.PSObject.Properties['matchedPolicies'].Value } else { $null } $acceptedDeviationCount = if ($metrics) { $metrics.PSObject.Properties['matchedWithAcceptedDeviations'].Value } else { $null } $unacceptedDeviationCount = if ($metrics) { $metrics.PSObject.Properties['deviatedPolicies'].Value } else { $null } $recommendedCount = if ($metrics) { $metrics.PSObject.Properties['recommendedPoliciesFromBaseline'].Value } else { $null } $existingCustomerCount = if ($metrics) { $metrics.PSObject.Properties['customerOnlyPolicies'].Value } else { $null } # Display metrics summary via host (not pipeline) so it doesn't break | Format-Table Write-Host "" Write-Host " Tenant: $tName ($tId)" -ForegroundColor Cyan Write-Host " Alignment Score: $alignmentScoreFormatted | Total: $totalPolicies | Aligned: $alignedCount | Accepted Deviation: $acceptedDeviationCount | Unaccepted Deviation: $unacceptedDeviationCount | Recommended: $recommendedCount | Customer Only: $existingCustomerCount" Write-Host " Completed: $completedAt" Write-Host "" # Per-policy rows foreach ($arrayName in $arrayStatusMap.Keys) { $policyArrayProp = $alignment.PSObject.Properties[$arrayName] if (-not $policyArrayProp -or $null -eq $policyArrayProp.Value) { continue } $status = $arrayStatusMap[$arrayName] foreach ($policy in @($policyArrayProp.Value)) { if (-not ($policy -is [PSObject])) { continue } $tagNames = [System.Collections.Generic.List[string]]::new() $policyTagsProp = $policy.PSObject.Properties['policyTags'] if ($policyTagsProp -and $null -ne $policyTagsProp.Value) { foreach ($t in @($policyTagsProp.Value)) { if ($t -is [PSObject] -and $t.PSObject.Properties['name']) { [void]$tagNames.Add($t.PSObject.Properties['name'].Value) } } } $row = [PSCustomObject]@{ PolicyName = $policy.PSObject.Properties['policyName'].Value AlignmentStatus = $status Product = $policy.PSObject.Properties['product'].Value PrimaryGroup = $policy.PSObject.Properties['primaryGroup'].Value SecondaryGroup = $policy.PSObject.Properties['secondaryGroup'].Value InforcerPolicyTypeName = $policy.PSObject.Properties['inforcerPolicyTypeName'].Value Tags = ($tagNames -join ', ') } Add-PipedTenantId $row $tId $row.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentDetailPolicy') $row } } } if ($pipedJson) { ConvertTo-Json -InputObject @($jsonItems) -Depth 100 } return } # The percentage exactly as inforcer reports it (a number, already rounded to two decimals by the API). function FormatAlignmentScore($scoreVal) { if ($null -eq $scoreVal) { return $null } $scoreNumeric = 0.0 if (-not [double]::TryParse($scoreVal.ToString().Replace(',', '.'), [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$scoreNumeric)) { return $null } $scoreNumeric } if ($Format -eq 'Raw') { Write-Verbose 'Retrieving alignment scores (raw)...' $requestOutputType = if ($piped) { 'PowerShellObject' } else { $OutputType } $response = Invoke-InforcerApiRequest -Endpoint '/alignmentScores' -Method GET -OutputType $requestOutputType -ObjectType AlignmentScore if ($null -eq $response) { return } $predicateFor = { param($resolvedTenantId) if ($isV1) { { param($p) "$($p.PSObject.Properties['msTenantId'].Value)" -eq $resolvedTenantId }.GetNewClosure() } else { { param($p) $tidProp = $p.PSObject.Properties['tenantId'] if (-not $tidProp) { $tidProp = $p.PSObject.Properties['clientTenantId'] } if ($tidProp -and [int]$tidProp.Value -eq $resolvedTenantId) { 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 $resolvedTenantId) { return $true } } } } $false }.GetNewClosure() } } if ($piped) { # One row per score item, deduped across piped tenants; TenantId is the item's own tenant. $items = @(ConvertTo-InforcerArray $response) $keep = [bool[]]::new($items.Count) foreach ($requestedTenantId in $requested) { if ($null -eq $requestedTenantId) { continue } try { $resolvedTenantId = Resolve-InforcerTenantId -TenantId $requestedTenantId -ErrorAction Stop } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument continue } $predicate = & $predicateFor $resolvedTenantId for ($i = 0; $i -lt $items.Count; $i++) { if (-not $keep[$i] -and $items[$i] -is [PSObject] -and (& $predicate $items[$i])) { $keep[$i] = $true } } } $selected = @(for ($i = 0; $i -lt $items.Count; $i++) { if ($keep[$i]) { $items[$i] } }) foreach ($item in $selected) { if ($item -is [PSObject]) { Add-PipedTenantId $item (Get-TenantKey $item) } } if ($OutputType -eq 'JsonObject') { return ConvertTo-Json -InputObject @($selected) -Depth 100 } foreach ($item in $selected) { if ($item -is [PSObject]) { $null = Add-InforcerPropertyAliases -InputObject $item -ObjectType AlignmentScore $item.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentScoreRaw') } $item } return } if ($null -ne $TenantId) { try { $resolvedTenantId = Resolve-InforcerTenantId -TenantId $TenantId -ErrorAction Stop } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument return } Write-Verbose "Filtering alignment scores to tenant ID $resolvedTenantId..." $predicate = & $predicateFor $resolvedTenantId 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]) { $null = Add-InforcerPropertyAliases -InputObject $item -ObjectType AlignmentScore $item.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentScoreRaw') } } $filtered } return } if ($OutputType -eq 'JsonObject') { return $response } foreach ($item in (ConvertTo-InforcerArray $response)) { if ($item -is [PSObject]) { $null = Add-InforcerPropertyAliases -InputObject $item -ObjectType AlignmentScore $item.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentScoreRaw') } } $response return } # Format Table: alignment scores, joined with the tenant list only when filtering needs it Write-Verbose 'Retrieving alignment scores for table...' $scoresErr = $null $alignmentResponse = Invoke-InforcerApiRequest -Endpoint '/alignmentScores' -Method GET -OutputType PowerShellObject -ObjectType AlignmentScore -ErrorVariable scoresErr if ($null -eq $alignmentResponse) { # No scores means no rows: the v1 (and piped) JSON table is still an array; a failed request returns nothing. if (-not $scoresErr -and $OutputType -eq 'JsonObject' -and ($isV1 -or $piped)) { return '[]' } return } $allAlignmentData = ConvertTo-InforcerArray $alignmentResponse $scoreTenantProperty = if ($isV1) { 'msTenantId' } else { 'tenantId' } $firstItem = $null if ($allAlignmentData.Count -gt 0) { $firstItem = $allAlignmentData[0] } $flatFormat = ($firstItem -is [PSObject]) -and $firstItem.PSObject.Properties[$scoreTenantProperty] -and $firstItem.PSObject.Properties['score'] -and -not $firstItem.PSObject.Properties['alignmentSummaries'] $anyTenantRequested = @($requested | Where-Object { $null -ne $_ }).Count -gt 0 $needTenants = (-not $flatFormat) -or $anyTenantRequested -or -not [string]::IsNullOrWhiteSpace($Tag) # Tenant objects come from the shared tenant cache: read them, never modify them. $allTenants = @() if ($needTenants) { Write-Verbose 'Retrieving tenant information for alignment table...' $allTenants = @(Get-InforcerTenantList) if ($allTenants.Count -eq 0) { return } } # Add to $targetIds every tenant aligned to a baseline owned by an id in $baselineOwnerIds, # read from the tenant list's alignmentSummaries (beta alignedBaselineTenantId, v1 alignedBaselineMsTenantId). function Add-ChildTenantIdsFromAlignments { param([System.Collections.Hashtable]$targetIds, [array]$allTenants, [array]$baselineOwnerIds) $ownerName = if ($isV1) { 'alignedBaselineMsTenantId' } else { 'alignedBaselineTenantId' } foreach ($t in $allTenants) { if (-not ($t -is [PSObject])) { continue } $sumProp = $t.PSObject.Properties['alignmentSummaries'] if (-not $sumProp -or $null -eq $sumProp.Value) { continue } foreach ($s in @($sumProp.Value)) { if (-not ($s -is [PSObject])) { continue } $ownerProp = $s.PSObject.Properties[$ownerName] if (-not $ownerProp -or $null -eq $ownerProp.Value) { continue } $owner = $ownerProp.Value.ToString() if (-not $isV1) { $n = 0 if (-not [int]::TryParse($owner, [ref]$n)) { continue } $owner = $n } if ($baselineOwnerIds -notcontains $owner) { continue } $childId = Get-TenantKey $t if ($null -ne $childId) { $targetIds[$childId] = $true } } } } # True when one of the tenant's tags contains $Tag (v1 tags are {name, description}; beta tags are strings). function Test-TenantTag($Tenant) { $tagsProp = $Tenant.PSObject.Properties['tags'] if (-not $tagsProp -or $null -eq $tagsProp.Value) { return $false } foreach ($x in @($tagsProp.Value)) { $text = if ($x -is [PSObject] -and $x.PSObject.Properties['name']) { $x.PSObject.Properties['name'].Value } else { $x } if ($text -and $text.ToString().IndexOf($Tag, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) { return $true } } $false } # Only filter by tenant when a tenant or -Tag was given; otherwise show all alignment rows. # Piped tenants: the union of what each tenant alone would select, built once. $tenantIds = @{} # A requested tenant or -Tag that matches nothing must return nothing, not every row. $filterTenants = $anyTenantRequested -or -not [string]::IsNullOrWhiteSpace($Tag) foreach ($requestedTenantId in $requested) { $tenants = @($allTenants | Where-Object { $_ -is [PSObject] }) if ($null -ne $requestedTenantId) { try { $resolvedTenantId = Resolve-InforcerTenantId -TenantId $requestedTenantId -TenantData $allTenants -ErrorAction Stop } catch { Write-Error -Message $_.Exception.Message -ErrorId 'InvalidTenantId' -Category InvalidArgument if ($piped) { continue } return } Write-Verbose "Filtering to tenant ID: $resolvedTenantId" $tenants = @($tenants | Where-Object { (Get-TenantKey $_) -eq $resolvedTenantId }) if ($tenants.Count -eq 0) { Write-Verbose "No tenant found with ID $resolvedTenantId. Returning no results." if ($piped) { continue } return } } if (-not [string]::IsNullOrWhiteSpace($Tag)) { Write-Verbose "Filtering to tenants with tag containing: $Tag" $tenants = @($tenants | Where-Object { Test-TenantTag $_ }) } if ($null -eq $requestedTenantId -and [string]::IsNullOrWhiteSpace($Tag)) { continue } $selectedIds = @{} foreach ($t in $tenants) { $tid = Get-TenantKey $t if ($null -ne $tid) { $selectedIds[$tid] = $true } } # If the requested tenant is a baseline owner, also include tenants aligned TO it if ($null -ne $requestedTenantId -and $selectedIds.Count -gt 0) { Add-ChildTenantIdsFromAlignments -targetIds $selectedIds -allTenants $allTenants -baselineOwnerIds @($selectedIds.Keys) } foreach ($id in $selectedIds.Keys) { $tenantIds[$id] = $true } } if (-not [string]::IsNullOrWhiteSpace($Tag) -and -not @($allTenants | Where-Object { $_ -is [PSObject] -and (Test-TenantTag $_) }).Count) { Write-Warning "No tenant has a tag matching '$Tag'." } Write-Verbose "Building alignment table from alignment scores ($($allAlignmentData.Count) source(s))..." # 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 } $scoreVal = $item.PSObject.Properties['score'].Value $alignmentScoreFormatted = FormatAlignmentScore $scoreVal if ($isV1) { $targetTenantMsTenantId = Get-TenantKey $item if ($filterTenants -and ($null -eq $targetTenantMsTenantId -or -not $tenantIds.ContainsKey($targetTenantMsTenantId))) { continue } $row = [PSCustomObject]@{ TargetTenantFriendlyName = ($item.PSObject.Properties['tenantFriendlyName'].Value -as [string]) TargetTenantMsTenantId = $targetTenantMsTenantId AlignmentScore = $alignmentScoreFormatted BaselineName = $item.PSObject.Properties['baselineGroupName'].Value BaselineId = $item.PSObject.Properties['baselineGroupId'].Value LastComparisonDateTime = $item.PSObject.Properties['lastComparisonDateTime'].Value } Add-PipedTenantId $row $targetTenantMsTenantId } else { $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 ($filterTenants -and -not $tenantIds.ContainsKey($targetTenantClientTenantId)) { continue } $row = [PSCustomObject]@{ TargetTenantFriendlyName = ($item.PSObject.Properties['tenantFriendlyName'].Value -as [string]) TargetTenantClientTenantId = $targetTenantClientTenantId AlignmentScore = $alignmentScoreFormatted BaselineName = $item.PSObject.Properties['baselineGroupName'].Value BaselineId = $item.PSObject.Properties['baselineGroupId'].Value LastComparisonDateTime = $item.PSObject.Properties['lastComparisonDateTime'].Value } Add-PipedTenantId $row $targetTenantClientTenantId } $row.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentScore') [void]$alignmentTable.Add($row) } } elseif (-not $isV1) { 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 ($filterTenants -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 = '' } foreach ($alignment in $summaries) { if (-not ($alignment -is [PSObject])) { continue } $scoreProp = $alignment.PSObject.Properties['alignmentScore'] $alignmentScoreFormatted = FormatAlignmentScore $(if ($scoreProp -and $null -ne $scoreProp.Value) { $scoreProp.Value } else { $null }) $row = [PSCustomObject]@{ TargetTenantFriendlyName = $targetTenantFriendlyName TargetTenantClientTenantId = $targetTenantClientTenantId AlignmentScore = $alignmentScoreFormatted BaselineName = $alignment.PSObject.Properties['alignedBaselineName'].Value BaselineId = $alignment.PSObject.Properties['alignedBaselineId'].Value LastComparisonDateTime = $alignment.PSObject.Properties['lastComparisonDateTime'].Value AlignedThreshold = $alignment.PSObject.Properties['alignedThreshold'].Value SemiAlignedThreshold = $alignment.PSObject.Properties['semiAlignedThreshold'].Value } Add-PipedTenantId $row $targetTenantClientTenantId $row.PSObject.TypeNames.Insert(0, 'InforcerCommunity.AlignmentScore') [void]$alignmentTable.Add($row) } } } Write-Verbose "Generated alignment table with $($alignmentTable.Count) row(s)." if ($OutputType -eq 'JsonObject') { if ($isV1 -or $piped) { return ConvertTo-Json -InputObject @($alignmentTable) -Depth 100 } $json = $alignmentTable | ConvertTo-Json -Depth 100 Write-Output $json return } $alignmentTable } } |