Add Start-FinOpsMultitool — cross-platform terminal UI for FinOps scanning - #2155
Add Start-FinOpsMultitool — cross-platform terminal UI for FinOps scanning#2155Zac larsen (z-larsen) wants to merge 110 commits into
Conversation
… GUI Adds the Azure FinOps Multitool as a new PowerShell cmdlet in the FinOps toolkit. The Multitool is a WPF-based GUI that scans an Azure tenant for cost optimization, governance, and FinOps insights including cost trends, orphaned resources, idle VMs, tag hygiene, reservation/savings plan utilization, AHB opportunities, budgets, anomaly alerts, and policy compliance. - Public/Start-FinOpsMultitool.ps1: thin launcher cmdlet with comment-based help - Private/FinOpsMultitool/: full implementation (24 scanner modules, WPF GUI, Power BI template) - Tests/Unit/Start-FinOpsMultitool.Tests.ps1: Pester unit tests Windows-only (requires WPF support).
|
@microsoft-github-policy-service agree company="Microsoft" |
|
Zac larsen (@z-larsen) This is exciting! I don't know much about the tool, but would love to learn more. Can you join us at the contributor sync next Wednesday to share? |
|
Thanks, Michael! Would love to join. |
…info - Add contract-aware cost access warning banner (EA/MCA/CSP) on Overview tab - Add contract-specific billing tab messages when billing access unavailable - Add MG hierarchy unavailable info node in tree view with role guidance - Fix tag cost queries: use TagKey grouping type (not Tag/Dimension) - Add batched TagKey+TagValue query attempt with per-tag fallback - Clear skipSubs between batched and per-tag strategies - Add throttle pacing (2s every 2 queries) to avoid 429s - Add EA/MCA cost access detection in Get-CostData - Add runspace pool for API call parallelization
1. TUI data-source selection failed before any scan ran. The local $dataSource reused the validated -DataSource parameter, since PowerShell variable names are case-insensitive, so assigning the picker hashtable threw ValidationMetadataException on every path. Renamed the local to $sourceChoice and added an AST regression test asserting no validated parameter is ever an assignment target. The test fails on 9a8fcf4. 2. MACC was not bounded by the selected scope. Get-MaccCommitment took $Subscriptions but never used it, so it returned commitments from every reachable billing account. 3. Billing Structure dropped readable EA enrollments. It relied on billingInfo/default, which is not a valid resource type and 404s on all api-versions, leaving the linked-account set empty and skipping every account. 2 and 3 share a root cause, so both now use a new Get-FinOpsBillingScope helper: billingProperty/default per subscription first, then the billingSubscriptions walk Get-ContractInfo already uses. It separates "read it, no match" from "could not read it" so a permissions gap reports as one. Get-CommitmentUtilization had the same invalid endpoint and was not flagged. There the empty set skipped the filter entirely, so it used every reachable MCA account. Same helper applied. Still outstanding: rerunning the TUI against the online Hub, local Hub, and API paths, which needs a live session.
# Conflicts: # docs-mslearn/toolkit/changelog.md
…resource Get-CostByTag emitted "(untagged resources)" on the live API path while every consumer compared against "(untagged)". Nothing matched, so untagged spend summed to zero. That produced three wrong outputs at once: 0% untagged, 100% tagging-policy compliant, and guidance claiming no CAF allocation tag was in use, all while the table above showed real untagged cost under CostCenter, Customer, Project, and Environment. The hub paths already emitted "(untagged)", which is why local hub testing never caught it. Normalized to one value. The KPI also selected the allocation tag with the LOWEST untagged percentage, so a single well-covered dimension reported the whole estate as compliant. The guidance selected the highest untagged cost, so the two contradicted each other despite a comment saying they agreed. Allocation coverage is now counted once per resource on the live path, where the loop already has both the cost and the resolved tags: a resource is allocated if it carries any CAF allocation tag. Per-tag totals cannot answer this, since a resource appears as untagged under every tag it lacks and summing double-counts the same spend. Exposed as AllocatedCost, UnallocatedCost, and ResourceCostSeen, null when a run did not walk resources so consumers can tell "none allocated" from "not measured". Sources that aggregate server-side keep a per-tag fallback, now reporting the worst-covered tag rather than the best. Guidance reads the same aggregate. Verified against the reported shape: 93 of 571 resource spend unallocated reports 16.3%, and the old sentinel still reproduces 0%/100%.
The TUI writes exported results to FinOpsResults, so ignore it to keep a local run out of the repository.
ee46a38 to
05c00ea
Compare
|
Thanks Brett - all three are fixed in 1. TUI data-source selection fails before scans start 2. MACC results are not bounded by the selected scope 3. Billing Structure drops a readable EA enrollment Unit suite: 2433 passed / 0 failed / 7 skipped. |
🛠️ Description
Adds the FinOps multitool to the FinOps toolkit. Discussed with Brett Wilson (@MSBrett), who suggested contributing the tool into the official toolkit.
The multitool scans an Azure environment for cost optimization, governance, and FinOps insights — cost trends, orphaned resources, idle VMs, tag hygiene, reservation and savings plan utilization, Azure Hybrid Benefit opportunities, budgets, anomaly alerts, and policy compliance — and grounds its findings in live resource state.
Everything in this PR is read-only. It needs Reader or Cost Management Reader on the target scope and never creates, changes, or deletes a resource. Remediation and the MCP server were split out to a separate branch and will follow as their own PR.
One scanner engine, two consumers:
Start-FinOpsMultitoolsrc/templates/agent-skills/azor an Azure MCP serverRunning it
The terminal UI uses arrow-key menus when the console supports them. Consoles that can't render those menus — PowerShell remoting sessions, some editor terminals — fall back to numbered prompts, which is also what a screen reader can follow. Both paths run the same scans and produce the same results.
For automation,
-NonInteractivewith-Scans,-DataSource,-SubscriptionId, and-OutputPathsupplies every choice, so the tool runs from a pipeline or a scheduled job:FinOps hub data paths
This addresses Brett Wilson (@MSBrett)'s scaling review. When a FinOps hub is present, cost scans prefer the hub's Kusto database — an Azure Data Explorer or Fabric cluster, auto-discovered through Resource Graph, or a local ftklocal emulator via
FINOPS_HUB_KUSTO_URI— and push aggregation into the engine, returning only summarized result sets. Raw cost rows are never materialized in PowerShell on that path.The storage-export reader remains as a small-dataset fallback rather than the scalable path, and the terminal UI warns before using it on a hub with no reachable cluster, offering the live Cost Management API instead.
Scans
30 scan modules across optimization, governance, cost analysis, commitments, monitoring, Advisor, account, AI and ML, and sustainability. The terminal UI surfaces 26 of them. Results render in the terminal and export to one CSV per scan, a
FinOpsReport.htmlsummary, and aScanSummary.txtfile.📦 Files added / changed
Public/Start-FinOpsMultitool.ps1Invoke-FinOpsMultitool.ps1+FinOpsMultitool.psm1modules/helpers/Get-FOHubProvider.ps1+Invoke-FOHubKustoQuery.ps1agent-skills/finops-multitool/+references/agent-skills/cost-data-source/agent-skills/{power-bi-finops, cost-allocation, …}/Tests/Unit/Start-FinOpsMultitool.Tests.ps1+FOHubProvider.Tests.ps1docs-mslearn/.../powershell/multitool/+docs/multitool.md📸 Screenshots
Screenshots are in the public repo README.
📋 Checklist
🧪 How did you test this change?
🐳 Deploy to test?
N/A — standalone PowerShell tooling, not a template deployment.
🏷️ Do any of the following that apply?
📄 Did you update
docs/changelog.md?📖 Did you update documentation?
docs-mslearn/.../powershell/multitool/, a Jekyll landing page, overview/TOC/changelog entries, and the module README plus thefinops-multitoolandcost-data-sourceskills.