Skip to content
Open
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 on lines 15 to +17
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description indicates a changelog update is "not needed", but this PR changes user-facing PowerShell command behavior (ShouldProcess/OutputType/WhatIf semantics) and CI behavior. Per the repo guidance for PRs targeting dev, external-facing changes should be captured in docs-mslearn/toolkit/changelog.md (see docs-wiki/Branching-strategy.md around the dev requirements).

Copilot uses AI. Check for mistakes.

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
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
2 changes: 1 addition & 1 deletion src/powershell/Private/Split-AzureResourceId.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function Split-AzureResourceId

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

# Add leading slash
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
}
}
35 changes: 19 additions & 16 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 @@ -446,23 +446,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 on lines +449 to +453
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New-FinOpsCostExport now declares SupportsShouldProcess, but only the export creation is guarded by ShouldProcess. Resource-provider registration (Register-AzResourceProvider) earlier in the function can still run even when -WhatIf is specified. To honor -WhatIf, wrap provider registration in ShouldProcess as well or pass -WhatIf:$WhatIfPreference through to the registration cmdlet.

Copilot uses AI. Check for mistakes.
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 on lines +106 to +107
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from Write-Host to Write-Information changes default visibility (Information stream is typically suppressed), so users may no longer see the list of resources being deleted. If you want to preserve the previous always-visible behavior, consider using Write-Information -InformationAction Continue (or another stream like Write-Verbose/Write-Warning, depending on intent).

Copilot uses AI. Check for mistakes.

# 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
16 changes: 11 additions & 5 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 @@ -146,6 +146,12 @@ function Start-FinOpsCostExport
{
Write-Verbose "Exporting $($StartDate) - $($EndDate)"
}

if (-not $PSCmdlet.ShouldProcess($Name, 'Run cost export'))
Comment on lines 146 to +150
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verbose messaging around $StartDate looks inverted: when $StartDate is set, it currently says it's exporting dates configured on the export definition, and when $StartDate is not set it prints "Exporting - " (blank start/end). Swap the branches so the messages match the actual behavior.

Copilot uses AI. Check for mistakes.
{
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 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
5 changes: 4 additions & 1 deletion src/powershell/Tests/Unit/Deploy-FinOpsHub.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ InModuleScope 'FinOpsToolkit' {
BeforeAll {
function Get-AzResourceGroup {}
function New-AzResourceGroup {}
function New-AzResourceGroupDeployment {}
function New-AzResourceGroupDeployment {
param($TemplateFile, $TemplateParameterObject, $ResourceGroupName)
}

[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
$hubName = 'ftk-test-Deploy-FinOpsHub'
Expand Down Expand Up @@ -114,6 +116,7 @@ InModuleScope 'FinOpsToolkit' {
Mock -CommandName 'Get-AzResourceGroup' -MockWith { return @{ ResourceGroupName = $rgName } }
Mock -CommandName 'New-AzResourceGroup'
Mock -CommandName 'Save-FinOpsHubTemplate'
Mock -CommandName 'Initialize-FinOpsHubDeployment'
}

It 'Should throw if template file is not found' {
Expand Down
4 changes: 2 additions & 2 deletions src/powershell/Tests/Unit/New-FinOpsCostExport.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ InModuleScope 'FinOpsToolkit' {

# Assert
Assert-MockCalled -ModuleName FinOpsToolkit -CommandName 'Invoke-Rest' -Times 1 -ParameterFilter {
$Body.properties.deliveryInfo.destination.rootFolderPath -eq ($scopeWithColons -replace ':','-')
$Body.properties.deliveryInfo.destination.rootFolderPath -eq (($scopeWithColons -replace ':','-').Trim('/'))
}
}

Expand Down Expand Up @@ -308,7 +308,7 @@ InModuleScope 'FinOpsToolkit' {

# Assert
Assert-MockCalled -ModuleName FinOpsToolkit -CommandName 'Invoke-Rest' -Times 1 -ParameterFilter {
$Body.properties.deliveryInfo.destination.rootFolderPath -eq $scopeWithoutColons
$Body.properties.deliveryInfo.destination.rootFolderPath -eq $scopeWithoutColons.Trim('/')
}
}
}
Expand Down
Loading