From e232180b81f75fe255f24c4039c0944b17ca7726 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:34:56 +0100 Subject: [PATCH 01/12] initial commit --- .github/actions-config/.linkspector.yml | 18 ++++++++ .github/linters/.markdown-lint.yml | 35 ++++++++++++++++ .github/linters/.yaml-lint.yml | 6 +++ .github/workflows/code-review.yml | 56 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 .github/actions-config/.linkspector.yml create mode 100644 .github/linters/.markdown-lint.yml create mode 100644 .github/linters/.yaml-lint.yml create mode 100644 .github/workflows/code-review.yml diff --git a/.github/actions-config/.linkspector.yml b/.github/actions-config/.linkspector.yml new file mode 100644 index 0000000..2adcef3 --- /dev/null +++ b/.github/actions-config/.linkspector.yml @@ -0,0 +1,18 @@ +--- +dirs: + - ./ +#baseUrl: https://example.com +# ignorePatterns: +# - pattern: '^https://example.com/skip/.*$' +# - pattern: "^(ftp)://[^\\s/$?#]*\\.[^\\s]*$" +# replacementPatterns: +# - pattern: "(https?://example.com)/(\\w+)/(\\d+)" +# replacement: '$1/id/$3' +# - pattern: "\\[([^\\]]+)\\]\\((https?://example.com)/file\\)" +# replacement: '$1' +aliveStatusCodes: + - 200 + - 201 + - 204 +useGitIgnore: true +modifiedFilesOnly: false \ No newline at end of file diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml new file mode 100644 index 0000000..ad54fd2 --- /dev/null +++ b/.github/linters/.markdown-lint.yml @@ -0,0 +1,35 @@ +--- +########################### +########################### +## Markdown Linter rules ## +########################### +########################### + +# Linter rules doc: +# - https://github.com/DavidAnson/markdownlint +# +# Note: +# To comment out a single error: +# +# any violations you want +# +# + +############### +# Rules by id # +############### +MD004: false # Unordered list style +MD007: + indent: 2 # Unordered list indentation +MD013: + line_length: 900 # Line length 80 is far too short +MD026: + punctuation: ".,;:!。,;:" # List of not allowed +MD029: false # Ordered list item prefix +MD033: false # Allow inline HTML +MD036: false # Emphasis used instead of a heading + +################# +# Rules by tags # +################# +blank_lines: false # Error on blank lines \ No newline at end of file diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 0000000..cb6ca77 --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,6 @@ +--- +rules: + line-length: + max: 900 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: false \ No newline at end of file diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml new file mode 100644 index 0000000..df38a3b --- /dev/null +++ b/.github/workflows/code-review.yml @@ -0,0 +1,56 @@ +--- +name: Code Review - Linting & Link Checks + +on: + pull_request: + branches: + - main + workflow_dispatch: {} + +jobs: + lint: + name: Lint code base + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run github/super-linter + uses: super-linter/super-linter@v7.3.0 + env: + # Lint all code - disabled in as part of #262 + VALIDATE_ALL_CODEBASE: false + # Need to define main branch as default is set to master in super-linter + DEFAULT_BRANCH: main + # Enable setting the status of each individual linter run in the Checks section of a pull request + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # The following linter types will be enabled: + VALIDATE_JSON: true + VALIDATE_MARKDOWN: true + VALIDATE_POWERSHELL: true + VALIDATE_YAML: true + #YAMLLINT_CONFIG_FILE: .github/linters/.yamllint.yml + #VALIDATE_EDITORCONFIG: true + # Disable errors to only generate a report + #DISABLE_ERRORS: true + + markdown-link-check: + name: Markdown Link Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@master + with: + fetch-depth: 0 + + - name: Run linkspector + uses: umbrelladocs/action-linkspector@v1.3.4 + with: + github_token: ${{ secrets.github_token }} + reporter: github-pr-review + fail_on_error: true + config_file: ".github/actions-config/.linkspector.yml" \ No newline at end of file From 77732fa53d8f353f0f6b14cbffd8e7f8c196abe5 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:58:37 +0100 Subject: [PATCH 02/12] Add to check psscriptanalyzer functionality --- 1-Collect/Get-AzureServices.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/1-Collect/Get-AzureServices.ps1 b/1-Collect/Get-AzureServices.ps1 index 6336b1f..75287c5 100644 --- a/1-Collect/Get-AzureServices.ps1 +++ b/1-Collect/Get-AzureServices.ps1 @@ -159,6 +159,7 @@ Function Invoke-CmdLine { Set-Variable -Name $outputVarName -Value $cmdResult -Scope Script } + function Get-rType { param ( [Parameter(Mandatory = $true)] [string] $filePath, From c0c45e31b9cb861828914c13021902820d376ff5 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:05:12 +0100 Subject: [PATCH 03/12] test --- 1-Collect/Get-AzureServices.Tests.ps1 | 1 + 1-Collect/Get-AzureServices.ps1 | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/1-Collect/Get-AzureServices.Tests.ps1 b/1-Collect/Get-AzureServices.Tests.ps1 index a482b97..8657728 100644 --- a/1-Collect/Get-AzureServices.Tests.ps1 +++ b/1-Collect/Get-AzureServices.Tests.ps1 @@ -6,6 +6,7 @@ Describe "Get-AzureServices.ps1 Tests" { Context "Parameter Validation" { It "Should accept valid scopeType values" { $validScopes = @('singleSubscription', 'resourceGroup', 'multiSubscription') + # Parse the script to check parameter validation $scriptContent = Get-Content $scriptPath -Raw diff --git a/1-Collect/Get-AzureServices.ps1 b/1-Collect/Get-AzureServices.ps1 index 75287c5..6336b1f 100644 --- a/1-Collect/Get-AzureServices.ps1 +++ b/1-Collect/Get-AzureServices.ps1 @@ -159,7 +159,6 @@ Function Invoke-CmdLine { Set-Variable -Name $outputVarName -Value $cmdResult -Scope Script } - function Get-rType { param ( [Parameter(Mandatory = $true)] [string] $filePath, From fddb382a5b37c70ad457c5a9539bb5e7eba8e057 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:14:39 +0100 Subject: [PATCH 04/12] fix vars --- 1-Collect/Get-AzureServices.Tests.ps1 | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/1-Collect/Get-AzureServices.Tests.ps1 b/1-Collect/Get-AzureServices.Tests.ps1 index 8657728..0352f4b 100644 --- a/1-Collect/Get-AzureServices.Tests.ps1 +++ b/1-Collect/Get-AzureServices.Tests.ps1 @@ -1,16 +1,17 @@ BeforeAll { $scriptPath = "$PSScriptRoot\Get-AzureServices.ps1" + $script:scriptContent = Get-Content $scriptPath -Raw } Describe "Get-AzureServices.ps1 Tests" { Context "Parameter Validation" { It "Should accept valid scopeType values" { $validScopes = @('singleSubscription', 'resourceGroup', 'multiSubscription') - - - # Parse the script to check parameter validation - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'ValidateSet.*singleSubscription.*resourceGroup.*multiSubscription' + + # Verify each scope is present in the script's ValidateSet + foreach ($scope in $validScopes) { + $script:scriptContent | Should -Match $scope + } } It "Should have required parameters defined" { @@ -24,26 +25,22 @@ Describe "Get-AzureServices.ps1 Tests" { } It "Should have default values for output files" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'fullOutputFile.*=.*"resources.json"' - $scriptContent | Should -Match 'summaryOutputFile.*=.*"summary.json"' + $script:scriptContent | Should -Match 'fullOutputFile.*=.*"resources.json"' + $script:scriptContent | Should -Match 'summaryOutputFile.*=.*"summary.json"' } } Context "Function Definitions" { It "Should define Get-Property function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Get-Property' + $script:scriptContent | Should -Match 'Function Get-Property' } It "Should define Get-SingleData function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Get-SingleData' + $script:scriptContent | Should -Match 'Function Get-SingleData' } It "Should define Get-Method function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Get-Method' + $script:scriptContent | Should -Match 'Function Get-Method' } } From d25bb5df3c628d6db652861c74984c838989953c Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:37:51 +0100 Subject: [PATCH 05/12] fix script --- 1-Collect/Get-AzureServices.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/1-Collect/Get-AzureServices.Tests.ps1 b/1-Collect/Get-AzureServices.Tests.ps1 index 0352f4b..78ca028 100644 --- a/1-Collect/Get-AzureServices.Tests.ps1 +++ b/1-Collect/Get-AzureServices.Tests.ps1 @@ -17,7 +17,6 @@ Describe "Get-AzureServices.ps1 Tests" { It "Should have required parameters defined" { $scriptAst = [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$null, [ref]$null) $params = $scriptAst.FindAll({$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true) - $paramNames = $params | ForEach-Object { $_.Name.VariablePath.UserPath } $paramNames | Should -Contain 'scopeType' $paramNames | Should -Contain 'fullOutputFile' From def9c5cd58ea3a26963a19d119073dd5d42d2d59 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:45:26 +0100 Subject: [PATCH 06/12] fix --- 1-Collect/Get-RessourcesFromAM.Tests.ps1 | 31 ++++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/1-Collect/Get-RessourcesFromAM.Tests.ps1 b/1-Collect/Get-RessourcesFromAM.Tests.ps1 index de2405e..c95f7a3 100644 --- a/1-Collect/Get-RessourcesFromAM.Tests.ps1 +++ b/1-Collect/Get-RessourcesFromAM.Tests.ps1 @@ -1,13 +1,13 @@ BeforeAll { $scriptPath = "$PSScriptRoot\Get-RessourcesFromAM.ps1" + $script:scriptContent = Get-Content $scriptPath -Raw } Describe "Get-RessourcesFromAM.ps1 Tests" { Context "Parameter Validation" { It "Should require filePath parameter" { $scriptAst = [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$null, [ref]$null) - $params = $scriptAst.FindAll({$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true) - + $params = $scriptAst.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true) $filePathParam = $params | Where-Object { $_.Name.VariablePath.UserPath -eq 'filePath' } $filePathParam | Should -Not -BeNullOrEmpty @@ -18,9 +18,7 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { } $isMandatory | Should -Not -BeNullOrEmpty } - It "Should have default output file" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'outputFile.*=.*".*summary\.json"' } } @@ -30,6 +28,7 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { Mock Test-Path { return $false } # Test would validate file existence check + $true | Should -Be $true } @@ -46,12 +45,12 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { $expected = "Premium_LRS" $result = switch -Wildcard ($testSku) { - "PremiumV2*" { "PremiumV2_LRS"; break } - "Premium*" { "Premium_LRS"; break } - "StandardSSD*" { "StandardSSD_LRS"; break } - "Standard*" { "Standard_LRS"; break } - "Ultra*" { "UltraSSD_LRS"; break } - default { "Unknown" } + "PremiumV2*" { "PremiumV2_LRS"; break } + "Premium*" { "Premium_LRS"; break } + "StandardSSD*" { "StandardSSD_LRS"; break } + "Standard*" { "Standard_LRS"; break } + "Ultra*" { "UltraSSD_LRS"; break } + default { "Unknown" } } $result | Should -Be $expected @@ -61,12 +60,12 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { $testSku = "StandardSSD E10" $result = switch -Wildcard ($testSku) { - "PremiumV2*" { "PremiumV2_LRS"; break } - "Premium*" { "Premium_LRS"; break } - "StandardSSD*" { "StandardSSD_LRS"; break } - "Standard*" { "Standard_LRS"; break } - "Ultra*" { "UltraSSD_LRS"; break } - default { "Unknown" } + "PremiumV2*" { "PremiumV2_LRS"; break } + "Premium*" { "Premium_LRS"; break } + "StandardSSD*" { "StandardSSD_LRS"; break } + "Standard*" { "Standard_LRS"; break } + "Ultra*" { "UltraSSD_LRS"; break } + default { "Unknown" } } $result | Should -Be "StandardSSD_LRS" From b10372b94c3b726ceab460711558f0a9a61ac80e Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:07:53 +0100 Subject: [PATCH 07/12] whitepace --- 1-Collect/Get-RessourcesFromAM.Tests.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/1-Collect/Get-RessourcesFromAM.Tests.ps1 b/1-Collect/Get-RessourcesFromAM.Tests.ps1 index c95f7a3..f60568a 100644 --- a/1-Collect/Get-RessourcesFromAM.Tests.ps1 +++ b/1-Collect/Get-RessourcesFromAM.Tests.ps1 @@ -10,10 +10,10 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { $params = $scriptAst.FindAll({ $args[0] -is [System.Management.Automation.Language.ParameterAst] }, $true) $filePathParam = $params | Where-Object { $_.Name.VariablePath.UserPath -eq 'filePath' } $filePathParam | Should -Not -BeNullOrEmpty - + # Check if parameter is mandatory - $isMandatory = $filePathParam.Attributes | Where-Object { - $_.TypeName.Name -eq 'Parameter' -and + $isMandatory = $filePathParam.Attributes | Where-Object { + $_.TypeName.Name -eq 'Parameter' -and $_.NamedArguments.ArgumentName -contains 'Mandatory' } $isMandatory | Should -Not -BeNullOrEmpty @@ -26,9 +26,9 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { Context "Excel File Processing" { It "Should check for Excel file existence" { Mock Test-Path { return $false } - + # Test would validate file existence check - + $true | Should -Be $true } @@ -43,7 +43,7 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { It "Should convert Premium disk SKU correctly" { $testSku = "Premium SSD P30" $expected = "Premium_LRS" - + $result = switch -Wildcard ($testSku) { "PremiumV2*" { "PremiumV2_LRS"; break } "Premium*" { "Premium_LRS"; break } @@ -52,13 +52,13 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { "Ultra*" { "UltraSSD_LRS"; break } default { "Unknown" } } - + $result | Should -Be $expected } It "Should convert StandardSSD disk SKU correctly" { $testSku = "StandardSSD E10" - + $result = switch -Wildcard ($testSku) { "PremiumV2*" { "PremiumV2_LRS"; break } "Premium*" { "Premium_LRS"; break } @@ -67,7 +67,7 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { "Ultra*" { "UltraSSD_LRS"; break } default { "Unknown" } } - + $result | Should -Be "StandardSSD_LRS" } } From 33f5bc0b651938cb028ecbe20a7b1291c1d1004e Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:20:18 +0100 Subject: [PATCH 08/12] whitespace --- 1-Collect/Get-RessourcesFromAM.Tests.ps1 | 2 +- .../Get-AvailabilityInformation.ps1 | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/1-Collect/Get-RessourcesFromAM.Tests.ps1 b/1-Collect/Get-RessourcesFromAM.Tests.ps1 index f60568a..caf6d8f 100644 --- a/1-Collect/Get-RessourcesFromAM.Tests.ps1 +++ b/1-Collect/Get-RessourcesFromAM.Tests.ps1 @@ -28,7 +28,7 @@ Describe "Get-RessourcesFromAM.ps1 Tests" { Mock Test-Path { return $false } # Test would validate file existence check - + $true | Should -Be $true } diff --git a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 index 1f4f3c7..106986b 100644 --- a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 +++ b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 @@ -71,7 +71,7 @@ Function Import-Provider { # This function retrieves all available Azure providers and their resource types, including locations. Write-Output "Retrieving all available providers" | Out-Host $Response = (Invoke-AzRestMethod -Uri "$uriRoot/providers?api-version=2021-04-01" -Method Get).Content | ConvertFrom-Json -depth 100 - + # Transform the response to the desired structure and remove unwanted properties $Providers = foreach ($provider in $Response.value) { # Build an array of resource types using plain hashtables @@ -224,7 +224,7 @@ Function Expand-NestedCollection { } } $script:SKUs = $lSkus - } + } } Function Get-ResourceType { @@ -247,7 +247,7 @@ Function Get-ResourceType { $baseObject = New-Object psobject Add-Member -InputObject $baseObject -MemberType NoteProperty -Name "regionCode" -Value $region $uri = $uri01 -f $subscriptionId, $region - + "Invoke-AzRestMethod -Uri $uri -Method Get" $Response = (Invoke-AzRestMethod -Uri $uri -Method Get).Content | ConvertFrom-Json -depth 100 If ($response.error.code -ne 'NoRegisteredProviderFound') { @@ -256,14 +256,14 @@ Function Get-ResourceType { $Response = $Response.Value } Expand-NestedCollection -InputObjects $response -Schema $propertyFilter - Add-Member -InputObject $baseObject -MemberType NoteProperty -Name "skus" -Value $Skus + Add-Member -InputObject $baseObject -MemberType NoteProperty -Name "skus" -Value $Skus } else { "No SKUs found for region $region" $baseObject | Add-Member -MemberType NoteProperty -Name "skus" -Value @() } $outArray += $baseObject - } + } } Else { "This api call gets all skus for all regions in one call" @@ -279,7 +279,7 @@ Function Get-ResourceType { $skusForRegion = $Response | Where-Object { $_.locations -contains $region } If ($skusForRegion) { Expand-NestedCollection -InputObjects $skusForRegion -Schema $propertyFilter - Add-Member -InputObject $baseObject -MemberType NoteProperty -Name "skus" -Value $Skus + Add-Member -InputObject $baseObject -MemberType NoteProperty -Name "skus" -Value $Skus } else { "No SKUs found for region $region" @@ -391,7 +391,7 @@ $Regions_All = Import-Region $Resources_All = (Import-Provider -uriRoot $uriRoot).Data # # Import current environment data from the summary file of script 1-Collect $AvailabilityMapping = (Import-CurrentEnvironment).Data -# # Expand the current implementation to show availability across all Azure regions +# # Expand the current implementation to show availability across all Azure regions Expand-CurrentToGlobal # # Initialize SKU to region mapping for resources that have implemented SKUs Initialize-SKU2Region @@ -404,27 +404,27 @@ Foreach ($cResource in $overAllObj) { $availScope = $availabilityMapping | Where-Object { $psitem.ResourceType -eq $cResource.ResourceType } $cResource.ResourceType Foreach ($sku in $availScope.ImplementedSkus) { - Foreach ($region in $cResource.Availability) { - $regionCode = $region.RegionCode; + Foreach ($region in $cResource.Availability) { + $regionCode = $region.RegionCode; If ($region.skus.count -ne 0) { $skuFound = $region.skus | Where-Object { Compare-ObjectsStrict -Object1 ([PSCustomObject]$PSItem) -Object2 $sku -verbose } - If ($skuFound -ne $null) { + If ($skuFound -ne $null) { "SUCCESS: SKU $sku found in region $regionCode"; Update-SKUProperties -RegionName $regionCode -Object $availScope -availabilityStatus true -sku $sku - } - else { - "SKU $sku not found in region $regionCode"; + } + else { + "SKU $sku not found in region $regionCode"; Update-SKUProperties -RegionName $regionCode -Object $availScope -availabilityStatus false -sku $sku } } else { "No SKUs found for region $regionCode"; } - } + } } $cResource.ResourceType } Out-JSONFile -Data $AvailabilityMapping -fileName "Availability_Mapping.json" $endtime = Get-Date $minutes = (New-TimeSpan -Start $starttime -End $endtime).TotalMinutes -Write-Output "Ending script $endtime after $minutes minutes" \ No newline at end of file +Write-Output "Ending script $endtime after $minutes minutes" From 4d65649f1d2cc9bc96b0355270cf9ef340808101 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:41:13 +0100 Subject: [PATCH 09/12] psscriptanalyzer --- .../Get-AvailabilityInformation.ps1 | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 index 106986b..e369edb 100644 --- a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 +++ b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 @@ -40,7 +40,7 @@ function Out-JSONFile { $Data | ConvertTo-Json -Depth 100 | Out-File -FilePath "$(Get-Location)\$FileName" -Force } -Function Convert-LocationsToRegionCodes { +Function Convert-LocationsToRegionCode { param ( [Parameter(Mandatory)][Object]$Data, [Parameter(Mandatory)][hashtable]$RegionMap @@ -89,7 +89,7 @@ Function Import-Provider { } } # Convert location display names to region codes using the provided region map - $Providers = Convert-LocationsToRegionCodes -Data $Providers -RegionMap $Regions_All.Map + $Providers = Convert-LocationsToRegionCode -Data $Providers -RegionMap $Regions_All.Map # Save providers to a JSON file Out-JSONFile -Data $Providers -fileName "Azure_Providers.json" return @{ @@ -107,9 +107,9 @@ function Import-Region { $ConsolidatedRegions = @() $TotalRegions = $Response.value.Count $CurrentRegionIndex = 0 - foreach ($Region in $Response.value | where { $_.metadata.regionType -eq "Physical" }) { - #Write-Output "$($region.name) is regionType: $($region.metadata.regionType)" | out-host} - $CurrentRegionIndex++ + foreach ($Region in $Response.value | Where-Object { $_.metadata.regionType -eq "Physical" }) { + #Write-Output "$($region.name) is regionType: $($region.metadata.regionType)" | out-host} + $CurrentRegionIndex++ Write-Output (" Removing information for region {0:D03} of {1:D03}: {2}" -f $CurrentRegionIndex, $TotalRegions, $Region.displayName) | Out-Host if ($Region.metadata ) { $region.metadata.regionType -eq "Physical" @@ -143,7 +143,7 @@ function Import-Region { } } -Function Get-ResourceTypeParameters { +Function Get-ResourceTypeParameter { param ( [Parameter(Mandatory = $true)][string]$ResourceType ) @@ -233,7 +233,7 @@ Function Get-ResourceType { ) $resourceObject = New-Object psobject Add-Member -InputObject $resourceObject -MemberType NoteProperty -Name "ResourceType" -Value $ResourceType - $resourceProps = Get-ResourceTypeParameters -ResourceType $ResourceType + $resourceProps = Get-ResourceTypeParameter -ResourceType $ResourceType if ($resourceProps) { "Processing resource type: $ResourceType" $outputFile = ($ResourceType -replace '[./]', '_') + ".json" @@ -364,7 +364,7 @@ function Initialize-SKU2Region { } } } -function Update-SKUProperties { +function Update-SKUPropertySet { param ( [Parameter(Mandatory)] [string]$RegionName, [Parameter(Mandatory)] [pscustomobject]$Object, @@ -408,13 +408,13 @@ Foreach ($cResource in $overAllObj) { $regionCode = $region.RegionCode; If ($region.skus.count -ne 0) { $skuFound = $region.skus | Where-Object { Compare-ObjectsStrict -Object1 ([PSCustomObject]$PSItem) -Object2 $sku -verbose } - If ($skuFound -ne $null) { - "SUCCESS: SKU $sku found in region $regionCode"; - Update-SKUProperties -RegionName $regionCode -Object $availScope -availabilityStatus true -sku $sku + If ($null -ne $skuFound) { + "SUCCESS: SKU $sku found in region $regionCode"; + Update-SKUPropertySet -RegionName $regionCode -Object $availScope -availabilityStatus true -sku $sku } else { "SKU $sku not found in region $regionCode"; - Update-SKUProperties -RegionName $regionCode -Object $availScope -availabilityStatus false -sku $sku + Update-SKUPropertySet -RegionName $regionCode -Object $availScope -availabilityStatus false -sku $sku } } else { From b754a71c6c21fe71fdabb245743abe76754a9b08 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:52:02 +0100 Subject: [PATCH 10/12] Commit --- .../Get-AvailabilityInformation.Tests.ps1 | 9 +-------- .../Get-AvailabilityInformation.ps1 | 10 +++++++--- 2-AvailabilityCheck/Get-Region.Tests.ps1 | 20 +++++++++---------- .../Get-CostInformation.Tests.ps1 | 7 +++---- .../Perform-RegionComparison.Tests.ps1 | 13 ++++++------ 5 files changed, 26 insertions(+), 33 deletions(-) diff --git a/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 b/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 index 7758188..842c520 100644 --- a/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 +++ b/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 @@ -1,48 +1,41 @@ BeforeAll { $scriptPath = "$PSScriptRoot\Get-AvailabilityInformation.ps1" + $script:scriptContent = Get-Content $scriptPath -Raw } Describe "Get-AvailabilityInformation.ps1 Tests" { Context "Function Definitions" { It "Should define Out-JSONFile function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'function Out-JSONFile' } It "Should define Convert-LocationsToRegionCodes function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'Function Convert-LocationsToRegionCodes' } It "Should define Import-Provider function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'Function Import-Provider' } It "Should define Import-Region function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'function Import-Region' } It "Should define Get-Property function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'Function Get-Property' } It "Should define Expand-NestedCollection function" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'Function Expand-NestedCollection' } } Context "Logic Validation" { It "Should have region map creation logic" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'RegionMap' } It "Should have SKU availability checking logic" { - $scriptContent = Get-Content $scriptPath -Raw $scriptContent | Should -Match 'available' } } diff --git a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 index e369edb..c3e96d3 100644 --- a/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 +++ b/2-AvailabilityCheck/Get-AvailabilityInformation.ps1 @@ -298,6 +298,9 @@ Function Get-ResourceType { } function Import-CurrentEnvironment { + param ( + [Parameter(Mandatory = $true)][string]$SummaryFilePath + ) # Check if the summary file exists and load it if (Test-Path $SummaryFilePath) { Write-Output " Loading summary file: $SummaryFilePath" | Out-Host @@ -365,6 +368,7 @@ function Initialize-SKU2Region { } } function Update-SKUPropertySet { + [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory)] [string]$RegionName, [Parameter(Mandatory)] [pscustomobject]$Object, @@ -372,10 +376,10 @@ function Update-SKUPropertySet { [Parameter(Mandatory)] [PSCustomObject]$sku ) $region = $Object.AllRegions | Where-Object { $_.region -eq $RegionName } - Write-Host "Updating SKUs in region '$RegionName'..." + Write-Output "Updating SKUs in region '$RegionName'..." foreach ($targetSku in $region.SKUs) { if (Compare-ObjectsStrict -Object1 $sku -Object2 $targetSku) { - Write-Host "Setting availability of '$($targetSku.Name)' to '$availabilityStatus' in region '$RegionName'" + Write-Output "Setting availability of '$($targetSku.Name)' to '$availabilityStatus' in region '$RegionName'" Add-Member -InputObject $targetSku -MemberType NoteProperty -Name "available" -Value $availabilityStatus -Force } } @@ -390,7 +394,7 @@ $script:overAllObj = @() $Regions_All = Import-Region $Resources_All = (Import-Provider -uriRoot $uriRoot).Data # # Import current environment data from the summary file of script 1-Collect -$AvailabilityMapping = (Import-CurrentEnvironment).Data +$AvailabilityMapping = (Import-CurrentEnvironment -SummaryFilePath $summaryFilePath).Data # # Expand the current implementation to show availability across all Azure regions Expand-CurrentToGlobal # # Initialize SKU to region mapping for resources that have implemented SKUs diff --git a/2-AvailabilityCheck/Get-Region.Tests.ps1 b/2-AvailabilityCheck/Get-Region.Tests.ps1 index b332ad8..69e4e03 100644 --- a/2-AvailabilityCheck/Get-Region.Tests.ps1 +++ b/2-AvailabilityCheck/Get-Region.Tests.ps1 @@ -1,18 +1,16 @@ BeforeAll { - $scriptPath = "$PSScriptRoot\Get-Region.ps1" + $script:scriptContent = Get-Content -Path "$PSScriptRoot\Get-Region.ps1" -Raw } Describe "Get-Region.ps1 Tests" { Context "Parameter Validation" { It "Should require Region parameter" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match '\[Parameter\(Mandatory\s*=\s*\$true' - $scriptContent | Should -Match '\[string\]\$Region' + $script:scriptContent | Should -Match '\[Parameter\(Mandatory\s*=\s*\$true' + $script:scriptContent | Should -Match '\[string\]\$Region' } It "Should have HelpMessage for Region parameter" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'HelpMessage.*region' + $script:scriptContent | Should -Match 'HelpMessage.*region' } } @@ -39,7 +37,7 @@ Describe "Get-Region.ps1 Tests" { ) } ) - + $filtered = $mockData[0].AllRegions | Where-Object { $_.region -eq $testRegion } $filtered.Count | Should -Be 1 $filtered[0].region | Should -Be $testRegion @@ -50,7 +48,7 @@ Describe "Get-Region.ps1 Tests" { It "Should generate region-specific output filename" { $testRegion = "eastus" $expectedFile = "Availability_Mapping_eastus.json" - + $outputFile = "Availability_Mapping_" + ($testRegion -replace "\s", "_") + ".json" $outputFile | Should -Be $expectedFile } @@ -58,7 +56,7 @@ Describe "Get-Region.ps1 Tests" { It "Should handle region names with spaces" { $testRegion = "East US" $expectedFile = "Availability_Mapping_East_US.json" - + $outputFile = "Availability_Mapping_" + ($testRegion -replace "\s", "_") + ".json" $outputFile | Should -Be $expectedFile } @@ -72,10 +70,10 @@ Describe "Get-Region.ps1 Tests" { [PSCustomObject]@{ region = "eastus"; available = "true" } ) } - + $regionMatch = $mockResource.AllRegions | Where-Object { $_.region -eq "eastus" } $mockResource | Add-Member -Force -MemberType NoteProperty -Name SelectedRegion -Value $regionMatch - + $mockResource.SelectedRegion | Should -Not -BeNullOrEmpty $mockResource.SelectedRegion.region | Should -Be "eastus" } diff --git a/3-CostInformation/Get-CostInformation.Tests.ps1 b/3-CostInformation/Get-CostInformation.Tests.ps1 index 9fa4628..860eea7 100644 --- a/3-CostInformation/Get-CostInformation.Tests.ps1 +++ b/3-CostInformation/Get-CostInformation.Tests.ps1 @@ -1,5 +1,4 @@ BeforeAll { - $scriptPath = "$PSScriptRoot\Get-CostInformation.ps1" } Describe "Get-CostInformation.ps1 Tests" { @@ -7,7 +6,7 @@ Describe "Get-CostInformation.ps1 Tests" { It "Should have default date parameters" { $defaultStartDate = (Get-Date).AddMonths(-1).ToString("yyyy-MM-01") $defaultEndDate = (Get-Date).AddDays(-1 * (Get-Date).Day).ToString("yyyy-MM-dd") - + $defaultStartDate | Should -Match "^\d{4}-\d{2}-01$" $defaultEndDate | Should -Match "^\d{4}-\d{2}-\d{2}$" } @@ -93,11 +92,11 @@ Describe "Get-CostInformation.ps1 Tests" { It "Should limit to first subscription in test mode" { $testSubscriptions = @("sub1", "sub2", "sub3") $testMode = $true - + if ($testMode) { $testSubscriptions = @($testSubscriptions[0]) } - + $testSubscriptions.Count | Should -Be 1 } } diff --git a/3-CostInformation/Perform-RegionComparison.Tests.ps1 b/3-CostInformation/Perform-RegionComparison.Tests.ps1 index abd7fa5..e5ddc97 100644 --- a/3-CostInformation/Perform-RegionComparison.Tests.ps1 +++ b/3-CostInformation/Perform-RegionComparison.Tests.ps1 @@ -1,5 +1,4 @@ BeforeAll { - $scriptPath = "$PSScriptRoot\Perform-RegionComparison.ps1" } Describe "Perform-RegionComparison.ps1 Tests" { @@ -31,7 +30,7 @@ Describe "Perform-RegionComparison.ps1 Tests" { It "Should set correct batch sizes" { $meterIdBatchSize = 10 $regionBatchSize = 10 - + $meterIdBatchSize | Should -Be 10 $regionBatchSize | Should -Be 10 } @@ -77,7 +76,7 @@ Describe "Perform-RegionComparison.ps1 Tests" { $origPrice = 100.00 $targetPrice = 90.00 $difference = $targetPrice - $origPrice - + $difference | Should -Be -10 } @@ -85,20 +84,20 @@ Describe "Perform-RegionComparison.ps1 Tests" { $origPrice = 100.00 $targetPrice = 90.00 $percentage = [math]::Round((($targetPrice - $origPrice) / $origPrice), 2) - + $percentage | Should -Be -0.1 } It "Should handle zero original price" { $origPrice = 0 $targetPrice = 10.00 - + if ($origPrice -ne 0) { $percentage = ($targetPrice - $origPrice) / $origPrice } else { $percentage = $null } - + $percentage | Should -BeNullOrEmpty } } @@ -133,7 +132,7 @@ Describe "Perform-RegionComparison.ps1 Tests" { It "Should detect UoM mismatches" { $origUoM = "1 Hour" $targetUoM = "100 Hours" - + $mismatch = $origUoM -ne $targetUoM $mismatch | Should -Be $true } From 936c77659089e24824654e7be1502de884fec26b Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:57:05 +0100 Subject: [PATCH 11/12] get-report --- 7-Report/Get-Report.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/7-Report/Get-Report.ps1 b/7-Report/Get-Report.ps1 index c5f2aa5..3f6c8b9 100644 --- a/7-Report/Get-Report.ps1 +++ b/7-Report/Get-Report.ps1 @@ -18,6 +18,8 @@ param( ) Function Set-ColumnColor { + [CmdletBinding(SupportsShouldProcess = $true)] + param( [Parameter(Mandatory = $true)] [object]$startColumn, [Parameter(Mandatory = $true)] [string[]]$cellValGreen, @@ -44,6 +46,7 @@ Function Set-ColumnColor { } Function New-Worksheet { + [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)][string]$WorksheetName, [Parameter(Mandatory = $true)][int]$LastColumnNumber, @@ -94,6 +97,8 @@ Function Get-Props { } Function Set-SvcAvailReportObj { + [CmdletBinding(SupportsShouldProcess = $true)] + param ( [string]$resourceType, [int]$resourceCount, @@ -112,7 +117,7 @@ Function Set-SvcAvailReportObj { elseif ($skuAvailability -eq "") { $skuAvailability = "NotCoveredByScript" } - + $reportItem = [PSCustomObject]@{ ResourceType = $resourceType ResourceCount = $resourceCount From 18bbe9b41bff57c8a63275e3dfc5fafe2dff20f2 Mon Sep 17 00:00:00 2001 From: Jan Faurskov <22591930+jfaurskov@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:11:19 +0100 Subject: [PATCH 12/12] update --- .../Get-AvailabilityInformation.Tests.ps1 | 2 +- 7-Report/Get-Report.Tests.ps1 | 30 ++++++++----------- support/AzureRetailPrice.ps1 | 6 ++-- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 b/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 index 842c520..a3218a1 100644 --- a/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 +++ b/2-AvailabilityCheck/Get-AvailabilityInformation.Tests.ps1 @@ -10,7 +10,7 @@ Describe "Get-AvailabilityInformation.ps1 Tests" { } It "Should define Convert-LocationsToRegionCodes function" { - $scriptContent | Should -Match 'Function Convert-LocationsToRegionCodes' + $scriptContent | Should -Match 'Function Convert-LocationsToRegionCode' } It "Should define Import-Provider function" { diff --git a/7-Report/Get-Report.Tests.ps1 b/7-Report/Get-Report.Tests.ps1 index 969d6c1..27ce08e 100644 --- a/7-Report/Get-Report.Tests.ps1 +++ b/7-Report/Get-Report.Tests.ps1 @@ -1,5 +1,6 @@ BeforeAll { - $scriptPath = "$PSScriptRoot\Get-Report.ps1" + $script:scriptPath = "$PSScriptRoot\Get-Report.ps1" + $script:scriptContent = Get-Content $script:scriptPath -Raw } Describe "Get-Report.ps1 Tests" { @@ -17,23 +18,19 @@ Describe "Get-Report.ps1 Tests" { Context "Helper Functions" { It "Should define Get-Props function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Get-Props' + $script:scriptContent | Should -Match 'Function Get-Props' } It "Should define Set-ColumnColor function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Set-ColumnColor' + $script:scriptContent | Should -Match 'Function Set-ColumnColor' } It "Should define New-Worksheet function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function New-Worksheet' + $script:scriptContent | Should -Match 'Function New-Worksheet' } It "Should define Set-SvcAvailReportObj function" { - $scriptContent = Get-Content $scriptPath -Raw - $scriptContent | Should -Match 'Function Set-SvcAvailReportObj' + $script:scriptContent | Should -Match 'Function Set-SvcAvailReportObj' } } @@ -67,7 +64,7 @@ Describe "Get-Report.ps1 Tests" { It "Should generate timestamped filename" { $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $filename = "Availability_Report_$timestamp.xlsx" - + $filename | Should -Match "^Availability_Report_\d{8}_\d{6}\.xlsx$" } } @@ -76,7 +73,7 @@ Describe "Get-Report.ps1 Tests" { It "Should define header colors" { $headerColor = "RoyalBlue" $headerFontColor = "White" - + $headerColor | Should -Be "RoyalBlue" $headerFontColor | Should -Be "White" } @@ -85,7 +82,7 @@ Describe "Get-Report.ps1 Tests" { $greenValues = @("Available", "N/A") $redValues = @("Not available") $yellowValues = @("NotCoveredByScript") - + $greenValues | Should -Contain "Available" $redValues | Should -Contain "Not available" $yellowValues | Should -Contain "NotCoveredByScript" @@ -111,16 +108,15 @@ Describe "Get-Report.ps1 Tests" { [PSCustomObject]@{ OrigMeterId = "meter1"; Region = "westus"; RetailPrice = 12.0 } [PSCustomObject]@{ OrigMeterId = "meter2"; Region = "eastus"; RetailPrice = 20.0 } ) - + $uniqueMeters = $mockData | Select-Object -Property OrigMeterId -Unique $uniqueMeters.Count | Should -Be 2 } It "Should create regional pricing properties" { $region = "eastus" - $price = 10.5 $propertyName = "$region-RetailPrice" - + $propertyName | Should -Be "eastus-RetailPrice" } @@ -137,14 +133,14 @@ Describe "Get-Report.ps1 Tests" { It "Should handle N/A SKUs" { $implementedSkus = @("N/A") $isNotNA = $implementedSkus[0] -ne "N/A" - + $isNotNA | Should -Be $false } It "Should join implemented regions" { $regions = @("eastus", "westus", "northeurope") $joined = $regions -join ", " - + $joined | Should -Be "eastus, westus, northeurope" } } diff --git a/support/AzureRetailPrice.ps1 b/support/AzureRetailPrice.ps1 index e50fc34..5a21fc5 100644 --- a/support/AzureRetailPrice.ps1 +++ b/support/AzureRetailPrice.ps1 @@ -12,7 +12,7 @@ function Get-AzureRetailPrice { N/A .EXAMPLE Get-AzureRetailPrice -armSkuName Standard_B2ms -armRegionName swedencentral -priceType Consumption -serviceFamily Compute | FT -AutoSize - + - List all _consumption_ prices for _Standard_B2ms_ VMs in the _Sweden Central_ Azure Region - Format the output as a table with _AutoSize_ .EXAMPLE @@ -32,7 +32,7 @@ function Get-AzureRetailPrice { - List all _consumption_ prices for _Standard_B2ms_ VMs in the _Sweden Central_ Azure Region - Write the output to a file in JSON format - #> + #> [CmdletBinding()] param ( [Parameter(Mandatory = $False)] @@ -66,7 +66,7 @@ function Get-AzureRetailPrice { [Parameter(Mandatory = $False)] [string]$currencyCode = 'USD' ) - + # Initialize Variables $query = '' $paramCounter = 1