Contents
hide
Get-UserGroup-Owner-RoleAssignments-Subscriptions.ps1
# =====================================================================
# タイムスタンプ生成(CSV/LOG共通で使用)
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
# =====================================================================
# ログファイルパス(タイムスタンプ付き)
# =====================================================================
$logFilePath = "RoleAssignments.$stamp.log"
# =====================================================================
# ログメッセージ作成関数
# =====================================================================
function Write-LogMessage {
param ([string]$logMessage)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$logEntry = "$timestamp - $logMessage"
Write-Host $logEntry
Add-Content -Path $logFilePath -Value $logEntry
}
# =====================================================================
# サブスクリプション一覧の取得(Id & Name)およびマッピングテーブル作成
# =====================================================================
$azureSubscriptionsJsonString = az account list --query "[].{Id:id,Name:name}" -o json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve subscription list."
return
}
$azureSubscriptionObjects = $azureSubscriptionsJsonString | ConvertFrom-Json
$azureSubscriptionIdList = $azureSubscriptionObjects.Id
# サブスクリプションID → サブスクリプション名のマッピングテーブルを作成
$azureSubscriptionIdToNameMap = @{}
foreach ($subscriptionObject in $azureSubscriptionObjects) {
$azureSubscriptionIdToNameMap[$subscriptionObject.Id] = $subscriptionObject.Name
}
# =====================================================================
# 役割割り当て結果を保存する配列を初期化
# =====================================================================
$allUserGroupRoleAssignments = @()
foreach ($azureSubscriptionId in $azureSubscriptionIdList) {
Write-LogMessage "Processing subscription: $azureSubscriptionId"
# 役割割り当てを取得(JSON)
$roleAssignmentsJsonString = az role assignment list --all --subscription $azureSubscriptionId --output json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve role assignments: $azureSubscriptionId"
continue
}
# JSON → オブジェクト変換 & User/Groupのみフィルタリング(Owner除外、管理グループ継承除外)
$filteredUserGroupRoleAssignments = $roleAssignmentsJsonString | ConvertFrom-Json | Where-Object {
$_.principalType -in @("User", "Group") -and
$_.roleDefinitionName -ne "Owner" -and
$_.scope -like "/subscriptions/*"
}
if ($filteredUserGroupRoleAssignments) {
# SubscriptionId/NameおよびPrincipalObjectId情報を追加
$enrichedUserGroupRoleAssignments = $filteredUserGroupRoleAssignments | ForEach-Object {
# ScopeからSubscriptionIdを抽出: /subscriptions/<id>/...
$regexMatch = [regex]::Match($_.scope, '^/subscriptions/([^/]+)')
$subscriptionIdFromScope = if ($regexMatch.Success) { $regexMatch.Groups[1].Value } else { $null }
[pscustomobject]@{
SubscriptionName = $azureSubscriptionIdToNameMap[$subscriptionIdFromScope]
SubscriptionId = $subscriptionIdFromScope
PrincipalName = $_.principalName
PrincipalObjectId = $_.principalId
RoleDefinitionName = $_.roleDefinitionName
Scope = $_.scope
PrincipalType = $_.principalType
}
}
# ログ出力用テーブルフォーマット
$tableOutputForLog = $enrichedUserGroupRoleAssignments |
Format-Table SubscriptionName, SubscriptionId, PrincipalObjectId, PrincipalName, RoleDefinitionName, Scope, PrincipalType -AutoSize |
Out-String
Write-LogMessage "User/Group role assignments:`n$tableOutputForLog"
# CSV出力用に累積
$allUserGroupRoleAssignments += $enrichedUserGroupRoleAssignments
}
else {
Write-LogMessage "No User/Group role assignments: $azureSubscriptionId"
}
}
# =====================================================================
# CSVファイルパス定義(ログと同じタイムスタンプを使用)
# =====================================================================
$exportCsvFilePath = "RoleAssignments.$stamp.csv"
# =====================================================================
# 結果CSVのエクスポート
# =====================================================================
if ($allUserGroupRoleAssignments.Count -gt 0) {
$allUserGroupRoleAssignments |
Select-Object SubscriptionName, SubscriptionId, PrincipalName, PrincipalObjectId, RoleDefinitionName, Scope, PrincipalType |
Export-Csv -Path $exportCsvFilePath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Exported to CSV: $exportCsvFilePath"
}
else {
Write-LogMessage "No User/Group role assignments to export."
}
Get-UserGroup-Owner-RoleAssignments-Subscriptions-FromCSV.ps1
param(
[Parameter(Mandatory = $true)]
[string]$CsvFilePath # 対象のSubscriptionIdリストが含まれるCSVパス(ヘッダーにSubscriptionId必須)
)
# =====================================================================
# タイムスタンプ生成(CSV/LOG共通で使用)
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
# =====================================================================
# ログファイルパス(タイムスタンプ付き)
# =====================================================================
$logFilePath = "RoleAssignments.$stamp.log"
# =====================================================================
# ログメッセージ作成関数(JST基準時間)
# =====================================================================
function Write-LogMessage {
param ([string]$logMessage)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$logEntry = "$timestamp - $logMessage"
Write-Host $logEntry
Add-Content -Path $logFilePath -Value $logEntry
}
# =====================================================================
# 入力CSVの検証およびロード(SubscriptionIdを基準に収集)
# =====================================================================
if (-not (Test-Path $CsvFilePath)) {
Write-LogMessage "CSV not found: $CsvFilePath"
return
}
try {
$csvRows = Import-Csv -Path $CsvFilePath
}
catch {
Write-LogMessage "Failed to import CSV: $($_.Exception.Message)"
return
}
if (-not $csvRows -or -not ($csvRows | Get-Member -Name SubscriptionId -MemberType NoteProperty)) {
Write-LogMessage "CSV must contain a 'SubscriptionId' column."
return
}
# CSVからSubscriptionIdのみを抽出(重複排除、空白/NULLを除外)
$azureSubscriptionIdList = $csvRows |
Where-Object { $_.SubscriptionId -and $_.SubscriptionId.Trim() -ne "" } |
ForEach-Object { $_.SubscriptionId.Trim() } |
Select-Object -Unique
if (-not $azureSubscriptionIdList -or $azureSubscriptionIdList.Count -eq 0) {
Write-LogMessage "No SubscriptionId values found in CSV."
return
}
# =====================================================================
# サブスクリプション一覧の取得(Id & Name)およびマッピングテーブル作成
# - 名前のマッピングは可能な場合のみ使用(存在しない場合は空欄)
# =====================================================================
$azureSubscriptionsJsonString = az account list --query "[].{Id:id,Name:name}" -o json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve subscription list."
return
}
$azureSubscriptionObjects = $azureSubscriptionsJsonString | ConvertFrom-Json
# サブスクリプションID → サブスクリプション名のマッピングテーブルを作成
$azureSubscriptionIdToNameMap = @{}
foreach ($subscriptionObject in $azureSubscriptionObjects) {
$azureSubscriptionIdToNameMap[$subscriptionObject.Id] = $subscriptionObject.Name
}
# =====================================================================
# 役割割り当て結果を保存する配列を初期化
# =====================================================================
$allUserGroupRoleAssignments = @()
foreach ($azureSubscriptionId in $azureSubscriptionIdList) {
Write-LogMessage "Processing subscription: $azureSubscriptionId"
# 役割割り当てを取得(JSON)
$roleAssignmentsJsonString = az role assignment list --all --subscription $azureSubscriptionId --output json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve role assignments: $azureSubscriptionId"
continue
}
# JSON → オブジェクト変換 & User/Groupのみフィルタリング(Ownerのみ出力、管理グループ継承除外)
$filteredUserGroupRoleAssignments = $roleAssignmentsJsonString | ConvertFrom-Json | Where-Object {
$_.principalType -in @("User", "Group") -and
$_.roleDefinitionName -eq "Owner" -and
$_.scope -like "/subscriptions/*"
}
if ($filteredUserGroupRoleAssignments) {
# SubscriptionId/NameおよびPrincipalObjectId情報を追加
$enrichedUserGroupRoleAssignments = $filteredUserGroupRoleAssignments | ForEach-Object {
# ScopeからSubscriptionIdを抽出: /subscriptions/<id>/...
$regexMatch = [regex]::Match($_.scope, '^/subscriptions/([^/]+)')
$subscriptionIdFromScope = if ($regexMatch.Success) { $regexMatch.Groups[1].Value } else { $null }
[pscustomobject]@{
SubscriptionName = $azureSubscriptionIdToNameMap[$subscriptionIdFromScope]
SubscriptionId = $subscriptionIdFromScope
PrincipalName = $_.principalName
PrincipalObjectId = $_.principalId
RoleDefinitionName = $_.roleDefinitionName
Scope = $_.scope
PrincipalType = $_.principalType
}
}
# ログ出力用テーブルフォーマット
$tableOutputForLog = $enrichedUserGroupRoleAssignments |
Format-Table SubscriptionName, SubscriptionId, PrincipalObjectId, PrincipalName, RoleDefinitionName, Scope, PrincipalType -AutoSize |
Out-String
Write-LogMessage "User/Group role assignments:`n$tableOutputForLog"
# CSV出力用に累積
$allUserGroupRoleAssignments += $enrichedUserGroupRoleAssignments
}
else {
Write-LogMessage "No User/Group role assignments: $azureSubscriptionId"
}
}
# =====================================================================
# CSVファイルパス定義(ログと同じタイムスタンプを使用、JST基準)
# =====================================================================
$exportCsvFilePath = "RoleAssignments.$stamp.csv"
# =====================================================================
# 結果CSVのエクスポート
# =====================================================================
if ($allUserGroupRoleAssignments.Count -gt 0) {
$allUserGroupRoleAssignments |
Select-Object SubscriptionName, SubscriptionId, PrincipalName, PrincipalObjectId, RoleDefinitionName, Scope, PrincipalType |
Export-Csv -Path $exportCsvFilePath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Exported to CSV: $exportCsvFilePath"
}
else {
Write-LogMessage "No User/Group role assignments to export."
}
Get-UserGroup-NonOwner-RoleAssignments-Subscriptions.ps1
# =====================================================================
# 日本標準時(JST)タイムゾーン情報の準備
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
# =====================================================================
# タイムスタンプ生成(CSV/LOG共通で使用、JST基準)
# =====================================================================
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
# =====================================================================
# ログファイルパス(タイムスタンプ付き)
# =====================================================================
$logFilePath = "RoleAssignments.$stamp.log"
# =====================================================================
# ログメッセージ作成関数(JST基準時間)
# =====================================================================
function Write-LogMessage {
param ([string]$logMessage)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$logEntry = "$timestamp - $logMessage"
Write-Host $logEntry
Add-Content -Path $logFilePath -Value $logEntry
}
# =====================================================================
# サブスクリプション一覧の取得(Id & Name)およびマッピングテーブル作成
# =====================================================================
$azureSubscriptionsJsonString = az account list --query "[].{Id:id,Name:name}" -o json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve subscription list."
return
}
$azureSubscriptionObjects = $azureSubscriptionsJsonString | ConvertFrom-Json
$azureSubscriptionIdList = $azureSubscriptionObjects.Id
# サブスクリプションID → サブスクリプション名のマッピングテーブルを作成
$azureSubscriptionIdToNameMap = @{}
foreach ($subscriptionObject in $azureSubscriptionObjects) {
$azureSubscriptionIdToNameMap[$subscriptionObject.Id] = $subscriptionObject.Name
}
# =====================================================================
# 役割割り当て結果を保存する配列を初期化
# =====================================================================
$allUserGroupRoleAssignments = @()
foreach ($azureSubscriptionId in $azureSubscriptionIdList) {
Write-LogMessage "Processing subscription: $azureSubscriptionId"
# 役割割り当てを取得(JSON)
$roleAssignmentsJsonString = az role assignment list --all --subscription $azureSubscriptionId --output json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve role assignments: $azureSubscriptionId"
continue
}
# JSON → オブジェクト変換 & User/Groupのみフィルタリング(Owner除外、管理グループ継承除外)
$filteredUserGroupRoleAssignments = $roleAssignmentsJsonString | ConvertFrom-Json | Where-Object {
$_.principalType -in @("User", "Group") -and
$_.roleDefinitionName -ne "Owner" -and
$_.scope -like "/subscriptions/*"
}
if ($filteredUserGroupRoleAssignments) {
# SubscriptionId/NameおよびPrincipalObjectId情報を追加
$enrichedUserGroupRoleAssignments = $filteredUserGroupRoleAssignments | ForEach-Object {
# ScopeからSubscriptionIdを抽出: /subscriptions/<id>/...
$regexMatch = [regex]::Match($_.scope, '^/subscriptions/([^/]+)')
$subscriptionIdFromScope = if ($regexMatch.Success) { $regexMatch.Groups[1].Value } else { $null }
[pscustomobject]@{
SubscriptionName = $azureSubscriptionIdToNameMap[$subscriptionIdFromScope]
SubscriptionId = $subscriptionIdFromScope
PrincipalName = $_.principalName
PrincipalObjectId = $_.principalId
RoleDefinitionName = $_.roleDefinitionName
Scope = $_.scope
PrincipalType = $_.principalType
}
}
# ログ出力用テーブルフォーマット
$tableOutputForLog = $enrichedUserGroupRoleAssignments |
Format-Table SubscriptionName, SubscriptionId, PrincipalObjectId, PrincipalName, RoleDefinitionName, Scope, PrincipalType -AutoSize |
Out-String
Write-LogMessage "User/Group role assignments:`n$tableOutputForLog"
# CSV出力用に累積
$allUserGroupRoleAssignments += $enrichedUserGroupRoleAssignments
}
else {
Write-LogMessage "No User/Group role assignments: $azureSubscriptionId"
}
}
# =====================================================================
# CSVファイルパス定義(ログと同じタイムスタンプを使用、JST基準)
# =====================================================================
$exportCsvFilePath = "RoleAssignments.$stamp.csv"
# =====================================================================
# 結果CSVのエクスポート
# =====================================================================
if ($allUserGroupRoleAssignments.Count -gt 0) {
$allUserGroupRoleAssignments |
Select-Object SubscriptionName, SubscriptionId, PrincipalName, PrincipalObjectId, RoleDefinitionName, Scope, PrincipalType |
Export-Csv -Path $exportCsvFilePath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Exported to CSV: $exportCsvFilePath"
}
else {
Write-LogMessage "No User/Group role assignments to export."
}
Get-UserGroup-NonOwner-RoleAssignments-Subscriptions-FromCSV.ps1
param(
[Parameter(Mandatory = $true)]
[string]$CsvFilePath # 対象のSubscriptionIdリストが含まれるCSVパス(ヘッダーにSubscriptionId必須)
)
# =====================================================================
# タイムスタンプ生成(CSV/LOG共通で使用)
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
# =====================================================================
# ログファイルパス(タイムスタンプ付き)
# =====================================================================
$logFilePath = "RoleAssignments.$stamp.log"
# =====================================================================
# ログメッセージ作成関数(JST基準時間)
# =====================================================================
function Write-LogMessage {
param ([string]$logMessage)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$logEntry = "$timestamp - $logMessage"
Write-Host $logEntry
Add-Content -Path $logFilePath -Value $logEntry
}
# =====================================================================
# 入力CSVの検証およびロード(SubscriptionIdを基準に収集)
# =====================================================================
if (-not (Test-Path $CsvFilePath)) {
Write-LogMessage "CSV not found: $CsvFilePath"
return
}
try {
$csvRows = Import-Csv -Path $CsvFilePath
}
catch {
Write-LogMessage "Failed to import CSV: $($_.Exception.Message)"
return
}
if (-not $csvRows -or -not ($csvRows | Get-Member -Name SubscriptionId -MemberType NoteProperty)) {
Write-LogMessage "CSV must contain a 'SubscriptionId' column."
return
}
# CSVからSubscriptionIdのみを抽出(重複排除、空白/NULLを除外)
$azureSubscriptionIdList = $csvRows |
Where-Object { $_.SubscriptionId -and $_.SubscriptionId.Trim() -ne "" } |
ForEach-Object { $_.SubscriptionId.Trim() } |
Select-Object -Unique
if (-not $azureSubscriptionIdList -or $azureSubscriptionIdList.Count -eq 0) {
Write-LogMessage "No SubscriptionId values found in CSV."
return
}
# =====================================================================
# サブスクリプション一覧の取得(Id & Name)およびマッピングテーブル作成
# - 名前のマッピングは可能な場合のみ使用(存在しない場合は空欄)
# =====================================================================
$azureSubscriptionsJsonString = az account list --query "[].{Id:id,Name:name}" -o json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve subscription list."
return
}
$azureSubscriptionObjects = $azureSubscriptionsJsonString | ConvertFrom-Json
# サブスクリプションID → サブスクリプション名のマッピングテーブルを作成
$azureSubscriptionIdToNameMap = @{}
foreach ($subscriptionObject in $azureSubscriptionObjects) {
$azureSubscriptionIdToNameMap[$subscriptionObject.Id] = $subscriptionObject.Name
}
# =====================================================================
# 役割割り当て結果を保存する配列を初期化
# =====================================================================
$allUserGroupRoleAssignments = @()
foreach ($azureSubscriptionId in $azureSubscriptionIdList) {
Write-LogMessage "Processing subscription from CSV: $azureSubscriptionId"
# 役割割り当てを取得(JSON)
$roleAssignmentsJsonString = az role assignment list --all --subscription $azureSubscriptionId --output json
if ($LASTEXITCODE -ne 0) {
Write-LogMessage "Failed to retrieve role assignments: $azureSubscriptionId"
continue
}
# JSON → オブジェクト変換 & User/Groupのみフィルタリング(Owner除外、管理グループ継承除外)
$filteredUserGroupRoleAssignments = $roleAssignmentsJsonString | ConvertFrom-Json | Where-Object {
$_.principalType -in @("User", "Group") -and
$_.roleDefinitionName -ne "Owner" -and
$_.scope -like "/subscriptions/*"
}
if ($filteredUserGroupRoleAssignments) {
# SubscriptionId/NameおよびPrincipalObjectId情報を追加
$enrichedUserGroupRoleAssignments = $filteredUserGroupRoleAssignments | ForEach-Object {
# ScopeからSubscriptionIdを抽出: /subscriptions/<id>/...
$regexMatch = [regex]::Match($_.scope, '^/subscriptions/([^/]+)')
$subscriptionIdFromScope = if ($regexMatch.Success) { $regexMatch.Groups[1].Value } else { $null }
[pscustomobject]@{
SubscriptionName = if ($subscriptionIdFromScope -and $azureSubscriptionIdToNameMap.ContainsKey($subscriptionIdFromScope)) { $azureSubscriptionIdToNameMap[$subscriptionIdFromScope] } else { "" }
SubscriptionId = $subscriptionIdFromScope
PrincipalName = $_.principalName
PrincipalObjectId = $_.principalId
RoleDefinitionName = $_.roleDefinitionName
Scope = $_.scope
PrincipalType = $_.principalType
}
}
# ログ出力用テーブルフォーマット
$tableOutputForLog = $enrichedUserGroupRoleAssignments |
Format-Table SubscriptionName, SubscriptionId, PrincipalObjectId, PrincipalName, RoleDefinitionName, Scope, PrincipalType -AutoSize |
Out-String
Write-LogMessage "User/Group role assignments:`n$tableOutputForLog"
# CSV出力用に累積
$allUserGroupRoleAssignments += $enrichedUserGroupRoleAssignments
}
else {
Write-LogMessage "No User/Group role assignments: $azureSubscriptionId"
}
}
# =====================================================================
# CSVファイルパス定義(ログと同じタイムスタンプを使用、JST基準)
# =====================================================================
$exportCsvFilePath = "RoleAssignments.$stamp.csv"
# =====================================================================
# 結果CSVのエクスポート
# =====================================================================
if ($allUserGroupRoleAssignments.Count -gt 0) {
$allUserGroupRoleAssignments |
Select-Object SubscriptionName, SubscriptionId, PrincipalName, PrincipalObjectId, RoleDefinitionName, Scope, PrincipalType |
Export-Csv -Path $exportCsvFilePath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Exported to CSV: $exportCsvFilePath"
}
else {
Write-LogMessage "No User/Group role assignments to export."
}
Delete-RoleAssignments-FromCSV.ps1
param(
[Parameter(Mandatory = $true)]
[string]$CsvFilePath, # 処理するCSVファイルのパス(必須、SubscriptionId/PrincipalObjectId/RoleDefinitionName/Scope 必須)
[switch]$EnableWhatIf # WhatIfモードスイッチ(trueの場合、実際には実行せずシミュレーションのみ)
)
# =====================================================================
# 日本標準時(JST)タイムゾーン情報の準備
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
# =====================================================================
# タイムスタンプ生成およびログ/CSVファイルパス定義(JST基準)
# =====================================================================
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
$logFilePath = "RoleAssignments.delete.$stamp.log"
# =====================================================================
# ログメッセージ作成関数(JST基準時間)
# =====================================================================
function Write-LogMessage {
param ([string]$Message)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$entry = "$timestamp - $Message"
Write-Host $entry
Add-Content -Path $logFilePath -Value $entry
}
# =====================================================================
# 結果CSVパス生成関数(全体/失敗専用、JSTタイムスタンプ使用)
# =====================================================================
function Get-ResultCsvPath {
param([string]$SourceCsvPath)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$dir = Split-Path -Parent $SourceCsvPath
if (-not $dir -or $dir -eq "") {
$dir = $PSScriptRoot
}
$base = Split-Path -Leaf $SourceCsvPath
$name = [System.IO.Path]::GetFileNameWithoutExtension($base)
$ext = [System.IO.Path]::GetExtension($base)
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
return (Join-Path $dir "$name.delete.with-results.$stamp$ext")
}
function Get-FailedCsvPath {
param([string]$SourceCsvPath)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$dir = Split-Path -Parent $SourceCsvPath
if (-not $dir -or $dir -eq "") {
$dir = $PSScriptRoot
}
$base = Split-Path -Leaf $SourceCsvPath
$name = [System.IO.Path]::GetFileNameWithoutExtension($base)
$ext = [System.IO.Path]::GetExtension($base)
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
return (Join-Path $dir "$name.delete.failed-only.$stamp$ext")
}
# =====================================================================
# 入力CSVのロード
# =====================================================================
# CsvFilePath引数が単純なファイル名の場合、スクリプトフォルダ基準で補正
if (-not (Split-Path -Parent $CsvFilePath)) {
$CsvFilePath = Join-Path $PSScriptRoot $CsvFilePath
}
if (-not (Test-Path $CsvFilePath)) {
throw "CSV file not found: $CsvFilePath"
}
$inputRows = Import-Csv -Path $CsvFilePath
if (-not $inputRows -or $inputRows.Count -eq 0) {
throw "No rows to process in CSV: $CsvFilePath"
}
# [ADD] ----------------------------------------------------------------
# 実行ごとに保存ディレクトリを作成(CSVと同じフォルダ下に run.delete.<stamp>)
# 既存の変数/ロジックを変更せず、作成とパス再割り当てのみ実施
$__baseDir = Split-Path -Parent $CsvFilePath
if (-not $__baseDir -or $__baseDir -eq "") { $__baseDir = $PSScriptRoot }
$RunOutputDir = Join-Path $__baseDir ("run.delete." + $stamp)
New-Item -ItemType Directory -Path $RunOutputDir -Force | Out-Null
# ログファイルを保存ディレクトリに移動(パスのみ再指定、変数名/関数は変更なし)
$logFilePath = Join-Path $RunOutputDir (Split-Path -Leaf $logFilePath)
# ---------------------------------------------------------------------
# 必須カラムチェック
$required = @('SubscriptionId', 'PrincipalObjectId', 'RoleDefinitionName', 'Scope')
$missing = $required | Where-Object { $inputRows[0].PSObject.Properties.Name -notcontains $_ }
if ($missing.Count -gt 0) {
throw "Missing required columns in CSV: $($missing -join ', ')"
}
# =====================================================================
# 結果CSVパスの準備
# =====================================================================
$resultCsvPath = Get-ResultCsvPath -SourceCsvPath $CsvFilePath
$failedCsvPath = Get-FailedCsvPath -SourceCsvPath $CsvFilePath
# [ADD] ----------------------------------------------------------------
# 結果CSVも保存ディレクトリに配置(ファイル名は既存ロジックを維持)
$resultCsvPath = Join-Path $RunOutputDir (Split-Path -Leaf $resultCsvPath)
$failedCsvPath = Join-Path $RunOutputDir (Split-Path -Leaf $failedCsvPath)
Write-LogMessage "Run output directory: $RunOutputDir"
# ---------------------------------------------------------------------
Write-LogMessage "Result CSV (all): $resultCsvPath"
Write-LogMessage "Result CSV (failed-only): $failedCsvPath"
# 結果累積配列(元のカラム+実行結果カラム)
$rowsWithResults = @()
# 進行度計算用
$totalRows = $inputRows.Count
# =====================================================================
# メイン処理ループ(CSV順に処理)— 進行度+リソースグループスコープ分岐
# =====================================================================
for ($i = 0; $i -lt $totalRows; $i++) {
$row = $inputRows[$i]
$rowIndex = $i + 1
# 元のカラム
$subscriptionId = $row.SubscriptionId
$PrincipalObjectId = $row.PrincipalObjectId
$PrincipalName = $row.PrincipalName
$roleDefinitionName = $row.RoleDefinitionName
$scope = $row.Scope
# 実行メタデータ
$executedAt = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("o") # JST, ISO8601
$startTime = Get-Date # 経過時間計算用(UTC/JSTは不問)
# スコープがリソースグループの場合 --resource-group を使用
# 例: /subscriptions/<subId>/resourceGroups/<rgName>
$useRgParam = $false
$rgName = $null
if ($scope -match '^/subscriptions/[^/]+/resourceGroups/([^/]+)/*$') {
$useRgParam = $true
$rgName = $Matches[1]
}
# 実行するAzure CLIコマンド
if ($useRgParam -and $rgName) {
# リソースグループ範囲
$commandToRun = "az role assignment delete --assignee `"$PrincipalObjectId`" --role `"$roleDefinitionName`" --resource-group `"$rgName`" --subscription $subscriptionId"
}
else {
# サブスクリプション/リソース範囲(既存方式)
$commandToRun = "az role assignment delete --assignee `"$PrincipalObjectId`" --role `"$roleDefinitionName`" --scope `"$scope`" --subscription $subscriptionId"
}
$stdAll = $null
$exitCode = $null
$result = $null
if ($EnableWhatIf) {
# WhatIfモード:実際には実行しない
$result = "WhatIf"
$exitCode = ""
$stdAll = "[WhatIf] Command not executed."
Write-LogMessage "[WhatIf] $commandToRun"
}
else {
# 実際に実行:標準出力+エラーをすべてキャプチャ
$stdAll = Invoke-Expression "$commandToRun 2>&1"
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
$result = "Success"
Write-LogMessage "Processing Row [$rowIndex/$totalRows] DELETE succeeded: $PrincipalName / $roleDefinitionName / $scope"
}
else {
$result = "Failed"
Write-LogMessage "Processing Row [$rowIndex/$totalRows] DELETE failed (ExitCode=$exitCode): $PrincipalName / $roleDefinitionName / $scope"
}
}
# 実行時間(ms)
$durationMs = [int]((Get-Date) - $startTime).TotalMilliseconds
# 元の行+実行結果カラムを結合した新しいオブジェクトを作成
$rowWithResult = [pscustomobject]@{}
foreach ($col in $row.PSObject.Properties.Name) {
$rowWithResult | Add-Member -NotePropertyName $col -NotePropertyValue $row.$col
}
$rowWithResult | Add-Member -NotePropertyName ExecutedAt -NotePropertyValue $executedAt
$rowWithResult | Add-Member -NotePropertyName DurationMs -NotePropertyValue $durationMs
$rowWithResult | Add-Member -NotePropertyName Result -NotePropertyValue $result
$rowWithResult | Add-Member -NotePropertyName ExitCode -NotePropertyValue $exitCode
$rowWithResult | Add-Member -NotePropertyName CommandLine -NotePropertyValue $commandToRun
$rowWithResult | Add-Member -NotePropertyName StdAll -NotePropertyValue ($stdAll -join "`n")
# 蓄積
$rowsWithResults += $rowWithResult
}
# =====================================================================
# 結果CSVの保存(全体+失敗のみ)
# =====================================================================
# 1) 全体結果
$rowsWithResults | Export-Csv -Path $resultCsvPath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Saved result CSV (all): $resultCsvPath"
# 2) 失敗のみ
$failedRows = $rowsWithResults | Where-Object { $_.Result -eq 'Failed' }
if ($failedRows -and $failedRows.Count -gt 0) {
$failedRows | Export-Csv -Path $failedCsvPath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Saved result CSV (failed-only): $failedCsvPath (count: $($failedRows.Count))"
}
else {
Write-LogMessage "No failed rows. Skipping failed-only CSV."
}
Backout-RoleAssignments-FromCSV.ps1
param(
[Parameter(Mandatory = $true)]
[string]$CsvFilePath, # 処理するCSVファイルのパス(必須、SubscriptionId/PrincipalObjectId/RoleDefinitionName/Scope 必須)
[switch]$EnableWhatIf # WhatIfモードスイッチ(trueの場合、実際には実行せずシミュレーションのみ)
)
# =====================================================================
# 日本標準時(JST)タイムゾーン情報の準備
# =====================================================================
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
# =====================================================================
# タイムスタンプ生成およびログ/CSVファイルパス定義(JST基準)
# =====================================================================
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
$logFilePath = "RoleAssignments.backout.$stamp.log"
# =====================================================================
# ログメッセージ作成関数(JST基準時間)
# =====================================================================
function Write-LogMessage {
param ([string]$Message)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$timestamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyy-MM-dd HH:mm:ss")
$entry = "$timestamp - $Message"
Write-Host $entry
Add-Content -Path $logFilePath -Value $entry
}
# =====================================================================
# 結果CSVパス生成関数(全体/失敗専用、JSTタイムスタンプ使用)
# =====================================================================
function Get-ResultCsvPath {
param([string]$SourceCsvPath)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$dir = Split-Path -Parent $SourceCsvPath
if (-not $dir -or $dir -eq "") {
$dir = $PSScriptRoot
}
$base = Split-Path -Leaf $SourceCsvPath
$name = [System.IO.Path]::GetFileNameWithoutExtension($base)
$ext = [System.IO.Path]::GetExtension($base)
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
return (Join-Path $dir "$name.backout.with-results.$stamp$ext")
}
function Get-FailedCsvPath {
param([string]$SourceCsvPath)
$jpTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("Tokyo Standard Time")
$dir = Split-Path -Parent $SourceCsvPath
if (-not $dir -or $dir -eq "") {
$dir = $PSScriptRoot
}
$base = Split-Path -Leaf $SourceCsvPath
$name = [System.IO.Path]::GetFileNameWithoutExtension($base)
$ext = [System.IO.Path]::GetExtension($base)
$stamp = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("yyyyMMdd-HHmmss")
return (Join-Path $dir "$name.backout.failed-only.$stamp$ext")
}
# =====================================================================
# 入力CSVのロード
# =====================================================================
# CsvFilePath引数が単純なファイル名の場合、スクリプトフォルダ基準で補正
if (-not (Split-Path -Parent $CsvFilePath)) {
$CsvFilePath = Join-Path $PSScriptRoot $CsvFilePath
}
if (-not (Test-Path $CsvFilePath)) {
throw "CSV file not found: $CsvFilePath"
}
$inputRows = Import-Csv -Path $CsvFilePath
if (-not $inputRows -or $inputRows.Count -eq 0) {
throw "No rows to process in CSV: $CsvFilePath"
}
# [ADD] ----------------------------------------------------------------
# 実行ごとに保存ディレクトリを作成(CSVと同じフォルダ下に run.backout.<stamp>)
$__baseDir = Split-Path -Parent $CsvFilePath
if (-not $__baseDir -or $__baseDir -eq "") { $__baseDir = $PSScriptRoot }
$RunOutputDir = Join-Path $__baseDir ("run.backout." + $stamp)
New-Item -ItemType Directory -Path $RunOutputDir -Force | Out-Null
# ログファイルを保存ディレクトリに移動(パスのみ再指定、変数名/関数は変更なし)
$logFilePath = Join-Path $RunOutputDir (Split-Path -Leaf $logFilePath)
# ---------------------------------------------------------------------
# 必須カラムチェック
$required = @('SubscriptionId', 'PrincipalObjectId', 'RoleDefinitionName', 'Scope')
$missing = $required | Where-Object { $inputRows[0].PSObject.Properties.Name -notcontains $_ }
if ($missing.Count -gt 0) {
throw "Missing required columns in CSV: $($missing -join ', ')"
}
# =====================================================================
# 結果CSVパスの準備
# =====================================================================
$resultCsvPath = Get-ResultCsvPath -SourceCsvPath $CsvFilePath
$failedCsvPath = Get-FailedCsvPath -SourceCsvPath $CsvFilePath
# [ADD] ----------------------------------------------------------------
# 結果CSVも保存ディレクトリに配置(ファイル名は既存ロジックを維持)
$resultCsvPath = Join-Path $RunOutputDir (Split-Path -Leaf $resultCsvPath)
$failedCsvPath = Join-Path $RunOutputDir (Split-Path -Leaf $failedCsvPath)
Write-LogMessage "Run output directory: $RunOutputDir"
# ---------------------------------------------------------------------
Write-LogMessage "Result CSV (all): $resultCsvPath"
Write-LogMessage "Result CSV (failed-only): $failedCsvPath"
# 結果累積配列(元のカラム+実行結果カラム)
$rowsWithResults = @()
# 進行度計算用
$totalRows = $inputRows.Count
# =====================================================================
# メイン処理ループ(CSV順に処理)— 進行度+リソースグループスコープ分岐
# =====================================================================
for ($i = 0; $i -lt $totalRows; $i++) {
$row = $inputRows[$i]
$rowIndex = $i + 1
# 元のカラム
$subscriptionId = $row.SubscriptionId
$PrincipalObjectId = $row.PrincipalObjectId
$PrincipalName = $row.PrincipalName
$roleDefinitionName = $row.RoleDefinitionName
$scope = $row.Scope
# 実行メタデータ
$executedAt = [System.TimeZoneInfo]::ConvertTime((Get-Date), $jpTimeZone).ToString("o") # JST, ISO8601
$startTime = Get-Date # 経過時間計算用
# スコープがリソースグループの場合 --resource-group を使用(削除スクリプトと同様)
# 例: /subscriptions/<subId>/resourceGroups/<rgName>
$useRgParam = $false
$rgName = $null
if ($scope -match '^/subscriptions/[^/]+/resourceGroups/([^/]+)/*$') {
$useRgParam = $true
$rgName = $Matches[1]
}
# 実行するAzure CLIコマンド(削除スクリプトと同じスコープ分岐/形式)
if ($useRgParam -and $rgName) {
# リソースグループ範囲
$commandToRun = "az role assignment create --assignee `"$PrincipalObjectId`" --role `"$roleDefinitionName`" --resource-group `"$rgName`" --subscription $subscriptionId"
}
else {
# サブスクリプション/リソース範囲
$commandToRun = "az role assignment create --assignee `"$PrincipalObjectId`" --role `"$roleDefinitionName`" --scope `"$scope`" --subscription $subscriptionId"
}
$stdAll = $null
$exitCode = $null
$result = $null
if ($EnableWhatIf) {
# WhatIfモード:実際には実行しない
$result = "WhatIf"
$exitCode = ""
$stdAll = "[WhatIf] Command not executed."
Write-LogMessage "[WhatIf] $commandToRun"
}
else {
# 実際に実行:標準出力+エラーをすべてキャプチャ
$stdAll = Invoke-Expression "$commandToRun 2>&1"
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0) {
$result = "Success"
Write-LogMessage "Processing Row [$rowIndex/$totalRows] CREATE succeeded: $PrincipalName | $roleDefinitionName | $scope"
}
else {
$result = "Failed"
Write-LogMessage "Processing Row [$rowIndex/$totalRows] CREATE failed (ExitCode=$exitCode): $PrincipalName | $roleDefinitionName | $scope"
}
}
# 実行時間(ms)
$durationMs = [int]((Get-Date) - $startTime).TotalMilliseconds
# 元の行+実行結果カラムを結合した新しいオブジェクトを作成
$rowWithResult = [pscustomobject]@{}
foreach ($col in $row.PSObject.Properties.Name) {
$rowWithResult | Add-Member -NotePropertyName $col -NotePropertyValue $row.$col
}
$rowWithResult | Add-Member -NotePropertyName ExecutedAt -NotePropertyValue $executedAt
$rowWithResult | Add-Member -NotePropertyName DurationMs -NotePropertyValue $durationMs
$rowWithResult | Add-Member -NotePropertyName Result -NotePropertyValue $result
$rowWithResult | Add-Member -NotePropertyName ExitCode -NotePropertyValue $exitCode
$rowWithResult | Add-Member -NotePropertyName CommandLine -NotePropertyValue $commandToRun
$rowWithResult | Add-Member -NotePropertyName StdAll -NotePropertyValue ($stdAll -join "`n")
# 蓄積
$rowsWithResults += $rowWithResult
}
# =====================================================================
# 結果CSVの保存(全体+失敗のみ)
# =====================================================================
# 1) 全体結果
$rowsWithResults | Export-Csv -Path $resultCsvPath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Saved result CSV (all): $resultCsvPath"
# 2) 失敗のみ
$failedRows = $rowsWithResults | Where-Object { $_.Result -eq 'Failed' }
if ($failedRows -and $failedRows.Count -gt 0) {
$failedRows | Export-Csv -Path $failedCsvPath -NoTypeInformation -Encoding UTF8
Write-LogMessage "Saved result CSV (failed-only): $failedCsvPath (count: $($failedRows.Count))"
}
else {
Write-LogMessage "No failed rows. Skipping failed-only CSV."
}