| #requries -modules "Az.Accounts" | | | 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] | |
| $IncludeAgentResources, | |
| [Parameter(Mandatory=$false)] | |
| [switch] | |
| $IncludeWorkflowResources | | | ) | | | 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": "project", | | | "FieldList": [ | | | "joinKey", | | | "resourceId = tolower(name)", | | | "type", | | | "resourceName = properties.displayName", | | | "resourceType = iif( type == 'microsoft.powerautomate/agentflows', 'Agent Workflow', iif( properties.isGithubCopilotAgent == 1, 'GitHub Copilot Harness Agent',iif(properties.model == 'Microsoft 365 Copilot','M365 Copilot Chat Harness Agent','Standard Harness Agent')))", | | | "properties" | | | ] | |
| }}, | |
| {{ | |
| "$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( $IncludeAgentResources.IsPresent ) | |
| { | |
| $resourceTypes += ""'microsoft.copilotstudio/agents'"" | |
| } | |
| if( $IncludeWorkflowResources.IsPresent ) | |
| { | |
| $resourceTypes += ""'microsoft.powerautomate/agentflows'"" | |
| } | |
| $page = 0 | |
| $resources = 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( $resource in $resources ) | |
| { | | | [PSCustomObject] @{ | | | ResourceType = $resource.resourceType | | | ResourceName = $resource.resourceName | | | ResourceId = $resource.resourceId | | | EnvironmentId = $resource.environmentId | | | EnvironmentName = $resource.environmentName | | | EnvironmentRegion = $resource.environmentRegion | | | EnvironmentType = $resource.environmentType | | | EnvironmentIsManaged = $resource.isManagedEnvironment | | | EnvironmentGroupId = $resource.environmentGroupId | | | EnvironmentGroupName = $resource.environmentGroupName | | | ResourceCreatedAt = $resource.properties.createdAt | | | ResourceCreatedBy = $resource.properties.createdBy | | | ResourceOwnerId = $resource.properties.ownerId | | | ResourceLastPublishedAt = $resource.properties.lastPublishedAt | | | } | | | } | | | if( $IncludeAgentResources.IsPresent ) | | | { | |
| $rows = @($resources | where-object -Property ResourceType -eq "Agent Workflow") | |
| Write-Verbose "[$(Get-Date)] - Discovered $($rows.Count) Power Platform Agent resources." | |
| } | | | if( $IncludeWorkflowResources.IsPresent ) | | | { | |
| $rows = @($resources | where-object -Property ResourceType -ne "Agent Workflow") | |
| Write-Verbose "[$(Get-Date)] - Discovered $($rows.Count) Power Platform Workflow resources." | |
| } | | | $resources | | | } | | | end | | | { | | | } | | | } | | | function Get-PowerPlatformResourceMessageConsumption | | | { | | | [CmdletBinding()] | | | param | | | ( | |
| [Parameter(Mandatory=$true)] | |
| [Guid] | |
| $ResourceId, | |
| [Parameter(Mandatory=$true,ParameterSetName="PerUserCreditConsumption")] | |
| [switch] | |
| $PerUserCreditConsumption, | |
| [Parameter(Mandatory=$true,ParameterSetName="TotalCreditConsumption")] | |
| [switch] | |
| $TotalCreditConsumption, | |
| [Parameter(Mandatory=$true)] | |
| [DateTime] | |
| $StartDate, | |
| [Parameter(Mandatory=$true)] | |
| [DateTime] | |
| $EndDate | | | ) | | | begin | | | { | |
| $accessToken = Get-AccessToken -ResourceUrl "https://api.powerplatform.com/" | |
| $headers = @{ "Authorization" = "Bearer $accessToken"; "Content-Type" = "application/json"; "Accept" = "application/json" } | |
| $uri = 'https://api.powerplatform.com/licensing/entitlements/MCSMessages/resources/{0}/users?fromDate={1}&toDate={2}&api-version=2024-10-01' -f $ResourceId, $StartDate.ToString("MM-dd-yyyy"), $EndDate.ToString("MM-dd-yyyy") | |
| } | | | process | | | { | |
| Write-Verbose "[$(Get-Date)] - Querying resource '$ResourceId' for usage." | |
| $response = Invoke-RestMethod -Method GET ` | |
| -Uri $uri | | | -Headers $headers | |
| -ErrorAction Stop ` | |
| -Verbose:$false | |
| if( $TotalCreditConsumption.IsPresent ) | |
| { | |
| Write-Verbose "[$(Get-Date)] - Calculating total usage across $($response.value.users.Count) users." | |
| $totalBillableCreditsUsed = $totalCreditsUsed = $totalUserCount = $consumedUserCount = $billableConsumedUserCount = 0 | |
| foreach( $user in $response.value.users ) | | | { | | | $totalUserCount++ | | | $totalBillableCreditsUsed += $user.consumed | | | $totalCreditsUsed += $user.metadata.NonBillableQuantity | | | if( $user.consumed -gt 0 ) | | | { | | | $billableConsumedUserCount++ | | | } | | | if( $user.metadata.NonBillableQuantity -gt 0 ) | | | { | | | $consumedUserCount++ | | | } | | | } | | | [PSCustomObject] @{ | | | BillableCreditsUsed = $totalBillableCreditsUsed | | | CreditsUsed = $totalCreditsUsed | | | BillableCreditUserCount = $billableConsumedUserCount | | | CreditUserCount = $consumedUserCount | | | TotalUserConsumptionCount = $totalUserCount | | | } | | | } | | | else | | | { | |
| Write-Verbose "[$(Get-Date)] - Calculating user level usage." | |
| if( $response.value.users ) | |
| { | | | foreach( $user in $response.value.users ) | | | { | | | [PSCustomObject] @{ | | | UserId = $user.userId | | | BillableCreditsUsed = $user.consumed | | | CreditsUsed = $user.metadata.NonBillableQuantity | | | CreditUsageAsOfDate = $user.asOfDate | | | } | | | } | | | } | | | } | | | } | | | end | | | { | | | } | | | } | | | # lookback last 30 days | |
| $startDate = [DateTime]::Today.AddDays(-31) | |
| $endDate = [DateTime]::Today.AddDays(-1) | |
| # show total or per user per resource credit consumption | | | $reportPerUserCreditConsumption = $false | | | # get all copilot studio agents and agent workflows | | | $resources = Get-PowerPlatformResource -IncludeAgentResources -IncludeWorkflowResources -Verbose | | | # example filters if you want to narrow the scope | |
| # $resources = $resources | Where-Object -Property ResourceType -eq "Agent Workflow" | |
| # $resources = $resources | Where-Object -Property ResourceType -eq "GitHub Copilot Harness Agent" | |
| # $resources = $resources | Where-Object -Property ResourceType -eq "Standard Harness Agent" | |
| # pull agent usage | | | $results = foreach( $resource in $resources ) | | | { | | | if( $reportPerUserCreditConsumption ) | | | { | |
| $messages = Get-PowerPlatformResourceMessageConsumption -ResourceId $resource.ResourceId -StartDate $startDate -EndDate $endDate -PerUserCreditConsumption -Verbose | |
| foreach( $message in $messages ) | |
| { | | | $row = $resource.psobject.Copy() | | | $row | Add-Member -MemberType NoteProperty -Name "UserId" -Value $message.UserId | | | $row | Add-Member -MemberType NoteProperty -Name "BillableCreditsConsumed" -Value $message.BillableCreditsUsed | | | $row | Add-Member -MemberType NoteProperty -Name "CreditsConsumed" -Value $message.CreditsUsed | | | $row | Add-Member -MemberType NoteProperty -Name "CreditUsageAsOfDate" -Value $message.CreditUsageAsOfDate | | | $row | | | } | | | } | | | else | | | { | | | $messages = Get-PowerPlatformResourceMessageConsumption -ResourceId $resource.ResourceId -StartDate $startDate -EndDate $endDate -TotalCreditConsumption -Verbose | | | $resource | Add-Member -MemberType NoteProperty -Name "BillableCreditsConsumed" -Value $messages.BillableCreditsUsed | | | $resource | Add-Member -MemberType NoteProperty -Name "CreditsConsumed" -Value $messages.CreditsUsed | | | $resource | Add-Member -MemberType NoteProperty -Name "BillableCreditUserCount" -Value $messages.BillableCreditUserCount | | | $resource | Add-Member -MemberType NoteProperty -Name "CreditUserCount" -Value $messages.CreditUserCount | | | $resource | Add-Member -MemberType NoteProperty -Name "TotalUserConsumptionCount" -Value $messages.TotalUserConsumptionCount | | | $resource | | | } | | | } | | | $timestamp = Get-Date -Format FileDateTime | | | $results | Export-Csv -Path "C:_temp\PowerPlatformResourceMessageConsumption_$timestamp.csv" -NoTypeInformation |