Last active
September 28, 2026 15:24
-
-
Save joerodgers/335c428de741451e2f1eecbbb49f58cb to your computer and use it in GitHub Desktop.
Example how to query resource (agents and workflows) Copilot credit consumption for a given time period.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requries -modules "Az.Accounts" | |
| class PowerPlatformResource | |
| { | |
| [string] | |
| $ResourceType | |
| [string] | |
| $ResourceName | |
| [Guid] | |
| $ResourceId | |
| [string] | |
| $EnvironmentId | |
| [string] | |
| $EnvironmentName | |
| [string] | |
| $EnvironmentType | |
| [string] | |
| $EnvironmentRegion | |
| [bool] | |
| $EnvironmentIsManaged | |
| [string] | |
| $EnvironmentGroupId | |
| [string] | |
| $EnvironmentGroupName | |
| } | |
| class PowerPlatformResourceMessageConsumption : PowerPlatformResource | |
| { | |
| PowerPlatformResourceMessageConsumption([PowerPlatformResource]$resource) | |
| { | |
| $this.ResourceType = $resource.ResourceType | |
| $this.ResourceName = $resource.ResourceName | |
| $this.ResourceId = $resource.ResourceId | |
| $this.EnvironmentId = $resource.EnvironmentId | |
| $this.EnvironmentName = $resource.EnvironmentName | |
| $this.EnvironmentType = $resource.EnvironmentType | |
| $this.EnvironmentRegion = $resource.EnvironmentRegion | |
| $this.EnvironmentIsManaged = $resource.EnvironmentIsManaged | |
| $this.EnvironmentGroupId = $resource.EnvironmentGroupId | |
| $this.EnvironmentGroupName = $resource.EnvironmentGroupName | |
| } | |
| [string] | |
| $UserId | |
| [int] | |
| $NonBillableCreditsConsumed | |
| [int] | |
| $BillableCreditsConsumed | |
| [DateTime] | |
| $AsOfDate | |
| } | |
| function Get-AccessToken | |
| { | |
| [CmdletBinding()] | |
| param | |
| ( | |
| [Parameter(Mandatory=$true)] | |
| [string] | |
| $ResourceUrl | |
| ) | |
| if (-not (Get-AzContext)) | |
| { | |
| Write-Host "No Azure context - launching sign-in..." -ForegroundColor Yellow | |
| Connect-AzAccount -WarningAction SilentlyContinue | Out-Null | |
| } | |
| $azAccessToken = Get-AzAccessToken -ResourceUrl $ResourceUrl -WarningAction SilentlyContinue | |
| if ($azAccessToken.Token -is [System.Security.SecureString]) | |
| { | |
| return [System.Net.NetworkCredential]::new('', $azAccessToken.Token).Password | |
| } | |
| return $azAccessToken.Token | |
| } | |
| function Get-PowerPlatformResource | |
| { | |
| [CmdletBinding()] | |
| param | |
| ( | |
| [Parameter(Mandatory=$false)] | |
| [switch] | |
| $IncludeStandardHarnessAgents, | |
| [Parameter(Mandatory=$false)] | |
| [switch] | |
| $IncludeCopilotChatHarnessAgents, | |
| [Parameter(Mandatory=$false)] | |
| [switch] | |
| $IncludeGithubHarnessAgents, | |
| [Parameter(Mandatory=$false)] | |
| [switch] | |
| $IncludeAgentWorkflows | |
| ) | |
| begin | |
| { | |
| $accessToken = Get-AccessToken -ResourceUrl "https://api.powerplatform.com/" | |
| $headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json"; "Accept" = "application/json" } | |
| $skipToken = "" | |
| $skip = 0 | |
| $top = 1000 | |
| $resourceQueryTemplate = @' | |
| {{ | |
| "Options": {{ | |
| "Top": {0}, | |
| "Skip": {1}, | |
| "SkipToken": "{2}" | |
| }}, | |
| "TableName": "PowerPlatformResources", | |
| "Clauses": [ | |
| {{ | |
| "$type": "where", | |
| "FieldName": "type", | |
| "Operator": "in~", | |
| "Values": [ {3} ] | |
| }}, | |
| {{ | |
| "$type": "extend", | |
| "FieldName": "joinKey", | |
| "Expression": "tolower(tostring(properties.environmentId))" | |
| }}, | |
| {{ | |
| "$type": "extend", | |
| "FieldName": "resourceType", | |
| "Expression": "iif( type == 'microsoft.powerautomate/agentflows', 'Agent Workflow', iif( tostring(properties.createdIn) == 'Copilot Studio' and tostring(properties.harness) == 'GitHub Copilot', 'Copilot Studio GitHub Copilot Agent', iif((tostring(properties.createdIn) == 'Copilot Studio' and tostring(properties.harness) == 'Standard'), 'Copilot Studio Standard Agent', iif((tostring(properties.createdIn) == 'Copilot Studio' and tostring(properties.harness) == 'Copilot Chat'), 'Copilot Studio Declarative Agent', iif((tostring(properties.createdIn) == 'Copilot Studio Lite' and tostring(properties.harness) == 'Copilot Chat'), 'Agent Builder Agent', 'Unknown')))))" | |
| }}, | |
| {{ | |
| "$type": "project", | |
| "FieldList": [ | |
| "joinKey", | |
| "resourceId = tolower(name)", | |
| "type", | |
| "harness = properties.harness", | |
| "createdId = properties.createdIn", | |
| "resourceName = properties.displayName", | |
| "resourceType" | |
| ] | |
| }}, | |
| {{ | |
| "$type": "join", | |
| "JoinKind": "leftouter", | |
| "RightTable": {{ | |
| "TableName": "PowerPlatformResources", | |
| "Clauses": [ | |
| {{ | |
| "$type": "where", | |
| "FieldName": "type", | |
| "Operator": "==", | |
| "Values": [ | |
| "'microsoft.powerplatform/environments'" | |
| ] | |
| }}, | |
| {{ | |
| "$type": "project", | |
| "FieldList": [ | |
| "joinKey = tolower(name)", | |
| "environmentId = tolower(name)", | |
| "environmentName = properties.displayName", | |
| "environmentRegion = location", | |
| "environmentType = properties.environmentType", | |
| "isManagedEnvironment = properties.isManaged", | |
| "environmentGroupId = tolower(properties.environmentGroupId)" | |
| ] | |
| }} | |
| ] | |
| }}, | |
| "LeftColumnName": "joinKey", | |
| "RightColumnName": "joinKey" | |
| }}, | |
| {{ | |
| "$type": "join", | |
| "JoinKind": "leftouter", | |
| "RightTable": {{ | |
| "TableName": "PowerPlatformResources", | |
| "Clauses": [ | |
| {{ | |
| "$type": "where", | |
| "FieldName": "type", | |
| "Operator": "==", | |
| "Values": [ | |
| "'microsoft.powerplatform/environmentgroups'" | |
| ] | |
| }}, | |
| {{ | |
| "$type": "project", | |
| "FieldList": [ | |
| "environmentGroupId = tolower(name)", | |
| "environmentGroupName = properties.displayName" | |
| ] | |
| }} | |
| ] | |
| }}, | |
| "LeftColumnName": "environmentGroupId", | |
| "RightColumnName": "environmentGroupId" | |
| }} | |
| ] | |
| }} | |
| '@ | |
| } | |
| process | |
| { | |
| $uri = "https://api.powerplatform.com/resourcequery/resources/query?api-version=2022-03-01-preview" | |
| $resourceTypes = @() | |
| if( $IncludeStandardHarnessAgents.IsPresent -or $IncludeGithubHarnessAgents.IsPresent -or $IncludeCopilotChatHarnessAgents.IsPresent ) | |
| { | |
| $resourceTypes += "`"'microsoft.copilotstudio/agents'`"" | |
| } | |
| if( $IncludeAgentWorkflows.IsPresent ) | |
| { | |
| $resourceTypes += "`"'microsoft.powerautomate/agentflows'`"" | |
| } | |
| $page = 0 | |
| $results = do | |
| { | |
| $page++ | |
| Write-Verbose "[$(Get-Date)] - Querying Power Platform resources, page $page." | |
| $body = $resourceQueryTemplate -f $top, $skip, $skipToken, ($resourceTypes -join ",") | |
| $response = Invoke-RestMethod -Method Post -Uri $uri -Body $body -Headers $headers -Verbose:$false | |
| $skipToken = $response.skipToken | |
| $skip += $top | |
| $response.data | |
| } | |
| while( $skipToken ) | |
| $resources = foreach( $result in $results ) | |
| { | |
| if( $result.resourceType -eq "Unknown" -or $result.resourceType -eq "Agent Builder Agent" ) | |
| { | |
| continue | |
| } | |
| if( -not $IncludeStandardHarnessAgents.IsPresent -and $result.resourceType -eq "Copilot Studio Standard Agent" ) | |
| { | |
| continue | |
| } | |
| if( -not $IncludeGithubHarnessAgents.IsPresent -and $result.resourceType -eq "Copilot Studio GitHub Copilot Agent" ) | |
| { | |
| continue | |
| } | |
| if( -not $IncludeCopilotChatHarnessAgents.IsPresent -and $result.resourceType -eq "Copilot Studio Declarative Agent" ) | |
| { | |
| continue | |
| } | |
| $resource = [PowerPlatformResource]::new() | |
| $resource.ResourceType = $result.resourceType | |
| $resource.ResourceName = $result.resourceName | |
| $resource.ResourceId = $result.resourceId | |
| $resource.EnvironmentId = $result.environmentId | |
| $resource.EnvironmentName = $result.environmentName | |
| $resource.EnvironmentRegion = $result.environmentRegion | |
| $resource.EnvironmentType = $result.environmentType | |
| $resource.EnvironmentIsManaged = $result.isManagedEnvironment | |
| $resource.EnvironmentGroupId = $result.environmentGroupId | |
| $resource.EnvironmentGroupName = $result.environmentGroupName | |
| $resource | |
| } | |
| Write-Verbose "[$(Get-Date)] - Discovered $($resources.Count) matching resources." | |
| $resources | |
| } | |
| end | |
| { | |
| } | |
| } | |
| function Get-PowerPlatformResourceMessageConsumption | |
| { | |
| [CmdletBinding()] | |
| param | |
| ( | |
| [Parameter(Mandatory=$false)] | |
| [PowerPlatformResource] | |
| $PowerPlatformResource, | |
| [Parameter(Mandatory=$true)] | |
| [DateTime] | |
| $StartDate, | |
| [Parameter(Mandatory=$true)] | |
| [DateTime] | |
| $EndDate | |
| ) | |
| begin | |
| { | |
| $continuationToken = $null | |
| $accessToken = Get-AccessToken -ResourceUrl "https://api.powerplatform.com/" | |
| $headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json"; "Accept" = "application/json" } | |
| } | |
| process | |
| { | |
| $uri = 'https://api.powerplatform.com/licensing/entitlements/MCSMessages/resources/{0}/users?fromDate={1}&toDate={2}&api-version=2024-10-01' -f $PowerPlatformResource.ResourceId, $StartDate.ToString("yyyy-MM-dd"), $EndDate.ToString("yyyy-MM-dd") | |
| Write-Verbose "[$(Get-Date)] - Querying consumption data for $($PowerPlatformResource.ResourceType) - $($PowerPlatformResource.ResourceName)" | |
| $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers -ErrorAction Stop -Verbose:$false | |
| foreach( $user in $response.value.users ) | |
| { | |
| $powerPlatformResourceMessageConsumption = [PowerPlatformResourceMessageConsumption]::new($PowerPlatformResource) | |
| $powerPlatformResourceMessageConsumption.UserId = $user.userId | |
| $powerPlatformResourceMessageConsumption.NonBillableCreditsConsumed = $user.metadata.NonBillableQuantity | |
| $powerPlatformResourceMessageConsumption.BillableCreditsConsumed = $user.consumed | |
| $powerPlatformResourceMessageConsumption.AsOfDate = $user.asOfDate | |
| $powerPlatformResourceMessageConsumption | |
| } | |
| } | |
| end | |
| { | |
| } | |
| } | |
| # output file timestamp | |
| $timestamp = Get-Date -Format FileDateTime | |
| # lookback last 180 days | |
| $startDate = [DateTime]::Today.AddDays(-180) | |
| $endDate = [DateTime]::Today.AddDays(-1) | |
| # query all agent types and workflows you desire... the more you pick the longer it'll take | |
| $resources = Get-PowerPlatformResource -IncludeGithubHarnessAgents -IncludeStandardHarnessAgents -IncludeAgentWorkflows -Verbose | |
| # enumerate each agent/workflow and query for message consumption data | |
| foreach( $resource in $resources | Sort-Object -Property EnvironmentId, ResourceName ) | |
| { | |
| $consumption = Get-PowerPlatformResourceMessageConsumption -PowerPlatformResource $resource -StartDate $startdate -EndDate $endDate -Verbose | |
| $consumption | Export-Csv -Path "C:\_temp\PowerPlatformResourceMessageConsumption_$timestamp.csv" -NoTypeInformation -Append | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment