← Files Power BI DesktopARCHIVED FILE

scripts/New-PowerBIFabricCapacityRiskForecast.ps1

6.6 KB · Oct 2, 2026 · 00:32 UTC

↓ Download file

param(
    [string]$Path = ".",
    [string]$VpaxPath,
    [string]$TracePath,
    [string]$ServiceScannerPath,
    [string]$OutputPath,
    [switch]$Json
)

$ErrorActionPreference = 'Stop'
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$resolved = (Resolve-Path -LiteralPath $Path).Path

function Read-JsonFile {
    param([string]$LiteralPath)
    if ($LiteralPath -and (Test-Path -LiteralPath $LiteralPath)) {
        $raw = Get-Content -Raw -LiteralPath $LiteralPath
        if ($raw.Trim().StartsWith('{') -or $raw.Trim().StartsWith('[')) {
            return ($raw | ConvertFrom-Json)
        }
    }
    $null
}

function New-RiskItem {
    param([string]$Area, [string]$Severity, [string]$Title, [string]$Detail, [string]$Mitigation)
    [pscustomobject]@{
        area = $Area
        severity = $Severity
        title = $Title
        detail = $Detail
        mitigation = $Mitigation
    }
}

function Get-SeverityScore {
    param([string]$Severity)
    switch ($Severity) {
        'Critical' { 100 }
        'High' { 80 }
        'Medium' { 50 }
        'Low' { 20 }
        default { 35 }
    }
}

$heatmap = & (Join-Path $scriptRoot 'New-PowerBIModelRiskHeatmap.ps1') -Path $resolved -Json | ConvertFrom-Json
$fabric = & (Join-Path $scriptRoot 'New-PowerBIFabricReadinessPlan.ps1') -Path $resolved -Json | ConvertFrom-Json
$vertipaq = & (Join-Path $scriptRoot 'Import-PowerBIVertiPaqAnalyzer.ps1') -Path $resolved -VpaxPath $VpaxPath -Json | ConvertFrom-Json
$trace = & (Join-Path $scriptRoot 'Import-PowerBIPerformanceTrace.ps1') -Path $resolved -TracePath $TracePath -Json | ConvertFrom-Json
$service = Read-JsonFile -LiteralPath $ServiceScannerPath
if (-not $service) {
    $service = & (Join-Path $scriptRoot 'New-PowerBIServiceScanner.ps1') -Path $resolved -Json | ConvertFrom-Json
}

$riskItems = New-Object System.Collections.Generic.List[object]
foreach ($area in @($heatmap.areas | Where-Object { $_.score -ge 35 })) {
    $severity = if ($area.score -ge 70) { 'High' } elseif ($area.score -ge 35) { 'Medium' } else { 'Low' }
    $riskItems.Add((New-RiskItem 'Model' $severity $area.name $area.detail 'Reduce semantic model risk before Fabric capacity rollout.'))
}
foreach ($step in @($fabric.steps | Where-Object required)) {
    $riskItems.Add((New-RiskItem 'Fabric readiness' 'Medium' $step.phase $step.action 'Complete required readiness step before deployment.'))
}
foreach ($column in @($vertipaq.columns | Where-Object { $_.risk -in @('High', 'Medium', 'Unknown') } | Select-Object -First 10)) {
    $severity = if ($column.risk -eq 'High') { 'High' } elseif ($column.risk -eq 'Medium') { 'Medium' } else { 'Low' }
    $riskItems.Add((New-RiskItem 'Storage' $severity "$($column.table)[$($column.column)]" $column.recommendation 'Capture VPAX and optimize high-cardinality or unused import columns.'))
}
foreach ($hotspot in @($trace.hotspots | Select-Object -First 10)) {
    $severity = if ($hotspot.durationMs -and $hotspot.durationMs -ge 3000) { 'High' } elseif ($hotspot.durationMs -and $hotspot.durationMs -ge 1000) { 'Medium' } else { 'Low' }
    $riskItems.Add((New-RiskItem 'Query' $severity $hotspot.name $hotspot.recommendation 'Validate DAX query plan and visual interaction cost under expected concurrency.'))
}
foreach ($finding in @($service.findings | Where-Object { $_.severity -in @('Critical', 'High', 'Medium') })) {
    $riskItems.Add((New-RiskItem 'Service governance' $finding.severity $finding.title $finding.detail 'Resolve service governance evidence before capacity commitment.'))
}

$capacityRiskScore = if ($riskItems.Count -gt 0) {
    [int]([math]::Min(100, (($riskItems | ForEach-Object { Get-SeverityScore $_.severity }) | Measure-Object -Average).Average + [math]::Min(20, $riskItems.Count * 2)))
}
else { 10 }
$refreshRisk = [int]([math]::Min(100, (@($riskItems | Where-Object { $_.area -in @('Storage', 'Service governance') }).Count * 18) + $(if ($service.releaseDecision -eq 'No-Go') { 25 } elseif ($service.releaseDecision -eq 'Warn') { 10 } else { 0 })))
$queryRisk = [int]([math]::Min(100, (@($riskItems | Where-Object area -eq 'Query').Count * 20) + [math]::Min(40, @($trace.hotspots).Count * 8)))

$mitigationPlan = @(
    [pscustomobject]@{ priority = 'P0'; action = 'Resolve No-Go release gate and high service scanner findings.'; condition = ($service.releaseDecision -eq 'No-Go' -or @($service.findings | Where-Object severity -in @('Critical', 'High')).Count -gt 0) },
    [pscustomobject]@{ priority = 'P1'; action = 'Capture VPAX and Performance Analyzer/DAX Studio traces for capacity sizing evidence.'; condition = (-not $VpaxPath -or -not $TracePath) },
    [pscustomobject]@{ priority = 'P1'; action = 'Reduce high-cardinality import columns and expensive DAX hotspots before Premium/Fabric rollout.'; condition = ($refreshRisk -ge 50 -or $queryRisk -ge 50) },
    [pscustomobject]@{ priority = 'P2'; action = 'Document workspace owner, refresh owner, gateway mapping, and deployment pipeline runbook.'; condition = $true }
) | Where-Object condition | ForEach-Object { [pscustomobject]@{ priority = $_.priority; action = $_.action } }

$result = [pscustomobject]@{
    schema = 'codex.powerbi.fabricCapacityRiskForecast.v1'
    generated = (Get-Date).ToString('s')
    source = $resolved
    vpaxPath = $VpaxPath
    tracePath = $TracePath
    serviceScannerPath = $ServiceScannerPath
    capacityRiskScore = $capacityRiskScore
    capacityRiskLevel = if ($capacityRiskScore -ge 70) { 'High' } elseif ($capacityRiskScore -ge 35) { 'Medium' } else { 'Low' }
    refreshRisk = $refreshRisk
    queryRisk = $queryRisk
    modelRisk = $heatmap.overallRisk
    fabricReleaseDecision = $fabric.releaseDecision
    serviceReleaseDecision = $service.releaseDecision
    riskItems = @($riskItems.ToArray())
    mitigationPlan = @($mitigationPlan)
}

if ($Json) {
    $text = $result | ConvertTo-Json -Depth 8
    if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $text -Encoding UTF8 }
    $text
    return
}

$lines = @(
    '# Power BI Fabric Capacity Risk Forecast',
    '',
    "Capacity risk score: $capacityRiskScore ($($result.capacityRiskLevel))",
    "Refresh risk: $refreshRisk",
    "Query risk: $queryRisk",
    '',
    '## Risk items'
) + @($riskItems | ForEach-Object { "- [$($_.severity)] $($_.area): $($_.title) - $($_.detail)" }) + @('', '## Mitigation plan') + @($mitigationPlan | ForEach-Object { "- [$($_.priority)] $($_.action)" })

$content = ($lines -join [Environment]::NewLine) + [Environment]::NewLine
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $content -Encoding UTF8 }
$content

SHA-256: 60c164cc811f1c8686f6fd76078c10646fe5b3d7800c2316cb1051abcf23c030