Skip to content
This repository was archived by the owner on Oct 29, 2025. It is now read-only.
Merged
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
116 changes: 110 additions & 6 deletions 1-Collect/Get-AzureServices.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
.PARAMETER summaryOutputFile
The name of the output file where the summary will be exported. Default is "summary.json".

.PARAMETER includeCost
A boolean flag indicating whether to include cost report generation. Default is $false. Note that this requires the identity
running the script to have permissions to access cost management APIs, i.e. Cost Management Contributor role.

.FUNCTION Get-SingleData
Queries Azure Resource Graph for resources within a single subscription and retrieves all results,
handling pagination if necessary.
Expand All @@ -41,16 +45,26 @@
.FUNCTION Get-Method
Determines the appropriate method to retrieve resource-specific data based on the resource type and flag type.

.FUNCTION Invoke-CostReportSchedule
Generates a cost report for a specified subscription by invoking the Azure REST API and retrieves cost details
for the previous month.

.FUNCTION Get-CostReport
Fetches the cost report details from the Azure REST API and processes the results.

.FUNCTION Get-MeterId
Retrieves unique meter IDs associated with a specific resource ID from the cost details CSV.

.EXAMPLE
PS C:\> .\assess_resources.ps1 -scopeType singleSubscription -subscriptionId "12345678-1234-1234-1234-123456789abc"
PS C:\> .\Get-AzureServices.ps1 -scopeType singleSubscription -subscriptionId "12345678-1234-1234-1234-123456789abc"
Runs the script for a single subscription with the specified subscription ID and outputs the results to the default file.

.EXAMPLE
PS C:\> .\assess_resources.ps1 -scopeType resourceGroup -resourceGroupName "MyResourceGroup"
PS C:\> .\Get-AzureServices.ps1 -scopeType resourceGroup -resourceGroupName "MyResourceGroup"
Runs the script for a specific resource group within the current subscription and outputs the results to the default file.

.EXAMPLE
PS C:\> .\assess_resources.ps1 -scopeType multiSubscription -workloadFile "subscriptions.json" -fullOutputFile "output.json"
PS C:\> .\Get-AzureServices.ps1 -scopeType multiSubscription -workloadFile "subscriptions.json" -fullOutputFile "output.json"
Runs the script for multiple subscriptions defined in the workload file and outputs the results to "output.json".


Expand All @@ -69,7 +83,8 @@ param(
[Parameter(Mandatory = $false)] [string] $resourceGroupName, # resource group to run the query against
[Parameter(Mandatory = $false)] [string] $workloadFile, # JSON file containing subscriptions
[Parameter(Mandatory = $false)] [string] $fullOutputFile = "resources.json", # Json file to export the results to
[Parameter(Mandatory = $false)] [string] $summaryOutputFile = "summary.json" # Json file to export the results to
[Parameter(Mandatory = $false)] [string] $summaryOutputFile = "summary.json", # Json file to export the results to
[Parameter(Mandatory = $false)] [bool] $includeCost = $false # Include cost report
)

Function Get-SingleData {
Expand Down Expand Up @@ -98,8 +113,14 @@ Function Get-MultiLoop {
$basequery = "resources | where subscriptionId == '$subscription'"
Get-SingleData -query $basequery
$tempArray += $Script:baseresult
If ($includeCost) {
Invoke-CostReportSchedule -SubscriptionId $subscription
Get-CostReport -PathForResult $pathForResult
$tempCostArray += $Script:costdetails
}
}
$Script:baseresult = $tempArray
$Script:costdetails = $tempCostArray
}

Function Get-Property {
Expand Down Expand Up @@ -191,6 +212,68 @@ Function Get-Method {
}
}

Function Invoke-CostReportSchedule {
param (
[Parameter(Mandatory = $true)] [string]$SubscriptionId
)
$uri = "https://management.azure.com/subscriptions/$($SubscriptionId)/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2025-03-01"
$startDate =(Get-Date).AddDays(-1)
$endDate = (Get-Date)
# Define the request body
$body = @{
metric = "AmortizedCost"
timePeriod = @{
start = $startDate.ToString("yyyy-MM-dd")
end = $endDate.ToString("yyyy-MM-dd")
}
}
#Convert the body to JSON
$bodyJson = $body | ConvertTo-Json
$result = invoke-AzRestMethod -Uri $uri -Method POST -Payload $bodyJson
$pathForResult = "https://management.azure.com" + $result.Headers.Location.AbsolutePath + "?api-version=2025-03-01"
write-output "Cost report request submitted. Path for result: $pathForResult"
Set-Variable -Name 'pathForResult' -Value $pathForResult -Scope Script
}

Function Get-CostReport {
param (
[Parameter(Mandatory = $true)] [string]$PathForResult
)
$i = 0
$details = Invoke-AzRestMethod -uri $PathForResult -Method GET
# Loop until $details.statuscode is 200
while ($details.StatusCode -eq 202) {
Start-Sleep -Seconds 10
$i = $i + 10
$details = Invoke-AzRestMethod -uri $PathForResult -Method GET
Write-Output "Waiting for the cost report to be ready. Elapsed time: $i seconds"
}
"Cost report is ready. Downloading the report..."
$subscriptionID = (($details.Content | ConvertFrom-Json).manifest.requestContext.requestScope) -replace "^/subscriptions/", "" -replace "/$", ""
$blobLink = ($details.Content | ConvertFrom-Json).manifest.blobs.bloblink
$blobContent = Invoke-RestMethod -Uri $blobLink -Method Get
$blobContent | out-file "$subscriptionID.csv"
$csv = Import-Csv -Path "$subscriptionID.csv"
Set-Variable -name costdetails -Value $csv -Scope Script
}

Function Get-MeterId {
param (
[Parameter(Mandatory = $true)] [string]$ResourceId,
[Parameter(Mandatory = $true)] [PSCustomObject]$csvObject
)
$outputArray = @()
#Reset variable to avoid conflicts
Set-Variable -Name 'meterIds' -Value @() -scope script
$resMeterIds = $csvObject | Where-Object { $_.resourceId -eq $ResourceId } | Select-Object meterId -Unique
# For each meterId, get the meterId value and add it to the output array
foreach ($meterId in $resMeterIds) {
$outputArray += $meterId.meterId
}
Set-Variable -Name 'meterIds' -Value $outputArray -Scope Script
}


# Main script starts here
# Turn off breaking change warnings for Azure PowerShell, for Get-AzMetric CmdLet
Set-Item -Path Env:\SuppressAzurePowerShellBreakingChangeWarnings -Value $true
Expand All @@ -204,6 +287,11 @@ Switch ($scopeType) {
}
$baseQuery = "resources | where subscriptionId == '$subscriptionId'"
Get-SingleData -query $baseQuery
If ($includeCost) {
# Generate cost report for the subscription
Invoke-CostReportSchedule -SubscriptionId $subscriptionId
Get-CostReport -PathForResult $pathForResult
}
}
'resourceGroup' {
# KQL Query to get all resources in a specific resource group and subscription
Expand All @@ -212,6 +300,11 @@ Switch ($scopeType) {
}
$baseQuery = "resources | where resourceGroup == '$resourceGroupName' and subscriptionId == '$subscriptionId'"
Get-SingleData -query $baseQuery
If ($includeCost) {
# Generate cost report for the subscription
Invoke-CostReportSchedule -SubscriptionId $subscriptionId
Get-CostReport -PathForResult $pathForResult
}
}
'multiSubscription' {
"multiple subscriptions"
Expand Down Expand Up @@ -248,6 +341,12 @@ $baseResult | ForEach-Object {
resiliencyProperties = $resiliencyProperties
dataSizeGB = $dataSize
ipAddress = $ipAddress
meterIds = @()
}
If ($includeCost) {
Get-MeterId -ResourceId $resourceId -csvObject $costDetails
# add meterIds to the output object
$outObject.meterIds += $meterIds
}
$outputArray += $outObject
}
Expand All @@ -256,17 +355,22 @@ $groupedResources = $outputArray | Group-Object -Property ResourceType
$summary = @()
foreach ($group in $groupedResources) {
$resourceType = $group.Name
$uniqueMeterIds = $group.Group | Select-Object -Property meterIds -Unique | Select-Object -ExpandProperty meterIds
$uniqueMeterIds = $uniqueMeterIds | Select-Object -Unique
if ($uniqueMeterIds -isnot [System.Array]) {
$uniqueMeterIds = @($uniqueMeterIds)
}
$uniqueLocations = $group.Group | Select-Object -Property ResourceLocation -Unique | Select-Object -ExpandProperty ResourceLocation
if ($uniqueLocations -isnot [System.Array]) {
$uniqueLocations = @($uniqueLocations)
}
If ($group.Group.ResourceSku -ne 'N/A') {

$uniqueSkus = $group.Group.ResourceSku | Select-Object * -Unique
$summary += [PSCustomObject]@{ResourceCount = $group.Count; ResourceType = $resourceType; ResourceSkus = $uniqueSkus; AzureRegions = $uniqueLocations }
$summary += [PSCustomObject]@{ResourceCount = $group.Count; ResourceType = $resourceType; ResourceSkus = $uniqueSkus; AzureRegions = $uniqueLocations; meterIds = $uniqueMeterIds }
}
Else {
$summary += [PSCustomObject]@{ResourceCount = $group.Count; ResourceType = $resourceType; ResourceSkus = @("N/A"); AzureRegions = $uniqueLocations }
$summary += [PSCustomObject]@{ResourceCount = $group.Count; ResourceType = $resourceType; ResourceSkus = @("N/A"); AzureRegions = $uniqueLocations; meterIds = $uniqueMeterIds }
}
}
$summary | ConvertTo-Json -Depth 100 | Out-File -FilePath $summaryOutputFile