Azure CLI SQL bits

Status: Reviewed Stack: PowerShell → SQL / stored procs → Blazor Depends on: SQL-CLI-01/03/04 for query/deploy into Azure SQL after infra is ready Goal: Use az sql (and a few adjacent Azure CLI commands) from Windows/PowerShell to provision, firewall, Entra admin, and inspect Azure SQL in ways that feed Rick’s pipelines — without turning this into a full Azure encyclopedia.


1. When to use az sql vs Portal vs ARM/Bicep vs Az.Sql

Rick’s loop: AD/Windows/PowerShell → SQL stored procs → Blazor. az sql is the control-plane CLI for Azure SQL infra (logical server, DB, firewall, Entra admin). It does not replace sqlcmd, Invoke-Sqlcmd, or sqlpackage.

Need az sql (CLI) Portal ARM / Bicep Az.Sql (PowerShell)
One-off lab: create server + DB + firewall ✅ Fast, scriptable ✅ Click-ops Overkill ✅ Equivalent
Repeatable env bootstrap from jump box ✅ Prefer ❌ Drift ✅ Best for IaC repos ✅ If PS-native shop
CI/CD desired-state infra Possible; fragile ✅ Source of truth Possible
Firewall for this client IP before sqlcmd ✅ Recipe-friendly Manual Static ranges only
Set / swap Microsoft Entra admin
Scale SKU / serverless pause knobs az sql db update
Query data / run procs ❌ → SQL-CLI-01 / 03 Query editor
Schema publish / BACPAC ❌ → SQL-CLI-04 Limited Limited
Full Azure encyclopedia (VNet, PE, MI farm) ❌ Stay lean here OK OK

Rule of thumb


2. Install / login (Windows → jump box)

Install

Method Command / note
winget (preferred) winget install --exact --id Microsoft.AzureCLI then reopen the terminal
MSI / ZIP Install Azure CLI on Windows
Cloud Shell Preinstalled; fine for poke, not for Rick’s local Agent jobs
az version
az upgrade          # keep current; sql surface changes with CLI releases

az is a Python CLI that works from PowerShell and cmd. Prefer calling it from PowerShell so you can pipe -o json | ConvertFrom-Json.

Login patterns Rick might use

Scenario Command Notes
Interactive (laptop / RDP) az login Browser or device code; MFA required for user identities (enforced for Azure CLI automation paths since Sep 2025)
Device code (locked-down jump box) az login --use-device-code No local browser
Service principal (CI / Agent) az login --service-principal -u <appId> -p <secret\|cert> --tenant <tenantId> Prefer cert over secret; store secret in Key Vault / pipeline secret store — never in the .ps1
Managed identity (Azure VM jump box) az login --identity System-assigned
User-assigned MI az login --identity --client-id <id> Or --object-id / --resource-id
az account show -o table
az account set -s "<subscriptionIdOrName>"
az configure --defaults group=rg-sql-lab location=eastus sql-server=sql-lab-01   # optional shortcuts

RBAC reminder: creating servers/DBs needs Contributor (or SQL-specific roles) on the RG. Setting Entra admin / Entra-only auth needs rights that can manage SQL + read directory (often SQL Security Manager + Directory Reader patterns — confirm with your tenant’s role model).


3. Scope map (what lives where)

Prioritize logical server + single database — the common Blazor app shape. MI is mentioned only so you do not confuse command groups.

Area Command group What it is Rick priority
Logical server az sql server PaaS “server” hostname *.database.windows.net; holds admin, firewall, Entra settings ✅ Core
Database az sql db Single DB (or pool member); SKU, TDE, audit, connection-string templates ✅ Core
Firewall (server IP rules) az sql server firewall-rule Client IP allow list; 0.0.0.0–0.0.0.0 = Allow Azure services ✅ Core
Entra admin az sql server ad-admin Required before Entra users/groups can be created in the DB ✅ Core
Entra-only auth az sql server ad-only-auth Disables SQL auth logins at server Use when policy demands
Auditing az sql db audit-policy / az sql server audit-policy Ship audit logs to storage / Log Analytics / Event Hub When compliance asks
VNet rules az sql server vnet-rule Allow specific subnets (service endpoints) When leaving public IP model
Managed Instance az sql mi Near-full SQL Server in VNet; different networking story Mention only — out of scope for most Blazor single-DB apps
Elastic pools az sql elastic-pool Shared eDTU/vCore across DBs Only if multi-tenant cost packing

Mental model

Subscription → Resource group → Logical server (firewall + Entra admin)
                                    └─ Database(s)  ← Blazor connection string points here

Hostname for tools: <server>.database.windows.net — but az sql flags take the short server name, not the FQDN.


4. Practical command cheat sheet

Variables used below (PowerShell):

$rg = "rg-sql-lab"
$loc = "eastus"
$server = "sql-lab-01"          # globally unique, lowercase
$db = "AppDb"
$admin = "sqladmin"             # SQL auth admin (skip if Entra-only from day one)

Server

# Create logical server (SQL auth admin — store password in secret store, not transcript)
az sql server create -g $rg -n $server -l $loc `
  --admin-user $admin --admin-password $dnv:SQL_ADMIN_PASSWORD `
  --enable-public-network true --minimal-tls-version 1.2

az sql server list -g $rg -o table
az sql server show -g $rg -n $server -o jsonc
az sql server delete -g $rg -n $server --yes   # destroys server + DBs

Useful create flags: --enable-ad-only-auth, --assign-identity, --enable-public-network false (forces private-path designs).

Database

# DTU-style
az sql db create -g $rg -s $server -n $db --service-objective S0

# vCore General Purpose Gen5, 2 vCores
az sql db create -g $rg -s $server -n $db `
  --edition GeneralPurpose --family Gen5 --capacity 2

# Serverless (common for labs / low Blazor traffic)
az sql db create -g $rg -s $server -n $db `
  -e GeneralPurpose -f Gen5 -c 2 --compute-model Serverless --auto-pause-delay 60

# Named objective shorthand
az sql db create -g $rg -s $server -n $db --service-objective GP_S_Gen5_2

az sql db list -g $rg -s $server -o table
az sql db show -g $rg -s $server -n $db -o jsonc
az sql db list-editions -l $loc -a -o table   # see what your sub can buy

# Scale / retier
az sql db update -g $rg -s $server -n $db --edition GeneralPurpose --family Gen5 --capacity 4
az sql db update -g $rg -s $server -n $db --service-objective S2 --max-size 250GB

Firewall

az sql server firewall-rule create -g $rg -s $server -n AllowClient `
  --start-ip-address 203.0.113.10 --end-ip-address 203.0.113.10

az sql server firewall-rule list -g $rg -s $server -o table
az sql server firewall-rule show -g $rg -s $server -n AllowClient
az sql server firewall-rule delete -g $rg -s $server -n AllowClient --yes

# Allow Azure services (portal toggle) — start=end=0.0.0.0
az sql server firewall-rule create -g $rg -s $server -n AllowAllWindowsAzureIps `
  --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0

Microsoft Entra admin

# Display name + object id of user OR group (group preferred for ops team)
az sql server ad-admin create -g $rg -s $server `
  --display-name "SQL-Admins" --object-id "<entra-object-guid>"

az sql server ad-admin list -g $rg -s $server -o table
az sql server ad-admin update -g $rg -s $server `
  --display-name "SQL-Admins" --object-id "<new-guid>"
az sql server ad-admin delete -g $rg -s $server

# Entra-only (kills SQL auth) — set admin first
az sql server ad-only-auth enable -g $rg -n $server
az sql server ad-only-auth get -g $rg -n $server
az sql server ad-only-auth disable -g $rg -n $server

Connection strings (templates — secrets are placeholders)

# Prints a template with <username>/<password> tokens — not live secrets from Azure
az sql db show-connection-string -s $server -n $db -c ado.net
az sql db show-connection-string -s $server -n $db -c ado.net --auth-type ADIntegrated
az sql db show-connection-string -s $server -n $db -c sqlcmd

Clients: ado.net, jdbc, odbc, php, php_pdo, sqlcmd. Auth types: SqlPassword (default), ADPassword, ADIntegrated.

Do not commit the output. Prefer Key Vault references / User Secrets for Blazor ConnectionStrings.

Auditing (lean)

az sql db audit-policy show -g $rg -s $server -n $db
# update needs storage / workspace targets — wire when compliance requires; skip for lab bootstrap

5. PowerShell orchestration

Capture JSON, check exit codes

$ErrorActionPreference = "Stop"

$serverObj = az sql server show -g $rg -n $server -o json | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { throw "az sql server show failed ($LASTEXITCODE)" }

$fqdn = $serverObj.fullyQualifiedDomainName   # sql-lab-01.database.windows.net
$state = $serverObj.state

$dbs = az sql db list -g $rg -s $server -o json | ConvertFrom-Json
$dbs | Where-Object name -ne "master" | Select-Object name, status, earliestRestoreDate, currentSku

JMESPath keeps payloads small:

az sql db list -g $rg -s $server --query "[?name!='master'].{name:name,sku:currentSku.name,status:status}" -o table

Idempotency patterns

az sql * create fails if the resource exists. Pattern o:

function Ensure-SqlFirewallRule {
  param($Rg, $Server, $Name, $Ip)
  $existing = az sql server firewall-rule show -g $Rg -s $Server -n $Name -o json 2>$null
  if ($LASTEXITCODE -eq 0 -and $existing) {
    az sql server firewall-rule update -g $Rg -s $Server -n $Name `
      --start-ip-address $Ip --end-ip-address $Ip | Out-Null
  } else {
    az sql server firewall-rule create -g $Rg -s $Server -n $Name `
      --start-ip-address $Ip --end-ip-address $Ip | Out-Null
  }
  if ($LASTEXITCODE -ne 0) { throw "firewall ensure failed" }
}

function Test-AzSqlDbExists {
  param($Rg, $Server, $Name)
  az sql db show -g $Rg -s $Server -n $Name -o none 2>$null
  return ($LASTEXITCODE -eq 0)
}

if (-not (Test-AzSqlDbExists $rg $server $db)) {
  az sql db create -g $rg -s $server -n $db --service-objective GP_S_Gen5_2
  if ($LASTEXITCODE -ne 0) { throw "db create failed" }
}

Hand off to sqlcmd / sqlpackage

Once firewall + Entra admin (or SQL admin) are in place:

Next step Doc Typical call
Smoke query / procs SQL-CLI-01 sqlcmd -S $fqdn -d $db -G (go-sqlcmd Entra)
Token from PS SQL-CLI-03 Invoke-Sqlcmd -ServerInstance $fqdn -Database $db -AccessToken $token
Schema publish SQL-CLI-04 sqlpackage /Action:nlish /TargetServerName:$fqdn /TargetDatabaseName:$db ...

az sql stops at the door; the other three walk inside.


6. Auth & networking gotchas

Gotcha Symptom Fix
No Entra admin set Cannot CREATE USER [user@tenant] FROM EXTERNAL PROVIDER az sql server ad-admin create with user or group object id
Entra-only enabled SQL auth / Uid=sqladmin fails Expected; use Entra token paths (SQL-CLI-01 -G / SQL-CLI-03 -AccessToken) or disable only if policy allows
Firewall missing client IP Error 40615 / cannot open server Add rule for egress IP (VPN/NAT may differ from whatip.com on the box)
Allow Azure services (0.0.0.0) App Service / Functions connect without PE Convenient; broad — any Azure tenant’s resource can attempt auth. Prefer VNet/PE for prod
Private endpoint only Public FQDN times out from internet Disable public network (--enable-public-network false) + PE/DNS; jump box must be on the VNet/VPN
Server name vs FQDN az 404 / weird errors -s / -n = short name; sqlcmd -S = FQDN
AAD admin ≠ DB users Admin works; app identity fails Still need T-SQL CREATE USER ... FROM EXTERNAL PROVIDER + roles inside the DB (sqlcmd)
MFA on user az login in automation Scripts break Use SP or managed identity — not interactive user login
show-connection-string looks “real” Accidental commit It is a template; still treat as sensitive once filled

7. Copy-paste recipes

Assume $rg, $server, $db are set. Run from elevated-enough PowerShell where az is on PATH.

R1 — Login + subscription

az login
# or: az login --use-device-code
# or: az login --identity
az account set -s "<subscriptionIdOrName>"
az account show -o table

R2 — Firewall for current public IP

$ip = (Invoke-RestMethod -Uri "https://api.ipify.org")
Ensure-SqlFirewallRule -Rg $rg -Server $server -Name "Client-$env:COMPUTERNAME" -Ip $ip
# or inline:
az sql server firewall-rule create -g $rg -s $server -n "Client-$env:COMPUTERNAME" `
  --start-ip-address $ip --end-ip-address $ip
az sql server firewall-rule list -g $rg -s $server -o table

R3 — Create server + single DB (lab)

az group create -n $rg -l $loc
az sql server create -g $rg -n $server -l $loc `
  --admin-user $admin --admin-password $env:SQL_ADMIN_PASSWORD `
  --minimal-tls-version 1.2
az sql db create -g $rg -s $server -n $db `
  --edition GeneralPurpose --family Gen5 --capacity 2 --compute-model Serverless --auto-pause-delay 60

R4 — Set Microsoft Entra admin (group preferred)

$groupName = "SQL-Admins"
$oid = az ad group show --group $groupName --query id -o tsv
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($oid)) { throw "group not found / Graph rights?" }

az sql server ad-admin create -g $rg -s $server --display-name $groupName --object-id $oid
az sql server ad-admin list -g $rg -s $server -o table

R5 — Show connection info (no live secrets)

$info = az sql server show -g $rg -n $server -o json | ConvertFrom-Json
$dbInfo = az sql db show -g $rg -s $server -n $db -o json | ConvertFrom-Json
[pscustomobject]@{
  Fqdn     = $info.fullyQualifiedDomainName
  Db       = $dbInfo.name
  Status   = $dbInfo.status
  Sku      = $dbInfo.currentSku.name
  Collation= $dbInfo.collation
  EarliestRestore = $dbInfo.earliestRestoreDate
}
az sql db show-connection-string -s $server -n $db -c ado.net --auth-type ADIntegrated

R6 — PowerShell loop: list DBs across servers in an RG

$servers = az sql server list -g $rg -o json | ConvertFrom-Json
foreach ($s in $servers) {
  Write-Host "`n=== $($s.name) ($($s.fullyQualifiedDomainName)) ===" -ForegroundColor Cyan
  az sql db list -g $rg -s $s.name --query "[].{name:name,status:status,sku:currentSku.name}" -o table
  if ($LASTEXITCODE -ne 0) { Write-Warning "list failed for $($s.name)" }
}

R7 — Open path for go-sqlcmd -G (Entra)

# 1) Firewall (R2)  2) Entra admin (R4)  3) then:
$fqdn = az sql server show -g $rg -n $server --query fullyQualifiedDomainName -o tsv

# Interactive Entra (go-sqlcmd) — see SQL-CLI-01 for auth-method details
sqlcmd -S $fqdn -d $db -G -Q "SELECT SUSER_SNAME() AS me, DB_NAME() AS db;"

After first Entra admin login, create the Blazor app identity inside the DB (still T-SQL via sqlcmd — not az sql):

CREATE USER [app-registration-or-mi-name] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [app-registration-or-mi-name];
-- add db_datawriter / exec on procs as needed

R8 — Scale then hand off to sqlpackage

az sql db update -g $rg -s $server -n $db --service-objective GP_Gen5_2
if ($LASTEXITCODE -ne 0) { throw "scale failed" }
$fqdn = az sql server show -g $rg -n $server --query fullyQualifiedDomainName -o tsv
# Schema deploy — SQL-CLI-04
# sqlpackage /Action:Publish /SourceFile:.\AppDb.dacpac /TargetServerName:$fqdn /TargetDatabaseName:$db ...

8. What NOT to do with az sql

Temptation Why not Use instead
Load CSV / inventory into tables Control plane only; no bulk insert path bcp → staging → stored proc
Run CREATE PROC / migrations as strings via az No T-SQL execution surface on az sql db sqlcmd / go-sqlcmd
“Backup” with az sql db export as DR BACPAC export ≠ PITR / native backup story PITR / LTR via Azure SQL backup; verify with SQL-CLI-03 where applicable
Schema drift deploy Wrong tool sqlpackage Publish / Script
Store SQL admin password in the script or transcript Credential leak $env: from Key Vault / pipeline secret; prefer Entra-only
Treat firewall 0.0.0.0 as “secure enough for prod” Allows Azure-wide network reach (auth still required) Private endpoint / VNet rules + tight IP rules
Encode full IaC sprawl in ad-hoc az history Unreviewable Bicep/ARM for shared envs; keep az for jump-box / break-glass

Pipeline shape that matches this stack

az sql          → server, db, firewall, Entra admin
sqlpackage      → schema (DACPAC)                    [SQL-CLI-04]
sqlcmd / -G     → CREATE USER FROM EXTERNAL PROVIDER, smoke procs  [SQL-CLI-01]
Invoke-Sqlcmd   → token-based PS automation / tests  [SQL-CLI-03]
Blazor          → connection string / MI to AppDb

References (Learn, azure-cli-latest / azuresql)

Cross-links: SQL-CLI-01 · SQL-CLI-03 · SQL-CLI-04