Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .build/BuildHelper/Start-PesterTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function Start-PesterTest
$rootPath = ((Get-Item -Path $PSScriptRoot).Parent.Parent).FullName
$pesterArgs = New-PesterConfiguration
$pesterArgs.Output.Verbosity = 'Detailed'
$pesterArgs.Run.Exit = $true
Comment thread
flanakin marked this conversation as resolved.

switch ($Type)
{
Expand Down
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ trim_trailing_whitespace = true

[*.ps1]
indent_size = 4
charset = utf-8-bom

[*.psm1]
indent_size = 4
charset = utf-8-bom

[*.psd1]
indent_size = 4
charset = utf-8-bom

[*.md]
max_line_length = off
Expand Down
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
},
"[powershell]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-vscode.powershell"
"editor.defaultFormatter": "ms-vscode.powershell",
"files.encoding": "utf8bom",
"files.trimTrailingWhitespace": true
},
"cSpell.words": [
"ADLS",
Expand Down
10 changes: 9 additions & 1 deletion docs-mslearn/toolkit/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: FinOps toolkit changelog
description: Review the latest features and enhancements in the FinOps toolkit, including updates to FinOps hubs, Power BI reports, and more.
author: MSBrett
ms.author: brettwil
ms.date: 02/23/2026
ms.date: 02/24/2026
ms.topic: reference
ms.service: finops
ms.subservice: finops-toolkit
Expand Down Expand Up @@ -42,6 +42,14 @@ The following section lists features and enhancements that are currently in deve
- Remote Hub configuration (storage URI, storage key, and purge protection) is now displayed in the Basics tab when Remote Hub mode is selected, making the mutual exclusivity clear.
- Data Explorer SKU and retention settings are now only visible when Azure Data Explorer mode is selected.

### [PowerShell module](powershell/powershell-commands.md) v14

- **Added**
- Added `-WhatIf` support for resource provider registration in [New-FinOpsCostExport](powershell/cost-management/New-FinOpsCostExport.md).
- **Fixed**
- Fixed inverted verbose logging in [Start-FinOpsCostExport](powershell/cost-management/Start-FinOpsCostExport.md) that showed blank dates when a date range was specified.
- Addressed minor lint warnings across PowerShell commands.

<br><a name="latest"></a>

## v14
Expand Down
6 changes: 3 additions & 3 deletions src/powershell/Private/Invoke-Rest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ function Invoke-Rest

# TODO: Remove after Az PowerShell 13.0
# Temporarily suppress warnings for Get-AzAccessToken
$prevWarningPreference = $WarningPreference
$WarningPreference = "SilentlyContinue"
$token = (Get-AzAccessToken -AsSecureString).Token | ConvertFrom-SecureString -AsPlainText
$prevWarningPreference = $WarningPreference
$WarningPreference = "SilentlyContinue"
$token = (Get-AzAccessToken -AsSecureString).Token | ConvertFrom-SecureString -AsPlainText
$WarningPreference = $prevWarningPreference

$arm = (Get-AzContext).Environment.ResourceManagerUrl
Expand Down
2 changes: 1 addition & 1 deletion src/powershell/Private/Save-FinOpsHubTemplate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function Save-FinOpsHubTemplate
Write-Information $LocalizedData.Hub_Deploy_02to021
$Version = '0.3'
}

# Get the version
if ($Version.ToLower() -eq 'latest')
{
Expand Down
5 changes: 0 additions & 5 deletions src/powershell/Private/Split-AzureResourceId.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ function Split-AzureResourceId
{
Write-Verbose "Parsing resource ID: '$Id'"

if (-not $Id)
{
return @{ ResourceId = $null }
}

# Add leading slash
if (-not $Id.StartsWith('/'))
{
Expand Down
2 changes: 1 addition & 1 deletion src/powershell/Public/Get-FinOpsCostExport.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Microsoft Corporation.
ο»Ώ# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

<#
Expand Down
5 changes: 4 additions & 1 deletion src/powershell/Public/Initialize-FinOpsHubDeployment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ function Initialize-FinOpsHubDeployment
[CmdletBinding(SupportsShouldProcess)]
param()

Register-FinOpsHubProviders -WhatIf:$WhatIfPreference | Out-Null
if ($PSCmdlet.ShouldProcess('FinOps hub resource providers', 'Register'))
{
Register-FinOpsHubProviders | Out-Null
}
}
42 changes: 24 additions & 18 deletions src/powershell/Public/New-FinOpsCostExport.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
function New-FinOpsCostExport
{
[Diagnostics.CodeAnalysis.SuppressMessage("PSReviewUnusedParameter", "", Justification = "False positive rule")]
[CmdletBinding(DefaultParameterSetName = "Scheduled")]
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "Scheduled")]
param
(
[Parameter(Mandatory = $true)]
Expand Down Expand Up @@ -418,8 +418,11 @@ function New-FinOpsCostExport
# Register the Microsoft.CostManagementExports RP
if ((Get-AzResourceProvider -ProviderNamespace Microsoft.CostManagementExports).RegistrationState -ne 'Registered')
{
Write-Verbose "Microsoft.CostManagementExports provider is not registered. Registering provider."
Register-AzResourceProvider -ProviderNamespace 'Microsoft.CostManagementExports'
if ($PSCmdlet.ShouldProcess('Microsoft.CostManagementExports', 'Register resource provider'))
{
Write-Verbose "Microsoft.CostManagementExports provider is not registered. Registering provider."
Register-AzResourceProvider -ProviderNamespace 'Microsoft.CostManagementExports'
}
}
else
{
Expand All @@ -446,23 +449,26 @@ function New-FinOpsCostExport
}

# Create/update export
$createResponse = Invoke-Rest -Method PUT -Uri $uri -Body $properties @commandDetails
if ($createResponse.Failure)
if ($PSCmdlet.ShouldProcess($Name, 'Create cost export'))
{
Write-Error "Unable to create export $Name in scope $Scope. Error: $($createResponse.Content.error.message) ($($createResponse.Content.error.code))" -ErrorAction Stop
return
}
$createResponse = Invoke-Rest -Method PUT -Uri $uri -Body $properties @commandDetails
if ($createResponse.Failure)
{
Comment thread
flanakin marked this conversation as resolved.
Write-Error "Unable to create export $Name in scope $Scope. Error: $($createResponse.Content.error.message) ($($createResponse.Content.error.code))" -ErrorAction Stop
return
}

# Run now if requested
if ($Backfill -gt 0 -and $OneTime -eq $false)
{
Start-FinOpsCostExport -Name $Name -Scope $Scope -Backfill $Backfill
}
elseif ($Execute -eq $true -or $OneTime -eq $true)
{
Start-FinOpsCostExport -Name $Name -Scope $Scope
}
# Run now if requested
if ($Backfill -gt 0 -and $OneTime -eq $false)
{
Start-FinOpsCostExport -Name $Name -Scope $Scope -Backfill $Backfill
}
elseif ($Execute -eq $true -or $OneTime -eq $true)
{
Start-FinOpsCostExport -Name $Name -Scope $Scope
}

return (Get-FinOpsCostExport -Name $Name -Scope $Scope -ApiVersion $ApiVersion)
return (Get-FinOpsCostExport -Name $Name -Scope $Scope -ApiVersion $ApiVersion)
}
}
}
2 changes: 1 addition & 1 deletion src/powershell/Public/Remove-FinOpsCostExport.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Microsoft Corporation.
ο»Ώ# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

<#
Expand Down
6 changes: 3 additions & 3 deletions src/powershell/Public/Remove-FinOpsHub.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ function Remove-FinOpsHub
}

# List all resources to be deleted
Write-Host "The following resources will be deleted:"
$resources | ForEach-Object { Write-Host "- $($_.ResourceId)" }
Write-Information "The following resources will be deleted:"
$resources | ForEach-Object { Write-Information "- $($_.ResourceId)" }
Comment thread
flanakin marked this conversation as resolved.

# Prompt the user for confirmation
if ($PSCmdlet.ShouldProcess($Name, 'DeleteFinOpsHub'))
Expand Down Expand Up @@ -165,7 +165,7 @@ function Remove-FinOpsHub
# Delete the resource
Write-Verbose -Message "Deleting resource: $($resource.Name)"
Remove-AzResource -ResourceId $resource.ResourceId -Force -ErrorAction Stop
Write-Host "Deleted resource: $($resource.Name)"
Write-Information "Deleted resource: $($resource.Name)"
}
catch
{
Expand Down
22 changes: 14 additions & 8 deletions src/powershell/Public/Start-FinOpsCostExport.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Microsoft Corporation.
ο»Ώ# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

<#
Expand Down Expand Up @@ -53,7 +53,7 @@
function Start-FinOpsCostExport
{
[OutputType([bool])]
[cmdletBinding()]
[CmdletBinding(SupportsShouldProcess)]
param
(
[Parameter(Mandatory = $true)]
Expand Down Expand Up @@ -140,12 +140,18 @@ function Start-FinOpsCostExport
$body = $null
if ($StartDate)
{
Write-Verbose "Exporting dates configured on the export definition"
Write-Verbose "Exporting $($StartDate) - $($EndDate)"
}
else
{
Write-Verbose "Exporting $($StartDate) - $($EndDate)"
Write-Verbose "Exporting dates configured on the export definition"
}

if (-not $PSCmdlet.ShouldProcess($Name, 'Run cost export'))
Comment thread
flanakin marked this conversation as resolved.
{
return $false
}

do
{
# Report progress
Expand Down Expand Up @@ -173,15 +179,15 @@ function Start-FinOpsCostExport
$firstDay = $StartDate
$lastDay = $EndDate
}

# Ensure end date is not in the future
$today = (Get-Date).ToUniversalTime().Date
if ($lastDay -ge $today)
{
Write-Verbose "Adjusting end date to yesterday as it cannot be in the future."
$lastDay = $today.AddDays(-1)
}

$body = @{ timePeriod = @{ from = $firstDay.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'"); to = $lastDay.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") } }
Write-Verbose "Executing $($firstDay.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")) to $($lastDay.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")) export $runpath"
}
Expand All @@ -195,7 +201,7 @@ function Start-FinOpsCostExport
{
Write-Verbose "Export executed successfully"
}
else
elseif (-not $response.Throttled)
{
Write-Error "Export failed to execute: ($($response.Content.error.code)) $($response.Content.error.message)"
}
Expand Down Expand Up @@ -224,7 +230,7 @@ function Start-FinOpsCostExport
{
# If not retrying, then track the success
$success = $success -and $response.Success

# Only increment month if not throttled
$monthToExport += 1
}
Expand Down
8 changes: 4 additions & 4 deletions src/powershell/Tests/Initialize-Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ Remove-Module FinOpsToolkit -ErrorAction SilentlyContinue
Import-Module -FullyQualifiedName "$PSScriptRoot/../FinOpsToolkit.psm1"

BeforeAll {
[Diagnostics.CodeAnalysis.SuppressMessage("PSAvoidGlobalVars", "", Justification = "Used for testing only")]
param()

# Bring the Monitor functions in to simplify debugging
. "$PSScriptRoot/../../scripts/Monitor.ps1"
}

$global:ftk_InitializeTests_Hubs_RequiredRPs = @( 'Microsoft.CostManagementExports', 'Microsoft.EventGrid' )
function Get-FinOpsHubRequiredResourceProvider
{
return @( 'Microsoft.CostManagementExports', 'Microsoft.EventGrid' )
}
4 changes: 2 additions & 2 deletions src/powershell/Tests/Integration/Hubs.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Describe 'Hubs' {
BeforeDiscovery {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$requiredRPs = $global:ftk_InitializeTests_Hubs_RequiredRPs
$requiredRPs = Get-FinOpsHubRequiredResourceProvider

# TODO: Automatically validate the last 3 versions only
# TODO: Automatically validate the last 3 versions only
Expand All @@ -17,7 +17,7 @@ Describe 'Hubs' {
BeforeAll {
# Must be duplicated because pre-discovery vars aren't accessible
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
$requiredRPs = $global:ftk_InitializeTests_Hubs_RequiredRPs
$requiredRPs = Get-FinOpsHubRequiredResourceProvider
}

Context 'Register-FinOpsHubProviders' {
Expand Down
8 changes: 8 additions & 0 deletions src/powershell/Tests/Unit/Deploy-FinOpsHub.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ InModuleScope 'FinOpsToolkit' {
}

Context 'Resource groups' {
BeforeAll {
Mock -CommandName 'Initialize-FinOpsHubDeployment'
}

It 'Should create RG if it does not exist' {
# Arrange
Mock -CommandName 'Get-AzResourceGroup' -MockWith { return $null }
Expand Down Expand Up @@ -93,6 +97,10 @@ InModuleScope 'FinOpsToolkit' {
}

Context 'Deploy' {
BeforeAll {
Mock -CommandName 'Initialize-FinOpsHubDeployment'
}

It 'Should deploy the template' {
# Arrange
Mock -CommandName 'Get-AzResourceGroup' -MockWith { return $rgName }
Expand Down