diff --git a/docs-mslearn/toolkit/changelog.md b/docs-mslearn/toolkit/changelog.md
index 66419e6ba..44c6e75c7 100644
--- a/docs-mslearn/toolkit/changelog.md
+++ b/docs-mslearn/toolkit/changelog.md
@@ -45,6 +45,12 @@ The following section lists features and enhancements that are currently in deve
- **Fixed**
- Corrected stale and incorrect descriptions for `BilledCost`, `EffectiveCost`, `BillingCurrency`, `BillingProfileId`, `BillingProfileName`, `CommitmentDiscountQuantity`, `ListUnitPrice`, `PricingQuantity`, `PricingUnitDescription`, and `TotalSavingsRunningTotal` to align with FOCUS 1.2 ([#2112](https://github.com/microsoft/finops-toolkit/pull/2112)).
+
+### [Optimization engine](optimization-engine/overview.md) updates
+
+- **Fixed**
+ - Removed call to Azure Classic administrators endpoint (deprecated on May 1, 2026) from Azure RBAC assignments exports ([#2142](https://github.com/microsoft/finops-toolkit/issues/2142)).
+
-->
diff --git a/src/optimization-engine/azuredeploy-nested.bicep b/src/optimization-engine/azuredeploy-nested.bicep
index 0e12a764d..3f9032fa8 100644
--- a/src/optimization-engine/azuredeploy-nested.bicep
+++ b/src/optimization-engine/azuredeploy-nested.bicep
@@ -984,7 +984,7 @@ var runbooks = [
}
{
name: rbacExportsRunbookName
- version: '1.1.1.0'
+ version: '1.1.2.0'
description: 'Exports RBAC assignments to Blob Storage using ARM and Microsoft Entra'
type: 'PowerShell'
scriptUri: uri(templateLocation, 'runbooks/data-collection/${rbacExportsRunbookName}.ps1')
@@ -1054,14 +1054,14 @@ var runbooks = [
}
{
name: reservationsPriceExportsRunbookName
- version: '1.0.2.0'
+ version: '1.0.3.0'
description: 'Exports Reservations Prices to Blob Storage using the Retail Prices API'
type: 'PowerShell'
scriptUri: uri(templateLocation, 'runbooks/data-collection/${reservationsPriceExportsRunbookName}.ps1')
}
{
name: priceSheetExportsRunbookName
- version: '1.1.2.0'
+ version: '1.1.3.0'
description: 'Exports Price Sheet to Blob Storage using the EA or MCA APIs'
type: 'PowerShell'
scriptUri: uri(templateLocation, 'runbooks/data-collection/${priceSheetExportsRunbookName}.ps1')
diff --git a/src/optimization-engine/runbooks/data-collection/Export-RBACAssignmentsToBlobStorage.ps1 b/src/optimization-engine/runbooks/data-collection/Export-RBACAssignmentsToBlobStorage.ps1
index e21b1b2c9..84edbcc18 100644
--- a/src/optimization-engine/runbooks/data-collection/Export-RBACAssignmentsToBlobStorage.ps1
+++ b/src/optimization-engine/runbooks/data-collection/Export-RBACAssignmentsToBlobStorage.ps1
@@ -55,12 +55,16 @@ if (-not([string]::IsNullOrEmpty($externalCredentialName)))
"Logging in to Azure with $authenticationOption..."
-switch ($authenticationOption) {
- "UserAssignedManagedIdentity" {
+switch ($authenticationOption)
+{
+ "UserAssignedManagedIdentity"
+ {
Connect-AzAccount -Identity -EnvironmentName $cloudEnvironment -AccountId $uamiClientID
break
}
- Default { #ManagedIdentity
+ Default
+ {
+ #ManagedIdentity
Connect-AzAccount -Identity -EnvironmentName $cloudEnvironment
break
}
@@ -86,7 +90,7 @@ if (-not([string]::IsNullOrEmpty($externalCredentialName)))
}
$tenantId = (Get-AzContext).Tenant.Id
-$datetime = (get-date).ToUniversalTime()
+$datetime = (Get-Date).ToUniversalTime()
$timestamp = $datetime.ToString("yyyy-MM-ddTHH:mm:00.000Z")
$subscriptions = Get-AzSubscription | Where-Object { $_.State -eq "Enabled" }
@@ -95,44 +99,30 @@ $roleAssignments = @()
"Iterating through all reachable subscriptions..."
-foreach ($subscription in $subscriptions) {
+foreach ($subscription in $subscriptions)
+{
Select-AzSubscription -SubscriptionId $subscription.Id -TenantId $tenantId | Out-Null
- $assignments = Get-AzRoleAssignment -IncludeClassicAdministrators -ErrorAction Continue
+ $assignments = Get-AzRoleAssignment -ErrorAction Continue
"Found $($assignments.Count) assignments for $($subscription.Name) subscription..."
- foreach ($assignment in $assignments) {
- if ($null -eq $assignment.ObjectId -and $assignment.Scope.Contains($subscription.Id))
+ foreach ($assignment in $assignments)
+ {
+ $duplicateRoleAssignment = $roleAssignments | Where-Object { $_.PrincipalId -eq $assignment.ObjectId -and $_.Scope -eq $assignment.Scope -and $_.RoleDefinition -eq $assignment.RoleDefinitionName }
+ if (-not($duplicateRoleAssignment))
{
$assignmentEntry = New-Object PSObject -Property @{
- Timestamp = $timestamp
- TenantGuid = $tenantId
- Cloud = $cloudEnvironment
- Model = "AzureClassic"
- PrincipalId = $assignment.SignInName
- Scope = $assignment.Scope
- RoleDefinition = $assignment.RoleDefinitionName
+ Timestamp = $timestamp
+ TenantGuid = $tenantId
+ Cloud = $cloudEnvironment
+ Model = "AzureRM"
+ PrincipalId = $assignment.ObjectId
+ Scope = $assignment.Scope
+ RoleDefinition = $assignment.RoleDefinitionName
}
$roleAssignments += $assignmentEntry
}
- else
- {
- $duplicateRoleAssignment = $roleAssignments | Where-Object { $_.PrincipalId -eq $assignment.ObjectId -and $_.Scope -eq $assignment.Scope -and $_.RoleDefinition -eq $assignment.RoleDefinitionName}
- if (-not($duplicateRoleAssignment))
- {
- $assignmentEntry = New-Object PSObject -Property @{
- Timestamp = $timestamp
- TenantGuid = $tenantId
- Cloud = $cloudEnvironment
- Model = "AzureRM"
- PrincipalId = $assignment.ObjectId
- Scope = $assignment.Scope
- RoleDefinition = $assignment.RoleDefinitionName
- }
- $roleAssignments += $assignmentEntry
- }
- }
}
}
@@ -148,7 +138,7 @@ $rbacObjectsJson | Export-Csv -NoTypeInformation -Path $csvExportPath
"Export to $csvExportPath"
$csvBlobName = $csvExportPath
-$csvProperties = @{"ContentType" = "text/csv"};
+$csvProperties = @{"ContentType" = "text/csv" }
Set-AzStorageBlobContent -File $csvExportPath -Container $storageAccountSinkContainer -Properties $csvProperties -Blob $csvBlobName -Context $saCtx -Force
@@ -171,27 +161,32 @@ $roleAssignments = @()
#workaround for https://github.com/microsoftgraph/msgraph-sdk-powershell/issues/888
$localPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::UserProfile)
-if (-not(get-item "$localPath\.graph\" -ErrorAction SilentlyContinue))
+if (-not(Get-Item "$localPath\.graph\" -ErrorAction SilentlyContinue))
{
New-Item -Type Directory "$localPath\.graph"
}
Import-Module Microsoft.Graph.Identity.DirectoryManagement
-switch ($cloudEnvironment) {
- "AzureUSGovernment" {
+switch ($cloudEnvironment)
+{
+ "AzureUSGovernment"
+ {
$graphEnvironment = "USGov"
break
}
- "AzureChinaCloud" {
+ "AzureChinaCloud"
+ {
$graphEnvironment = "China"
break
}
- "AzureGermanCloud" {
+ "AzureGermanCloud"
+ {
$graphEnvironment = "Germany"
break
}
- Default {
+ Default
+ {
$graphEnvironment = "Global"
}
}
@@ -205,12 +200,16 @@ else
{
"Logging in to Microsoft Graph with $authenticationOption..."
- switch ($authenticationOption) {
- "UserAssignedManagedIdentity" {
+ switch ($authenticationOption)
+ {
+ "UserAssignedManagedIdentity"
+ {
Connect-MgGraph -Identity -ClientId $uamiClientID -Environment $graphEnvironment -NoWelcome
break
}
- Default { #ManagedIdentity
+ Default
+ {
+ #ManagedIdentity
Connect-MgGraph -Identity -Environment $graphEnvironment -NoWelcome
break
}
@@ -219,20 +218,20 @@ else
$domainName = (Get-MgDomain | Where-Object { $_.IsVerified -and $_.IsDefault } | Select-Object -First 1).Id
-$roles = Get-MgDirectoryRole -ExpandProperty Members -Property DisplayName,Members
+$roles = Get-MgDirectoryRole -ExpandProperty Members -Property DisplayName, Members
foreach ($role in $roles)
{
$roleMembers = $role.Members | Where-Object { -not($_.DeletedDateTime) }
foreach ($roleMember in $roleMembers)
{
$assignmentEntry = New-Object PSObject -Property @{
- Timestamp = $timestamp
- TenantGuid = $tenantId
- Cloud = $cloudEnvironment
- Model = "AzureAD"
- PrincipalId = $roleMember.Id
- Scope = $domainName
- RoleDefinition = $role.DisplayName
+ Timestamp = $timestamp
+ TenantGuid = $tenantId
+ Cloud = $cloudEnvironment
+ Model = "AzureAD"
+ PrincipalId = $roleMember.Id
+ Scope = $domainName
+ RoleDefinition = $role.DisplayName
}
$roleAssignments += $assignmentEntry
}
@@ -250,7 +249,7 @@ $rbacObjectsJson | Export-Csv -NoTypeInformation -Path $csvExportPath
"Export to $csvExportPath"
$csvBlobName = $csvExportPath
-$csvProperties = @{"ContentType" = "text/csv"};
+$csvProperties = @{"ContentType" = "text/csv" }
Set-AzStorageBlobContent -File $csvExportPath -Container $storageAccountSinkContainer -Properties $csvProperties -Blob $csvBlobName -Context $saCtx -Force
diff --git a/src/optimization-engine/upgrade-manifest.json b/src/optimization-engine/upgrade-manifest.json
index 457e0c712..362f489c6 100644
--- a/src/optimization-engine/upgrade-manifest.json
+++ b/src/optimization-engine/upgrade-manifest.json
@@ -532,7 +532,7 @@
{
"runbook": {
"name": "runbooks/data-collection/Export-RBACAssignmentsToBlobStorage.ps1",
- "version": "1.1.1.0"
+ "version": "1.1.2.0"
},
"container": "rbacexports",
"requiredVariables": [
@@ -689,7 +689,7 @@
{
"runbook": {
"name": "runbooks/data-collection/Export-PriceSheetToBlobStorage.ps1",
- "version": "1.1.2.0"
+ "version": "1.1.3.0"
},
"container": "pricesheetexports",
"requiredVariables": [
@@ -704,7 +704,7 @@
{
"runbook": {
"name": "runbooks/data-collection/Export-ReservationsPriceToBlobStorage.ps1",
- "version": "1.0.2.0"
+ "version": "1.0.3.0"
},
"container": "reservationspriceexports",
"requiredVariables": [