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.
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
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
7590Function 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
105126Function 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
196279Set-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 = @ ()
257356foreach ($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