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": [