Add Set-AzureSQLPermissions script and CSV for managing Azure SQL permissions
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Grants Azure SQL Database permissions to managed identities listed in a CSV file.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads a CSV of ServerName/DatabaseName/ManagedIdentityName/DataReader/DataWriter/Execute
|
||||
rows, authenticates to Azure SQL using an access token from 'az account get-access-token',
|
||||
creates each managed identity as an external database user if it does not already exist,
|
||||
and grants the requested db_datareader/db_datawriter/EXECUTE permissions idempotently.
|
||||
Requires the Azure CLI ('az') and the SqlServer module (Invoke-Sqlcmd) to be installed,
|
||||
and an active 'az login' session with rights to request database access tokens.
|
||||
|
||||
.PARAMETER CsvPath
|
||||
Path to the input CSV file. Defaults to 'AzureSQLPermissions.csv' next to this script.
|
||||
|
||||
.EXAMPLE
|
||||
PS> .\Set-AzureSQLPermissions.ps1
|
||||
Processes AzureSQLPermissions.csv from the script folder and applies the permissions.
|
||||
|
||||
.EXAMPLE
|
||||
PS> .\Set-AzureSQLPermissions.ps1 -CsvPath C:\data\perms.csv -WhatIf
|
||||
Shows which grants would be applied without changing any database.
|
||||
|
||||
.NOTES
|
||||
Author: Michal Horák
|
||||
Created: 2026-07-21
|
||||
Version: 1.1.0
|
||||
Changelog:
|
||||
1.1.0 - Added standard header, ShouldProcess/-WhatIf support, CsvPath parameter,
|
||||
Set-StrictMode/$ErrorActionPreference, function help blocks, exit 0 on success.
|
||||
1.0.0 - Initial version
|
||||
#>
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
|
||||
param(
|
||||
[Parameter(Mandatory = $false, HelpMessage = 'Path to the CSV file containing permission rows.')]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]
|
||||
$CsvPath = (Join-Path $PSScriptRoot "AzureSQLPermissions.csv")
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$LogFile = Join-Path $PSScriptRoot ("AzureSQLPermissions_{0}.log" -f (Get-Date -Format "yyyyMMdd_HHmmss"))
|
||||
|
||||
function Write-Log {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Writes a timestamped message to the console and to the run's log file.
|
||||
.PARAMETER Message
|
||||
The text to log.
|
||||
.PARAMETER Level
|
||||
Severity label prefixed to the log line (e.g. INFO, ERROR, SUCCESS). Defaults to INFO.
|
||||
.EXAMPLE
|
||||
Write-Log -Message "Starting" -Level INFO
|
||||
#>
|
||||
param(
|
||||
[string]$Message,
|
||||
[string]$Level = "INFO"
|
||||
)
|
||||
|
||||
$Line = "{0} [{1}] {2}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Level, $Message
|
||||
Write-Host $Line
|
||||
Add-Content -Path $LogFile -Value $Line -ErrorAction Stop
|
||||
}
|
||||
|
||||
function Test-Yes {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Interprets a CSV cell value as a boolean flag.
|
||||
.PARAMETER Value
|
||||
The raw CSV cell text (e.g. "Yes", "Y", "True", "1").
|
||||
.EXAMPLE
|
||||
Test-Yes -Value 'Yes'
|
||||
#>
|
||||
param([string]$Value)
|
||||
return $Value -match '^(Yes|Y|True|1)$'
|
||||
}
|
||||
|
||||
Write-Log "Starting Azure SQL permission script"
|
||||
Write-Log "CSV file: $CsvPath"
|
||||
Write-Log "Log file: $LogFile"
|
||||
|
||||
try {
|
||||
if (!(Test-Path $CsvPath)) {
|
||||
throw "CSV file not found: $CsvPath"
|
||||
}
|
||||
|
||||
if (-not (Get-Command az -ErrorAction SilentlyContinue)) {
|
||||
throw "Azure CLI 'az' is not available."
|
||||
}
|
||||
|
||||
if (-not (Get-Command Invoke-Sqlcmd -ErrorAction SilentlyContinue)) {
|
||||
throw "Invoke-Sqlcmd is not available. Install it with: Install-Module SqlServer -Scope CurrentUser -Force"
|
||||
}
|
||||
|
||||
$Permissions = Import-Csv -Path $CsvPath -ErrorAction Stop
|
||||
|
||||
if (-not $Permissions) {
|
||||
throw "CSV file is empty."
|
||||
}
|
||||
|
||||
$AccessToken = az account get-access-token `
|
||||
--resource https://database.windows.net/ `
|
||||
--query accessToken `
|
||||
-o tsv
|
||||
|
||||
if (-not $AccessToken) {
|
||||
throw "Failed to obtain Azure SQL access token. Run 'az login' first."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log $_.Exception.Message "ERROR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
foreach ($Entry in $Permissions) {
|
||||
|
||||
$ServerName = $Entry.ServerName.Trim()
|
||||
$DatabaseName = $Entry.DatabaseName.Trim()
|
||||
$ManagedIdentityName = $Entry.ManagedIdentityName.Trim()
|
||||
|
||||
$GrantDataReader = Test-Yes $Entry.DataReader
|
||||
$GrantDataWriter = Test-Yes $Entry.DataWriter
|
||||
$GrantExecute = Test-Yes $Entry.Execute
|
||||
|
||||
Write-Log "--------------------------------------------"
|
||||
Write-Log "Server: $ServerName"
|
||||
Write-Log "Database: $DatabaseName"
|
||||
Write-Log "Managed Identity: $ManagedIdentityName"
|
||||
Write-Log "DataReader: $GrantDataReader, DataWriter: $GrantDataWriter, Execute: $GrantExecute"
|
||||
|
||||
try {
|
||||
if ([string]::IsNullOrWhiteSpace($ServerName) -or
|
||||
[string]::IsNullOrWhiteSpace($DatabaseName) -or
|
||||
[string]::IsNullOrWhiteSpace($ManagedIdentityName)) {
|
||||
throw "Missing ServerName, DatabaseName or ManagedIdentityName in CSV row."
|
||||
}
|
||||
|
||||
$PermissionSql = ""
|
||||
|
||||
if ($GrantDataReader) {
|
||||
$PermissionSql += @"
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.database_role_members rm
|
||||
JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id
|
||||
JOIN sys.database_principals u ON rm.member_principal_id = u.principal_id
|
||||
WHERE r.name = 'db_datareader'
|
||||
AND u.name = N'$ManagedIdentityName'
|
||||
)
|
||||
BEGIN
|
||||
ALTER ROLE db_datareader ADD MEMBER [$ManagedIdentityName];
|
||||
END;
|
||||
"@
|
||||
}
|
||||
|
||||
if ($GrantDataWriter) {
|
||||
$PermissionSql += @"
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.database_role_members rm
|
||||
JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id
|
||||
JOIN sys.database_principals u ON rm.member_principal_id = u.principal_id
|
||||
WHERE r.name = 'db_datawriter'
|
||||
AND u.name = N'$ManagedIdentityName'
|
||||
)
|
||||
BEGIN
|
||||
ALTER ROLE db_datawriter ADD MEMBER [$ManagedIdentityName];
|
||||
END;
|
||||
"@
|
||||
}
|
||||
|
||||
if ($GrantExecute) {
|
||||
$PermissionSql += @"
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.database_permissions p
|
||||
JOIN sys.database_principals pr
|
||||
ON p.grantee_principal_id = pr.principal_id
|
||||
WHERE pr.name = N'$ManagedIdentityName'
|
||||
AND p.permission_name = 'EXECUTE'
|
||||
AND p.state IN ('G','W')
|
||||
)
|
||||
BEGIN
|
||||
GRANT EXECUTE TO [$ManagedIdentityName];
|
||||
END;
|
||||
"@
|
||||
}
|
||||
|
||||
$Query = @"
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys.database_principals
|
||||
WHERE name = N'$ManagedIdentityName'
|
||||
)
|
||||
BEGIN
|
||||
CREATE USER [$ManagedIdentityName] FROM EXTERNAL PROVIDER;
|
||||
END;
|
||||
|
||||
$PermissionSql
|
||||
"@
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("$ServerName/$DatabaseName", "Grant permissions to '$ManagedIdentityName'")) {
|
||||
Invoke-Sqlcmd `
|
||||
-ServerInstance "$ServerName.database.windows.net" `
|
||||
-Database $DatabaseName `
|
||||
-AccessToken $AccessToken `
|
||||
-Query $Query `
|
||||
-ErrorAction Stop
|
||||
|
||||
Write-Log "Permissions successfully configured for '$ManagedIdentityName' on $ServerName / $DatabaseName" "SUCCESS"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "FAILED configuring permissions for '$ManagedIdentityName' on $ServerName / $DatabaseName" "ERROR"
|
||||
Write-Log $_.Exception.Message "ERROR"
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Script finished"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user