Skip to content
This repository was archived by the owner on Oct 29, 2025. It is now read-only.

Commit 32f38d0

Browse files
Merge pull request #34 from Azure/include-meterid
Include meterid in collect script
2 parents 92c257d + 54a0d83 commit 32f38d0

1 file changed

Lines changed: 110 additions & 6 deletions

File tree

1-Collect/Get-AzureServices.ps1

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
.PARAMETER summaryOutputFile
2626
The name of the output file where the summary will be exported. Default is "summary.json".
2727
28+
.PARAMETER includeCost
29+
A boolean flag indicating whether to include cost report generation. Default is $false. Note that this requires the identity
30+
running the script to have permissions to access cost management APIs, i.e. Cost Management Contributor role.
31+
2832
.FUNCTION Get-SingleData
2933
Queries Azure Resource Graph for resources within a single subscription and retrieves all results,
3034
handling pagination if necessary.
@@ -41,16 +45,26 @@
4145
.FUNCTION Get-Method
4246
Determines the appropriate method to retrieve resource-specific data based on the resource type and flag type.
4347
48+
.FUNCTION Invoke-CostReportSchedule
49+
Generates a cost report for a specified subscription by invoking the Azure REST API and retrieves cost details
50+
for the previous month.
51+
52+
.FUNCTION Get-CostReport
53+
Fetches the cost report details from the Azure REST API and processes the results.
54+
55+
.FUNCTION Get-MeterId
56+
Retrieves unique meter IDs associated with a specific resource ID from the cost details CSV.
57+
4458
.EXAMPLE
45-
PS C:\> .\assess_resources.ps1 -scopeType singleSubscription -subscriptionId "12345678-1234-1234-1234-123456789abc"
59+
PS C:\> .\Get-AzureServices.ps1 -scopeType singleSubscription -subscriptionId "12345678-1234-1234-1234-123456789abc"
4660
Runs the script for a single subscription with the specified subscription ID and outputs the results to the default file.
4761
4862
.EXAMPLE
49-
PS C:\> .\assess_resources.ps1 -scopeType resourceGroup -resourceGroupName "MyResourceGroup"
63+
PS C:\> .\Get-AzureServices.ps1 -scopeType resourceGroup -resourceGroupName "MyResourceGroup"
5064
Runs the script for a specific resource group within the current subscription and outputs the results to the default file.
5165
5266
.EXAMPLE
53-
PS C:\> .\assess_resources.ps1 -scopeType multiSubscription -workloadFile "subscriptions.json" -fullOutputFile "output.json"
67+
PS C:\> .\Get-AzureServices.ps1 -scopeType multiSubscription -workloadFile "subscriptions.json" -fullOutputFile "output.json"
5468
Runs the script for multiple subscriptions defined in the workload file and outputs the results to "output.json".
5569
5670
@@ -69,7 +83,8 @@ param(
6983
[Parameter(Mandatory = $false)] [string] $resourceGroupName, # resource group to run the query against
7084
[Parameter(Mandatory = $false)] [string] $workloadFile, # JSON file containing subscriptions
7185
[Parameter(Mandatory = $false)] [string] $fullOutputFile = "resources.json", # Json file to export the results to
72-
[Parameter(Mandatory = $false)] [string] $summaryOutputFile = "summary.json" # Json file to export the results to
86+
[Parameter(Mandatory = $false)] [string] $summaryOutputFile = "summary.json", # Json file to export the results to
87+
[Parameter(Mandatory = $false)] [bool] $includeCost = $false # Include cost report
7388
)
7489

7590
Function Get-SingleData {
@@ -98,8 +113,14 @@ Function Get-MultiLoop {
98113
$basequery = "resources | where subscriptionId == '$subscription'"
99114
Get-SingleData -query $basequery
100115
$tempArray += $Script:baseresult
116+
If ($includeCost) {
117+
Invoke-CostReportSchedule -SubscriptionId $subscription
118+
Get-CostReport -PathForResult $pathForResult
119+
$tempCostArray += $Script:costdetails
120+
}
101121
}
102122
$Script:baseresult = $tempArray
123+
$Script:costdetails = $tempCostArray
103124
}
104125

105126
Function Get-Property {
@@ -191,6 +212,68 @@ Function Get-Method {
191212
}
192213
}
193214

215+
Function Invoke-CostReportSchedule {
216+
param (
217+
[Parameter(Mandatory = $true)] [string]$SubscriptionId
218+
)
219+
$uri = "https://management.azure.com/subscriptions/$($SubscriptionId)/providers/Microsoft.CostManagement/generateCostDetailsReport?api-version=2025-03-01"
220+
$startDate =(Get-Date).AddDays(-1)
221+
$endDate = (Get-Date)
222+
# Define the request body
223+
$body = @{
224+
metric = "AmortizedCost"
225+
timePeriod = @{
226+
start = $startDate.ToString("yyyy-MM-dd")
227+
end = $endDate.ToString("yyyy-MM-dd")
228+
}
229+
}
230+
#Convert the body to JSON
231+
$bodyJson = $body | ConvertTo-Json
232+
$result = invoke-AzRestMethod -Uri $uri -Method POST -Payload $bodyJson
233+
$pathForResult = "https://management.azure.com" + $result.Headers.Location.AbsolutePath + "?api-version=2025-03-01"
234+
write-output "Cost report request submitted. Path for result: $pathForResult"
235+
Set-Variable -Name 'pathForResult' -Value $pathForResult -Scope Script
236+
}
237+
238+
Function Get-CostReport {
239+
param (
240+
[Parameter(Mandatory = $true)] [string]$PathForResult
241+
)
242+
$i = 0
243+
$details = Invoke-AzRestMethod -uri $PathForResult -Method GET
244+
# Loop until $details.statuscode is 200
245+
while ($details.StatusCode -eq 202) {
246+
Start-Sleep -Seconds 10
247+
$i = $i + 10
248+
$details = Invoke-AzRestMethod -uri $PathForResult -Method GET
249+
Write-Output "Waiting for the cost report to be ready. Elapsed time: $i seconds"
250+
}
251+
"Cost report is ready. Downloading the report..."
252+
$subscriptionID = (($details.Content | ConvertFrom-Json).manifest.requestContext.requestScope) -replace "^/subscriptions/", "" -replace "/$", ""
253+
$blobLink = ($details.Content | ConvertFrom-Json).manifest.blobs.bloblink
254+
$blobContent = Invoke-RestMethod -Uri $blobLink -Method Get
255+
$blobContent | out-file "$subscriptionID.csv"
256+
$csv = Import-Csv -Path "$subscriptionID.csv"
257+
Set-Variable -name costdetails -Value $csv -Scope Script
258+
}
259+
260+
Function Get-MeterId {
261+
param (
262+
[Parameter(Mandatory = $true)] [string]$ResourceId,
263+
[Parameter(Mandatory = $true)] [PSCustomObject]$csvObject
264+
)
265+
$outputArray = @()
266+
#Reset variable to avoid conflicts
267+
Set-Variable -Name 'meterIds' -Value @() -scope script
268+
$resMeterIds = $csvObject | Where-Object { $_.resourceId -eq $ResourceId } | Select-Object meterId -Unique
269+
# For each meterId, get the meterId value and add it to the output array
270+
foreach ($meterId in $resMeterIds) {
271+
$outputArray += $meterId.meterId
272+
}
273+
Set-Variable -Name 'meterIds' -Value $outputArray -Scope Script
274+
}
275+
276+
194277
# Main script starts here
195278
# Turn off breaking change warnings for Azure PowerShell, for Get-AzMetric CmdLet
196279
Set-Item -Path Env:\SuppressAzurePowerShellBreakingChangeWarnings -Value $true
@@ -204,6 +287,11 @@ Switch ($scopeType) {
204287
}
205288
$baseQuery = "resources | where subscriptionId == '$subscriptionId'"
206289
Get-SingleData -query $baseQuery
290+
If ($includeCost) {
291+
# Generate cost report for the subscription
292+
Invoke-CostReportSchedule -SubscriptionId $subscriptionId
293+
Get-CostReport -PathForResult $pathForResult
294+
}
207295
}
208296
'resourceGroup' {
209297
# KQL Query to get all resources in a specific resource group and subscription
@@ -212,6 +300,11 @@ Switch ($scopeType) {
212300
}
213301
$baseQuery = "resources | where resourceGroup == '$resourceGroupName' and subscriptionId == '$subscriptionId'"
214302
Get-SingleData -query $baseQuery
303+
If ($includeCost) {
304+
# Generate cost report for the subscription
305+
Invoke-CostReportSchedule -SubscriptionId $subscriptionId
306+
Get-CostReport -PathForResult $pathForResult
307+
}
215308
}
216309
'multiSubscription' {
217310
"multiple subscriptions"
@@ -248,6 +341,12 @@ $baseResult | ForEach-Object {
248341
resiliencyProperties = $resiliencyProperties
249342
dataSizeGB = $dataSize
250343
ipAddress = $ipAddress
344+
meterIds = @()
345+
}
346+
If ($includeCost) {
347+
Get-MeterId -ResourceId $resourceId -csvObject $costDetails
348+
# add meterIds to the output object
349+
$outObject.meterIds += $meterIds
251350
}
252351
$outputArray += $outObject
253352
}
@@ -256,17 +355,22 @@ $groupedResources = $outputArray | Group-Object -Property ResourceType
256355
$summary = @()
257356
foreach ($group in $groupedResources) {
258357
$resourceType = $group.Name
358+
$uniqueMeterIds = $group.Group | Select-Object -Property meterIds -Unique | Select-Object -ExpandProperty meterIds
359+
$uniqueMeterIds = $uniqueMeterIds | Select-Object -Unique
360+
if ($uniqueMeterIds -isnot [System.Array]) {
361+
$uniqueMeterIds = @($uniqueMeterIds)
362+
}
259363
$uniqueLocations = $group.Group | Select-Object -Property ResourceLocation -Unique | Select-Object -ExpandProperty ResourceLocation
260364
if ($uniqueLocations -isnot [System.Array]) {
261365
$uniqueLocations = @($uniqueLocations)
262366
}
263367
If ($group.Group.ResourceSku -ne 'N/A') {
264368

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

0 commit comments

Comments
 (0)