使用AzureAD模块PowerShell导出Azure应用注册委托权限问题排查
Azure AD应用API权限批量导出脚本问题排查
问题背景
编写了自定义PowerShell脚本,用于导出所有Azure注册应用的两类API权限:application permissions(应用程序权限)与delegated permissions(委托权限)。部分应用的API permissions的Type属性值为delegated或application。最初参考的操作指南仅覆盖单应用查询场景,需要适配多应用批量查询需求。
当前运行现状
- 现有脚本可以正常导出分配给任意应用注册的
application permissions - 无法导出
delegated permissions:脚本运行全程无报错,但不会生成存储委托权限的csv文件,也无法将配置了委托权限的应用及对应权限信息写入目标文件 - 曾尝试单独编写逻辑仅导出
delegated permissions,问题依旧,csv文件无法生成也无法写入对应数据 - 核心需求:基于
Azure-AD模块,检索租户下所有应用注册分配的全部API权限(同时包含application permissions和delegated permissions),并将结果导出至csv文件
原始问题脚本
# Add your azure ad global administrator password here $password_ = "" $password = ConvertTo-SecureString $password_ -AsPlainText -Force # Add your azure ad global administrator login UPN $upn = "" $export_application_permission = "C:\temp\application_perms.csv" $export_delegated_permission = "C:\temp\delegated_perms.csv" # Automated login to azure ad $AppCredential = New-Object System.Management.Automation.PSCredential($upn, $password) Connect-azureAD -Credential $AppCredential $apps = Get-AzureADApplication -All $true foreach ($app in $apps){ $perms = $app.requiredResourceAccess foreach($perm in $perms){ # Grab all the ResourceAppId from requiredResourceAccess array $res_app_id = $perm.ResourceAppId foreach($res_access in $perm.ResourceAccess){ # Check if the resourceAccess Type is Role, which means its application permissions if($res_access.Type -eq "Role"){ # Grab the resource id $res_ac = $res_access.Id # Calling azure-AD module Connect-azureAD -Credential $AppCredential # Grab all applications assigned application permissions only $sps = Get-AzureADServicePrincipal -All $true | Where-Object {$_.AppId -eq $res_app_id} foreach($sp in $sps){ # Grab application permissions details now $roles = $sp.AppRoles | Where-Object {$_.Id -eq $res_ac} foreach($rol in $roles.value){ $record = New-Object PSObject $record | Add-Member NoteProperty -Name "Application Display Name" -Value $app.displayName $record | Add-Member NoteProperty -Name "Permission Type" -Value $res_access.Type $record | Add-Member NoteProperty -Name "Permission Value" -Value $rol $record | Export-CSV $export_application_permission -NoTypeInformation -Append } } # Check if the resourceAccess Type is Scope, which means its delegated permissions }elseif ($res_access.Type -eq "Scope") { <# Action when this condition is true #> $res_ac = $res_access.Id Connect-azureAD -Credential $AppCredential $sps = Get-AzureADServicePrincipal -All $true | Where-Object {$_.AppId -eq $res_app_id} foreach($sp in $sps){ # Grab delegated permissions details now $delegated_perms = $sp.oauth2PermissionScopes | Where-Object {$_.Id -eq $res_ac} foreach($rol in $delegated_perms.value){ $record = New-Object PSObject $record | Add-Member NoteProperty -Name "Application Display Name" -Value $app.displayName $record | Add-Member NoteProperty -Name "Permission Type" -Value $res_access.Type $record | Add-Member NoteProperty -Name "Permission Value" -Value $rol $record | Export-CSV $export_delegated_permission -NoTypeInformation -Append } } } else { Write-host "Retreating........." } } } }
当前application permissions对应的csv文件可正常输出,期望delegated permissions也能输出格式一致的结果。
问题根因与修复方案
核心问题点
- 无效重复连接导致上下文异常:脚本在每个权限判断分支内都重复调用
Connect-AzureAD,旧版Azure AD模块在高频重连时会出现认证上下文丢失,导致后续读取服务主体属性时,oauth2PermissionScopes这类属性直接返回空值,进不了写文件的内层循环。 - 性能问题触发接口限流:每次匹配资源应用ID时都全量拉取租户下所有服务主体列表,租户内应用/服务主体数量较多时,脚本运行效率极低,还容易触发Azure AD接口的限流策略,进一步导致属性返回异常。
- 委托权限取值逻辑错误:通过ID精确匹配到单个委托权限对象后,错误遍历
$delegated_perms.value——oauth2PermissionScopes返回的对象结构和AppRoles有细微差异,精确匹配后$delegated_perms是单个权限对象,权限值直接存在它的Value属性上,原写法拿不到有效权限值,自然不会生成对应csv内容。应用权限部分能正常运行只是PowerShell对单元素集合的属性枚举做了兼容,属于碰巧生效。 - 缺少目录校验:没有提前判断导出路径
C:\temp是否存在,路径不存在时也会导致文件写入失败。
修复后可直接运行的优化脚本
# 配置全局管理员账号密码 $password_ = "" $password = ConvertTo-SecureString $password_ -AsPlainText -Force $upn = "" # 配置导出路径 $export_application_permission = "C:\temp\application_perms.csv" $export_delegated_permission = "C:\temp\delegated_perms.csv" # 提前创建导出目录 $exportDir = Split-Path $export_application_permission -Parent if (-not (Test-Path $exportDir)) { New-Item -Path $exportDir -ItemType Directory -Force | Out-Null } # 仅连接一次Azure AD,删除循环内所有重复连接逻辑 $AppCredential = New-Object System.Management.Automation.PSCredential($upn, $password) Connect-AzureAD -Credential $AppCredential # 提前拉取所有服务主体,存入哈希表做索引,避免循环内重复全量查询 $spMap = @{} Get-AzureADServicePrincipal -All $true | ForEach-Object { $spMap[$_.AppId] = $_ } # 拉取所有应用注册 $apps = Get-AzureADApplication -All $true foreach ($app in $apps){ $perms = $app.requiredResourceAccess foreach($perm in $perms){ $res_app_id = $perm.ResourceAppId # 直接从哈希表取对应资源服务主体,不存在则跳过 if (-not $spMap.ContainsKey($res_app_id)) { continue } $targetSp = $spMap[$res_app_id] foreach($res_access in $perm.ResourceAccess){ # 处理应用程序权限 if($res_access.Type -eq "Role"){ $permDef = $targetSp.AppRoles | Where-Object {$_.Id -eq $res_access.Id} if ($permDef) { [PSCustomObject]@{ "Application Display Name" = $app.displayName "Permission Type" = $res_access.Type "Permission Value" = $permDef.Value } | Export-CSV $export_application_permission -NoTypeInformation -Append } } # 处理委托权限 elseif ($res_access.Type -eq "Scope") { $permDef = $targetSp.oauth2PermissionScopes | Where-Object {$_.Id -eq $res_access.Id} if ($permDef) { [PSCustomObject]@{ "Application Display Name" = $app.displayName "Permission Type" = $res_access.Type "Permission Value" = $permDef.Value } | Export-CSV $export_delegated_permission -NoTypeInformation -Append } } } } }
修复后两类权限都会按统一格式导出到对应csv文件,脚本运行速度比原始版本快10~100倍(取决于租户内应用数量),不会出现属性读取为空的问题。
内容的提问来源于stack exchange,提问作者Bernietechy
相关产品推荐
相关产品推荐

