CodexAzure · Terraform · FortiGate · Git
Azure · Terraform · Bicep · FortiGate · Git · DevOps

Codex

Commands, ready-made workflows and forms for daily work. Search at the top (key /), click a line = copy, ? shows all keyboard shortcuts.

OutageThe first 10 minutes when something is not reachableChecklistsSpoke, certificate, firmware, go-live – to tick offGlossaryTerms from Azure, DevOps, networking explained briefly

Topics

Start

Azure

Code and DevOps

Network and Firewall

Clients and Logs

Reference

#I want to …

#Complete Workflows

All in 00 Workflows:

AreaWorkflows
Sign-in and BasicsSign in to Azure · Set subscription (CLI and PowerShell) · Unblock and run downloaded scripts · Find a resource without knowing where it is · Who changed or deleted what · Create a resource group with tags and delete lock
Virtual MachinesVM: run a command, reset password, console · Stop / start all VMs in a resource group · Find a Marketplace image and accept the terms · Before deploying: check zones and quota
NetworkConnect a new spoke to the hub · Extend address space and sync peering · NSG: temporarily allow your own IP · VM cannot reach the internet · Create a private endpoint for a web app · Private endpoint does not resolve privately · Check a VPN connection · Reach the FortiGate GUI or a VM without public IP via Bastion
Application Gateway and WAFApp Gateway: add a certificate from Key Vault · App Gateway: renew / replace a certificate · Import a PFX into Key Vault · App Gateway: backend unhealthy · WAF: find a false positive and add an exclusion · Send diagnostic logs to Log Analytics
Permissions, Key Vault, PolicyGive a service principal a role (subscription, management group, root) · Create a managed identity and assign a role · Which permissions do I (or someone else) have? · Policy blocks a deployment · Find expiring certificates
Terraform and BicepNew Terraform project with remote state · Terraform locally with a service principal · Import an existing resource into Terraform · Rename a resource or move it into a module · State lock is stuck · Check drift (changes made in the portal) · Update the provider version · Deploy Bicep safely
FortiGateFortiGate: apply and check the license · FortiGate: let a spoke reach the internet · FortiGate: allow spoke-to-spoke · FortiGate: port forwarding from outside (VIP) · FortiGate: trace a packet · FortiGate: test HA failover · FortiGate: HA out of sync · FortiGate: debug an IPsec tunnel · FortiGate: back up the configuration
Linux and WindowsLinux: test web server showing the hostname · Linux: service does not start · Linux: disk full · Windows: check a connection step by step
Git and SSHSSH key for Azure DevOps and first push · Push a folder as a new repo (HTTPS) · Git: quickly switch branches in between · Git: feature branch with pull request · Git: resolve a merge conflict · Git: secret committed by accident
Azure DevOps: Repo and Pull RequestsClone an existing DevOps repo · Connect a local folder to an existing DevOps repo · Change via feature branch and pull request to Dev · Push rejected: TF402455 (protected branch) · Reviewers in a pull request · Resolve a merge conflict in a pull request · Undo a pull request (abandon / revert) · Promote Dev to main (release)

#Azure General

…CommandDetails
switch the subscriptionaz account set --subscription <x>01
find a resource without knowing whereaz graph query -q "Resources | where name contains '<x>'"01
see who changed whataz monitor activity-log list -g <rg> --offset 2h01
run a command in a VMaz vm run-command invoke … --scripts "<cmd>"01
stop VM costsaz vm deallocate -g <rg> -n <vm>01
check whether a SKU works in a zoneaz vm list-skus -l <region> --size <sku>01
accept Marketplace termsaz vm image terms accept --urn <urn>01
see a web app's live logaz webapp log tail -g <rg> -n <app>01

#Network

…CommandDetails
know which route appliesaz network nic show-effective-route-table -g <rg> -n <nic>02
check the next hopaz network watcher show-next-hop …02
check peering statusaz network vnet peering list -g <rg> --vnet-name <vnet>02
point a 0/0 route to the firewallaz network route-table route create … --next-hop-type VirtualAppliance02
see NSG rules incl. defaultsaz network nsg rule list … --include-default02
see VPN/BGP statusaz network vnet-gateway list-bgp-peer-status02
add a rule to the Azure Firewallaz network firewall policy rule-collection-group collection rule add …02
link a private DNS zone to a VNetaz network private-dns link vnet create …02
check App Gateway backendsaz network application-gateway show-backend-health02
set the WAF to Preventionaz network application-gateway waf-policy policy-setting update --mode Prevention02
enable IP forwarding on an NVA NICaz network nic update --ip-forwarding true01
look up a subnet sizetable14
split an address space into subnetsGenerator → Subnet calculator14
create privatelink DNS zones for a landing zoneGenerator → Private DNS zones for a landing zone02
troubleshoot systematicallyDNS → route → NSG → firewall → return path → target14

#Permissions and Secrets

…CommandDetails
see which roles someone hasaz role assignment list --assignee <x> --all -o table03
give a service principal Owneraz role assignment create --assignee-principal-type ServicePrincipal --assignee-object-id $id --scope <scope> --role Owner00
my object IDaz ad signed-in-user show --query id -o tsv03
read a secretaz keyvault secret show --vault-name <kv> -n <name> --query value -o tsv03
recover a deleted secretaz keyvault secret recover …03
publish an app via Front DoorGenerator → Front Door: publish app32
assign a domain to an existing Front Door routeaz afd route update --custom-domains <all-ids>32
query across all subscriptionsaz graph query -q "<kql>"19
find orphaned disks/IPsResource Graph → cleanup19
create security groups per role in bulkNew-AzADGroup -DisplayName <name> -MailNickname <name> -SecurityEnabled21
find expiring app secretsaz ad app credential list21
build resource names after our conventionGenerator → Naming convention27
look up a resource abbreviationGenerator → Abbreviation lookup33
create an alertaz monitor metrics alert create25
find out which policy blocksaz policy assignment list --disable-scope-strict-match …03

#Terraform / Bicep

…CommandDetails
plan and apply safelyterraform plan -out tfplan → terraform apply tfplan05
rename a resource without rebuildmoved { from = … to = … }05
take over an existing resourceimport { to = … id = … }05
see drift onlyterraform plan -refresh-only05
release a lockterraform force-unlock <id>05
calculate subnetscidrsubnets("10.0.0.0/24", 2, 2, 2, 2)05
test Bicep without riskaz deployment group what-if …06
a pipeline for Terraformtemplate12

#FortiGate

…CommandDetails
check version/licenseget system status07
capture packetsdiagnose sniffer packet any 'host <ip>' 4 2007
know why a packet is droppeddebug flow07
understand a debug flow messagetable07
check the route for an IPget router info routing-table details <ip>07
check HA syncdiagnose sys ha checksum cluster07
test failoverexecute ha failover set 107
check an IPsec tunnelget vpn ipsec tunnel summary07
see BGP neighborsget router info bgp summary07
set up port forwardingconfig firewall vip07
search the configurationshow full-configuration | grep -f <text>07

#Clients (Linux / Windows)

…LinuxWindowsDetails
test a portnc -zv <ip> <port>Test-NetConnection <ip> -Port <port>08 / 09
check DNSdig +short <name>Resolve-DnsName <name>08 / 09
see the public IPcurl -s ifconfig.meInvoke-RestMethod https://api.ipify.org08 / 09
path to a targetip r get <target>Find-NetRoute -RemoteIPAddress <target>08 / 09
listening portsss -tlnpGet-NetTCPConnection -State Listen08 / 09
continuous failover testwhile true; do curl …; donewhile ($true) { … }08 / 09
check a server's certificateopenssl s_client -connect <host>:443 …–13

#Git / SSH

…CommandDetails
push a folder as a repogit init -b main … git push -u origin main11
undo the last commitgit reset --soft HEAD~1 / git revert <commit>11
park changes brieflygit stash / git stash pop11
search the historygit log -S "<text>"11
get an existing DevOps repogit clone git@ssh.dev.azure.com:v3/<org>/<project>/<repo>00
push is rejected (TF402455)git switch -c feature/<name> → git push -u origin feature/<name> → PR00
create a pull requestaz repos pr create --source-branch <from> --target-branch <to> --open00
resolve a merge conflict in a PRgit merge origin/Dev → fix files → git commit → git push00
undo a PRabandon (open) / revert (completed)00
bring Dev to mainPR Dev → main, "Delete Dev after merging" off00
SSH key for Azure DevOpsssh-keygen -t rsa -b 409613
test the connection to Azure DevOpsssh -T git@ssh.dev.azure.com13

#Search Script

Searches all files under docs/ and shows file, line and command. Several terms = all must appear.

PowerShell
.\search.ps1 peering               # Windows.\search.ps1 sniffer 168.63        # both terms in one line
Bash
./search.sh peering                # Linux / Git Bash

#Rebuild the HTML View

After changes to the .md files (Python 3, no extra packages):

PowerShell
python tools/build-html.py

Look and features live in tools/template.html. Produces index.html – a single file that runs offline in the browser (fonts are loaded online, otherwise system font).

#Publish on Your Own Domain (e.g. codex.cloudorbis.at)

index.html is a single static file – any static hosting works. Example with an Azure Storage static website:

PowerShell
python tools/build-html.pyaz storage blob service-properties update --account-name <sa> --static-website --index-document index.html --auth-mode loginaz storage blob upload --account-name <sa> -c '$web' -f index.html -n index.html --overwrite --auth-mode login --content-type "text/html; charset=utf-8"az storage account show -n <sa> --query "primaryEndpoints.web" -o tsv      # target for the CNAME

Then at the DNS provider: CNAME codex.cloudorbis.at -> <web-endpoint-host> and add HTTPS via Front Door (custom domain with managed certificate, see 32 Front Door) – or use Azure Static Web Apps, which includes a free certificate for custom domains. Placeholders, favorites and checklists are stored per browser (localStorage) – nothing is sent to a server.

#Contributing / Maintenance

  • Put new commands into the matching file under docs/, comment with # (or // in KQL) after it – that way the search script finds them.
  • Add frequently used tasks to "I want to …" above or to 16 Daily Commands.
  • Then run python tools/build-html.py and commit index.html too.
  • Never check in real IPs, names, keys, tokens or licenses – placeholders <...> only.
Start

#Copy-Paste Workflows

Ready-made command sequences for recurring tasks. Adjust the variables at the top, then run block by block. Do not write real names, secrets or IDs back into this repo. Single commands live in the topic files.

Sign-in and Basics: Sign in to Azure · Set subscription (CLI and PowerShell) · Unblock and run downloaded scripts · Find a resource without knowing where it is · Who changed or deleted what · Create a resource group with tags and delete lock

Virtual Machines: VM: run a command, reset password, console · Stop / start all VMs in a resource group · Find a Marketplace image and accept the terms · Before deploying: check zones and quota

Network: Connect a new spoke to the hub · Extend address space and sync peering · NSG: temporarily allow your own IP · VM cannot reach the internet · Create a private endpoint for a web app · Private endpoint does not resolve privately · Check a VPN connection · Reach the FortiGate GUI or a VM without public IP via Bastion

Application Gateway and WAF: App Gateway: add a certificate from Key Vault · App Gateway: renew / replace a certificate · Import a PFX into Key Vault · App Gateway: backend unhealthy · WAF: find a false positive and add an exclusion · Send diagnostic logs to Log Analytics

Permissions, Key Vault, Policy: Give a service principal a role (subscription, management group, root) · Create a managed identity and assign a role · Which permissions do I (or someone else) have? · Policy blocks a deployment · Find expiring certificates

Terraform and Bicep: New Terraform project with remote state · Terraform locally with a service principal · Import an existing resource into Terraform · Rename a resource or move it into a module · State lock is stuck · Check drift (changes made in the portal) · Update the provider version · Deploy Bicep safely

FortiGate: FortiGate: apply and check the license · FortiGate: let a spoke reach the internet · FortiGate: allow spoke-to-spoke · FortiGate: port forwarding from outside (VIP) · FortiGate: trace a packet · FortiGate: test HA failover · FortiGate: HA out of sync · FortiGate: debug an IPsec tunnel · FortiGate: back up the configuration

Linux and Windows: Linux: test web server showing the hostname · Linux: service does not start · Linux: disk full · Windows: check a connection step by step

Git and SSH: SSH key for Azure DevOps and first push · Push a folder as a new repo (HTTPS) · Git: quickly switch branches in between · Git: feature branch with pull request · Git: resolve a merge conflict · Git: secret committed by accident

Azure DevOps: Repo and Pull Requests: Clone an existing DevOps repo · Connect a local folder to an existing DevOps repo · Change via feature branch and pull request to Dev · Push rejected: TF402455 (protected branch) · Reviewers in a pull request · Resolve a merge conflict in a pull request · Undo a pull request (abandon / revert) · Promote Dev to main (release)

#Sign-in and Basics

#Sign in to Azure

Interactive (browser)

PowerShell
az loginaz login --tenant <tenant-id>                       # with several tenantsConnect-AzAccountConnect-AzAccount -Tenant <tenant-id>

Device code – when no browser is available (server, RDP, jump host). Enter the code at https://microsoft.com/devicelogin.

PowerShell
az login --use-device-codeaz login --use-device-code --tenant <tenant-id>az login --use-device-code --allow-no-subscriptions  # tenant without subscription (e.g. Entra ID only)Connect-AzAccount -UseDeviceAuthenticationConnect-AzAccount -UseDeviceAuthentication -Tenant <tenant-id>

Service principal (app registration with secret)

PowerShell
az login --service-principal `  --username <app-id> `  --password <client-secret> `  --tenant <tenant-id> # better: keep the secret out of the history – prompt instead of typing it$secret = Read-Host "Client secret" -AsSecureString$plain  = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret))az login --service-principal --username <app-id> --password $plain --tenant <tenant-id>Remove-Variable plain # PowerShell (Az module)$cred = Get-Credential -UserName <app-id> -Message "Client secret as password"Connect-AzAccount -ServicePrincipal -Credential $cred -Tenant <tenant-id>

Service principal with certificate

PowerShell
az login --service-principal --username <app-id> --password .\sp-cert.pem --tenant <tenant-id>   # PEM incl. private key (newer CLI: --certificate)Connect-AzAccount -ServicePrincipal -ApplicationId <app-id> -CertificateThumbprint <thumbprint> -Tenant <tenant-id>

Managed identity (on an Azure VM or agent)

PowerShell
az login --identityaz login --identity --username <client-id>          # user-assigned identity (newer CLI: --client-id)Connect-AzAccount -IdentityConnect-AzAccount -Identity -AccountId <client-id>

Check and sign out

PowerShell
az account show -o tableGet-AzContextaz logout ; az account clearDisconnect-AzAccount ; Clear-AzContext -Force

#Set subscription (CLI and PowerShell)

CLI and PowerShell have separate sign-ins – set both if you use both.

PowerShell
$sub = "<subscription-name-or-id>"az account set --subscription $subSet-AzContext -Subscription $subaz account show --query "{name:name, id:id}" -o table(Get-AzContext).Subscription.Name

#Unblock and run downloaded scripts

PowerShell
Get-ChildItem -Recurse *.ps1 | Unblock-File          # remove the download markSet-ExecutionPolicy -Scope Process Bypass            # this session only.\<script>.ps1

Permanently for the user: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.

#Find a resource without knowing where it is

PowerShell
az extension add -n resource-graph$part = "<name-part>"az graph query -q "Resources | where name contains '$part' | project name, type, resourceGroup, subscriptionId" -o table# then switch to the right subscriptionaz account set --subscription <subscription-id-from-result>az resource show --ids <resource-id>

#Who changed or deleted what

PowerShell
$rg = "<rg>"az monitor activity-log list -g $rg --offset 24h `  --query "[?status.value=='Succeeded'].{time:eventTimestamp, who:caller, what:operationName.localizedValue, resource:resourceId}" -o table# deletions onlyaz monitor activity-log list -g $rg --offset 7d `  --query "[?contains(operationName.value,'delete')].{time:eventTimestamp, who:caller, resource:resourceId}" -o table

Longer than 90 days or across all resource groups: KQL → Azure activity.

#Create a resource group with tags and delete lock

PowerShell
$rg = "<rg>" ; $loc = "<region>"az group create -n $rg -l $loc --tags Owner=<name> environment=<dev|test|prod> costcenter=<cost-center>az lock create -n lock-$rg -g $rg --lock-type CanNotDelete --notes "Protection against accidental deletion"az group show -n $rg --query tagsaz lock list -g $rg -o table

#Virtual Machines

#VM: run a command, reset password, console

PowerShell
$rg = "<rg>" ; $vm = "<vm>"# run a command without signing inaz vm run-command invoke -g $rg -n $vm --command-id RunShellScript --scripts "hostname; ip a; ip r" --query "value[0].message" -o tsvaz vm run-command invoke -g $rg -n $vm --command-id RunPowerShellScript --scripts "ipconfig /all" --query "value[0].message" -o tsv# reset password / SSH keyaz vm user update -g $rg -n $vm -u <user> -p <new-password>az vm user update -g $rg -n $vm -u <user> --ssh-key-value "$(Get-Content $HOME\.ssh\id_rsa.pub)"# when nothing else works: boot log and serial consoleaz vm boot-diagnostics enable -g $rg -n $vmaz vm boot-diagnostics get-boot-log -g $rg -n $vmaz extension add -n serial-consoleaz serial-console connect -g $rg -n $vm

#Stop / start all VMs in a resource group

PowerShell
$rg = "<rg>"az vm deallocate --ids (az vm list -g $rg --query "[].id" -o tsv) --no-wait     # no compute costsaz vm start      --ids (az vm list -g $rg --query "[].id" -o tsv) --no-waitaz vm list -d -g $rg --query "[].{name:name, state:powerState}" -o table

Bash: az vm deallocate --ids $(az vm list -g $rg --query "[].id" -o tsv)

#Find a Marketplace image and accept the terms

Example FortiGate:

PowerShell
$loc = "<region>"az vm image list-offers -l $loc -p fortinet --query "[?contains(name,'fortigate')].name" -o tsvaz vm image list-skus   -l $loc -p fortinet -f fortinet_fortigate-vm -o tableaz vm image list -p fortinet -f fortinet_fortigate-vm -s <sku> -l $loc --all --query "[].version" -o tsv$urn = "fortinet:fortinet_fortigate-vm:<sku>:latest"az vm image terms show   --urn $urn --query acceptedaz vm image terms accept --urn $urn

For Terraform: set plan { publisher, product, name } in the VM resource.

#Before deploying: check zones and quota

PowerShell
$loc = "<region>" ; $sku = "<vm-size>"az vm list-skus -l $loc --size $sku --query "[].{sku:name, zones:locationInfo[0].zones, restriction:restrictions[0].reasonCode}" -o tableaz vm list-usage -l $loc --query "[?contains(name.value,'Total') || contains(localName,'<family>')].{name:localName, used:currentValue, limit:limit}" -o table

Empty zones or a restriction = SKU not available in this subscription/zone.

#Network

#Connect a new spoke to the hub

PowerShell
$hubRg   = "<rg-hub>"   ; $hubVnet = "<vnet-hub>"$rg      = "<rg-spoke>" ; $vnet    = "<vnet-spoke>" ; $loc = "<region>"$space   = "10.1.0.0/24" ; $snet = "snet-app" ; $snetPrefix = "10.1.0.0/26"$nvaIp   = "<ilb-ip-of-firewall>" # 1. VNet and subnetaz group create -n $rg -l $locaz network vnet create -g $rg -n $vnet -l $loc --address-prefixes $spaceaz network vnet subnet create -g $rg --vnet-name $vnet -n $snet --address-prefixes $snetPrefix --default-outbound false # 2. Peering in both directions$hubId   = az network vnet show -g $hubRg -n $hubVnet --query id -o tsv$spokeId = az network vnet show -g $rg    -n $vnet    --query id -o tsvaz network vnet peering create -g $rg    --vnet-name $vnet    -n "$vnet-to-$hubVnet" --remote-vnet $hubId   --allow-vnet-access --allow-forwarded-trafficaz network vnet peering create -g $hubRg --vnet-name $hubVnet -n "$hubVnet-to-$vnet" --remote-vnet $spokeId --allow-vnet-access --allow-forwarded-traffic#    with VPN/ER gateway in the hub additionally: hub side --allow-gateway-transit, spoke side --use-remote-gateways # 3. Route table: everything via the firewallaz network route-table create -g $rg -n "rt-$vnet" -l $loc --disable-bgp-route-propagation trueaz network route-table route create -g $rg --route-table-name "rt-$vnet" -n default-to-nva `  --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address $nvaIpaz network vnet subnet update -g $rg --vnet-name $vnet -n $snet --route-table "rt-$vnet" # 4. Checkaz network vnet peering list -g $rg --vnet-name $vnet --query "[].{name:name, state:peeringState, sync:peeringSyncLevel}" -o table

Then on the FortiGate: route and policy for the spoke. With a VPN gateway: add a GatewaySubnet UDR for the spoke prefix → NVA.

#Extend address space and sync peering

PowerShell
$rg = "<rg>" ; $vnet = "<vnet>"az network vnet update -g $rg -n $vnet --address-prefixes <existing-prefix> <new-prefix>az network vnet peering list -g $rg --vnet-name $vnet --query "[].{name:name, sync:peeringSyncLevel}" -o tableaz network vnet peering sync -g $rg --vnet-name $vnet -n <peering>          # for each peering, status -> FullyInSync

Run sync on the remote side (hub) too, and add the new prefixes to UDRs/firewall routes.

#NSG: temporarily allow your own IP

PowerShell
$rg = "<rg>" ; $nsg = "<nsg>"$myIp = Invoke-RestMethod https://api.ipify.orgaz network nsg rule create -g $rg --nsg-name $nsg -n Temp-Allow-MyIp --priority 110 `  --direction Inbound --access Allow --protocol Tcp `  --source-address-prefixes "$myIp/32" --destination-port-ranges 22 443# … work …az network nsg rule delete -g $rg --nsg-name $nsg -n Temp-Allow-MyIp

#VM cannot reach the internet

PowerShell
$rg = "<rg>" ; $vm = "<vm>" ; $nic = "<nic>"az network nic show-effective-route-table -g $rg -n $nic -o table                     # 0.0.0.0/0 -> where?az network watcher show-next-hop -g $rg --vm $vm --source-ip <vm-ip> --dest-ip 8.8.8.8 -o tableaz network nic list-effective-nsg -g $rg -n $nic --query "value[].effectiveSecurityRules[?direction=='Outbound'].{name:name, access:access, prio:priority}"az vm run-command invoke -g $rg -n $vm --command-id RunShellScript --scripts "curl -s -m 5 ifconfig.me; echo"

Then on the firewall: trace a packet.

#Create a private endpoint for a web app

PowerShell
$rg = "<rg>" ; $app = "<webapp>" ; $vnet = "<vnet>" ; $snet = "<snet-pe>"$rgDns = "<rg-dns>" ; $zone = "privatelink.azurewebsites.net"$dnsVnetId = "<vnet-id-that-resolves-dns>" $appId = az webapp show -g $rg -n $app --query id -o tsvaz network private-endpoint create -g $rg -n "pe-$app" --vnet-name $vnet --subnet $snet `  --private-connection-resource-id $appId --group-id sites --connection-name "pe-$app" # DNS zone (once per environment) and linkaz network private-dns zone create -g $rgDns -n $zoneaz network private-dns link vnet create -g $rgDns -z $zone -n "link-$vnet" -v $dnsVnetId -e false$zoneId = az network private-dns zone show -g $rgDns -n $zone --query id -o tsvaz network private-endpoint dns-zone-group create -g $rg --endpoint-name "pe-$app" -n default `  --private-dns-zone $zoneId --zone-name websites # disable public access and checkaz webapp update -g $rg -n $app --set publicNetworkAccess=Disablednslookup "$app.azurewebsites.net"            # from the VNet: private IP expected

Other services: zone and --group-id from the table.

#Private endpoint does not resolve privately

PowerShell
nslookup <name>.privatelink.<zone>                    # or the public name, e.g. <app>.azurewebsites.netaz network vnet show -g <rg> -n <vnet> --query dhcpOptions.dnsServers      # empty = Azure DNSaz network private-dns link vnet list -g <rg-dns> -z <zone> --query "[].{name:name, vnet:virtualNetwork.id}" -o tableaz network private-dns record-set a list -g <rg-dns> -z <zone> --query "[].{name:name, ip:aRecords[0].ipv4Address}" -o tableaz network private-endpoint show -g <rg> -n <pe> --query "customDnsConfigs"

Must match: the zone is linked to the VNet whose DNS server does the resolution, and the A record points to the PE IP.

#Check a VPN connection

PowerShell
$rg = "<rg>" ; $gw = "<vpn-gateway>" ; $conn = "<connection>"az network vpn-connection show -g $rg -n $conn --query "{status:connectionStatus, in:ingressBytesTransferred, out:egressBytesTransferred}"az network vnet-gateway list-bgp-peer-status -g $rg -n $gw -o tableaz network vnet-gateway list-learned-routes -g $rg -n $gw -o tableaz network vpn-connection shared-key show -g $rg --connection-name $conn       # compare PSK with the remote side

Remote side FortiGate: debug an IPsec tunnel.

#Reach the FortiGate GUI or a VM without public IP via Bastion

PowerShell
$rg = "<rg-bastion>" ; $bastion = "<bastion>"$vmId = az vm show -g <rg-vm> -n <vm> --query id -o tsv# GUI (HTTPS) -> then in the browser https://localhost:8443az network bastion tunnel -g $rg -n $bastion --target-resource-id $vmId --resource-port 443 --port 8443# SSH -> in a second window: ssh -p 50022 <admin>@127.0.0.1az network bastion tunnel -g $rg -n $bastion --target-resource-id $vmId --resource-port 22 --port 50022# RDP directlyaz network bastion rdp -g $rg -n $bastion --target-resource-id $vmId

Requires Bastion Standard with Native client support. Keep the tunnel window open.

#Application Gateway and WAF

#App Gateway: add a certificate from Key Vault

Prerequisite: the App Gateway has a user-assigned identity with the role Key Vault Secrets User on the vault (or access policy Get on secrets), and the vault is reachable for the App Gateway.

PowerShell

PowerShell
$sub      = "<subscription>"$rg       = "<rg-agw>"$agwName  = "<agw-name>"$kvName   = "<keyvault-name>"$certName = "<certificate-name>"           # name in Key Vault and in the App Gateway Set-AzContext -Subscription $sub $AppGW    = Get-AzApplicationGateway -Name $agwName -ResourceGroupName $rg$secret   = Get-AzKeyVaultSecret -VaultName $kvName -Name $certName$secretId = $secret.Id.Replace($secret.Version, "")   # versionless: https://<kv>.vault.azure.net/secrets/<name>/ $AppGW = Add-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name $certName -KeyVaultSecretId $secretIdSet-AzApplicationGateway -ApplicationGateway $AppGW     # takes a few minutes

Azure CLI

PowerShell
az account set --subscription <subscription>$secretId = "https://<keyvault-name>.vault.azure.net/secrets/<certificate-name>"     # without versionaz network application-gateway ssl-cert create -g <rg-agw> --gateway-name <agw-name> `  -n <certificate-name> --key-vault-secret-id $secretIdaz network application-gateway ssl-cert list -g <rg-agw> --gateway-name <agw-name> `  --query "[].{name:name, kv:keyVaultSecretId}" -o table

Versionless = the App Gateway picks up a renewed version automatically (checked about every 4 hours).

Assign an identity if there is none yet

PowerShell
$id    = Get-AzUserAssignedIdentity -ResourceGroupName <rg> -Name <identity>$AppGW = Get-AzApplicationGateway -Name <agw-name> -ResourceGroupName <rg-agw>Set-AzApplicationGatewayIdentity -ApplicationGateway $AppGW -UserAssignedIdentityId $id.IdSet-AzApplicationGateway -ApplicationGateway $AppGWNew-AzRoleAssignment -ObjectId $id.PrincipalId -RoleDefinitionName "Key Vault Secrets User" `  -Scope (Get-AzKeyVault -VaultName <keyvault-name>).ResourceId

Assign the certificate to a listener

PowerShell
$AppGW    = Get-AzApplicationGateway -Name <agw-name> -ResourceGroupName <rg-agw>$cert     = Get-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <certificate-name>$listener = Get-AzApplicationGatewayHttpListener   -ApplicationGateway $AppGW -Name <listener-name>$listener.SslCertificate = $certSet-AzApplicationGateway -ApplicationGateway $AppGW

#App Gateway: renew / replace a certificate

PowerShell
# Which certificates exist and where do they come from?$AppGW = Get-AzApplicationGateway -Name <agw-name> -ResourceGroupName <rg-agw>$AppGW.SslCertificates | Select Name, KeyVaultSecretId, ProvisioningState # Switch to a new Key Vault source$AppGW = Set-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <certificate-name> -KeyVaultSecretId <new-secret-id>Set-AzApplicationGateway -ApplicationGateway $AppGW # Remove a certificate that is no longer used (detach from all listeners first)$AppGW = Remove-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <old-name>Set-AzApplicationGateway -ApplicationGateway $AppGW

#Import a PFX into Key Vault

PowerShell
az keyvault certificate import --vault-name <keyvault-name> -n <certificate-name> -f .\cert.pfx --password <pfx-password>az keyvault certificate show --vault-name <keyvault-name> -n <certificate-name> --query "{expires:attributes.expires, sid:sid}"# PowerShell$pw = Read-Host "PFX password" -AsSecureStringImport-AzKeyVaultCertificate -VaultName <keyvault-name> -Name <certificate-name> -FilePath .\cert.pfx -Password $pw

Then continue with add the certificate.

#App Gateway: backend unhealthy

PowerShell
$rg = "<rg-agw>" ; $agw = "<agw>"az network application-gateway show-backend-health -g $rg -n $agw `  --query "backendAddressPools[].backendHttpSettingsCollection[].servers[].{server:address, health:health, log:healthProbeLog}" -o tableaz network application-gateway http-settings list -g $rg --gateway-name $agw `  --query "[].{name:name, port:port, proto:protocol, host:hostName, pickHost:pickHostNameFromBackendAddress, probe:probe.id}" -o tableaz network application-gateway probe list -g $rg --gateway-name $agw `  --query "[].{name:name, proto:protocol, host:host, path:path, pickHost:pickHostNameFromBackendHttpSettings}" -o table# Does the backend resolve privately? (from a VM in the gateway VNet)nslookup <backend-fqdn>

Interpreting messages: Troubleshooting → App Gateway.

#WAF: find a false positive and add an exclusion

PowerShell
$rg = "<rg>" ; $policy = "<waf-policy>"# 1. Temporarily only log (if production traffic is being blocked)az network application-gateway waf-policy policy-setting update -g $rg --policy-name $policy --mode Detection --state Enabled
KQL
// 2. Which rule, which field? (Log Analytics)AzureDiagnostics| where Category == "ApplicationGatewayFirewallLog" and TimeGenerated > ago(1h)| where requestUri_s contains "<path>"| project TimeGenerated, ruleId_s, Message, details_message_s, details_data_s, requestUri_s
PowerShell
# 3. Exclusion for the affected field (e.g. form field "comment")az network application-gateway waf-policy managed-rule exclusion add -g $rg --policy-name $policy `  --match-variable RequestArgNames --selector-match-operator Equals --selector commentaz network application-gateway waf-policy managed-rule exclusion list -g $rg --policy-name $policy # 4. Switch back to blockingaz network application-gateway waf-policy policy-setting update -g $rg --policy-name $policy --mode Prevention --state Enabled

More --match-variable values: RequestHeaderNames, RequestCookieNames, RequestArgValues … Disable a single rule instead of an exclusion: Portal → WAF policy → Managed rules.

#Send diagnostic logs to Log Analytics

PowerShell
$resId = az network application-gateway show -g <rg> -n <agw> --query id -o tsv      # any resource$lawId = az monitor log-analytics workspace show -g <rg-law> -n <law> --query id -o tsvaz monitor diagnostic-settings create -n diag-to-law --resource $resId --workspace $lawId `  --logs '[{"categoryGroup":"allLogs","enabled":true}]' --metrics '[{"category":"AllMetrics","enabled":true}]' `  --export-to-resource-specific trueaz monitor diagnostic-settings list --resource $resId -o table

--export-to-resource-specific true = dedicated tables (e.g. AGWAccessLogs) instead of AzureDiagnostics. In PowerShell, pass JSON strings with '…' and escaped quotes if needed, or put them in a file (--logs '@logs.json').

#Permissions, Key Vault, Policy

#Give a service principal a role (subscription, management group, root)

PowerShell
$SPName = "<sp-display-name>"$id = az ad sp list --display-name $SPName --query "[].{id:id}" --output tsv$id                                          # exactly ONE ID? With several SPs of the same name stop and look up by app ID:# $id = az ad sp show --id <app-id> --query id -o tsv # Choose the scope – as small as possible:$scope = "/subscriptions/<subscription-id>"                                       # one subscription# $scope = "/subscriptions/<subscription-id>/resourceGroups/<rg>"                 # one resource group# $scope = "/providers/Microsoft.Management/managementGroups/<mg-id>"             # management group# $scope = "/"                                                                    # tenant root (everything!) az role assignment create --assignee-principal-type ServicePrincipal --assignee-object-id $id --scope $scope --role "Owner"az role assignment list --assignee $id --all --query "[].{role:roleDefinitionName, scope:scope}" -o table

Scope / (Tenant Root Group) – careful: grants Owner on all subscriptions and management groups in the tenant. You need User Access Administrator on / yourself, which a Global Admin only gets temporarily:

PowerShell
# 1. Elevate your own access on root (Entra ID: "Access management for Azure resources" = Yes)az rest --method post --url "/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"# 2. Assignment as above with --scope "/"az role assignment create --assignee-principal-type ServicePrincipal --assignee-object-id $id --scope "/" --role "Owner"# 3. Remove your own elevation again$me = az ad signed-in-user show --query id -o tsvaz role assignment delete --assignee $me --role "User Access Administrator" --scope "/"

If possible use Contributor instead of Owner (+ User Access Administrator only where the SP must assign roles). Remove again: az role assignment delete --assignee $id --role "Owner" --scope $scope.

#Create a managed identity and assign a role

PowerShell
$rg = "<rg>" ; $name = "<identity>"$principalId = az identity create -g $rg -n $name --query principalId -o tsv$scope = az keyvault show -n <keyvault-name> --query id -o tsv          # or RG/subscription/resourceaz role assignment create --assignee-object-id $principalId --assignee-principal-type ServicePrincipal `  --role "Key Vault Secrets User" --scope $scopeaz role assignment list --assignee $principalId --all -o table

#Which permissions do I (or someone else) have?

PowerShell
$who = az ad signed-in-user show --query id -o tsv          # or: <upn> / <object-id>az role assignment list --assignee $who --all --include-inherited `  --query "[].{role:roleDefinitionName, scope:scope}" -o tableaz ad user get-member-groups --id $who --query "[].displayName" -o tsv     # permissions via groups

#Policy blocks a deployment

PowerShell
# 1. Copy policyAssignmentId / policyDefinitionId from the error message$assignmentId = "<policyAssignmentId>"az policy assignment list --disable-scope-strict-match --scope /subscriptions/<sub>/resourceGroups/<rg> `  --query "[?id=='$assignmentId'].{name:displayName, scope:scope, def:policyDefinitionId}" -o tableaz policy definition show --name <definition-name> --query "{name:displayName, rule:policyRule}"# 2. Make it compliant (preferred) – or a time-limited exemption for the scopeaz policy exemption create -n <name> --policy-assignment $assignmentId `  --scope /subscriptions/<sub>/resourceGroups/<rg> --exemption-category Waiver `  --expires-on 2026-12-31T00:00:00Z --description "<reason, ticket>"

#Find expiring certificates

PowerShell
# In Key Vaultaz keyvault certificate list --vault-name <keyvault-name> `  --query "sort_by([].{name:name, expires:attributes.expires}, &expires)" -o table# What a server actually serves (Git Bash / Linux)openssl s_client -connect <host>:443 -servername <host> </dev/null 2>/dev/null | openssl x509 -noout -enddate -subject
PowerShell
# Without openssl, in PowerShell$tcp = New-Object Net.Sockets.TcpClient("<host>", 443)$ssl = New-Object Net.Security.SslStream($tcp.GetStream(), $false, { $true })$ssl.AuthenticateAsClient("<host>")$ssl.RemoteCertificate | Select Subject, @{n='Expires';e={$_.GetExpirationDateString()}}$tcp.Close()

#Terraform and Bicep

#New Terraform project with remote state

PowerShell
$rg = "rg-tfstate" ; $loc = "<region>" ; $sa = "<sttfstateunique>"     # 3–24 chars, lowercase letters/digits onlyaz group create -n $rg -l $locaz storage account create -g $rg -n $sa -l $loc --sku Standard_ZRS --min-tls-version TLS1_2 --allow-blob-public-access falseaz storage account blob-service-properties update -g $rg --account-name $sa --enable-versioning true$saId = az storage account show -g $rg -n $sa --query id -o tsv$me   = az ad signed-in-user show --query id -o tsvaz role assignment create --assignee-object-id $me --assignee-principal-type User --role "Storage Blob Data Contributor" --scope $saIdaz storage container create --account-name $sa -n tfstate --auth-mode login     # role may take 1–2 minutes

backend.tf:

HCL
terraform {  backend "azurerm" {    resource_group_name  = "rg-tfstate"    storage_account_name = "<sttfstateunique>"    container_name       = "tfstate"    key                  = "<project>.tfstate"    use_azuread_auth     = true  }}
PowerShell
$env:ARM_SUBSCRIPTION_ID = az account show --query id -o tsvterraform initterraform plan -out tfplan

#Terraform locally with a service principal

PowerShell
$env:ARM_TENANT_ID       = "<tenant-id>"$env:ARM_SUBSCRIPTION_ID = "<subscription-id>"$env:ARM_CLIENT_ID       = "<app-id>"$env:ARM_CLIENT_SECRET   = "<client-secret>"terraform initterraform plan -out tfplanterraform apply tfplanRemove-Item Env:ARM_CLIENT_SECRET, Env:ARM_CLIENT_ID      # remove afterwards

Without ARM_CLIENT_* Terraform uses the az login session.

#Import an existing resource into Terraform

HCL
# imports.tfimport {  to = azurerm_virtual_network.hub  id = "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Network/virtualNetworks/<vnet>"}
PowerShell
az network vnet show -g <rg> -n <vnet> --query id -o tsv          # get the IDterraform plan -generate-config-out=generated.tf                   # let Terraform generate code# review generated.tf, clean up, move into the right fileterraform plan                                                     # goal: "1 to import, 0 to add, 0 to change, 0 to destroy"terraform apply# then delete the import block (or keep it as documentation)

#Rename a resource or move it into a module

HCL
moved {  from = azurerm_subnet.app  to   = module.spoke.azurerm_subnet.this["app"]}
PowerShell
terraform plan            # must show "has moved to" and NO destroy/createterraform apply

#State lock is stuck

PowerShell
# The lock ID is in the error message ("ID: …")terraform force-unlock <lock-id># If that doesn't work: break the lease on the blobaz storage blob lease break --account-name <sa> -c tfstate -b <project>.tfstate --auth-mode login

Make sure no other run (pipeline, colleague) is active first.

#Check drift (changes made in the portal)

PowerShell
terraform plan -refresh-only               # shows what changed outside Terraform# keep the change -> put it into code, then normal plan/apply# revert the change -> normal plan/apply overwrites itterraform plan -detailed-exitcode ; $LASTEXITCODE    # 0 = clean, 2 = differences (e.g. for a scheduled check)

#Update the provider version

PowerShell
# 1. Raise the version in required_providers (e.g. "~> 4.0" -> "~> 4.20")terraform init -upgradeterraform providers lock -platform=windows_amd64 -platform=linux_amd64terraform plan                             # watch for unexpected changes/replacementsgit add .terraform.lock.hcl ; git commit -m "Update azurerm provider"

#Deploy Bicep safely

PowerShell
$rg = "<rg>"az bicep lint -f main.bicepaz deployment group validate -g $rg -f main.bicep -p main.bicepparamaz deployment group what-if  -g $rg -f main.bicep -p main.bicepparamaz deployment group create   -g $rg -f main.bicep -p main.bicepparam -n "deploy-$(Get-Date -Format yyyyMMdd-HHmm)"# On failure: which resource?az deployment operation group list -g $rg -n <deployment-name> `  --query "[?properties.provisioningState=='Failed'].{res:properties.targetResource.resourceName, error:properties.statusMessage.error.message}" -o table

#FortiGate

#FortiGate: apply and check the license

Text
execute vm-license <FortiFlex-token>          # the FortiGate reboots afterwards# after the rebootget system status | grep -i -E "version|serial|license"diagnose debug vm-print-licenseexecute update-nowdiagnose autoupdate versions | grep -A3 "AV Engine"

With HA: on both units separately (secondary via execute ha manage <index> <admin>).

#FortiGate: let a spoke reach the internet

Text
config firewall address    edit "net-<spoke>"        set subnet 10.1.0.0 255.255.255.0    nextendconfig router static    edit 0        set dst 10.1.0.0 255.255.255.0        set gateway <first-ip-of-port2-subnet>     # .1 of the internal subnet        set device "port2"    nextendconfig firewall policy    edit 0        set name "<spoke>-to-internet"        set srcintf "port2"        set dstintf "port1"        set srcaddr "net-<spoke>"        set dstaddr "all"        set action accept        set schedule "always"        set service "HTTP" "HTTPS" "DNS"        set nat enable        set logtraffic all    nextend

Test from the spoke: curl -s ifconfig.me → must show the firewall's public IP.

#FortiGate: allow spoke-to-spoke

Text
config firewall policy    edit 0        set name "<spoke-a>-to-<spoke-b>"        set srcintf "port2"        set dstintf "port2"        set srcaddr "net-<spoke-a>"        set dstaddr "net-<spoke-b>"        set action accept        set schedule "always"        set service "<services>"        set nat disable        set logtraffic all    nextend

Both spokes need the UDR to the firewall, otherwise routing is asymmetric.

#FortiGate: port forwarding from outside (VIP)

Text
config firewall vip    edit "vip-<service>"        set extip <private-ip-of-port1>       # or LB frontend IP with floating IP        set extintf "port1"        set portforward enable        set mappedip "<internal-server-ip>"        set extport 443        set mappedport 443    nextendconfig firewall policy    edit 0        set name "in-<service>"        set srcintf "port1"        set dstintf "port2"        set srcaddr "all"        set dstaddr "vip-<service>"        set action accept        set schedule "always"        set service "HTTPS"        set logtraffic all    nextend

In Azure additionally: a load balancer rule for the port and an open NSG on port1. Test with the sniffer: diagnose sniffer packet any 'port 443' 4 20.

#FortiGate: trace a packet

Text
# 1. Does it arrive and does it leave?diagnose sniffer packet any 'host <ip>' 4 20 # 2. Why is it dropped?diagnose debug resetdiagnose debug flow filter cleardiagnose debug flow filter addr <ip>diagnose debug flow show function-name enablediagnose debug flow trace start 10diagnose debug enable#    (generate traffic)diagnose debug disablediagnose debug reset # 3. Cross-check route and policyget router info routing-table details <dest-ip>show firewall policy | grep -i -A20 <name>

Interpreting messages: debug flow table.

#FortiGate: test HA failover

Start a continuous test on a client in the spoke:

PowerShell
while ($true) { "$((Get-Date).ToString('HH:mm:ss')) $(curl.exe -s -m 2 https://api.ipify.org)"; Start-Sleep 1 }

On the primary FortiGate:

Text
get system ha statusdiagnose sys ha checksum cluster          # in sync before?execute ha failover set 1                 # trigger failover# … watch the continuous test (interruption in seconds, possibly a new public IP)execute ha failover unset 1diagnose sys ha reset-uptime              # with override disable: hand back the role

#FortiGate: HA out of sync

Text
diagnose sys ha checksum cluster              # compare on bothdiagnose sys ha checksum show                 # which area differs?execute ha manage <index> <admin>             # switch to the secondaryexecute ha synchronize startdiagnose sys ha checksum recalculate          # if still differentget system ha status

#FortiGate: debug an IPsec tunnel

Text
get vpn ipsec tunnel summarydiagnose vpn ike log filter cleardiagnose vpn ike log filter rem-addr4 <peer-ip>diagnose debug application ike -1diagnose debug console timestamp enablediagnose debug enablediagnose vpn tunnel up <phase2> <phase1>      # trigger negotiation# … read the output …diagnose debug disablediagnose debug resetdiagnose vpn tunnel list name <phase1>        # SAs/counters once it is up

Messages: IPsec table.

#FortiGate: back up the configuration

Text
config system global    set admin-scp enableend
PowerShell
scp <admin>@<mgmt-ip>:sys_config ".\fgt-backup-$(Get-Date -Format yyyyMMdd).conf"

Or GUI → admin menu → Configuration → Backup. Backups contain hashes/secrets – not into the repo.

#Linux and Windows

#Linux: test web server showing the hostname

Bash
sudo apt update && sudo apt install -y nginxecho "ok $(hostname) $(hostname -I)" | sudo tee /var/www/html/index.htmlsudo systemctl enable --now nginxcurl -s localhostsudo ufw allow 80/tcp 2>/dev/null || true

Ideal for testing load balancers, App Gateways or VIPs – the response shows which server answered.

#Linux: service does not start

Bash
d=<service>sudo systemctl status $d --no-pagersudo journalctl -u $d -n 50 --no-pagersudo systemctl restart $d && sudo systemctl is-active $dsudo ss -tlnp | grep -i $d                  # is it listening?

#Linux: disk full

Bash
df -hsudo du -xh / --max-depth=1 2>/dev/null | sort -h | tail -10sudo du -xh /var/log --max-depth=1 | sort -h | tailsudo journalctl --vacuum-size=200Msudo apt clean

#Windows: check a connection step by step

PowerShell
$target = "<host-or-ip>" ; $port = 443Resolve-DnsName $target                                        # 1. DNSFind-NetRoute -RemoteIPAddress (Resolve-DnsName $target -Type A)[0].IPAddress | Select -Last 1   # 2. routeTest-NetConnection $target -Port $port                         # 3. portcurl.exe -v -m 10 "https://$target"                            # 4. TLS/HTTP

#Git and SSH

#SSH key for Azure DevOps and first push

PowerShell
# 1. Generate a key (Azure DevOps only accepts RSA)ssh-keygen -t rsa -b 4096 -f $HOME\.ssh\id_rsa # 2. Copy the public key -> Azure DevOps: top right User settings -> SSH public keys -> + New Key -> pasteGet-Content $HOME\.ssh\id_rsa.pub | Set-Clipboard # 3. Remove an old, possibly broken SSH config (optional)Remove-Item $HOME\.ssh\config -ErrorAction SilentlyContinue # 4. Test the connection – answer "Shell access is not supported" = OKssh -T git@ssh.dev.azure.com # 5. Connect the repo and pushgit remote add origin git@ssh.dev.azure.com:v3/<org>/<project>/<repo>git push -u origin main

Error Permission denied (publickey) → check step 2; ssh -vT git@ssh.dev.azure.com shows which key is tried.

#Push a folder as a new repo (HTTPS)

PowerShell
cd <folder>git init -b maingit add .git status                                   # no tfstate, tfvars, pfx, licenses?git commit -m "Initial commit"git remote add origin https://dev.azure.com/<org>/<project>/_git/<repo>git push -u origin main                      # sign in via browser or PAT as password

Remote already has a README: before pushing run git pull origin main --allow-unrelated-histories.

#Git: quickly switch branches in between

PowerShell
git stash -u                               # park current workgit switch main ; git pullgit switch -c fix/<name># … change …git add -A ; git commit -m "<message>" ; git push -u origin fix/<name>git switch <old-branch>git stash pop                              # continue working

#Git: feature branch with pull request

PowerShell
git switch main ; git pullgit switch -c feature/<name># … change …git add -Agit commit -m "<what and why>"git push -u origin feature/<name>az repos pr create --source-branch feature/<name> --target-branch main --title "<title>" --open# after the mergegit switch main ; git pullgit branch -d feature/<name>

#Git: resolve a merge conflict

PowerShell
git pull                                   # reports CONFLICTgit status                                 # affected files# open the file, keep the right version between <<<<<<< ======= >>>>>>>, delete the markersgit add <file>git commit                                 # or git rebase --continuegit push# abort everything:git merge --abort

#Git: secret committed by accident

PowerShell
# NOT pushed yetgit reset --soft HEAD~1                    # undo the commit, files staygit restore --staged <file>Add-Content .gitignore "<file-or-pattern>"git add -A ; git commit -m "<message>" # Already pushed# 1. Change/revoke the secret IMMEDIATELY (Key Vault, app registration, PAT …)# 2. Remove the file from the repogit rm --cached <file>Add-Content .gitignore "<file-or-pattern>"git commit -m "Remove secret file" ; git push

The old version stays in the history – that is why only changing the secret counts.

#Azure DevOps: Repo and Pull Requests

How a change flows in a repo with protected branches:

StepFrom → toHow
1. Workown branch feature/<name>change locally, commit, push
2. Testfeature/<name> → Devpull request + approval
3. ProductionDev → mainpull request + approval

Pushing directly to Dev or main is not possible (error TF402455) – always via a pull request (PR).

#Clone an existing DevOps repo

Address: DevOps → Repos → select repo → top right Clone → copy the URL.

PowerShell
cd C:\Git                                               # folder where the repo should gogit clone git@ssh.dev.azure.com:v3/<org>/<project>/<repo>         # via SSH (key registered in DevOps)git clone https://dev.azure.com/<org>/<project>/_git/<repo>       # via HTTPS (browser sign-in / PAT)cd <repo>git branch -a                                           # which branches exist? (e.g. main, Dev)git switch Dev                                          # switch to the working branch

Repo name unknown:

PowerShell
az extension add -n azure-devopsaz devops configure --defaults organization=https://dev.azure.com/<org> project=<project>az repos list --query "[].{name:name, ssh:sshUrl, https:remoteUrl}" -o table

#Connect a local folder to an existing DevOps repo

PowerShell
cd <your-folder>git init -b main                                        # only if there is no .git folder yetgit remote add origin git@ssh.dev.azure.com:v3/<org>/<project>/<repo>git pull origin main --allow-unrelated-histories        # only needed if the repo already has commitsgit add -A ; git commit -m "<message>"git push -u origin main                                 # with protected main: feature branch + PR (see below)

#Change via feature branch and pull request to Dev

PowerShell
git switch Dev ; git pull                               # get the latest stategit switch -c feature/<name>                            # own branch# … change …git add -Agit commit -m "<what and why>"git push -u origin feature/<name>

Create the PR – browser: after the push, Create a pull request appears at the top → choose target Dev. Or via CLI:

PowerShell
az repos pr create --organization https://dev.azure.com/<org> --project <project> --repository <repo> `  --source-branch feature/<name> --target-branch Dev --title "<title>" --open

After the merge:

PowerShell
git switch Dev ; git pullgit branch -d feature/<name>                            # clean up the local feature branch

#Push rejected: TF402455 (protected branch)

Text
! [remote rejected] Dev -> Dev (TF402455: Pushes to this branch are not permitted; you must use a pull request to update this branch.)

The branch has a branch policy. Your commits are already on the local Dev – this is how they get into a PR:

PowerShell
git switch -c feature/<name>                            # new branch from the current state – commits come alonggit push -u origin feature/<name>                       # this is allowedgit log --oneline origin/feature/<name> -3              # check: your commits are on the server# then create PR feature/<name> -> Dev (see above) # bring local Dev back to the server stategit switch Devgit fetch origingit reset --hard origin/Dev

Only run reset --hard once the feature branch is safely on the server.

#Reviewers in a pull request

FieldMeaning
Required reviewersmandatory reviewers – without their Approve the PR cannot be completed
Optional reviewersget notified, approval not needed
  • First click Create without entries – the branch policy often adds the required reviewers (e.g. a team group) automatically.
  • If nobody is listed afterwards: add the colleague/team lead who approves for your team. Old PRs show who that is: Repos → Pull requests → Completed.
  • You usually cannot approve your own PR.
  • Once all approvals are in and the checks are green → Complete.

#Resolve a merge conflict in a pull request

The PR shows "There are merge conflicts": the same lines were changed in Dev since your branch was created.

PowerShell
git switch feature/<name>git fetch origingit merge origin/Dev                                    # reports CONFLICTgit status                                              # affected files ("both modified")code .                                                  # in VS Code: Accept Current / Incoming / Both

In the file:

Text
<<<<<<< HEADparam location = 'westeurope'        ← your version=======param location = 'austriaeast'       ← version from Dev>>>>>>> origin/Dev

Keep the right version, delete the three marker lines, then:

PowerShell
git add <file>                                          # for each resolved filegit commit                                              # merge commitgit push                                                # the PR updates automaticallygit merge --abort                                       # instead: abort everything
  • For parameter files: keep new parameters from Dev. Who changed them: git log origin/Dev -- <file>.
  • Depending on the policy the approval is reset after the push – the reviewer has to click Approve again.

#Undo a pull request (abandon / revert)

PR still open → abandon it, nothing changes in Dev:

  • Browser: in the PR, next to Complete click ▾ / ⋯ → Abandon (can be revived later with Reactivate).
PowerShell
az repos pr list --organization https://dev.azure.com/<org> --project <project> -o table      # PR numberaz repos pr update --id <pr-number> --status abandoned --organization https://dev.azure.com/<org>

PR already completed → counter-change:

  • Open the PR → top right Revert → Create pull request → new PR to Dev, needs approval again.

Clean up locally:

PowerShell
git merge --abort                                       # if in the middle of a conflict mergegit switch Devgit fetch origingit reset --hard origin/Dev                             # local Dev = server stategit branch -D feature/<name>                            # delete feature branch locally (only if no longer needed)git push origin --delete feature/<name>                 # and on the server

To keep the changes and just start over: only Abandon, don't delete the branch.

#Promote Dev to main (release)

Check first what comes along – that is everything in Dev, including colleagues' work:

PowerShell
git fetch origingit log --oneline origin/main..origin/Dev               # commits in Dev that are not in main yetgit diff --stat origin/main...origin/Dev                # changed files

Create the PR – browser: Repos → Pull requests → New pull request → source Dev, target main. Or:

PowerShell
az repos pr create --organization https://dev.azure.com/<org> --project <project> --repository <repo> `  --source-branch Dev --target-branch main --title "Release: Dev to main" --open

When you click Complete:

  • Turn off "Delete Dev after merging"! Otherwise the Dev branch is gone.
  • Merge type: Merge (no fast-forward), not Squash – otherwise Dev and main diverge in the history.

Conflict in the Dev → main PR (main was changed directly, e.g. a hotfix) – bring main into Dev first:

PowerShell
git switch Dev ; git pullgit switch -c merge/main-into-devgit merge origin/main                                   # resolve conflicts as abovegit add -A ; git commitgit push -u origin merge/main-into-dev                  # PR to Dev -> afterwards Dev -> main is conflict-free
Start

#Daily Commands

The commands you type every day – compact on one page.

#Azure CLI

TaskCommand
Who am I, where am I?az account show --query "{user:user.name, sub:name}" -o table
Switch subscriptionaz account set --subscription "<name>"
List subscriptionsaz account list --query "[].{name:name, id:id, active:isDefault}" -o table
Set default RG/region (saves -g/-l)az config set defaults.group=<rg> defaults.location=<region>
Remove defaults againaz config unset defaults.group defaults.location
Help for a commandaz network vnet peering create --help
Find examplesaz find "vnet peering"
Interactive shell with autocompleteaz interactive
Resources in an RGaz resource list -g <rg> --query "[].{name:name, type:type}" -o table
Running VMsaz vm list -d --query "[?powerState=='VM running'].{name:name, rg:resourceGroup}" -o table
VM IPsaz vm list-ip-addresses -g <rg> -o table
Public IPsaz network public-ip list --query "[].{name:name, ip:ipAddress}" -o table
Effective routesaz network nic show-effective-route-table -g <rg> -n <nic> -o table
Recent changesaz monitor activity-log list -g <rg> --offset 4h --query "[].{t:eventTimestamp, who:caller, what:operationName.localizedValue}" -o table
Read a secretaz keyvault secret show --vault-name <kv> -n <name> --query value -o tsv
Get a resource IDaz <type> show -g <rg> -n <name> --query id -o tsv
JSON to PowerShell objectaz vm list -o json | ConvertFrom-Json
CLI version / updateaz version · az upgrade

#Azure PowerShell

TaskCommand
Sign inConnect-AzAccount · Connect-AzAccount -UseDeviceAuthentication
ContextGet-AzContext
Switch subscriptionSet-AzContext -Subscription "<name>"
Pick a subscription in a windowGet-AzSubscription | Out-GridView -PassThru | Set-AzContext
Find resourcesGet-AzResource -Name "*<part>*" | ft Name, ResourceGroupName, ResourceType
VMs with statusGet-AzVM -Status | ft Name, ResourceGroupName, PowerState
Load App Gateway$AppGW = Get-AzApplicationGateway -Name <agw> -ResourceGroupName <rg>
Secret as plain textGet-AzKeyVaultSecret -VaultName <kv> -Name <name> -AsPlainText
Update moduleUpdate-Module Az

#Terraform

TaskCommand
Start in a new folderterraform init
Format + checkterraform fmt -recursive ; terraform validate
Plan (saved)terraform plan -out tfplan
Apply exactly this planterraform apply tfplan
Show only deletions/replacements in the planterraform plan -no-color | Select-String "destroy|replace"
Find resources in the stateterraform state list | Select-String <part>
Show one resourceterraform state show '<address>'
Output for scriptsterraform output -raw <name>
Test an expressionterraform console
Release a lockterraform force-unlock <id>
Update providersterraform init -upgrade

#Git

TaskCommand
What's going on?git status -s
Get the latestgit pull
Commit everythinggit add -A ; git commit -m "<message>"
Uploadgit push
Recent commitsgit log --oneline --graph -15
What did I change?git diff · git diff --staged
New branchgit switch -c feature/<name>
Back to maingit switch main ; git pull
Reset a filegit restore <file>
Park changesgit stash -u · git stash pop
Change the last commit messagegit commit --amend -m "<new>" (only before pushing)
Which branches?git branch -a
Clean up deleted remote branchesgit fetch --prune
PR to Devaz repos pr create --source-branch feature/<name> --target-branch Dev --open
What goes from Dev to main?git log --oneline origin/main..origin/Dev
Local branch = servergit fetch origin ; git reset --hard origin/<branch>

#FortiGate

TaskCommand
Status / version / licenseget system status
CPU / RAM / sessionsget system performance status
HA statusget system ha status
HA in sync?diagnose sys ha checksum cluster
Routing tableget router info routing-table all
Route for an IPget router info routing-table details <ip>
Interfaces, shortget system interface physical
ARPget system arp
Search the configshow full-configuration | grep -f <text>
Show a policyshow firewall policy <id>
Capture packetsdiagnose sniffer packet any 'host <ip>' 4 20
Sessions to an IPdiagnose sys session filter dst <ip> → diagnose sys session list
Ping with source IPexecute ping-options source <ip> → execute ping <target>
Test a portexecute telnet <ip> <port>
VPN overviewget vpn ipsec tunnel summary
BGP overviewget router info bgp summary
Turn all debug offdiagnose debug disable ; diagnose debug reset

#Network Tests

TaskWindows (PowerShell)Linux
Own IPsipconfigip -br a
Public IPirm https://api.ipify.orgcurl -s ifconfig.me
DNSResolve-DnsName <name>dig +short <name>
Flush DNS cacheipconfig /flushdnsresolvectl flush-caches
PortTest-NetConnection <ip> -Port 443nc -zv <ip> 443
HTTP(S) with detailscurl.exe -v https://<target>curl -v https://<target>
HTTP status onlycurl.exe -s -o NUL -w "%{http_code}" https://<target>curl -s -o /dev/null -w "%{http_code}\n" https://<target>
Test against a specific IPcurl.exe -k --resolve <host>:443:<ip> https://<host>curl -k --resolve <host>:443:<ip> https://<host>
Pathtracert -d <ip>tracepath -n <ip>
Routesroute print -4ip r
Listening portsnetstat -ano | findstr LISTENss -tlnp

#PowerShell Tricks

PowerShell
Get-Command *route*                         # find a commandGet-Help Test-NetConnection -Examples       # examplesGet-History ; Invoke-History <nr>           # history(Get-PSReadLineOption).HistorySavePath      # file with the full history (old sessions too)Select-String -Path (Get-PSReadLineOption).HistorySavePath -Pattern "<text>"     # search the history<command> | Set-Clipboard                   # output to the clipboard<command> | Out-GridView                    # table with filter (Windows)<command> | Export-Csv .\out.csv -NoTypeInformation -Encoding UTF8<command> | ConvertTo-Json -Depth 5 | Set-Content .\out.json<command> | Select-Object -First 5<command> | Where-Object Name -like "*<part>*"<command> | Format-Table -AutoSizeMeasure-Command { <command> }               # measure runtimeStart-Transcript .\session.log ; Stop-Transcript     # record the whole sessioncode .                                      # open the current folder in VS Codeexplorer .                                  # open in Explorerwinget upgrade --all                        # update all tools (az, terraform, git …)

#PowerShell Profile: Your Own Shortcuts

Open with notepad $PROFILE (create the file first if needed: New-Item -Force $PROFILE), paste the snippets, start a new console.

PowerShell
# --- Azure ---function azsub {                         # pick a subscription in a window for CLI and PowerShell    $s = az account list --query "[].{Name:name, Id:id}" -o json | ConvertFrom-Json |         Out-GridView -Title "Choose subscription" -OutputMode Single    if ($s) { az account set --subscription $s.Id; Set-AzContext -Subscription $s.Id | Out-Null; "Active: $($s.Name)" }}function azwho { az account show --query "{user:user.name, sub:name, tenant:tenantId}" -o table } # --- Terraform ---Set-Alias tf terraformfunction tfi { terraform init @args }function tfp { terraform plan -out tfplan @args }function tfa { terraform apply tfplan }function tfs { param($part) terraform state list | Select-String $part } # --- Git ---function gst { git status -s }function glg { git log --oneline --graph --decorate -15 }function gpush { param([string]$msg) git add -A; git commit -m $msg; git push } # --- Network ---function myip { Invoke-RestMethod https://api.ipify.org }function port { param($target, [int]$p) Test-NetConnection $target -Port $p -WarningAction SilentlyContinue |                Select ComputerName, RemoteAddress, RemotePort, TcpTestSucceeded }function dns  { param($name) Resolve-DnsName $name -DnsOnly | Select Name, Type, IPAddress, NameHost } # --- Search Codex ---function cmds { & "<path-to-repo>\search.ps1" @args }

Then e.g.: azsub, tfp, port 10.0.1.4 443, cmds peering. Note: gl, gp, gc are already taken in PowerShell (Get-Location etc.) – hence the other names.

#Keyboard Shortcuts

WhereKeyEffect
PowerShellCtrl+Rsearch the history backwards
PowerShellF8search the history for what you already typed
PowerShellCtrl+Spaceshow all options (parameters, values)
PowerShellTab / Shift+Tabcomplete forward/backward
PowerShellCtrl+Ccancel the running command
PowerShell` at end of linecontinue the command on the next line
Windows TerminalAlt+Shift+Dsplit pane (same profile)
Windows TerminalCtrl+Shift+T / Ctrl+Shift+Wnew / close tab
Windows TerminalCtrl+Shift+Fsearch in the window
VS CodeCtrl+Popen a file
VS CodeCtrl+Shift+Fsearch all files
VS CodeCtrl+Shift+Pcommand palette
VS CodeCtrl+`toggle terminal
VS CodeShift+Alt+Fformat the file
VS CodeCtrl+Shift+VMarkdown preview
Linux / Git BashCtrl+Rsearch the history
Linux / Git Bashsudo !!repeat the last command with sudo
FortiGateTab / ?complete / help
FortiGateCtrl+Ccancel output/debug
Browser (GitHub/DevOps)tfind a file in the repo (GitHub)
Start

#Runbook: Outage "Something Is Not Reachable"

Stay calm, go step by step, write everything down with the time.

#Minute 0–2: Clarify the Situation

Questions for the reporter (or answer them yourself):

QuestionWhy
What exactly doesn't work? URL, IP, port, error message, screenshotdefine the target
Who is affected? Everyone, one site, only internal/external?narrow down the fault
Since when? Was there a change/deployment before?most common cause
Does it work from somewhere else? (other client, VPN, phone)client vs. server

Immediately: send an outage notice to those involved (template).

#Minute 2–5: Is Azure Itself Affected?

PowerShell
# Recent changes in the affected environmentaz monitor activity-log list -g <rg> --offset 4h `  --query "[?status.value=='Succeeded'].{time:eventTimestamp, who:caller, what:operationName.localizedValue}" -o table # State of the most important resourcesaz vm list -d -g <rg> --query "[].{name:name, state:powerState}" -o tableaz network application-gateway show-backend-health -g <rg> -n <agw> `  --query "backendAddressPools[].backendHttpSettingsCollection[].servers[].{server:address, health:health}" -o table
  • Azure status: https://azure.status.microsoft – and in the portal Service Health (shows outages for your subscriptions).
  • Merged PR / pipeline in the last hours? → DevOps Pipelines → Runs.

#Minute 5–10: Check the Path

Always in this order: DNS → route → NSG → firewall → return path → target.

PowerShell
# 1. DNSResolve-DnsName <name>                                            # right IP? private or public? # 2. Port from the clientTest-NetConnection <target> -Port <port> # 3. Route from Azure's point of viewaz network watcher show-next-hop -g <rg> --vm <source-vm> --source-ip <source-ip> --dest-ip <target-ip> -o tableaz network nic show-effective-route-table -g <rg> -n <nic> -o table # 4. NSGaz network watcher test-ip-flow -g <rg> --vm <vm> --direction Inbound --protocol TCP --local <vm-ip>:<port> --remote <source>:12345

FortiGate:

Text
get system status                                   # running, license ok?get system ha status                                # did a failover happen?diagnose sniffer packet any 'host <target-ip>' 4 20 # does traffic arrive and leave?# if it doesn't leave -> debug flow (why dropped):diagnose debug flow filter addr <target-ip>diagnose debug flow trace start 10diagnose debug enablediagnose debug disable ; diagnose debug reset

The target itself:

Bash
ss -tlnp | grep <port>                   # is the service listening?sudo systemctl status <service>sudo journalctl -u <service> -n 50

Details on messages: Error → Fix · Debug flow messages.

#Once the Cause Is Found

#After the Outage

Start

#Checklists

On the web page the items can be ticked off (saved in the browser, "Reset" per list).

#Connect a New Spoke

Workflow with commands: Connect a new spoke to the hub

#Certificate Change on the Application Gateway

Workflow: Add a certificate from Key Vault

#FortiGate Firmware Upgrade in HA

#Go-Live of an Application

#Before Creating a Pull Request

#Put a New Subscription into Operation

Azure

#Azure CLI – Basics, Resources, VMs, Services

Replace placeholders <...> · PowerShell line continuation `, Bash \

Top 5: az account show -o table · az account set --subscription <x> · az resource list -g <rg> -o table · az vm list -d -o table · az monitor activity-log list -g <rg> --offset 2h

#Sign-in and Subscription

PowerShell
az version                                         # installed versionaz upgrade                                         # update the CLIaz login                                           # browser sign-inaz login --tenant <tenant-id>                      # specific tenantaz login --use-device-code                         # sign in with a code (other device)az login --use-device-code --tenant <tenant-id>az login --allow-no-subscriptions                  # tenant without subscriptionaz login --identity                                # managed identity (Azure VM/pipeline)az login --service-principal --username <app-id> --password <secret> --tenant <tenant-id>   # workflow: 00-workflows.mdaz account show -o table                           # current subscriptionaz account list -o table                           # all subscriptionsaz account list --all -o table                     # incl. disabled onesaz account set --subscription <name-or-id>         # switch subscriptionaz account list --query "[?name=='<name>'].id" -o tsvaz account tenant list -o tableaz logout ; az account clear                       # sign out, clear cacheaz config set core.output=table                    # default outputaz extension list -o tableaz extension add -n <name>                         # e.g. serial-console, resource-graphaz extension update -n <name>az provider register -n Microsoft.Network          # register a resource provideraz provider show -n Microsoft.Network --query registrationState

#Output and Filters (JMESPath)

PowerShell
-o table | tsv | json | jsonc | yaml | none--query "[].{name:name, rg:resourceGroup}"         # choose columns--query "[?location=='austriaeast'].name"          # filter--query "[?contains(name,'<part>')].name"          # contains--query "[?tags.environment=='prod']"              # filter by tag--query "length(@)"                                # count--query "[0].id"                                   # first element$id = az <...> --query id -o tsv                   # value into a variable (PowerShell)id=$(az <...> --query id -o tsv)                   # value into a variable (Bash)--debug                                            # show REST calls (troubleshooting)

#Resource Groups, Resources, Locks

PowerShell
az group list -o tableaz group create -n <rg> -l <region> --tags Owner=<x> environment=<y>az group delete -n <rg> --yes --no-waitaz resource list -g <rg> -o tableaz resource list --tag environment=<y> -o tableaz resource show --ids <resource-id>az resource tag --ids <resource-id> --tags key=value -i       # -i = keep existing tagsaz resource delete --ids <resource-id>az resource move --destination-group <rg-new> --ids <id1> <id2>az lock list -g <rg> -o tableaz lock create -n <name> -g <rg> --lock-type CanNotDeleteaz lock delete -n <name> -g <rg> # Availability / regionaz account list-locations --query "[].{name:name, display:displayName}" -o tableaz vm list-skus -l <region> --size <sku> -o table

#Resource Graph

Search across all subscriptions – ideal when you don't know where something lives.

PowerShell
az extension add -n resource-graphaz graph query -q "Resources | where name contains '<part>' | project name, type, resourceGroup, subscriptionId"az graph query -q "Resources | where type =~ 'microsoft.network/virtualnetworks' | project name, resourceGroup, properties.addressSpace.addressPrefixes"az graph query -q "Resources | where type =~ 'microsoft.network/publicipaddresses' | project name, ip=properties.ipAddress, resourceGroup"az graph query -q "Resources | summarize Count=count() by type | order by Count desc"az graph query -q "ResourceContainers | where type =~ 'microsoft.resources/subscriptions' | project name, subscriptionId"

Many more queries: 19 Resource Graph.

#Virtual Machines

PowerShell
az vm list -d -o tableaz vm list -d -g <rg> --query "[].{name:name, state:powerState, zone:zones[0], ip:privateIps, pip:publicIps}" -o tableaz vm show -g <rg> -n <vm> --query "{size:hardwareProfile.vmSize, zones:zones, image:storageProfile.imageReference}"az vm get-instance-view -g <rg> -n <vm> --query "instanceView.statuses[].displayStatus"az vm start      -g <rg> -n <vm>az vm stop       -g <rg> -n <vm>                 # stopped, compute still billedaz vm deallocate -g <rg> -n <vm> --no-wait       # deallocated, no compute costsaz vm restart    -g <rg> -n <vm>az vm resize     -g <rg> -n <vm> --size <sku>az vm list-sizes -l <region> -o tableaz vm list-skus  -l <region> --size <sku> --query "[].locationInfo[].zones" -o tsv     # zonesaz vm list-usage -l <region> -o table                                                   # quotaaz vm run-command invoke -g <rg> -n <vm> --command-id RunShellScript --scripts "hostname; ip a"az vm run-command invoke -g <rg> -n <vm> --command-id RunPowerShellScript --scripts "ipconfig /all"az vm boot-diagnostics enable -g <rg> -n <vm>az vm boot-diagnostics get-boot-log -g <rg> -n <vm>az serial-console connect -g <rg> -n <vm>        # extension serial-consoleaz vm user update -g <rg> -n <vm> -u <user> -p <new-password>        # reset passwordaz vm user update -g <rg> -n <vm> -u <user> --ssh-key-value "<public-key>"az vm disk attach -g <rg> --vm-name <vm> --name <disk> --new --size-gb 32az vm extension list -g <rg> --vm-name <vm> -o tableaz vm identity assign -g <rg> -n <vm>            # system-assigned identity # NICaz network nic list -g <rg> -o tableaz network nic show -g <rg> -n <nic> --query "{ip:ipConfigurations[].privateIPAddress, fwd:enableIPForwarding, accel:enableAcceleratedNetworking}"az network nic update -g <rg> -n <nic> --ip-forwarding true          # required for NVAs

#Marketplace Images

PowerShell
az vm image list-publishers -l <region> --query "[?contains(name,'<part>')].name" -o tsvaz vm image list-offers -l <region> -p <publisher> -o tableaz vm image list-skus   -l <region> -p <publisher> -f <offer> -o tableaz vm image list -p <publisher> -f <offer> -s <sku> -l <region> --all -o tableaz vm image terms show   --urn <publisher>:<offer>:<sku>:<version>az vm image terms accept --urn <publisher>:<offer>:<sku>:<version>
ProductPublisherOffer
FortiGatefortinetfortinet_fortigate-vm
Ubuntu 24.04Canonicalubuntu-24_04-lts
Windows ServerMicrosoftWindowsServerWindowsServer

#App Service / Functions

PowerShell
az webapp list --query "[].{name:name, rg:resourceGroup, state:state, host:defaultHostName}" -o tableaz webapp show -g <rg> -n <app> --query "{state:state, https:httpsOnly, pna:publicNetworkAccess, vnet:virtualNetworkSubnetId}"az webapp restart -g <rg> -n <app>az webapp log tail -g <rg> -n <app>                                  # live logaz webapp config appsettings list -g <rg> -n <app> -o tableaz webapp config appsettings set  -g <rg> -n <app> --settings KEY=valueaz webapp config access-restriction show -g <rg> -n <app>az webapp vnet-integration list -g <rg> -n <app>az webapp update -g <rg> -n <app> --set publicNetworkAccess=Disabledaz functionapp list -o table

#Storage Account

PowerShell
az storage account list --query "[].{name:name, rg:resourceGroup, sku:sku.name, pna:publicNetworkAccess}" -o tableaz storage account show -g <rg> -n <sa> --query "{tls:minimumTlsVersion, public:allowBlobPublicAccess, key:allowSharedKeyAccess}"az storage account network-rule list -g <rg> --account-name <sa>az storage account update -g <rg> -n <sa> --default-action Denyaz storage container list --account-name <sa> --auth-mode login -o tableaz storage blob list   --account-name <sa> -c <container> --auth-mode login -o tableaz storage blob upload --account-name <sa> -c <container> -f <file> -n <name> --auth-mode loginaz storage blob download --account-name <sa> -c <container> -n <name> -f <target> --auth-mode login

--auth-mode login needs a data role (e.g. Storage Blob Data Contributor); Owner alone is not enough. More: 23 Storage.

#AKS (short)

PowerShell
az aks list -o tableaz aks get-credentials -g <rg> -n <aks>              # get kubeconfigaz aks show -g <rg> -n <aks> --query "{k8s:kubernetesVersion, net:networkProfile.networkPlugin, outbound:networkProfile.outboundType}"kubectl get nodes -o wide ; kubectl get pods -A

#Monitoring and Diagnostic Settings

PowerShell
az monitor activity-log list -g <rg> --offset 2h `  --query "[].{time:eventTimestamp, op:operationName.localizedValue, status:status.value, by:caller}" -o tableaz monitor activity-log list --correlation-id <id>az monitor diagnostic-settings list --resource <resource-id> -o tableaz monitor diagnostic-settings categories list --resource <resource-id> -o tableaz monitor diagnostic-settings create -n <name> --resource <resource-id> --workspace <law-id> `  --logs '[{"categoryGroup":"allLogs","enabled":true}]' --metrics '[{"category":"AllMetrics","enabled":true}]'az monitor log-analytics workspace list -o tableaz monitor log-analytics workspace show -g <rg> -n <law> --query customerId -o tsv     # workspace GUIDaz monitor log-analytics query -w <workspace-guid> --analytics-query "<KQL>" -o tableaz monitor metrics list-definitions --resource <resource-id> -o tableaz monitor metrics list --resource <resource-id> --metric <name> --interval PT5M -o tableaz monitor metrics alert list -g <rg> -o table

#REST and Token

PowerShell
az account get-access-token --query accessToken -o tsvaz rest --method get --url "https://management.azure.com/subscriptions/<sub>/resourceGroups?api-version=2021-04-01"az rest --method get --url "<resource-id>?api-version=<version>"     # raw resource as JSON

Useful when a CLI command doesn't (yet) know a property.

Azure

#Azure CLI – Network

Background: Network basics

Top 5: az network nic show-effective-route-table · az network vnet peering list · az network watcher show-next-hop · az network nsg rule list --include-default · az network application-gateway show-backend-health

#VNet and Subnets

PowerShell
az network vnet list --query "[].{name:name, rg:resourceGroup, space:addressSpace.addressPrefixes[0]}" -o tableaz network vnet show -g <rg> -n <vnet> --query "{space:addressSpace.addressPrefixes, dns:dhcpOptions.dnsServers}"az network vnet create -g <rg> -n <vnet> --address-prefixes 10.0.0.0/22 -l <region>az network vnet update -g <rg> -n <vnet> --address-prefixes 10.0.0.0/22 10.0.8.0/24     # extend address spaceaz network vnet update -g <rg> -n <vnet> --dns-servers <ip1> <ip2>                       # custom DNSaz network vnet update -g <rg> -n <vnet> --dns-servers ""                                # back to Azure DNSaz network vnet subnet list -g <rg> --vnet-name <vnet> `  --query "[].{name:name, prefix:addressPrefix, nsg:networkSecurityGroup.id, rt:routeTable.id}" -o tableaz network vnet subnet create -g <rg> --vnet-name <vnet> -n <subnet> --address-prefixes 10.0.1.0/24 `  --default-outbound falseaz network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --network-security-group <nsg>az network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --route-table <rt-id>az network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --remove routeTableaz network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --remove networkSecurityGroupaz network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --private-endpoint-network-policies Enabledaz network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --delegations Microsoft.Web/serverFarmsaz network vnet list-available-ips -g <rg> -n <vnet>az network vnet check-ip-address -g <rg> -n <vnet> --ip-address <ip>       # free?

#Peering

PowerShell
az network vnet peering list -g <rg> --vnet-name <vnet> `  --query "[].{name:name, state:peeringState, sync:peeringSyncLevel, access:allowVirtualNetworkAccess, fwd:allowForwardedTraffic, gwTransit:allowGatewayTransit, remoteGw:useRemoteGateways}" -o tableaz network vnet peering create -g <rg> --vnet-name <vnet-a> -n <a-to-b> --remote-vnet <vnet-b-id> `  --allow-vnet-access --allow-forwarded-trafficaz network vnet peering update -g <rg> --vnet-name <vnet> -n <peering> --set allowForwardedTraffic=trueaz network vnet peering sync -g <rg> --vnet-name <vnet> -n <peering>       # after an address space changeaz network vnet peering delete -g <rg> --vnet-name <vnet> -n <peering>
SettingHub sideSpoke side
Allow forwarded trafficyesyes
Allow gateway transityes (if VPN/ER GW in hub)–
Use remote gateways–yes (if VPN/ER GW in hub)

Peering must exist on both sides: Connected / FullyInSync.

#Route Tables (UDR)

PowerShell
az network route-table list -o tableaz network route-table create -g <rg> -n <rt> -l <region> --disable-bgp-route-propagation trueaz network route-table route create -g <rg> --route-table-name <rt> -n default-to-nva `  --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address <nva-or-ilb-ip>az network route-table route list -g <rg> --route-table-name <rt> -o tableaz network route-table route update -g <rg> --route-table-name <rt> -n <route> --next-hop-ip-address <new-ip>az network route-table route delete -g <rg> --route-table-name <rt> -n <route>az network route-table show -g <rg> -n <rt> --query "subnets[].id" -o tsv        # where is it associated?

Next hop types: VirtualAppliance, Internet, VnetLocal, VirtualNetworkGateway, None.

#Effective Routes and Connectivity Tests

PowerShell
az network nic show-effective-route-table -g <rg> -n <nic> -o tableaz network nic list-effective-nsg -g <rg> -n <nic>az network watcher show-next-hop -g <rg> --vm <vm> --source-ip <vm-ip> --dest-ip <target> -o tableaz network watcher test-connectivity -g <rg> --source-resource <vm> --dest-address <target> --dest-port 443az network watcher test-ip-flow -g <rg> --vm <vm> --direction Inbound --protocol TCP `  --local <vm-ip>:443 --remote <source>:12345az network watcher list -o table                                   # Network Watcher per region

Effective routes are only available for a running VM.

#NSG

PowerShell
az network nsg list -o tableaz network nsg create -g <rg> -n <nsg>az network nsg rule list -g <rg> --nsg-name <nsg> --include-default -o tableaz network nsg rule create -g <rg> --nsg-name <nsg> -n Allow-Https-In --priority 100 `  --direction Inbound --access Allow --protocol Tcp `  --source-address-prefixes <source-or-ServiceTag> --destination-port-ranges 443az network nsg rule update -g <rg> --nsg-name <nsg> -n <rule> --access Denyaz network nsg rule delete -g <rg> --nsg-name <nsg> -n <rule>az network nsg show -g <rg> -n <nsg> --query "{subnets:subnets[].id, nics:networkInterfaces[].id}"

Service tags: Internet, VirtualNetwork, AzureLoadBalancer, GatewayManager, AzureCloud, Storage.<region>, AzureMonitor, AzureKeyVault … Priority: the lower number wins (100–4096).

#NAT Gateway and Bastion

PowerShell
az network nat gateway list -o tableaz network nat gateway create -g <rg> -n <natgw> --public-ip-addresses <pip> --idle-timeout 4az network vnet subnet update -g <rg> --vnet-name <vnet> -n <subnet> --nat-gateway <natgw>az network bastion list -o tableaz network bastion ssh -g <rg> -n <bastion> --target-resource-id <vm-id> --auth-type ssh-key --username <user> --ssh-key <path>az network bastion rdp -g <rg> -n <bastion> --target-resource-id <vm-id>az network bastion tunnel -g <rg> -n <bastion> --target-resource-id <vm-id> --resource-port 22 --port 50022

bastion ssh/rdp/tunnel requires Bastion Standard with Native client support.

#VPN Gateway (Site-to-Site)

PowerShell
az network vnet-gateway list -g <rg> -o tableaz network vnet-gateway show -g <rg> -n <gw> --query "{sku:sku.name, bgp:bgpSettings, ips:ipConfigurations[].publicIPAddress.id}"az network vnet-gateway list-bgp-peer-status -g <rg> -n <gw> -o tableaz network vnet-gateway list-learned-routes  -g <rg> -n <gw> -o tableaz network vnet-gateway list-advertised-routes -g <rg> -n <gw> --peer <bgp-peer-ip> -o tableaz network vnet-gateway reset -g <rg> -n <gw>                       # restart the instance(s)az network local-gateway list -g <rg> -o tableaz network vpn-connection list -g <rg> --query "[].{name:name, status:connectionStatus, type:connectionType}" -o tableaz network vpn-connection show -g <rg> -n <conn> --query "{status:connectionStatus, in:ingressBytesTransferred, out:egressBytesTransferred}"az network vpn-connection shared-key show  -g <rg> --connection-name <conn>az network vpn-connection shared-key update -g <rg> --connection-name <conn> --value <psk>

#Azure Firewall

Firewall policy structure: rule collection group (priority) → rule collection (priority, action Allow/Deny/DNAT) → rules. Evaluation order: DNAT → network rules → application rules, each by priority (lower = first). With a form: web page → Generator → Azure Firewall.

PowerShell
az extension add -n azure-firewallaz network firewall list -o tableaz network firewall show -g <rg> -n <fw> --query "{ip:ipConfigurations[0].privateIPAddress, policy:firewallPolicy.id}"az network firewall policy list -o tableaz network firewall policy rule-collection-group list -g <rg> --policy-name <policy> --query "[].{name:name, prio:priority}" -o tableaz network firewall policy rule-collection-group show -g <rg> --policy-name <policy> -n <rcg> `  --query "ruleCollections[].{name:name, prio:priority, action:action.type, rules:length(rules)}" -o table # Add a rule to an existing collectionaz network firewall policy rule-collection-group collection rule add -g <rg> --policy-name <policy> --rcg-name <rcg> `  --collection-name <collection> --name <rule> --rule-type NetworkRule `  --source-addresses 10.1.0.0/24 --destination-addresses 10.2.0.0/24 --destination-ports 443 --ip-protocols TCP # New collection with a ruleaz network firewall policy rule-collection-group collection add-filter-collection -g <rg> --policy-name <policy> --rcg-name <rcg> `  --name <collection> --collection-priority 200 --action Allow --rule-name <rule> --rule-type ApplicationRule `  --source-addresses 10.1.0.0/24 --target-fqdns "*.microsoft.com" --protocols Https=443 # Remove a ruleaz network firewall policy rule-collection-group collection rule remove -g <rg> --policy-name <policy> --rcg-name <rcg> `  --collection-name <collection> --name <rule>

Extend an existing rule (PowerShell – get, change, write back):

PowerShell
$pol  = Get-AzFirewallPolicy -Name <policy> -ResourceGroupName <rg>$rcg  = Get-AzFirewallPolicyRuleCollectionGroup -Name <rcg> -AzureFirewallPolicyName <policy> -ResourceGroupName <rg>$rule = ($rcg.Properties.RuleCollection | Where-Object Name -eq "<collection>").Rules | Where-Object Name -eq "<rule>"$rule.DestinationAddresses.Add("10.3.0.0/24")Set-AzFirewallPolicyRuleCollectionGroup -Name <rcg> -Priority $rcg.Properties.Priority `  -RuleCollection $rcg.Properties.RuleCollection -FirewallPolicyObject $pol

Logs (resource-specific): AZFWNetworkRule, AZFWApplicationRule, AZFWNatRule – column Action = Deny shows what is blocked.

#Private DNS and Private Endpoints

PowerShell
az network private-dns zone list -o tableaz network private-dns zone create -g <rg> -n privatelink.azurewebsites.netaz network private-dns link vnet list -g <rg-dns> -z <zone> -o tableaz network private-dns link vnet create -g <rg-dns> -z <zone> -n <link-name> -v <vnet-id> -e false   # -e = auto-registrationaz network private-dns record-set a list -g <rg-dns> -z <zone> -o tableaz network private-dns record-set a add-record -g <rg-dns> -z <zone> -n <name> -a <ip>az network private-dns record-set a remove-record -g <rg-dns> -z <zone> -n <name> -a <ip> az network private-endpoint list -g <rg> -o tableaz network private-endpoint show -g <rg> -n <pe> --query "customDnsConfigs"az network private-endpoint create -g <rg> -n <pe> --vnet-name <vnet> --subnet <subnet> `  --private-connection-resource-id <target-resource-id> --group-id <sites|vault|blob|...> --connection-name <name>az network private-endpoint dns-zone-group create -g <rg> --endpoint-name <pe> -n default `  --private-dns-zone <zone-id> --zone-name <zone>az network private-endpoint-connection list --id <target-resource-id> -o tableaz network private-endpoint-connection approve --id <connection-id> --description "ok" # DNS Private Resolveraz dns-resolver list -o tableaz dns-resolver inbound-endpoint list -g <rg> --dns-resolver-name <resolver> -o tableaz dns-resolver forwarding-ruleset list -g <rg> -o table
ServiceZonegroup-id
App Service / Functionsprivatelink.azurewebsites.netsites
Key Vaultprivatelink.vaultcore.azure.netvault
Storage Blobprivatelink.blob.core.windows.netblob
Storage Fileprivatelink.file.core.windows.netfile
Storage Queue / Tableprivatelink.queue.core.windows.net / privatelink.table.core.windows.netqueue / table
PostgreSQL Flexibleprivatelink.postgres.database.azure.compostgresqlServer
SQLprivatelink.database.windows.netsqlServer
Cosmos DB (SQL)privatelink.documents.azure.comSql
Container Registryprivatelink.azurecr.ioregistry
Service Bus / Event Hubprivatelink.servicebus.windows.netnamespace
Azure Monitor (AMPLS)privatelink.monitor.azure.com and othersazuremonitor

Rule: the zone must be linked to the VNet whose DNS setting does the resolution (Azure-provided DNS → client VNet; custom DNS → VNet of the DNS server/resolver). Test: nslookup <name>.azurewebsites.net must return a private IP.

#Application Gateway and WAF

PowerShell
az network application-gateway list -o tableaz network application-gateway show -g <rg> -n <agw> --query "{state:operationalState, sku:sku.name}"az network application-gateway show-backend-health -g <rg> -n <agw> `  --query "backendAddressPools[].backendHttpSettingsCollection[].servers[].{server:address, health:health, log:healthProbeLog}" -o tableaz network application-gateway stop  -g <rg> -n <agw>az network application-gateway start -g <rg> -n <agw>az network application-gateway http-listener list -g <rg> --gateway-name <agw> -o tableaz network application-gateway rule list -g <rg> --gateway-name <agw> -o tableaz network application-gateway address-pool list -g <rg> --gateway-name <agw> -o tableaz network application-gateway http-settings list -g <rg> --gateway-name <agw> -o tableaz network application-gateway probe list -g <rg> --gateway-name <agw> -o tableaz network application-gateway ssl-cert list -g <rg> --gateway-name <agw> -o tableaz network application-gateway ssl-cert create -g <rg> --gateway-name <agw> -n <name> `  --key-vault-secret-id https://<kv>.vault.azure.net/secrets/<name>      # certificate from Key Vaultaz network application-gateway identity show -g <rg> --gateway-name <agw> # WAF policiesaz network application-gateway waf-policy list -g <rg> -o tableaz network application-gateway waf-policy show -g <rg> -n <policy> `  --query "{mode:policySettings.mode, state:policySettings.state, sets:managedRules.managedRuleSets[].{t:ruleSetType,v:ruleSetVersion}}"az network application-gateway waf-policy policy-setting update -g <rg> --policy-name <policy> --mode Prevention --state Enabledaz network application-gateway waf-policy managed-rule rule-set list -g <rg> --policy-name <policy>az network application-gateway waf-policy managed-rule rule-set add -g <rg> --policy-name <policy> `  --type Microsoft_BotManagerRuleSet --version 1.1az network application-gateway waf-policy managed-rule exclusion list -g <rg> --policy-name <policy>az network application-gateway waf-policy custom-rule list -g <rg> --policy-name <policy> -o table

Rules of thumb for App Gateway v2:

  • Dedicated subnet, no 0.0.0.0/0 UDR to anything other than Internet.
  • NSG on the subnet: allow inbound GatewayManager TCP 65200-65535 and AzureLoadBalancer.
  • App Service as backend: FQDN, HTTPS 443, pick host name from backend, private DNS zone linked to the App Gateway VNet.
  • Certificates: Key Vault + user-assigned identity (Key Vault Secrets User), versionless secret ID.
  • WAF: Detection = log only, Prevention = block (403); a separate policy per listener is possible.

#Load Balancer and Public IPs

PowerShell
az network lb list -o tableaz network lb show -g <rg> -n <lb> --query "{fe:frontendIPConfigurations[].privateIPAddress, sku:sku.name}"az network lb rule list -g <rg> --lb-name <lb> -o tableaz network lb probe list -g <rg> --lb-name <lb> -o tableaz network lb address-pool list -g <rg> --lb-name <lb> -o tableaz network lb outbound-rule list -g <rg> --lb-name <lb> -o tableaz network lb inbound-nat-rule list -g <rg> --lb-name <lb> -o table az network public-ip list -g <rg> --query "[].{name:name, ip:ipAddress, sku:sku.name, zones:zones, on:ipConfiguration.id}" -o tableaz network public-ip create -g <rg> -n <pip> --sku Standard --allocation-method Static --zone 1 2 3

Rules of thumb:

  • The health probe source is always 168.63.129.16.
  • Floating IP on: destination stays the frontend IP (for VIPs on an NVA). Off: destination is rewritten to the backend IP (for services on the NVA itself, e.g. IPsec).
  • HA ports (internal Standard LB): all protocols/ports in one rule – the basis for NVA high availability.
  • Outbound rules only translate TCP/UDP, no ICMP (ping to the internet does not work).

#Flow Logs

PowerShell
az network watcher flow-log list -l <region> -o tableaz network watcher flow-log show -l <region> -n <flowlog>

New flow logs are created at VNet level (NSG flow logs are being retired). Analysis: KQL.

Azure

#Azure Front Door

With a form: web page → Generator → Front Door · Counterpart in the VNet: App Gateway

Top 5: az afd custom-domain list · az afd route show · az afd origin list · az afd endpoint purge · az afd custom-domain show --query domainValidationState

#Structure

text
Profile (Standard / Premium)└── Endpoint                 <name>-<hash>.z01.azurefd.net    └── Route                which domains + paths -> which origin group        ├── Custom domains   app.company.tld (+ certificate)        └── Origin group     health probe, load balancing            └── Origins      e.g. <app>.azurewebsites.net (priority, weight, optional Private Link)
TermMeaning
Originbackend, e.g. App Service, storage website, own server
Origin groupgroup of origins with a shared health probe
Routeconnects domains + paths to an origin group
Custom domainown host name, needs TXT validation + CNAME
Security policybinds a WAF policy to domains
Private LinkFront Door reaches the origin privately (Premium only)

#Get an Overview

PowerShell
$rg = "<rg-afd>" ; $afd = "<afd-profile>" ; $ep = "<afd-endpoint>"az afd profile list --query "[].{name:name, rg:resourceGroup, sku:sku.name}" -o tableaz afd endpoint list -g $rg --profile-name $afd --query "[].{name:name, host:hostName}" -o tableaz afd custom-domain list -g $rg --profile-name $afd --query "[].{name:name, host:hostName, validation:domainValidationState, cert:tlsSettings.certificateType}" -o tableaz afd origin-group list -g $rg --profile-name $afd --query "[].name" -o tsvaz afd origin list -g $rg --profile-name $afd --origin-group-name <origin-group> -o tableaz afd route list -g $rg --profile-name $afd --endpoint-name $ep --query "[].{route:name, og:originGroup.id, domains:customDomains[].id, paths:patternsToMatch}" -o json

#Origin Group and Origin

PowerShell
az afd origin-group create -g $rg --profile-name $afd --origin-group-name og-<app> `  --probe-request-type HEAD --probe-protocol Https --probe-interval-in-seconds 60 --probe-path "/" `  --sample-size 4 --successful-samples-required 3 --additional-latency-in-milliseconds 50 $appHost = az webapp show -g <rg-app> -n <app> --query defaultHostName -o tsvaz afd origin create -g $rg --profile-name $afd --origin-group-name og-<app> --origin-name o-<app> `  --host-name $appHost --origin-host-header $appHost --http-port 80 --https-port 443 `  --priority 1 --weight 1000 --enabled-state Enabled

Private Link (Premium) additionally: --enable-private-link true --private-link-resource <app-id> --private-link-location <region> --private-link-sub-resource-type sites, then approve the request on the app:

PowerShell
az network private-endpoint-connection list --id <app-id> --query "[?properties.privateLinkServiceConnectionState.status=='Pending'].id" -o tsvaz network private-endpoint-connection approve --id <connection-id> --description "Front Door"

#Custom Domain and Certificate

PowerShell
# Certificate managed by Front Dooraz afd custom-domain create -g $rg --profile-name $afd --custom-domain-name app-company-tld --host-name app.company.tld `  --minimum-tls-version TLS12 --certificate-type ManagedCertificate # Or your own certificate from Key Vault (versionless = automatic renewal)$kvId = az keyvault show -n <keyvault-name> --query id -o tsvaz afd secret create -g $rg --profile-name $afd --secret-name sec-<certificate> `  --secret-source "$kvId/secrets/<certificate>" --use-latest-version trueaz afd custom-domain create -g $rg --profile-name $afd --custom-domain-name app-company-tld --host-name app.company.tld `  --minimum-tls-version TLS12 --certificate-type CustomerCertificate --secret sec-<certificate> # DNS recordsaz afd custom-domain show -g $rg --profile-name $afd --custom-domain-name app-company-tld --query "validationProperties.validationToken" -o tsv#   TXT   _dnsauth.app.company.tld  ->  <token>#   CNAME app.company.tld           ->  <endpoint-host>.azurefd.netaz afd custom-domain show -g $rg --profile-name $afd --custom-domain-name app-company-tld --query "{validation:domainValidationState, status:deploymentStatus}"

The custom domain's name in Front Door may only contain letters, digits and hyphens (app-company-tld); the host name is the real one (app.company.tld).

#Create a Route or Assign to an Existing Route

PowerShell
# New routeaz afd route create -g $rg --profile-name $afd --endpoint-name $ep --route-name rt-<app> `  --origin-group og-<app> --custom-domains app-company-tld --patterns-to-match "/*" `  --supported-protocols Http Https --https-redirect Enabled --forwarding-protocol HttpsOnly --link-to-default-domain Disabled # Add a domain to an EXISTING route – pass the existing domains too, otherwise they are removed!$cdId = az afd custom-domain show -g $rg --profile-name $afd --custom-domain-name app-company-tld --query id -o tsv$ids  = @(az afd route show -g $rg --profile-name $afd --endpoint-name $ep --route-name <route> --query "customDomains[].id" -o tsv) + $cdIdaz afd route update -g $rg --profile-name $afd --endpoint-name $ep --route-name <route> --custom-domains $ids

Caution: az afd route update --custom-domains replaces the list. Always pass the existing IDs.

#Lock Down the App Service

Only Front Door may reach the app – via service tag and your own Front Door ID (otherwise any Front Door would get through):

PowerShell
$fdId = az afd profile show -g $rg --profile-name $afd --query frontDoorId -o tsvaz webapp config access-restriction add -g <rg-app> -n <app> --rule-name AllowFrontDoor --priority 100 `  --action Allow --service-tag AzureFrontDoor.Backend --http-header x-azure-fdid=$fdId

With Private Link (Premium) instead: disable public access on the app (az webapp update --set publicNetworkAccess=Disabled).

#WAF

PowerShell
az network front-door waf-policy create -g $rg -n wafafd<app> --sku Premium_AzureFrontDoor --mode Detectionaz network front-door waf-policy managed-rules add -g $rg --policy-name wafafd<app> --type Microsoft_DefaultRuleSet --version 2.1 --action Block$wafId = az network front-door waf-policy show -g $rg -n wafafd<app> --query id -o tsvaz afd security-policy create -g $rg --profile-name $afd --security-policy-name sp-<app> --waf-policy $wafId --domains $cdId

Logs: diagnostic setting of the profile → Log Analytics, table AzureDiagnostics with Category == "FrontDoorWebApplicationFirewallLog".

#Cache and Troubleshooting

PowerShell
az afd endpoint purge -g $rg --profile-name $afd --endpoint-name $ep --content-paths "/*"curl.exe -sI https://app.company.tld                      # X-Azure-Ref, X-Cache in the responsecurl.exe -sI https://<endpoint-host>.azurefd.net          # test without the custom domain
SymptomCauseFix
Domain stays PendingTXT _dnsauth missing/wrong, token expiredcheck TXT, az afd custom-domain regenerate-validation-token
404 "Page not found" from Front Doordomain not assigned to a route / path doesn't matchcheck route → domains and patternsToMatch
502/503/504origin unreachable, probe fails, wrong host headerorigin host + origin-host-header, probe path, app access restriction
403 from the appaccess restriction without x-azure-fdid or wrong IDrule as above, your own Front Door ID
Private Link origin doesn't workrequest on the app not approvedprivate-endpoint-connection approve
Change has no effectdeployment running (up to ~10 min) / cachecheck deploymentStatus, endpoint purge
Azure

#Azure CLI – Key Vault, Identity, Roles, Policy

Top 5: az role assignment list --assignee <x> --all -o table · az ad signed-in-user show --query id -o tsv · az keyvault secret show · az policy assignment list · az identity show

#Key Vault

PowerShell
az keyvault list -o tableaz keyvault show -n <kv> --query "{rbac:properties.enableRbacAuthorization, pna:properties.publicNetworkAccess, softDelete:properties.enableSoftDelete}"az keyvault secret list --vault-name <kv> -o tableaz keyvault secret show --vault-name <kv> -n <name> --query value -o tsvaz keyvault secret set  --vault-name <kv> -n <name> --value <value>az keyvault secret set  --vault-name <kv> -n <name> --file <file>           # value from a fileaz keyvault secret delete --vault-name <kv> -n <name>az keyvault secret list-deleted --vault-name <kv> -o tableaz keyvault secret recover --vault-name <kv> -n <name>                      # undo soft deleteaz keyvault certificate list --vault-name <kv> -o tableaz keyvault certificate import --vault-name <kv> -n <name> -f .\cert.pfx --password <pfx-password>az keyvault certificate show --vault-name <kv> -n <name> --query "{sid:sid, expires:attributes.expires}"# versionless secret ID: https://<kv>.vault.azure.net/secrets/<name>az keyvault certificate download --vault-name <kv> -n <name> -f cert.pem   # public part onlyaz keyvault list-deleted -o tableaz keyvault purge -n <kv>                                                   # delete permanently (careful!)az keyvault update -n <kv> --bypass AzureServices                           # allow trusted servicesaz keyvault network-rule add -n <kv> --ip-address <ip>/32az keyvault network-rule list -n <kv>

Error Forbidden … not authorized: on an RBAC vault the role Key Vault Secrets User/Officer is missing, on an access-policy vault an access policy.

#Create a Key Vault

PowerShell
az keyvault create -n <keyvault-name> -g <rg> -l <region> --sku standard `  --enable-rbac-authorization true --enable-purge-protection true --retention-days 90 --public-network-access Enabled$kvId = az keyvault show -n <keyvault-name> --query id -o tsv$me   = az ad signed-in-user show --query id -o tsvaz role assignment create --assignee-object-id $me --assignee-principal-type User --role "Key Vault Administrator" --scope $kvId

Purge protection cannot be turned off again; after deletion the name stays blocked for the retention period.

#Generate a certificate in Key Vault

PowerShell
# Self-signed (test) – PowerShell$policy = New-AzKeyVaultCertificatePolicy -SubjectName "CN=<fqdn>" -DnsName "<fqdn>" -IssuerName Self `  -ValidityInMonths 12 -SecretContentType "application/x-pkcs12" -KeyType RSA -KeySize 2048Add-AzKeyVaultCertificate -VaultName <keyvault-name> -Name <certificate-name> -CertificatePolicy $policyGet-AzKeyVaultCertificateOperation -VaultName <keyvault-name> -Name <certificate-name>      # status # For your own CA: IssuerName Unknown -> get CSR -> have it signed -> mergeaz keyvault certificate pending show  --vault-name <keyvault-name> -n <certificate-name> --query csr -o tsvaz keyvault certificate pending merge --vault-name <keyvault-name> -n <certificate-name> --file .\signed.cer

Easier with a form: web page → Generator → Key Vault → "Generate a certificate in Key Vault".

#Managed Identity

PowerShell
az identity list -g <rg> -o tableaz identity create -g <rg> -n <identity>az identity show -g <rg> -n <identity> --query "{principal:principalId, client:clientId, id:id}"az vm identity show -g <rg> -n <vm>az webapp identity assign -g <rg> -n <app> --identities <identity-id>

principalId = object ID for role assignments · clientId = for sign-in in code/pipelines.

#Roles (RBAC)

PowerShell
az role assignment list --assignee <principal-id-or-upn> --all -o tableaz role assignment list --scope <resource-id> --include-inherited -o tableaz role assignment create --assignee-object-id <principal-id> --assignee-principal-type ServicePrincipal `  --role "<role>" --scope <resource-id>az role assignment delete --assignee <principal-id> --role "<role>" --scope <resource-id>az role definition list --name "<role>" -o jsonaz role definition list --custom-role-only true -o tableaz ad signed-in-user show --query id -o tsv           # your own object ID # Service principal by display name -> object ID -> role$id = az ad sp list --display-name "<sp-name>" --query "[].{id:id}" --output tsvaz role assignment create --assignee-principal-type ServicePrincipal --assignee-object-id $id --scope "/subscriptions/<sub>" --role "Owner" # Scopes#   /subscriptions/<sub>/resourceGroups/<rg>                      resource group#   /subscriptions/<sub>                                          subscription#   /providers/Microsoft.Management/managementGroups/<mg-id>      management group#   /                                                             tenant root (everything – only with elevated access)az rest --method post --url "/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"   # Global Admin: get UAA on "/"az account management-group list -o table                    # management groups

--assignee-object-id + --assignee-principal-type avoids Graph lookups and replication errors with freshly created identities.

RoleFor
Readerread only
Contributoreverything except granting access
Ownereverything incl. access
User Access Administratorgranting access only
Network Contributornetwork resources
Private DNS Zone ContributorDNS zones/records
Key Vault Secrets Userread secrets (RBAC vault)
Key Vault Secrets Officermanage secrets
Key Vault Administratoreverything in the vault
Key Vault Certificates Officermanage certificates
Storage Blob Data Reader/Contributorblob data (e.g. Terraform state)
Monitoring Readerread metrics/logs

#Entra ID (Users, Groups, Apps)

PowerShell
az ad user show --id <upn> --query "{id:id, name:displayName}"az ad group list --display-name <part> -o tableaz ad group member list -g <group> --query "[].userPrincipalName" -o tsvaz ad group member check -g <group> --member-id <object-id>az ad sp list --display-name <name> -o tableaz ad sp show --id <app-id> --query "{objectId:id, appId:appId}"az ad app list --display-name <name> -o tableaz ad app credential list --id <app-id> --query "[].{end:endDateTime, name:displayName}" -o table   # secret expiryaz ad sp create-for-rbac -n <name> --role Contributor --scopes /subscriptions/<sub>

More: 21 Entra ID.

#Azure Policy

PowerShell
az policy assignment list --scope /subscriptions/<sub> -o tableaz policy assignment list --disable-scope-strict-match --scope /subscriptions/<sub>/resourceGroups/<rg> -o tableaz policy assignment show -n <assignment> --scope <scope>az policy definition show -n <definition-name> --query "{name:displayName, effect:policyRule.then.effect}"az policy state list --resource <resource-id> --query "[].{policy:policyDefinitionName, compliant:complianceState}" -o tableaz policy state summarize -g <rg>az policy state trigger-scan -g <rg> --no-wait                    # re-evaluate complianceaz policy exemption list --scope <scope> -o tableaz policy exemption create -n <name> --policy-assignment <assignment-id> --scope <scope> --exemption-category Waiver

Error RequestDisallowedByPolicy: the message contains policyAssignmentId and policyDefinitionId – use them to search.

Azure

#Azure PowerShell (Az Module)

Same tasks with the CLI: Azure CLI · Side by side: 20 CLI ↔ PowerShell

PowerShell
Install-Module Az -Scope CurrentUser -Repository PSGalleryUpdate-Module AzConnect-AzAccount [-Tenant <id>] [-UseDeviceAuthentication]Connect-AzAccount -Identity                                # managed identityConnect-AzAccount -UseDeviceAuthentication                 # device code$cred = Get-Credential -UserName <app-id> ; Connect-AzAccount -ServicePrincipal -Credential $cred -Tenant <tenant-id>Get-AzContextGet-AzSubscriptionSet-AzContext -Subscription <name-or-id>Disconnect-AzAccount

#Resources

PowerShell
Get-AzResourceGroup | Format-Table ResourceGroupName, LocationGet-AzResource -ResourceGroupName <rg> | Format-Table Name, ResourceTypeGet-AzResource -Name "*<part>*" | Format-Table Name, ResourceGroupName, ResourceTypeSearch-AzGraph -Query "Resources | where name contains '<part>' | project name, type, resourceGroup"   # module Az.ResourceGraph

#VMs

PowerShell
Get-AzVM -Status | Select Name, ResourceGroupName, PowerStateStart-AzVM -ResourceGroupName <rg> -Name <vm>Stop-AzVM  -ResourceGroupName <rg> -Name <vm> -Force          # deallocatedInvoke-AzVMRunCommand -ResourceGroupName <rg> -VMName <vm> -CommandId RunPowerShellScript -ScriptString "ipconfig /all"

#Network

PowerShell
Get-AzVirtualNetwork | Select Name, @{n='Space';e={$_.AddressSpace.AddressPrefixes}}Get-AzVirtualNetworkPeering -ResourceGroupName <rg> -VirtualNetworkName <vnet> | Select Name, PeeringState, AllowForwardedTrafficGet-AzEffectiveRouteTable -ResourceGroupName <rg> -NetworkInterfaceName <nic> | Format-TableGet-AzEffectiveNetworkSecurityGroup -ResourceGroupName <rg> -NetworkInterfaceName <nic>Get-AzNetworkSecurityGroup -Name <nsg> -ResourceGroupName <rg> | Get-AzNetworkSecurityRuleConfig | Format-TableGet-AzPublicIpAddress | Select Name, IpAddressGet-AzApplicationGatewayBackendHealth -ResourceGroupName <rg> -Name <agw>

#Application Gateway

PowerShell
$AppGW = Get-AzApplicationGateway -Name <agw> -ResourceGroupName <rg>$AppGW.SslCertificates | Select Name, KeyVaultSecretId$AppGW.HttpListeners   | Select Name, Protocol, HostNames$secret   = Get-AzKeyVaultSecret -VaultName <kv> -Name <name>$secretId = $secret.Id.Replace($secret.Version, "")          # versionless secret ID$AppGW = Add-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <name> -KeyVaultSecretId $secretId$AppGW = Set-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <name> -KeyVaultSecretId <new-id>$AppGW = Remove-AzApplicationGatewaySslCertificate -ApplicationGateway $AppGW -Name <name>Set-AzApplicationGateway -ApplicationGateway $AppGW         # changes only take effect hereStop-AzApplicationGateway -ApplicationGateway $AppGW ; Start-AzApplicationGateway -ApplicationGateway $AppGW

Complete with identity and listener: Workflows → App Gateway.

#Permissions and Key Vault

PowerShell
Get-AzRoleAssignment -SignInName <upn>Get-AzRoleAssignment -Scope <resource-id>New-AzRoleAssignment -ObjectId <id> -RoleDefinitionName "<role>" -Scope <resource-id>Get-AzKeyVaultSecret -VaultName <kv> -Name <name> -AsPlainText
Azure

#Azure CLI ↔ Azure PowerShell

The same task in both worlds – handy when a team script is written in the other language.

#Basics

TaskAzure CLIAzure PowerShell
Sign inaz loginConnect-AzAccount
Device codeaz login --use-device-codeConnect-AzAccount -UseDeviceAuthentication
Managed identityaz login --identityConnect-AzAccount -Identity
Current contextaz account showGet-AzContext
Subscriptionsaz account list -o tableGet-AzSubscription
Switch subscriptionaz account set --subscription <x>Set-AzContext -Subscription <x>
Sign outaz logoutDisconnect-AzAccount
Get a tokenaz account get-access-tokenGet-AzAccessToken
Versionaz versionGet-InstalledModule Az

#Resources and VMs

TaskAzure CLIAzure PowerShell
Resource groupsaz group list -o tableGet-AzResourceGroup
Create an RGaz group create -n <rg> -l <region>New-AzResourceGroup -Name <rg> -Location <region>
Resources in an RGaz resource list -g <rg>Get-AzResource -ResourceGroupName <rg>
Resource by IDaz resource show --ids <id>Get-AzResource -ResourceId <id>
Set tags (merge)az resource tag --ids <id> --tags k=v -iUpdate-AzTag -ResourceId <id> -Tag @{k='v'} -Operation Merge
VMs with statusaz vm list -d -o tableGet-AzVM -Status
Start a VMaz vm start -g <rg> -n <vm>Start-AzVM -ResourceGroupName <rg> -Name <vm>
Deallocate a VMaz vm deallocate -g <rg> -n <vm>Stop-AzVM -ResourceGroupName <rg> -Name <vm> -Force
Command in a VMaz vm run-command invoke … --scripts "<cmd>"Invoke-AzVMRunCommand … -ScriptString "<cmd>"
Resource Graphaz graph query -q "<kql>"Search-AzGraph -Query "<kql>"

#Network

TaskAzure CLIAzure PowerShell
VNetsaz network vnet listGet-AzVirtualNetwork
Subnetaz network vnet subnet show -g <rg> --vnet-name <vnet> -n <snet>Get-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet -Name <snet>
Peeringsaz network vnet peering list -g <rg> --vnet-name <vnet>Get-AzVirtualNetworkPeering -ResourceGroupName <rg> -VirtualNetworkName <vnet>
Effective routesaz network nic show-effective-route-table -g <rg> -n <nic>Get-AzEffectiveRouteTable -ResourceGroupName <rg> -NetworkInterfaceName <nic>
Effective NSGaz network nic list-effective-nsg -g <rg> -n <nic>Get-AzEffectiveNetworkSecurityGroup -ResourceGroupName <rg> -NetworkInterfaceName <nic>
NSG rulesaz network nsg rule list -g <rg> --nsg-name <nsg>Get-AzNetworkSecurityGroup -Name <nsg> -ResourceGroupName <rg> | Get-AzNetworkSecurityRuleConfig
Public IPsaz network public-ip listGet-AzPublicIpAddress
App Gatewayaz network application-gateway show -g <rg> -n <agw>Get-AzApplicationGateway -ResourceGroupName <rg> -Name <agw>
Backend healthaz network application-gateway show-backend-health …Get-AzApplicationGatewayBackendHealth …

Key difference: PowerShell often works with objects: get → modify → write back.

PowerShell
$vnet = Get-AzVirtualNetwork -ResourceGroupName <rg> -Name <vnet>Add-AzVirtualNetworkSubnetConfig -VirtualNetwork $vnet -Name <snet> -AddressPrefix 10.0.5.0/24 | Out-Null$vnet | Set-AzVirtualNetwork                         # only now is it changed in Azure

The CLI changes directly: az network vnet subnet create -g <rg> --vnet-name <vnet> -n <snet> --address-prefixes 10.0.5.0/24.

#Permissions, Key Vault, Deployments

TaskAzure CLIAzure PowerShell
A user's rolesaz role assignment list --assignee <upn> --allGet-AzRoleAssignment -SignInName <upn>
Assign a roleaz role assignment create --assignee <id> --role "<role>" --scope <scope>New-AzRoleAssignment -ObjectId <id> -RoleDefinitionName "<role>" -Scope <scope>
Own object IDaz ad signed-in-user show --query id -o tsv(Get-AzADUser -SignedIn).Id
Find a service principalaz ad sp list --display-name <name>Get-AzADServicePrincipal -DisplayName <name>
Read a secretaz keyvault secret show --vault-name <kv> -n <name> --query value -o tsvGet-AzKeyVaultSecret -VaultName <kv> -Name <name> -AsPlainText
Set a secretaz keyvault secret set --vault-name <kv> -n <name> --value <v>Set-AzKeyVaultSecret -VaultName <kv> -Name <name> -SecretValue (ConvertTo-SecureString <v> -AsPlainText -Force)
Deploy Bicepaz deployment group create -g <rg> -f main.bicep -p <p>.bicepparamNew-AzResourceGroupDeployment -ResourceGroupName <rg> -TemplateFile main.bicep -TemplateParameterFile <p>.bicepparam
what-ifaz deployment group what-if …New-AzResourceGroupDeployment … -WhatIf
Policy assignmentsaz policy assignment list --scope <scope>Get-AzPolicyAssignment -Scope <scope>

#Output and Filtering

TaskAzure CLIAzure PowerShell
Table-o table| Format-Table
Single value--query id -o tsv(Get-AzVM …).Id
Filter--query "[?location=='westeurope']"| Where-Object Location -eq 'westeurope'
Choose columns--query "[].{n:name, rg:resourceGroup}"| Select-Object Name, ResourceGroupName
Count--query "length(@)"(… ).Count
As JSON-o json| ConvertTo-Json -Depth 5
CLI output as objectaz … -o json | ConvertFrom-Json–
Azure

#Resource Graph – Queries Across All Subscriptions

Basics: 01 → Resource Graph

Resource Graph searches all subscriptions you have read access to – in seconds. The query language is KQL.

#How to Run

PowerShell
az extension add -n resource-graphaz graph query -q "<query>" --first 1000 -o tableaz graph query -q "<query>" --management-groups <mg-id> -o table        # only below one management groupaz graph query -q "<query>" --subscriptions <sub-id> -o table # Long queries: into a variable (PowerShell here-string)$q = @"Resources| where type =~ 'microsoft.compute/virtualmachines'| project name, resourceGroup"@az graph query -q $q -o table # Result as CSVaz graph query -q $q --first 1000 --query data -o json | ConvertFrom-Json | Export-Csv .\result.csv -NoTypeInformation -Encoding UTF8 # PowerShell moduleSearch-AzGraph -Query $q -First 1000 | Format-Table

In the portal: Resource Graph Explorer – paste the query, run it, save as chart/dashboard. Always use single quotes inside the queries so they fit into PowerShell's "…".

#Inventory

KQL
// Count per resource typeResources | summarize Count = count() by type | order by Count desc // All VMs with size and stateResources| where type =~ 'microsoft.compute/virtualmachines'| project name, resourceGroup, subscriptionId, location,          size = tostring(properties.hardwareProfile.vmSize),          state = tostring(properties.extended.instanceView.powerState.code),          zone = tostring(zones[0]) // Resources per subscription with the subscription nameResources| join kind=inner (ResourceContainers | where type =~ 'microsoft.resources/subscriptions' | project subscriptionId, subName = name) on subscriptionId| summarize Count = count() by subName // Application gateways with SKU and stateResources| where type =~ 'microsoft.network/applicationgateways'| project name, resourceGroup, sku = tostring(properties.sku.name), state = tostring(properties.operationalState) // Key vaults with network accessResources| where type =~ 'microsoft.keyvault/vaults'| project name, resourceGroup, rbac = properties.enableRbacAuthorization, publicAccess = tostring(properties.publicNetworkAccess)

#Network

KQL
// VNets with address space and number of peeringsResources| where type =~ 'microsoft.network/virtualnetworks'| project name, resourceGroup, space = properties.addressSpace.addressPrefixes,          peerings = array_length(properties.virtualNetworkPeerings) // All subnets with NSG and route tableResources| where type =~ 'microsoft.network/virtualnetworks'| mv-expand s = properties.subnets| project vnet = name, subnet = tostring(s.name), prefix = tostring(s.properties.addressPrefix),          nsg = tostring(split(s.properties.networkSecurityGroup.id, '/')[8]),          routeTable = tostring(split(s.properties.routeTable.id, '/')[8]) // Subnets WITHOUT an NSGResources| where type =~ 'microsoft.network/virtualnetworks'| mv-expand s = properties.subnets| where isnull(s.properties.networkSecurityGroup)  and tostring(s.name) !in ('GatewaySubnet', 'AzureFirewallSubnet', 'AzureBastionSubnet', 'RouteServerSubnet')| project vnet = name, subnet = tostring(s.name), resourceGroup // Peerings that are not connectedResources| where type =~ 'microsoft.network/virtualnetworks'| mv-expand p = properties.virtualNetworkPeerings| where tostring(p.properties.peeringState) != 'Connected'| project vnet = name, peering = tostring(p.name), state = tostring(p.properties.peeringState) // All public IPs and what they are attached toResources| where type =~ 'microsoft.network/publicipaddresses'| project name, ip = tostring(properties.ipAddress), sku = tostring(sku.name),          attachedTo = tostring(split(properties.ipConfiguration.id, '/')[8]), resourceGroup // Private endpoints and their targetsResources| where type =~ 'microsoft.network/privateendpoints'| mv-expand c = properties.privateLinkServiceConnections| project name, resourceGroup,          target = tostring(split(c.properties.privateLinkServiceId, '/')[8]),          groupId = tostring(c.properties.groupIds[0]),          status = tostring(c.properties.privateLinkServiceConnectionState.status) // All routes with next hopResources| where type =~ 'microsoft.network/routetables'| mv-expand r = properties.routes| project routeTable = name, route = tostring(r.name), prefix = tostring(r.properties.addressPrefix),          nextHop = tostring(r.properties.nextHopType), ip = tostring(r.properties.nextHopIpAddress) // Who owns this private IP?Resources| where type =~ 'microsoft.network/networkinterfaces'| mv-expand ipc = properties.ipConfigurations| project nic = name, ip = tostring(ipc.properties.privateIPAddress),          vm = tostring(split(properties.virtualMachine.id, '/')[8]), resourceGroup| where ip == '<ip>'

#Security

KQL
// NSG rules that allow SSH/RDP/everything from the internetResources| where type =~ 'microsoft.network/networksecuritygroups'| mv-expand r = properties.securityRules| where r.properties.direction == 'Inbound' and r.properties.access == 'Allow'| where tostring(r.properties.sourceAddressPrefix) in ('*', 'Internet', '0.0.0.0/0')| where tostring(r.properties.destinationPortRange) in ('22', '3389', '*')| project nsg = name, rule = tostring(r.name), port = tostring(r.properties.destinationPortRange), resourceGroup // Storage accounts with public accessResources| where type =~ 'microsoft.storage/storageaccounts'| project name, resourceGroup, publicNetwork = tostring(properties.publicNetworkAccess),          blobPublic = properties.allowBlobPublicAccess, minTls = tostring(properties.minimumTlsVersion) // Open Defender recommendations per topicSecurityResources| where type == 'microsoft.security/assessments'| where properties.status.code == 'Unhealthy'| summarize Count = count() by recommendation = tostring(properties.displayName)| order by Count desc

#Cleanup and Costs

KQL
// Disks without a VMResources| where type =~ 'microsoft.compute/disks' and properties.diskState =~ 'Unattached'| project name, resourceGroup, sizeGB = properties.diskSizeGB, sku = tostring(sku.name) // Unassigned public IPsResources| where type =~ 'microsoft.network/publicipaddresses'| where isnull(properties.ipConfiguration) and isnull(properties.natGateway)| project name, resourceGroup, ip = tostring(properties.ipAddress) // NICs without VM and without private endpointResources| where type =~ 'microsoft.network/networkinterfaces'| where isnull(properties.virtualMachine) and isnull(properties.privateEndpoint)| project name, resourceGroup // Stopped but not deallocated VMs (still cost money)Resources| where type =~ 'microsoft.compute/virtualmachines'| where properties.extended.instanceView.powerState.code == 'PowerState/stopped'| project name, resourceGroup // Empty resource groupsResourceContainers| where type =~ 'microsoft.resources/subscriptions/resourcegroups'| join kind=leftouter (Resources | summarize n = count() by resourceGroup, subscriptionId) on resourceGroup, subscriptionId| where isnull(n)| project resourceGroup, subscriptionId

#Tags

KQL
// Resources without tag "Environment"Resources| where isnull(tags['Environment']) or tostring(tags['Environment']) == ''| project name, type, resourceGroup // Resource groups without ownerResourceContainers| where type =~ 'microsoft.resources/subscriptions/resourcegroups'| where isnull(tags['Owner'])| project name, subscriptionId // Values used for a tagResources| summarize Count = count() by env = tostring(tags['Environment'])

Tag names are case-sensitive in queries – mind your spelling.

#Changes, Permissions, Policy

KQL
// What changed in the last 24 h?ResourceChanges| extend time = todatetime(properties.changeAttributes.timestamp),         kind = tostring(properties.changeType),         id = tostring(properties.targetResourceId)| where time > ago(1d)| project time, kind, id| order by time desc // Role assignments at subscription levelAuthorizationResources| where type =~ 'microsoft.authorization/roleassignments'| extend scope = tostring(properties.scope), principal = tostring(properties.principalId),         type = tostring(properties.principalType), role = tostring(split(properties.roleDefinitionId, '/')[-1])| where scope startswith '/subscriptions/' and scope !contains 'resourceGroups'| project scope, principal, type, role // Non-compliant resources per policyPolicyResources| where type =~ 'microsoft.policyinsights/policystates'| where properties.complianceState =~ 'NonCompliant'| summarize Count = count() by policy = tostring(properties.policyDefinitionName)| order by Count desc // Subscriptions with their management groupResourceContainers| where type =~ 'microsoft.resources/subscriptions'| project name, subscriptionId, mg = tostring(properties.managementGroupAncestorsChain[0].displayName)

role is the role ID – its name: az role definition list --name <id> --query "[0].roleName" -o tsv.

Azure

#Entra ID – Apps, Secrets, Groups, PIM

Roles in Azure: 03 Security

App registration = the definition of the application (app ID, secrets). Service principal = its "user account" in the tenant that roles are given to. For role assignments always use the object ID of the service principal, not the app's.

#App Registration and Service Principal

PowerShell
az ad app list --display-name <name> --query "[].{name:displayName, appId:appId, objectId:id}" -o tableaz ad sp  list --display-name <name> --query "[].{name:displayName, appId:appId, spObjectId:id}" -o tableaz ad sp show --id <app-id> --query id -o tsv                # SP object ID from the app IDaz ad app owner list --id <app-id> --query "[].userPrincipalName" -o tsvaz ad app owner add  --id <app-id> --owner-object-id <user-object-id> # New app with SP and role (secret is shown ONCE – store it in Key Vault right away)az ad sp create-for-rbac -n <name> --role Contributor --scopes /subscriptions/<sub-id>

#Find Expiring Secrets and Certificates

PowerShell
# One appaz ad app credential list --id <app-id> --query "[].{name:displayName, end:endDateTime, keyId:keyId}" -o tableaz ad app credential list --id <app-id> --cert --query "[].{name:displayName, end:endDateTime}" -o table # All apps whose secret expires within the next 30 days$limit = (Get-Date).AddDays(30)az ad app list --all --query "[].{name:displayName, appId:appId, secrets:passwordCredentials[].endDateTime}" -o json |  ConvertFrom-Json | ForEach-Object {    $app = $_    $app.secrets | Where-Object { $_ -and [datetime]$_ -lt $limit } |      ForEach-Object { [pscustomobject]@{ App = $app.name; AppId = $app.appId; Expires = ([datetime]$_).ToString('yyyy-MM-dd') } }  } | Sort-Object Expires | Format-Table

With --all this can take a while in large tenants; without --all only your own or the first 100.

#Renew a Secret

PowerShell
# Create the new secret IN ADDITION (--append), the old one stays valid for nowaz ad app credential reset --id <app-id> --append --display-name "secret-$(Get-Date -Format yyyyMM)" --years 1 --query password -o tsv# -> enter it right away in Key Vault / the service connection / pipeline variableaz keyvault secret set --vault-name <kv> -n <secret-name> --value "<new-secret>" # Once everything is switched over: delete the old secretaz ad app credential list   --id <app-id> -o tableaz ad app credential delete --id <app-id> --key-id <old-key-id>

Where is the secret used? Typically: service connections in DevOps, pipeline variables, Key Vault, app settings.

#Groups

PowerShell
az ad group list --display-name <part> --query "[].{name:displayName, id:id}" -o tableaz ad group create --display-name <name> --mail-nickname <name>az ad group member list  -g <group> --query "[].{name:displayName, upn:userPrincipalName}" -o tableaz ad group member add   -g <group> --member-id <object-id>az ad group member remove -g <group> --member-id <object-id>az ad group member check -g <group> --member-id <object-id> --query valueaz ad user get-member-groups --id <upn> --query "[].displayName" -o tsv     # a user's groups

#Create Security Groups in Bulk

One group per role, existing ones are skipped – safe to run again. With a form (incl. role assignments and Azure CLI variant): web page → Generator → Entra ID: create security groups (bulk).

PowerShell
# Azure Cloud Shell (PowerShell) or locally after Connect-AzAccount$groups = @(    @{ Name = "az-<customer>-owner";                    Desc = "<Customer> - Owner" }    @{ Name = "az-<customer>-contributor";              Desc = "<Customer> - Contributor" }    @{ Name = "az-<customer>-reader";                   Desc = "<Customer> - Reader" }    @{ Name = "az-<customer>-costmanagementreader";     Desc = "<Customer> - Cost Management Reader" }    @{ Name = "az-<customer>-landingzones-owner";       Desc = "<Customer> - Landing Zones Owner" }    @{ Name = "az-<customer>-landingzones-contributor"; Desc = "<Customer> - Landing Zones Contributor" }    @{ Name = "az-<customer>-landingzones-reader";      Desc = "<Customer> - Landing Zones Reader" }) foreach ($g in $groups) {    $existing = Get-AzADGroup -DisplayName $g.Name -ErrorAction SilentlyContinue    if ($existing) {        Write-Host "Already exists: $($g.Name) (Id: $($existing.Id))" -ForegroundColor Yellow        continue    }    try {        $new = New-AzADGroup -DisplayName $g.Name -MailNickname $g.Name -Description $g.Desc -SecurityEnabled -ErrorAction Stop        Write-Host "Created: $($g.Name) (Id: $($new.Id))" -ForegroundColor Green    }    catch {        Write-Host "Error for $($g.Name): $($_.Exception.Message)" -ForegroundColor Red    }} # OverviewGet-AzADGroup -Filter "startswith(displayName,'az-<customer>')" |    Select-Object DisplayName, Id, SecurityEnabled | Format-Table -AutoSize

Then give each group its role, e.g. New-AzRoleAssignment -ObjectId <group-id> -RoleDefinitionName "Reader" -Scope /subscriptions/<sub-id> – or use the generator option Also assign Azure roles. Needs the Entra permission to create groups (e.g. Groups Administrator, or "Users can create security groups" enabled).

#Users

PowerShell
az ad user show --id <upn> --query "{name:displayName, id:id, enabled:accountEnabled}"az ad user list --filter "startswith(displayName,'<part>')" --query "[].{name:displayName, upn:userPrincipalName}" -o tableaz ad user list --filter "userType eq 'Guest'" --query "[].{name:displayName, mail:mail}" -o table     # guests

#PIM: Activate a Role

Privileged Identity Management: the role is only "eligible" and must be activated before working.

In the portal: Microsoft Entra Privileged Identity Management → My roles → Azure resources or Microsoft Entra roles → Activate → duration + justification.

PowerShell (Azure resource roles):

PowerShell
$scope = "/subscriptions/<sub-id>"$me    = (Get-AzADUser -SignedIn).IdGet-AzRoleEligibilitySchedule -Scope $scope -Filter "asTarget()" |  Select-Object RoleDefinitionDisplayName, Scope, RoleDefinitionId          # what may I activate? $roleId = "<RoleDefinitionId from the list>"New-AzRoleAssignmentScheduleRequest -Name (New-Guid).Guid -Scope $scope `  -PrincipalId $me -RoleDefinitionId $roleId -RequestType SelfActivate `  -ScheduleInfoStartDateTime (Get-Date).ToUniversalTime().ToString("o") `  -ExpirationType AfterDuration -ExpirationDuration PT8H `  -Justification "<reason / ticket>"

After activation sign in again if needed (az account clear ; az login) so the role is in the token.

Azure

#Management Groups and Landing Zone

Policies: 03 → Azure Policy

#Landing Zone Structure

Typical hierarchy according to the Cloud Adoption Framework (your names may differ):

text
Tenant Root Group  (/)└── <Company>    ├── Platform    │   ├── Identity        – domain controllers, Entra helper services    │   ├── Management      – Log Analytics, Automation, monitoring    │   └── Connectivity    – hub VNet, firewall/FortiGate, VPN, DNS    ├── Landing Zones    │   ├── Corp            – applications connected to the corporate network    │   └── Online          – applications directly from the internet    ├── Sandbox             – playground, no connectivity    └── Decommissioned      – retired subscriptions
RuleWhy
Assign policies and permissions on management groups where possibleapply automatically to every new subscription below
One subscription per application/environmentclean boundaries for costs, permissions, limits
Network (hub) lives in Connectivity, spokes in the landing zonesseparation platform ↔ application
Never put anything directly under the Tenant Root Grouproot policies would hit everything

#Show the Hierarchy

PowerShell
az account management-group list -o tableaz account management-group show -n <mg-id> --expand --recurse `  --query "{name:displayName, children:children[].{name:displayName, type:type}}" -o jsonaz account management-group entities list --query "[].{name:displayName, type:type, parent:parent.id}" -o table   # everything flat

Resource Graph: which subscription sits where?

PowerShell
az graph query -q "ResourceContainers | where type =~ 'microsoft.resources/subscriptions' | project name, subscriptionId, mgChain = properties.managementGroupAncestorsChain" -o json

#Move a Subscription

PowerShell
az account management-group subscription show-sub-under-mg -n <mg-id> -o table      # subscriptions under an MGaz account management-group subscription add --name <target-mg-id> --subscription <sub-id>

Caution: moving changes inherited policies and roles immediately. Check first which policies apply at the target (deny policies can break existing deployments). You need permissions on the old and the new MG.

#What Applies at Which Level?

PowerShell
$mg = "/providers/Microsoft.Management/managementGroups/<mg-id>"az policy assignment list --scope $mg --query "[].{name:displayName, effect:parameters.effect.value, def:policyDefinitionId}" -o tableaz role assignment list --scope $mg --query "[].{who:principalName, role:roleDefinitionName}" -o table # Everything that affects a subscription (incl. inheritance from above)az policy assignment list --disable-scope-strict-match --scope /subscriptions/<sub-id> -o tableaz role assignment list --scope /subscriptions/<sub-id> --include-inherited -o table

#Deployments at Management Group Level

PowerShell
az deployment mg what-if -m <mg-id> -l <region> -f main.bicep -p params/mg.bicepparamaz deployment mg create  -m <mg-id> -l <region> -f main.bicep -p params/mg.bicepparamaz deployment mg list -m <mg-id> -o tableaz deployment tenant create -l <region> -f tenant.bicep           # at the very top (needs permissions on "/")

In Bicep: targetScope = 'managementGroup' at the start of the file.

Azure

#Defender for Cloud and Security

Queries: 19 Resource Graph → Security

#Secure Score and Recommendations

PowerShell
az security secure-scores list --query "[].{name:displayName, current:score.current, max:score.max, percent:score.percentage}" -o tableaz security secure-score-controls list --query "[].{name:displayName, current:score.current, max:score.max, open:unhealthyResourceCount}" -o tableaz security assessment list --query "[?status.code=='Unhealthy'].{recommendation:displayName, resource:resourceDetails.Id}" -o table

In the portal: Microsoft Defender for Cloud → Recommendations, sort by Risk level, per recommendation Fix or Exempt.

#Defender Plans

PowerShell
az security pricing list --query "value[].{plan:name, tier:pricingTier}" -o tableaz security pricing show -n VirtualMachinesaz security pricing create -n KeyVaults --tier Standard          # enable a plan (costs money)

Free = recommendations only, Standard = Defender plan with protection and alerts.

#Alerts

PowerShell
az security alert list --query "[].{time:timeGeneratedUtc, severity:severity, title:alertDisplayName, resource:compromisedEntity, status:status}" -o tableaz security alert update --location <region> -n <alert-name> -g <rg> --status Dismiss       # close after review

In Log Analytics: table SecurityAlert.

#Just-in-Time Access to VMs

JIT opens RDP/SSH only on request, for a limited time and only for your IP.

Portal: VM → Connect → Request access, or Defender for Cloud → Workload protections → Just-in-time VM access.

PowerShell:

PowerShell
$vmId = (Get-AzVM -ResourceGroupName <rg> -Name <vm>).Id$myIp = Invoke-RestMethod https://api.ipify.org$req = @{ id = $vmId; ports = @(@{ number = 22; endTimeUtc = (Get-Date).ToUniversalTime().AddHours(3).ToString("o"); allowedSourceAddressPrefix = @($myIp) }) }Start-AzJitNetworkAccessPolicy -ResourceGroupName <rg> -Location <region> -Name default -VirtualMachine @($req)

#Quick Check of a Subscription

PowerShell
$sub = "<sub-id>"az account set --subscription $subaz security secure-scores list --query "[0].score.percentage" -o tsv                   # secure score in %az security pricing list --query "value[?pricingTier=='Free'].name" -o tsv              # plans that are offaz graph query --subscriptions $sub -q "Resources | where type =~ 'microsoft.network/networksecuritygroups' | mv-expand r = properties.securityRules | where r.properties.access == 'Allow' and r.properties.direction == 'Inbound' and tostring(r.properties.sourceAddressPrefix) in ('*','Internet') | project name, rule = tostring(r.name), port = tostring(r.properties.destinationPortRange)" -o tableaz graph query --subscriptions $sub -q "Resources | where type =~ 'microsoft.storage/storageaccounts' and properties.allowBlobPublicAccess == true | project name" -o tableaz keyvault list -o json | ConvertFrom-Json | Where-Object { -not $_.properties.enablePurgeProtection } | ForEach-Object name   # vaults without purge protection
Azure

#Storage in Daily Work

Basic commands: 01 → Storage Account

--auth-mode login needs a data role (e.g. Storage Blob Data Contributor). Owner/Contributor alone is not enough.

#AzCopy: Upload and Download

Install: winget install Microsoft.Azure.AZCopy.10

PowerShell
azcopy login --tenant-id <tenant-id>                                   # sign in with Entra IDazcopy copy ".\folder" "https://<sa>.blob.core.windows.net/<container>/" --recursiveazcopy copy "https://<sa>.blob.core.windows.net/<container>/path/*" ".\target" --recursiveazcopy sync ".\folder" "https://<sa>.blob.core.windows.net/<container>/folder" --delete-destination=false   # changes onlyazcopy copy "https://<sa-a>.blob.core.windows.net/<c>/?<sas>" "https://<sa-b>.blob.core.windows.net/<c>/?<sas>" --recursive   # account to accountazcopy list "https://<sa>.blob.core.windows.net/<container>"azcopy jobs list ; azcopy jobs resume <job-id>                         # resume an aborted transfer

#Create a SAS Token

PowerShell
# User delegation SAS (with Entra sign-in, without account key – preferred)$expiry = (Get-Date).ToUniversalTime().AddHours(8).ToString("yyyy-MM-ddTHH:mmZ")$sas = az storage container generate-sas --account-name <sa> -n <container> `  --permissions rl --expiry $expiry --auth-mode login --as-user -o tsv"https://<sa>.blob.core.windows.net/<container>?$sas" # For a single blobaz storage blob generate-sas --account-name <sa> -c <container> -n <file> `  --permissions r --expiry $expiry --auth-mode login --as-user --full-uri -o tsv

Permissions: r read · l list · w write · c create · d delete. Always as short and as little as possible.

#Restore Deleted and Old Versions

PowerShell
# Show and restore deleted blobs (soft delete must be on)az storage blob list --account-name <sa> -c <container> --include d --auth-mode login `  --query "[?deleted].{name:name, deleted:properties.deletedTime}" -o tableaz storage blob undelete --account-name <sa> -c <container> -n <file> --auth-mode login # Versions of a blob (versioning must be on)az storage blob list --account-name <sa> -c <container> --prefix <file> --include v --auth-mode login `  --query "[].{name:name, version:versionId, current:isCurrentVersion}" -o table# Make an old version the current one againaz storage blob copy start --account-name <sa> --destination-container <container> --destination-blob <file> `  --source-uri "https://<sa>.blob.core.windows.net/<container>/<file>?versionid=<version-id>" --auth-mode login # Restore a deleted containeraz storage container list --account-name <sa> --include-deleted --auth-mode login --query "[?deleted]" -o tableaz storage container restore --account-name <sa> -n <container> --deleted-version <version> --auth-mode login

#Turn On Protection

PowerShell
az storage account blob-service-properties update -g <rg> --account-name <sa> `  --enable-delete-retention true --delete-retention-days 14 `  --enable-container-delete-retention true --container-delete-retention-days 14 `  --enable-versioning trueaz storage account update -g <rg> -n <sa> --allow-blob-public-access false --min-tls-version TLS1_2az storage account update -g <rg> -n <sa> --allow-shared-key-access false     # Entra sign-in only (check first who uses keys!)az lock create -n lock-<sa> -g <rg> --resource-type Microsoft.Storage/storageAccounts --resource <sa> --lock-type CanNotDelete

#Azure Files

PowerShell
az storage share-rm list -g <rg> --storage-account <sa> -o tableaz storage share-rm create -g <rg> --storage-account <sa> -n <share> --quota 100# Windows: test the connection and map a drive (port 445 must be open)Test-NetConnection <sa>.file.core.windows.net -Port 445net use Z: \\<sa>.file.core.windows.net\<share> /user:localhost\<sa> <storage-key>

Many ISPs block port 445 to the internet – then use VPN/private endpoint.

#Lifecycle (Move/Delete Old Data Automatically)

policy.json:

json
{  "rules": [{    "name": "cleanup-logs",    "enabled": true,    "type": "Lifecycle",    "definition": {      "filters": { "blobTypes": [ "blockBlob" ], "prefixMatch": [ "logs/" ] },      "actions": {        "baseBlob": {          "tierToCool": { "daysAfterModificationGreaterThan": 30 },          "delete":     { "daysAfterModificationGreaterThan": 365 }        }      }    }  }]}
PowerShell
az storage account management-policy create -g <rg> --account-name <sa> --policy "@policy.json"az storage account management-policy show   -g <rg> --account-name <sa>
Azure

#Monitoring and Alerts

Queries: 10 KQL · Turn on diagnostic logs: 00 → Diagnostic logs

#Action Group (Who Gets Notified)

PowerShell
$rg = "<rg-monitoring>"az monitor action-group create -g $rg -n ag-net-team --short-name netteam `  --action email team <team@company.tld>az monitor action-group update -g $rg -n ag-net-team --add-action email oncall <oncall@company.tld>az monitor action-group update -g $rg -n ag-net-team --add-action webhook teams "<teams-workflow-url>"az monitor action-group show -g $rg -n ag-net-team --query id -o tsv

Teams: in the channel Workflows → "Post to a channel when a webhook request is received" → enter the URL as webhook (or use the channel's e-mail address). --short-name max. 12 characters (appears in SMS/mail).

#Metric Alerts

PowerShell
$ag = az monitor action-group show -g <rg-monitoring> -n ag-net-team --query id -o tsv # VM not available$vmId = az vm show -g <rg> -n <vm> --query id -o tsvaz monitor metrics alert create -g <rg-monitoring> -n "vm-<vm>-down" --scopes $vmId `  --condition "avg VmAvailabilityMetric < 1" --window-size 5m --evaluation-frequency 1m `  --severity 1 --action $ag --description "VM <vm> not available" # VM CPU constantly highaz monitor metrics alert create -g <rg-monitoring> -n "vm-<vm>-cpu" --scopes $vmId `  --condition "avg Percentage CPU > 90" --window-size 15m --evaluation-frequency 5m --severity 2 --action $ag # App Gateway: backend unhealthy$agwId = az network application-gateway show -g <rg> -n <agw> --query id -o tsvaz monitor metrics alert create -g <rg-monitoring> -n "agw-<agw>-unhealthy" --scopes $agwId `  --condition "max UnhealthyHostCount > 0" --window-size 5m --evaluation-frequency 1m --severity 1 --action $ag # Load balancer: health probe fails (e.g. FortiGate ILB)$lbId = az network lb show -g <rg> -n <lb> --query id -o tsvaz monitor metrics alert create -g <rg-monitoring> -n "lb-<lb>-probe" --scopes $lbId `  --condition "avg DipAvailability < 100" --window-size 5m --evaluation-frequency 1m --severity 2 --action $ag # VPN tunnel down$conId = az network vpn-connection show -g <rg> -n <conn> --query id -o tsvaz monitor metrics alert create -g <rg-monitoring> -n "vpn-<conn>-down" --scopes $conId `  --condition "max TunnelIngressBytes < 1" --window-size 15m --evaluation-frequency 5m --severity 2 --action $ag

Which metrics exist? az monitor metrics list-definitions --resource <id> --query "[].name.value" -o tsv

#Activity Log Alerts

React to changes in Azure – e.g. when the FortiGate SDN connector rewrites routes during a failover.

PowerShell
$ag = az monitor action-group show -g <rg-monitoring> -n ag-net-team --query id -o tsv$sub = "/subscriptions/<sub-id>" # Route changed (FortiGate failover via SDN connector, or someone touched a UDR)az monitor activity-log alert create -g <rg-monitoring> -n "route-changed" --scope $sub `  --condition category=Administrative and operationName=Microsoft.Network/routeTables/routes/write `  --action-group $ag # NSG rule changedaz monitor activity-log alert create -g <rg-monitoring> -n "nsg-rule-changed" --scope $sub `  --condition category=Administrative and operationName=Microsoft.Network/networkSecurityGroups/securityRules/write `  --action-group $ag # Service Health (Microsoft outages)az monitor activity-log alert create -g <rg-monitoring> -n "service-health" --scope $sub `  --condition category=ServiceHealth --action-group $ag

#Log Alerts (KQL)

Easiest in the portal: Monitor → Alerts → Create → Alert rule → scope = Log Analytics workspace → Custom log search → paste KQL.

KQL
// FortiGate reports an HA change (Syslog/CEF)CommonSecurityLog| where DeviceVendor == "Fortinet"| where Activity has "HA" and (Activity has "state" or Message has "failover") // WAF suddenly blocks a lot (> 100 in 5 min)AzureDiagnostics| where Category == "ApplicationGatewayFirewallLog" and action_s == "Blocked"| summarize Count = count() by bin(TimeGenerated, 5m)| where Count > 100 // VM no longer sends a heartbeatHeartbeat| summarize last = max(TimeGenerated) by Computer| where last < ago(10m)

Condition: Number of results > 0, evaluated every 5 min, period 10–15 min. Check the field names of the FortiGate logs first with CommonSecurityLog | take 20.

#Resource Health

PowerShell
az resource show --ids <resource-id>/providers/Microsoft.ResourceHealth/availabilityStatuses/current `  --api-version 2022-10-01 --query "properties.{status:availabilityState, reason:summary}"az monitor activity-log alert create -g <rg-monitoring> -n "resource-health" --scope /subscriptions/<sub-id> `  --condition category=ResourceHealth --action-group $ag

#Manage Alerts

PowerShell
az monitor metrics alert list -g <rg-monitoring> --query "[].{name:name, enabled:enabled, severity:severity}" -o tableaz monitor activity-log alert list -g <rg-monitoring> -o tableaz monitor metrics alert update -g <rg-monitoring> -n <alert> --enabled false        # temporarily off (maintenance)az monitor alert-processing-rule create -g <rg-monitoring> -n maintenance-<date> `  --scopes /subscriptions/<sub-id>/resourceGroups/<rg> --rule-type RemoveAllActionGroups `  --schedule-start-datetime "2026-11-01 22:00:00" --schedule-end-datetime "2026-11-02 02:00:00" --schedule-time-zone "W. Europe Standard Time"

The alert processing rule suppresses notifications during a maintenance window without deleting alerts.

SeverityMeaning
0critical
1error
2warning
3informational
4verbose
Azure

#Costs and Cleanup

Find orphaned resources: 19 Resource Graph → Cleanup

#What Costs What?

Fastest in the portal: Cost Management → Cost analysis → view Resources or Resource groups, choose the period at the top, Group by → tag (e.g. CostCenter).

PowerShell
az extension add -n costmanagement# Costs of the current month per resource groupaz costmanagement query --type ActualCost --timeframe MonthToDate --scope "/subscriptions/<sub-id>" `  --dataset-aggregation '{\"totalCost\":{\"name\":\"Cost\",\"function\":\"Sum\"}}' `  --dataset-grouping name="ResourceGroupName" type="Dimension" -o json

JSON escaping in PowerShell is tedious – for regular reports better save a view in the portal or set up an export (daily CSV into a storage account): Cost Management → Exports.

#Budget with Alert

Portal: Cost Management → Budgets → Add → amount, period Monthly → alerts e.g. at 80 % Actual and 100 % Forecasted → e-mail or action group.

PowerShell
az consumption budget create --budget-name budget-<rg> --resource-group <rg> `  --amount 500 --category cost --time-grain monthly `  --start-date 2026-11-01 --end-date 2027-10-31az consumption budget list -o table

Notifications (thresholds, recipients) are easiest to add afterwards on the budget in the portal.

#Azure Advisor Recommendations

PowerShell
az advisor recommendation list --category Cost `  --query "[].{recommendation:shortDescription.problem, resource:impactedValue, impact:impact}" -o tableaz advisor recommendation list --category Cost --query "[?contains(shortDescription.problem,'Right-size')]" -o table

Typical: downsize oversized VMs, buy reservations/savings plans, delete unused resources.

#Clean Up Orphaned Resources

PowerShell
az extension add -n resource-graph# 1. Findaz graph query -q "Resources | where type =~ 'microsoft.compute/disks' and properties.diskState =~ 'Unattached' | project id, name, resourceGroup, sizeGB = properties.diskSizeGB" -o tableaz graph query -q "Resources | where type =~ 'microsoft.network/publicipaddresses' and isnull(properties.ipConfiguration) and isnull(properties.natGateway) | project id, name, resourceGroup" -o tableaz graph query -q "Resources | where type =~ 'microsoft.network/networkinterfaces' and isnull(properties.virtualMachine) and isnull(properties.privateEndpoint) | project id, name, resourceGroup" -o table # 2. Check (tags, owner, who created it?)az resource show --ids <id> --query "{tags:tags, created:createdTime}"az monitor activity-log list --resource-id <id> --offset 90d --query "[?contains(operationName.value,'write')].{time:eventTimestamp, who:caller}" -o table # 3. Mark first, delete lateraz resource tag --ids <id> --tags DeleteAfter=2026-12-01 Reason=orphaned -iaz resource delete --ids <id>                     # after consultation / when the deadline has passed

Back up disks as a snapshot before deleting if needed: az snapshot create -g <rg> -n snap-<disk> --source <disk-id>.

#Common Cost Traps

TrapRemedy
VM only "stopped", not deallocatedaz vm deallocate instead of shutting down in the OS
Premium disks on test VMsswitch to Standard SSD
Disks/public IPs left over after deleting a VMset "Delete with VM" when deleting, Resource Graph query above
Application Gateway / firewall running permanently in teststop outside working hours (az network application-gateway stop)
Log Analytics collects too muchreduce diagnostic categories, check retention, Basic Logs for bulk data
Bastion Standard in every environmentone central Bastion in the hub
Old snapshots and backupsdefine retention, lifecycle rules
Traffic between regions/zonesworkloads and hub in the same region
Azure

#Naming Convention and Tags

Find missing tags: 19 Resource Graph → Tags · All abbreviations: 33 Abbreviations (CAF) · Build names: web page → Generator → Naming convention

Template based on the Cloud Adoption Framework (Define your naming convention) – adapt it to your standards and then make it binding in the team.

#Naming Components (CAF)

ComponentMeaningExamples
Resource typeabbreviation of the resource type (33)rg, vm, st, app
Workload / application / projectwhat the resource belongs tohub, webshop, sap
Environmentstage of the lifecycleprod, dev, qa, stage, test
RegionAzure region (full or short)westeurope / weu, germanywestcentral / gwc
Instancenumber for otherwise identical names01, 001

Rules from CAF:

  • Names can't be changed after creation for most resources – only put stable information in the name, everything else into tags.
  • Fix the order of the components once and keep it (CAF examples: type → workload → environment → region → instance, e.g. vm-workload3-dev-001).
  • Hyphen as delimiter – except where the resource doesn't allow it (storage account, container registry): stworkload1data001, crworkload1prod001.
  • No personal or confidential data in names or tags.
  • Automate with the generator on the web page or the Azure Naming Tool.

#Scope of Uniqueness

ScopeMeaningExamples
Globalunique in all of Azure (public endpoint / DNS name)storage account, key vault, web app, function app, container registry, SQL server, Cosmos DB, Service Bus, API Management, Front Door endpoint
Resource groupunique within the RGVNet, VM, NSG, public IP, load balancer, App Gateway
Parent resourceunique within the parentsubnet (in the VNet), SQL database (in the server), NSG rule

#Length and Character Rules (Most Used)

ResourceLengthAllowedScope
Resource group1–90letters, digits, - _ . () – not ending in .subscription
Virtual network2–64letters, digits, - _ .RG
Subnet1–80letters, digits, - _ .VNet
NSG / route table / public IP / NIC1–80letters, digits, - _ .RG
Virtual machine1–15 (Windows) / 1–64 (Linux)letters, digits, - (Windows computer name max. 15!)RG
Storage account3–24lowercase letters and digits onlyglobal
Key vault3–24letters, digits, -; starts with a letter, no --global
Container registry5–50letters and digits onlyglobal
Web app / function app2–60letters, digits, -global
SQL server1–63lowercase letters, digits, -global
Log Analytics workspace4–63letters, digits, -RG
AKS cluster1–63letters, digits, - _RG
Managed identity3–128letters, digits, - _RG
Management group (ID)1–90letters, digits, - _ . ()tenant

Full list: Naming rules and restrictions for Azure resources.

#Structure of a Name

text
<type>-<workload>-<environment>-<region>-<nr> rg-hub-prd-weu-01          resource groupvnet-hub-prd-weu-01        VNetsnet-fw-trust-prd-weu-01   subnetvm-fgt-prd-weu-01          FortiGate Astappprdweu01              storage account (lowercase letters/digits only, max. 24, no "-")kv-app-prd-weu-01          Key Vault (max. 24 characters, globally unique)

Rules: lowercase only, hyphen as separator, no special characters, running number always two digits.

#Abbreviations per Resource Type

ResourceAbbr.ResourceAbbr.
Management groupmgResource grouprg
Virtual networkvnetSubnetsnet
Network security groupnsgRoute tablert
Public IPpipNAT gatewayng
Load balancer (internal/external)lbi / lbeApplication gatewayagw
WAF policywafAzure Firewall / policyafw / afwp
BastionbasVirtual network gateway (VPN)vgw
Local network gatewaylgwVPN connectioncon
Private endpointpepPrivate DNS resolverdnspr
Virtual machinevmNetwork interfacenic
OS disk / data diskosdisk / diskAvailability setavail
Key VaultkvStorage accountst
Managed identityidLog Analytics workspacelog
Application InsightsappiApp Service planasp
Web appappFunction appfunc
SQL server / DBsql / sqldbContainer registrycr
AKS clusteraksRecovery Services vaultrsv

#Abbreviations for Regions and Environments

RegionAbbr.EnvironmentAbbr.
West EuropeweuProductionprd
North EuropeneuTest / acceptancetst / uat
Germany West CentralgwcDevelopmentdev
Austria EastateSandboxsbx
Switzerland NorthsznShared (platform)shd

#Tag Standard

TagRequiredExamplePurpose
Owneryesnetwork-teamwho is responsible
Environmentyesprdenvironment
CostCenteryes4711cost allocation
Applicationyeshubwhich application
Criticalityrecommendedhigh / medium / lowpriority during incidents
DataClassificationrecommendedinternalprotection level
CreatedByoptionalbicep-pipelinehow it was created
ExpiryDatefor tests2026-12-31when to delete

Tags are not inherited automatically – assign the built-in policy "Inherit a tag from the resource group if missing", and "Require a tag on resource groups" for required tags.

#Set and Check Tags

PowerShell
# Set on a resource group (keep existing ones)az group update -n <rg> --set tags.Owner=<team> tags.Environment=<env> tags.CostCenter=<cc># Add to a resourceaz resource tag --ids <id> --tags Owner=<team> -i# Tag all resources of an RGaz resource list -g <rg> --query "[].id" -o tsv | ForEach-Object { az resource tag --ids $_ --tags Environment=<env> -i | Out-Null }# Which RGs have no owner?az group list --query "[?tags.Owner==null].name" -o tsv

Centrally in Bicep: define param tags object once and pass tags: tags to every resource.

Azure

#Azure Resource Abbreviations (CAF)

Source: Microsoft Learn – Abbreviation recommendations for Azure resources (as of 10/2026) · Build names: web page → Generator → Naming convention · Rules: 27 Naming and Tags

Tip: search at the top, e.g. firewall policy or afwp. Some abbreviations are used twice (proj, afd, np) – check the namespace when you need an exact match.

#AI and Machine Learning

ResourceProvider namespaceAbbreviation
AI SearchMicrosoft.Search/searchServicessrch
Foundry Tools (multi-service account)Microsoft.CognitiveServices/accounts (kind: CognitiveServices)ais
Foundry accountMicrosoft.CognitiveServices/accounts (kind: AIServices)aif
Foundry account projectMicrosoft.CognitiveServices/accounts/projectsproj
Foundry hubMicrosoft.MachineLearningServices/workspaces (kind: Hub)hub
Foundry hub projectMicrosoft.MachineLearningServices/workspaces (kind: Project)proj
Azure AI Video IndexerMicrosoft.VideoIndexer/accountsavi
Azure Machine Learning workspaceMicrosoft.MachineLearningServices/workspacesmlw
Azure OpenAI ServiceMicrosoft.CognitiveServices/accounts (kind: OpenAI)oai
Bot serviceMicrosoft.BotService/botServices (kind: azurebot)bot
Computer visionMicrosoft.CognitiveServices/accounts (kind: ComputerVision)cv
Content moderatorMicrosoft.CognitiveServices/accounts (kind: ContentModerator)cm
Content safetyMicrosoft.CognitiveServices/accounts (kind: ContentSafety)cs
Custom vision (prediction)Microsoft.CognitiveServices/accounts (kind: CustomVision.Prediction)cstv
Custom vision (training)Microsoft.CognitiveServices/accounts (kind: CustomVision.Training)cstvt
Document intelligenceMicrosoft.CognitiveServices/accounts (kind: FormRecognizer)di
Face APIMicrosoft.CognitiveServices/accounts (kind: Face)face
Health InsightsMicrosoft.CognitiveServices/accounts (kind: HealthInsights)hi
Immersive readerMicrosoft.CognitiveServices/accounts (kind: ImmersiveReader)ir
Language serviceMicrosoft.CognitiveServices/accounts (kind: TextAnalytics)lang
Speech serviceMicrosoft.CognitiveServices/accounts (kind: SpeechServices)spch
TranslatorMicrosoft.CognitiveServices/accounts (kind: TextTranslation)trsl

#Analytics and IoT

ResourceProvider namespaceAbbreviation
Azure Analysis Services serverMicrosoft.AnalysisServices/serversas
Azure Databricks Access ConnectorMicrosoft.Databricks/workspaces/accessConnectorsdbac
Azure Databricks workspaceMicrosoft.Databricks/workspacesdbw
Azure Data Explorer clusterMicrosoft.Kusto/clustersdec
Azure Data Explorer cluster databaseMicrosoft.Kusto/clusters/databasesdedb
Azure Data FactoryMicrosoft.DataFactory/factoriesadf
Azure Digital Twin instanceMicrosoft.DigitalTwins/digitalTwinsInstancesdt
Azure Stream AnalyticsMicrosoft.StreamAnalytics/clusterasa
Synapse Analytics private link hubMicrosoft.Synapse/privateLinkHubssynplh
Synapse Analytics SQL dedicated poolMicrosoft.Synapse/workspaces/sqlPoolssyndp
Synapse Analytics Spark poolMicrosoft.Synapse/workspaces/bigDataPoolssynsp
Synapse Analytics workspaceMicrosoft.Synapse/workspacessynw
Data Lake Store accountMicrosoft.DataLakeStore/accountsdls
Event Hubs namespaceMicrosoft.EventHub/namespacesevhns
Event hubMicrosoft.EventHub/namespaces/eventHubsevh
Event hub consumer groupMicrosoft.EventHub/namespaces/eventhubs/consumerGroupsevhcg
Event Grid domainMicrosoft.EventGrid/domainsevgd
Event Grid namespaceMicrosoft.EventGrid/namespacesevgns
Event Grid subscriptionMicrosoft.EventGrid/eventSubscriptionsevgs
Event Grid topicMicrosoft.EventGrid/domains/topicsevgt
Event Grid system topicMicrosoft.EventGrid/systemTopicsegst
Fabric capacityMicrosoft.Fabric/capacitiesfc
HDInsight – Hadoop clusterMicrosoft.HDInsight/clustershadoop
HDInsight – HBase clusterMicrosoft.HDInsight/clustershbase
HDInsight – Kafka clusterMicrosoft.HDInsight/clusterskafka
HDInsight – Spark clusterMicrosoft.HDInsight/clustersspark
HDInsight – Storm clusterMicrosoft.HDInsight/clustersstorm
HDInsight – ML Services clusterMicrosoft.HDInsight/clustersmls
IoT hubMicrosoft.Devices/IotHubsiot
Provisioning servicesMicrosoft.Devices/provisioningServicesprovs
Provisioning services certificateMicrosoft.Devices/provisioningServices/certificatespcert
Power BI EmbeddedMicrosoft.PowerBIDedicated/capacitiespbi
Time Series Insights environmentMicrosoft.TimeSeriesInsights/environmentstsi

#Compute and Web

ResourceProvider namespaceAbbreviation
App Service environmentMicrosoft.Web/hostingEnvironmentsase
App Service planMicrosoft.Web/serverFarmsasp
Azure Load Testing instanceMicrosoft.LoadTestService/loadTestslt
Availability setMicrosoft.Compute/availabilitySetsavail
Azure Arc enabled serverMicrosoft.HybridCompute/machinesarcs
Azure Arc enabled Kubernetes clusterMicrosoft.Kubernetes/connectedClustersarck
Azure Arc private link scopeMicrosoft.HybridCompute/privateLinkScopespls
Azure Arc gatewayMicrosoft.HybridCompute/gatewaysarcgw
Batch accountMicrosoft.Batch/batchAccountsba
Cloud serviceMicrosoft.Compute/cloudServicescld
Communication ServicesMicrosoft.Communication/communicationServicesacs
Disk encryption setMicrosoft.Compute/diskEncryptionSetsdes
Function appMicrosoft.Web/sitesfunc
GalleryMicrosoft.Compute/galleriesgal
Hosting environmentMicrosoft.Web/hostingEnvironmentshost
Image templateMicrosoft.VirtualMachineImages/imageTemplatesit
Managed disk (OS)Microsoft.Compute/disksosdisk
Managed disk (data)Microsoft.Compute/disksdisk
Notification HubsMicrosoft.NotificationHubs/namespaces/notificationHubsntf
Notification Hubs namespaceMicrosoft.NotificationHubs/namespacesntfns
Proximity placement groupMicrosoft.Compute/proximityPlacementGroupsppg
Restore point collectionMicrosoft.Compute/restorePointCollectionsrpc
SnapshotMicrosoft.Compute/snapshotssnap
Static web appMicrosoft.Web/staticSitesstapp
Virtual machineMicrosoft.Compute/virtualMachinesvm
Virtual machine scale setMicrosoft.Compute/virtualMachineScaleSetsvmss
VM maintenance configurationMicrosoft.Maintenance/maintenanceConfigurationsmc
VM storage accountMicrosoft.Storage/storageAccountsstvm
Web appMicrosoft.Web/sitesapp

#Containers

ResourceProvider namespaceAbbreviation
AKS clusterMicrosoft.ContainerService/managedClustersaks
AKS system node poolMicrosoft.ContainerService/managedClusters/agentPools (mode: System)npsystem
AKS user node poolMicrosoft.ContainerService/managedClusters/agentPools (mode: User)np
AKS Fleet managerMicrosoft.ContainerService/fleetsfleet
Container appMicrosoft.App/containerAppsca
Container apps environmentMicrosoft.App/managedEnvironmentscae
Container apps jobMicrosoft.App/jobscaj
Container registryMicrosoft.ContainerRegistry/registriescr
Container instanceMicrosoft.ContainerInstance/containerGroupsci
Service Fabric clusterMicrosoft.ServiceFabric/clusterssf
Service Fabric managed clusterMicrosoft.ServiceFabric/managedClusterssfmc

#Databases

ResourceProvider namespaceAbbreviation
Azure Cosmos DB databaseMicrosoft.DocumentDB/databaseAccounts/sqlDatabasescosmos
Cosmos DB for Apache Cassandra accountMicrosoft.DocumentDB/databaseAccountscoscas
Cosmos DB for MongoDB accountMicrosoft.DocumentDB/databaseAccountscosmon
Cosmos DB for NoSQL accountMicrosoft.DocumentDb/databaseAccountscosno
Cosmos DB for Table accountMicrosoft.DocumentDb/databaseAccountscostab
Cosmos DB for Apache Gremlin accountMicrosoft.DocumentDb/databaseAccountscosgrm
Cosmos DB PostgreSQL clusterMicrosoft.DBforPostgreSQL/serverGroupsv2cospos
Azure Managed RedisMicrosoft.Cache/RedisEnterpriseamr
Azure SQL Database serverMicrosoft.Sql/serverssql
Azure SQL databaseMicrosoft.Sql/servers/databasessqldb
Azure SQL Elastic Job agentMicrosoft.Sql/servers/jobAgentssqlja
Azure SQL Elastic PoolMicrosoft.Sql/servers/elasticpoolsqlep
MySQL databaseMicrosoft.DBforMySQL/serversmysql
PostgreSQL flexible serverMicrosoft.DBforPostgreSQL/flexibleServerspgsql
SQL Managed InstanceMicrosoft.Sql/managedInstancessqlmi

#Developer Tools and DevOps

ResourceProvider namespaceAbbreviation
App Configuration storeMicrosoft.AppConfiguration/configurationStoresappcs
Maps accountMicrosoft.Maps/accountsmap
SignalRMicrosoft.SignalRService/SignalRsigr
Web PubSubMicrosoft.SignalRService/webPubSubwps
Azure Managed GrafanaMicrosoft.Dashboard/grafanaamg
Managed DevOps PoolsMicrosoft.DevOpsInfrastructure/poolsmdp

#Integration

ResourceProvider namespaceAbbreviation
API Management service instanceMicrosoft.ApiManagement/serviceapim
Integration accountMicrosoft.Logic/integrationAccountsia
Logic appMicrosoft.Logic/workflowslogic
Service Bus namespaceMicrosoft.ServiceBus/namespacessbns
Service Bus queueMicrosoft.ServiceBus/namespaces/queuessbq
Service Bus topicMicrosoft.ServiceBus/namespaces/topicssbt
Service Bus topic subscriptionMicrosoft.ServiceBus/namespaces/topics/subscriptionssbts

#Management and Governance

ResourceProvider namespaceAbbreviation
Automation accountMicrosoft.Automation/automationAccountsaa
Azure Policy definitionMicrosoft.Authorization/policyDefinitionsdescriptive
Application InsightsMicrosoft.Insights/componentsappi
Azure Monitor action groupMicrosoft.Insights/actionGroupsag
Azure Monitor data collection ruleMicrosoft.Insights/dataCollectionRulesdcr
Azure Monitor alert processing ruleMicrosoft.AlertsManagement/actionRulesapr
Azure Monitor Private Link ScopeMicrosoft.Insights/privateLinkScopesampls
Data collection endpointMicrosoft.Insights/dataCollectionEndpointsdce
Deployment scriptMicrosoft.Resources/deploymentScriptsscript
Log Analytics workspaceMicrosoft.OperationalInsights/workspaceslog
Log Analytics query packMicrosoft.OperationalInsights/querypackspack
Management groupMicrosoft.Management/managementGroupsmg
Microsoft Purview instanceMicrosoft.Purview/accountspview
Resource groupMicrosoft.Resources/resourceGroupsrg
Template specMicrosoft.Resources/templateSpecsts

#Migration

ResourceProvider namespaceAbbreviation
Azure Migrate projectMicrosoft.Migrate/assessmentProjectsmigr
Database Migration Service instanceMicrosoft.DataMigration/servicesdms
Recovery Services vaultMicrosoft.RecoveryServices/vaultsrsv

#Networking

ResourceProvider namespaceAbbreviation
Application gatewayMicrosoft.Network/applicationGatewaysagw
Application security group (ASG)Microsoft.Network/applicationSecurityGroupsasg
Azure Enclave communityMicrosoft.Mission/communitiescmt
Azure Enclave community endpointMicrosoft.Mission/communities/communityEndpointsce
Azure Enclave dedicated hubMicrosoft.Mission/communities/dedicatedHubsdh
Azure Enclave transit hubMicrosoft.Mission/communities/transitHubsth
Azure Enclave enclaveMicrosoft.Mission/virtualEnclavesve
Azure Enclave enclave endpointMicrosoft.Mission/virtualEnclaves/enclaveEndpointsee
Azure Enclave enclave connectionMicrosoft.Mission/enclaveConnectionsec
Azure Enclave workloadMicrosoft.Mission/virtualEnclaves/workloadswl
CDN profileMicrosoft.Cdn/profilescdnp
CDN endpointMicrosoft.Cdn/profiles/endpointscdne
ConnectionMicrosoft.Network/connectionscon
DNS zone (public)Microsoft.Network/dnsZonesDNS domain name
DNS forwarding rulesetMicrosoft.Network/dnsForwardingRulesetsdnsfrs
DNS private resolverMicrosoft.Network/dnsResolversdnspr
DNS private resolver inbound endpointMicrosoft.Network/dnsResolvers/inboundEndpointsin
DNS private resolver outbound endpointMicrosoft.Network/dnsResolvers/outboundEndpointsout
Private DNS zoneMicrosoft.Network/privateDnsZonesDNS domain name
FirewallMicrosoft.Network/azureFirewallsafw
Firewall policyMicrosoft.Network/firewallPoliciesafwp
ExpressRoute circuitMicrosoft.Network/expressRouteCircuitserc
ExpressRoute DirectMicrosoft.Network/expressRoutePortserd
ExpressRoute gatewayMicrosoft.Network/virtualNetworkGatewaysergw
Front Door (Standard/Premium) profileMicrosoft.Cdn/profilesafd
Front Door (Standard/Premium) endpointMicrosoft.Cdn/profiles/afdEndpointsfde
Front Door firewall policyMicrosoft.Network/frontdoorWebApplicationFirewallPoliciesfdfp
Front Door (classic)Microsoft.Network/frontDoorsafd
IP groupMicrosoft.Network/ipGroupsipg
Load balancer (internal)Microsoft.Network/loadBalancerslbi
Load balancer (external)Microsoft.Network/loadBalancerslbe
Load balancer ruleMicrosoft.Network/loadBalancers/inboundNatRulesrule
Local network gatewayMicrosoft.Network/localNetworkGatewayslgw
NAT gatewayMicrosoft.Network/natGatewaysng
Network interface (NIC)Microsoft.Network/networkInterfacesnic
Network security perimeterMicrosoft.Network/networkSecurityPerimetersnsp
Network security group (NSG)Microsoft.Network/networkSecurityGroupsnsg
NSG security ruleMicrosoft.Network/networkSecurityGroups/securityRulesnsgsr
Network WatcherMicrosoft.Network/networkWatchersnw
Private Link serviceMicrosoft.Network/privateLinkServicespl
Private endpointMicrosoft.Network/privateEndpointspep
Public IP addressMicrosoft.Network/publicIPAddressespip
Public IP address prefixMicrosoft.Network/publicIPPrefixesippre
Route filterMicrosoft.Network/routeFiltersrf
Route serverMicrosoft.Network/virtualHubsrtserv
Route tableMicrosoft.Network/routeTablesrt
Service endpoint policyMicrosoft.Network/serviceEndPointPoliciesse
Traffic Manager profileMicrosoft.Network/trafficManagerProfilestraf
User defined route (UDR)Microsoft.Network/routeTables/routesudr
Virtual networkMicrosoft.Network/virtualNetworksvnet
Virtual network gatewayMicrosoft.Network/virtualNetworkGatewaysvgw
Virtual network managerMicrosoft.Network/networkManagersvnm
Virtual network peeringMicrosoft.Network/virtualNetworks/virtualNetworkPeeringspeer
Virtual network subnetMicrosoft.Network/virtualNetworks/subnetssnet
Virtual WANMicrosoft.Network/virtualWansvwan
Virtual WAN hubMicrosoft.Network/virtualHubsvhub

#Security

ResourceProvider namespaceAbbreviation
Azure BastionMicrosoft.Network/bastionHostsbas
Key vaultMicrosoft.KeyVault/vaultskv
Key Vault Managed HSMMicrosoft.KeyVault/managedHSMskvmhsm
Managed identityMicrosoft.ManagedIdentity/userAssignedIdentitiesid
SSH keyMicrosoft.Compute/sshPublicKeyssshkey
VPN Gateway (Virtual WAN)Microsoft.Network/vpnGatewaysvpng
VPN connection (Virtual WAN)Microsoft.Network/vpnGateways/vpnConnectionsvcn
VPN site (Virtual WAN)Microsoft.Network/vpnGateways/vpnSitesvst
Web Application Firewall (WAF) policyMicrosoft.Network/firewallPolicieswaf
WAF policy rule groupMicrosoft.Network/firewallPolicies/ruleGroupswafrg

#Storage

ResourceProvider namespaceAbbreviation
Azure Backup Resource GuardMicrosoft.DataProtection/resourceGuardsrgd
Backup vaultMicrosoft.DataProtection/backupVaultsbvault
Backup vault policyMicrosoft.DataProtection/backupVaults/backupPoliciesbkpol
File shareMicrosoft.Storage/storageAccounts/fileServices/sharesshare
Storage accountMicrosoft.Storage/storageAccountsst
Storage Sync ServiceMicrosoft.StorageSync/storageSyncServicessss

#Virtual Desktop

ResourceProvider namespaceAbbreviation
Virtual desktop host poolMicrosoft.DesktopVirtualization/hostPoolsvdpool
Virtual desktop application groupMicrosoft.DesktopVirtualization/applicationGroupsvdag
Virtual desktop workspaceMicrosoft.DesktopVirtualization/workspacesvdws
Virtual desktop scaling planMicrosoft.DesktopVirtualization/scalingPlansvdscaling
Code and DevOps

#Bicep in a Team Repo

Basic commands: 06 Bicep / ARM · Pipeline: 18 Pipelines

Top 5: az bicep build · az bicep lint · az deployment group what-if · az deployment group create -p <file>.bicepparam · az deployment operation group list

#Typical Folder Structure

text
infra/├── main.bicep                  # entry point, calls modules├── modules/│   ├── network.bicep│   └── appgateway.bicep├── params/│   ├── dev.bicepparam          # values per environment│   └── prod.bicepparam└── bicepconfig.json            # linter rules

Rule: logic in .bicep, values in .bicepparam. A PR that only changes values only touches params/.

#Parameter File per Environment (.bicepparam)

Bicep
// params/dev.bicepparamusing '../main.bicep' param environment = 'dev'param location = 'westeurope'param vnetAddressSpace = '10.10.0.0/22'param tags = {  Environment: 'dev'  Owner: '<team>'}param adminPassword = readEnvironmentVariable('ADMIN_PASSWORD')   // secret not in the file
PowerShell
$env:ADMIN_PASSWORD = "<secret>"                         # this session onlyaz bicep build-params -f params/dev.bicepparam           # checks that all required parameters are set

Compare environments:

PowerShell
git diff --no-index params/dev.bicepparam params/prod.bicepparamcode --diff params/dev.bicepparam params/prod.bicepparam    # side by side in VS Code

#Check Before the Pull Request

PowerShell
az bicep format -f main.bicep                            # consistent formattingaz bicep lint   -f main.bicep                            # linter warningsaz bicep build  -f main.bicep --stdout > $null           # check syntax/types, write nothingaz deployment group validate -g <rg> -f main.bicep -p params/dev.bicepparamaz deployment group what-if  -g <rg> -f main.bicep -p params/dev.bicepparam `  --exclude-change-types NoChange Ignore                 # show real changes only

Reading what-if:

SymbolMeaning
+ Createwill be created
~ Modifywill be changed – details below
- Deletewill be deleted (only in Complete mode or with stacks)
= NoChangestays the same
* Ignorenot in the template – left untouched
! Deploycannot be predicted, will be rewritten

Pasting the what-if output into the PR helps the reviewer a lot.

#Deploy and Find Errors

PowerShell
$name = "deploy-$(Get-Date -Format yyyyMMdd-HHmm)"az deployment group create -g <rg> -n $name -f main.bicep -p params/dev.bicepparamaz deployment group create -g <rg> -n $name -f main.bicep -p params/dev.bicepparam -p location=northeurope   # override one value (newer CLI) # What went wrong?az deployment group show -g <rg> -n $name --query "properties.error" -o jsonaz deployment operation group list -g <rg> -n $name `  --query "[?properties.provisioningState=='Failed'].{res:properties.targetResource.resourceName, type:properties.targetResource.resourceType, error:properties.statusMessage.error.message}" -o table # Historyaz deployment group list -g <rg> --query "sort_by([].{name:name, status:properties.provisioningState, time:properties.timestamp}, &time)" -o table

Subscription or management group level: az deployment sub create -l <region> … or az deployment mg create -m <mg-id> -l <region> ….

#Modules from the Registry (AVM)

Azure Verified Modules (AVM) are reviewed Microsoft modules from the public registry:

Bicep
module vnet 'br/public:avm/res/network/virtual-network:<version>' = {  name: 'vnet'  params: {    name: 'vnet-hub-dev-weu-01'    addressPrefixes: [ '10.10.0.0/22' ]  }}
PowerShell
az bicep restore -f main.bicep                           # download modules (cache)

Current versions: https://aka.ms/avm – always pin the version.

#Deployment Stacks

A stack remembers which resources it created and can clean up removed resources or protect them from changes.

PowerShell
az stack group create -n <stack> -g <rg> -f main.bicep -p params/dev.bicepparam `  --action-on-unmanage detachAll --deny-settings-mode noneaz stack group list -g <rg> -o tableaz stack group show -n <stack> -g <rg> --query "resources[].id" -o tsvaz stack group delete -n <stack> -g <rg> --action-on-unmanage detachAll    # stack gone, resources stay

--action-on-unmanage deleteAll deletes resources removed from the template – careful in prod.

#bicepconfig.json

json
{  "analyzers": {    "core": {      "rules": {        "no-hardcoded-location": { "level": "warning" },        "no-unused-params":      { "level": "warning" },        "secure-parameter-default": { "level": "error" },        "use-recent-api-versions":  { "level": "warning" }      }    }  }}
Code and DevOps

#Bicep / ARM

In a team repo: 17 Bicep in a team repo

PowerShell
az bicep install ; az bicep upgrade ; az bicep versionaz bicep build -f main.bicep                          # -> ARM JSONaz bicep build-params -f main.bicepparam              # parameter file -> JSONaz bicep decompile -f template.json                   # ARM -> Bicepaz bicep lint -f main.bicep                           # linter # Check (changes nothing)az deployment group validate -g <rg> -f main.bicep -p main.bicepparamaz deployment group what-if  -g <rg> -f main.bicep -p main.bicepparamaz deployment sub   what-if  -l <region> -f main.bicep -p main.bicepparam # Deployaz deployment group create -g <rg> -f main.bicep -p main.bicepparamaz deployment group create -g <rg> -f main.bicep -p main.bicepparam -p adminPassword=<value>   # override a single parameteraz deployment sub   create -l <region> -f main.bicep -p main.bicepparamaz deployment group create -g <rg> -f main.bicep --mode Complete    # deletes everything not in the template (careful!) # History and errorsaz deployment group list -g <rg> --query "[].{name:name, state:properties.provisioningState, time:properties.timestamp}" -o tableaz deployment operation group list -g <rg> -n <deployment> --query "[?properties.provisioningState=='Failed'].properties.statusMessage"az deployment group cancel -g <rg> -n <deployment> # Exportaz group export -n <rg> > export.json

#Building Blocks

Bicep
param location string = resourceGroup().location@secure()param adminPassword string@allowed(['dev', 'test', 'prod'])param environment string var tags = { environment: environment, owner: 'network' } resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = {  name: 'vnet-hub'                       // reference an existing resource} module spoke 'modules/spoke.bicep' = {  name: 'spoke-deploy'  params: { location: location, tags: tags }} resource nsgs 'Microsoft.Network/networkSecurityGroups@2024-05-01' = [for n in ['a', 'b']: {  name: 'nsg-${n}'  location: location}] output vnetId string = vnet.id
Careful with subnets: subnets defined inline in the VNet → a redeployment removes subnets that are not in the list, and NSG/route table associations that are not in the template. Always run what-if first.
Code and DevOps

#Terraform

Top 5: terraform init · terraform plan -out tfplan · terraform apply tfplan · terraform state list · terraform output

#Basic Workflow

PowerShell
terraform versionterraform init                         # initialize providers + backendterraform init -upgrade                # get newer provider versionsterraform init -reconfigure            # re-read the backendterraform init -migrate-state          # move state to a new backendterraform fmt -recursive               # formatterraform fmt -check -recursive        # check only (pipeline)terraform validate                     # check syntax/typesterraform test                         # tests in tests/*.tftest.hcl terraform plan                         # show changesterraform plan -out tfplan             # save the planterraform apply tfplan                 # apply exactly this planterraform apply                        # plan, ask, applyterraform apply -auto-approve          # without confirmation (pipeline)terraform plan -destroy                # show what a destroy would deleteterraform destroy terraform plan -var-file=<file>.tfvars -var="name=value"terraform plan -target='<address>'                 # one resource only (exception!)terraform apply -replace='<address>'               # rebuild deliberatelyterraform plan -refresh-only                       # show drift onlyterraform apply -refresh-only                      # accept drift into the stateterraform plan -detailed-exitcode                  # exit 0 = no change, 2 = changes, 1 = errorterraform plan -parallelism=5                      # fewer parallel API calls (throttling)terraform show -json tfplan > plan.json            # machine-readable planterraform -chdir=<folder> plan                     # run in another folder

In PowerShell always put addresses in single quotes: 'module.hub.azurerm_subnet.this["fw"]'.

#Reading a Plan

SymbolMeaning
+create
~update in place
-/+replace = destroy and recreate – check carefully!
+/-replace with create_before_destroy
-destroy
<=read data source
# forces replacementthis attribute triggers the rebuild

#Outputs and State

PowerShell
terraform outputterraform output -raw <name>terraform output -json > outputs.jsonterraform show                             # current state, readableterraform state listterraform state list | Select-String <part>terraform state show '<address>'terraform state mv '<old>' '<new>'         # rename without rebuild (better: moved block)terraform state rm '<address>'             # detach from Terraform, resource staysterraform state pull > state-backup.json   # back up the stateterraform import '<address>' <azure-id>    # take over an existing resourceterraform plan -generate-config-out=generated.tf   # generate code for import blocks (TF >= 1.5)terraform force-unlock <lock-id>terraform console                          # test expressionsterraform graph | dot -Tsvg > graph.svgterraform providersterraform providers lock -platform=windows_amd64 -platform=linux_amd64   # lock file for all platformsterraform workspace list | new <name> | select <name> | delete <name>

#Code Snippets

HCL
# Import in code (TF >= 1.5)import {  to = azurerm_resource_group.rg  id = "/subscriptions/<sub>/resourceGroups/<rg>"} # Rename without rebuildmoved {  from = azurerm_resource_group.old  to   = azurerm_resource_group.new} # Remove from Terraform, resource stays (TF >= 1.7)removed {  from = azurerm_resource_group.old  lifecycle {    destroy = false  }} # Ignore fields after creation / protectionlifecycle {  ignore_changes        = [tags["DateCreatedUTC"]]  prevent_destroy       = true  create_before_destroy = true} # Several instances from a mapresource "azurerm_subnet" "this" {  for_each             = var.subnets          # { fw = "10.0.0.0/26", mgmt = "10.0.0.64/27" }  name                 = each.key  address_prefixes     = [each.value]  resource_group_name  = azurerm_resource_group.rg.name  virtual_network_name = azurerm_virtual_network.vnet.name} # Optional resourceresource "azurerm_public_ip" "pip" {  count = var.create_pip ? 1 : 0  # ...} # Variable with validationvariable "environment" {  type = string  validation {    condition     = contains(["dev", "test", "prod"], var.environment)    error_message = "environment must be dev, test or prod."  }} # Secret variablevariable "admin_password" {  type      = string  sensitive = true} # Provider with pinned versionterraform {  required_version = ">= 1.6"  required_providers {    azurerm = {      source  = "hashicorp/azurerm"      version = "~> 4.0"    }  }}provider "azurerm" {  features {}  subscription_id = var.subscription_id      # required from azurerm 4.x (or ARM_SUBSCRIPTION_ID)}

#Useful Functions

Test in terraform console:

HCL
cidrsubnets("10.0.0.0/24", 2, 2, 2, 2)     # ["10.0.0.0/26","10.0.0.64/26",...]cidrsubnet("10.0.0.0/16", 8, 3)            # "10.0.3.0/24"cidrhost("10.0.1.0/24", 4)                 # "10.0.1.4"  (first usable IP in Azure)merge(local.tags, { role = "fw" })lookup(var.map, "key", "default")try(var.obj.value, null)coalesce(var.name, "${var.prefix}-vm")format("%s-%02d", "vm", 1)                 # "vm-01"jsonencode({ a = 1 }) ; yamldecode(file("x.yaml"))templatefile("${path.module}/init.tpl", { hostname = "fgt-a" })toset(["a","b"]) ; flatten([[1],[2,3]]) ; distinct(list)[for k, v in var.subnets : k if v != null]{ for s in var.list : s.name => s }

#Remote State in Azure

HCL
terraform {  backend "azurerm" {    resource_group_name  = "<rg-tfstate>"    storage_account_name = "<storageaccount>"    container_name       = "tfstate"    key                  = "<project>.tfstate"    use_azuread_auth     = true  }}

Create the storage:

PowerShell
az group create -n <rg-tfstate> -l <region>az storage account create -g <rg-tfstate> -n <storageaccount> --sku Standard_ZRS --min-tls-version TLS1_2 --allow-blob-public-access falseaz storage account blob-service-properties update -g <rg-tfstate> --account-name <storageaccount> --enable-versioning trueaz storage container create --account-name <storageaccount> -n tfstate --auth-mode loginaz role assignment create --assignee <user-or-sp> --role "Storage Blob Data Contributor" --scope <storage-id>

Lock stuck? Blob lease: az storage blob lease break --account-name <sa> -c tfstate -b <project>.tfstate --auth-mode login

#Environment Variables and Sign-in

PowerShell
$env:ARM_SUBSCRIPTION_ID = "<sub>"$env:ARM_TENANT_ID = "<tenant>"$env:ARM_CLIENT_ID = "<app-id>" ; $env:ARM_CLIENT_SECRET = "<secret>"   # service principal$env:ARM_USE_OIDC = "true"                                             # pipeline with workload identity$env:TF_VAR_admin_password = "<secret>"       # set a variable without a file$env:TF_LOG = "DEBUG" ; $env:TF_LOG_PATH = "tf.log"Remove-Item Env:TF_VAR_admin_passwordGet-ChildItem Env:ARM_* , Env:TF_*            # what is set?

Bash: export TF_VAR_name=value · unset TF_VAR_name

#Quality and Security

PowerShell
tflint --init ; tflint --recursive               # linter (provider rules)checkov -d .                                     # security checkstrivy config .                                   # alternative to checkovterraform-docs markdown table . > README.md      # generate module docs

#.gitignore

Text
.terraform/*.tfstate*.tfstate.*tfplan*.tfplancrash.logcrash.*.logterraform.tfvars*.auto.tfvarsoverride.tfoverride.tf.json

Commit .terraform.lock.hcl (pinned provider versions).

Code and DevOps

#Git

Pull requests in Azure DevOps: Workflows

Top 5: git status · git pull · git add -A ; git commit -m "..." · git push · git log --oneline --graph -20

#Setup

PowerShell
git --versiongit config --global user.name  "First Last"git config --global user.email "<mail>"git config --global init.defaultBranch maingit config --global core.autocrlf true          # Windows: CRLF locally, LF in the repogit config --global pull.rebase falsegit config --global alias.lg "log --oneline --graph --decorate -20"    # git lggit config --list --show-origin

#New Repo / Push a Folder

PowerShell
cd <folder>git init -b maingit add .git status                                      # check: no secrets, states, licenses!git commit -m "Initial commit"git remote add origin <url>git push -u origin main# Remote already has commits (e.g. README):git pull origin main --allow-unrelated-historiesgit push -u origin main

#Daily Work

PowerShell
git clone <url> [<folder>]git pull                                        # fetch + mergegit fetch --all --prune                         # fetch onlygit status ; git status -sgit diff ; git diff --stagedgit add <file> ; git add -A                     # everything incl. deletionsgit add -p                                      # changes piece by piecegit commit -m "<message>"git commit --amend                              # change the last commit (only if not pushed)git commit --amend --no-edit                    # add a file, keep the messagegit push

#Branches and Merge

PowerShell
git branch -agit switch -c feature/<name>                    # create a new branch and switchgit switch maingit push -u origin feature/<name>git merge feature/<name>git merge --abort                               # abort a merge with conflictsgit rebase main                                 # update your branch onto maingit rebase --abortgit cherry-pick <commit>                        # take a single commitgit branch -d feature/<name>                    # delete locallygit push origin --delete feature/<name>         # delete remotelygit tag v1.0 ; git push origin --tags

Resolve a conflict: edit the file (remove <<<<<<< … >>>>>>>) → git add <file> → git commit (or git rebase --continue).

#Search and History

PowerShell
git log --oneline --graph --decorate -20git log -p -- <file>                            # history of a file with changesgit log --follow -- <file>                      # across renames toogit log --grep "<text>"                         # search commit messagesgit log -S "<text>"                             # commits that added/removed <text>git log --since "2 weeks ago" --author "<name>"git grep -n "<text>"                            # search the current stategit blame <file>git show <commit>git show <commit>:<path>                        # show a file at an old stategit diff main...feature/<name>                  # what does the branch bring?git diff --stat <commit1> <commit2>

#Undo

PowerShell
git restore <file>                              # discard local changegit restore --staged <file>                     # unstagegit restore --source <commit> <file>            # get a file from an old stategit reset --soft HEAD~1                         # undo last commit, keep changesgit reset --hard HEAD~1                         # discard last commit (careful!)git reset --hard origin/main                    # local = remote (careful!)git revert <commit>                             # counter-commit (safe for pushed state)git stash ; git stash list ; git stash pop      # park changesgit stash -u                                    # incl. untracked filesgit clean -n ; git clean -fd                    # show/delete untracked filesgit reflog                                      # find "lost" commits
SituationCommand
File broken, not committedgit restore <file>
Wrong file addedgit restore --staged <file>
Wrong commit message, not pushedgit commit --amend
Last commit wrong, not pushedgit reset --soft HEAD~1
Commit already pushedgit revert <commit>
Everything gone after resetgit reflog → git reset --hard <hash>

#Ignore and Secrets

PowerShell
git check-ignore -v <file>                      # why is it ignored?git add -f <file>                               # add despite .gitignoregit rm --cached <file>                          # remove from Git, keep locallygit rm -r --cached . ; git add .                # rebuild after a .gitignore changegit ls-files | Select-String "tfstate|tfvars|\.pfx|\.key|\.lic"     # check before pushing

Secret pushed by accident: change/revoke the secret immediately – removing it from history is not enough.

#Remote

PowerShell
git remote -vgit remote set-url origin <new-url>             # e.g. SSH <-> HTTPSgit remote add upstream <url>git ls-remote origin                            # branches/tags on the server

#Rebase or Merge?

MergeRebase
What happensa merge commit joins both linesyour commits are "replayed" onto the new state
Historybranched, completestraight line
Risklowrewrites commits – never on shared branches (Dev, main)
Whenbranch is already pushed and others work on ittidy up your own feature branch before the PR
PowerShell
# Bring your feature branch up to the latest Dev (rebase)git switch feature/<name>git fetch origingit rebase origin/Dev# on conflict: fix the file -> git add <file> -> git rebase --continue   (abort: git rebase --abort)git push --force-with-lease               # needed after rebase; only overwrites if nobody else pushed # Squash several own commits into one before the PRgit reset --soft origin/Dev               # all changes stay, commits gonegit commit -m "<one clean message>"git push --force-with-lease

#Take Single Commits and Files

PowerShell
git log --oneline origin/<other-branch> -10                   # find the commit hashgit cherry-pick <commit>                                       # take exactly this commitgit cherry-pick <commit1> <commit2>git cherry-pick --abort git restore --source origin/<other-branch> -- <path/file>      # get only one file from another branchgit show origin/<branch>:<path/file>                           # just view a file from another branchgit diff Dev..feature/<name> -- <path/file>                    # compare one file between branches

#Who Changed This?

PowerShell
git blame -L 10,30 <file>                      # lines 10–30 with author and commitgit log -L 10,30:<file>                        # history of exactly these linesgit log --follow -p -- <file>                  # all changes to a file, even after renamesgit log -S "<text>" --oneline                  # when was <text> added / removedgit log --author "<name>" --since "1 week ago" --onelinegit shortlog -sn --since "1 month ago"         # who has how many commits

#Aliases and Useful Settings

PowerShell
git config --global alias.st "status -s"git config --global alias.lg "log --oneline --graph --decorate -20"git config --global alias.last "log -1 --stat"git config --global alias.unstage "restore --staged"git config --global alias.amend "commit --amend --no-edit"git config --global alias.br "branch -vv"                     # branches with upstream and state git config --global fetch.prune true                          # remove deleted remote branches automaticallygit config --global rerere.enabled true                       # remember resolved conflictsgit config --global push.autoSetupRemote true                 # "git push" without -u the first timegit config --global core.editor "code --wait"                 # VS Code as editorgit config --global merge.tool vscodegit config --global mergetool.vscode.cmd 'code --wait $MERGED'

Then: git st, git lg, git last, git br.

#Work on Several Branches at Once

PowerShell
git worktree add ..\<repo>-hotfix main         # second working folder on main, without switchinggit worktree listgit worktree remove ..\<repo>-hotfix

Handy when a hotfix on main comes in while the feature branch is half done.

#Commit Messages

Short, imperative, what and why: Hub: add UDR for GatewaySubnet · FortiGate: license token as variable. Optional prefix: feat:, fix:, docs:, refactor:, chore:.

Code and DevOps

#Azure DevOps

SSH key for it: SSH · Pipelines: 18 Pipelines

#In the Portal

TaskWhere
Create a repoProject → Repos → repo picker → + New repository (without README when pushing an existing folder)
Clone URLRepos → Clone (HTTPS or SSH)
SSH keytop right User settings → SSH public keys → + New Key (RSA only)
Personal access tokenUser settings → Personal access tokens → scope Code: Read & Write
Branch protectionRepos → Branches → main → Branch policies (pull request, reviewers, build)
Pull requestRepos → Pull requests → New (choose source → target)
Abandon a PRin the PR: Complete ▾ / ⋯ → Abandon
Undo a completed PRin the PR: Revert → Create pull request
ReviewersRequired = mandatory approval, Optional = info only
Service connectionProject settings → Service connections → Azure Resource Manager (workload identity federation)
Variable group / secretsPipelines → Library (optionally linked to Key Vault)
Approval before prodPipelines → Environments → Approvals and checks

Complete flow feature → Dev → main: Workflows → Azure DevOps.

URL formats:

Text
HTTPS: https://dev.azure.com/<org>/<project>/_git/<repo>SSH:   git@ssh.dev.azure.com:v3/<org>/<project>/<repo>

#CLI

PowerShell
az extension add -n azure-devopsaz devops login                                 # enter PATaz devops configure --defaults organization=https://dev.azure.com/<org> project=<project>az repos list -o tableaz repos create --name <repo>az repos pr list -o tableaz repos pr create --source-branch feature/<name> --target-branch Dev --title "<title>" --openaz repos pr create --source-branch Dev --target-branch main --title "Release: Dev to main" --openaz repos pr show --id <pr-number> --query "{status:status, merge:mergeStatus, reviewers:reviewers[].{name:displayName, vote:vote}}"az repos pr reviewer add --id <pr-number> --reviewers <mail-or-group>az repos pr update --id <pr-number> --status abandoned            # abandon PRaz repos pr update --id <pr-number> --status active               # reactivateaz repos policy list --branch Dev --repository-id <repo-id> -o table   # which branch policies applyaz pipelines list -o tableaz pipelines run --name <pipeline>az pipelines runs list --top 5 -o table

#Pipeline Template: Terraform Plan/Apply

azure-pipelines.yml – plan on every branch, apply only on main with approval via an environment.

YAML
trigger:  branches:    include: [ main ] pool:  vmImage: ubuntu-latest variables:  serviceConnection: '<service-connection>'  workDir: '$(System.DefaultWorkingDirectory)/terraform' stages:- stage: Plan  jobs:  - job: plan    steps:    - task: AzureCLI@2      displayName: terraform plan      inputs:        azureSubscription: $(serviceConnection)        scriptType: bash        scriptLocation: inlineScript        addSpnToEnvironment: true        workingDirectory: $(workDir)        inlineScript: |          export ARM_CLIENT_ID=$servicePrincipalId          export ARM_TENANT_ID=$tenantId          export ARM_OIDC_TOKEN=$idToken          export ARM_USE_OIDC=true          export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)          terraform init -input=false          terraform fmt -check -recursive          terraform validate          terraform plan -input=false -out tfplan    - publish: $(workDir)/tfplan      artifact: tfplan - stage: Apply  dependsOn: Plan  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))  jobs:  - deployment: apply    environment: prod                       # configure approval in the environment    strategy:      runOnce:        deploy:          steps:          - checkout: self          - download: current            artifact: tfplan          - task: AzureCLI@2            displayName: terraform apply            inputs:              azureSubscription: $(serviceConnection)              scriptType: bash              scriptLocation: inlineScript              addSpnToEnvironment: true              workingDirectory: $(workDir)              inlineScript: |                export ARM_CLIENT_ID=$servicePrincipalId                export ARM_TENANT_ID=$tenantId                export ARM_OIDC_TOKEN=$idToken                export ARM_USE_OIDC=true                export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)                terraform init -input=false                terraform apply -input=false $(Pipeline.Workspace)/tfplan/tfplan

Note: the OIDC token from addSpnToEnvironment is short-lived – for long runs the azurerm provider (≥ 4.x) can refresh the token itself (ARM_OIDC_AZURE_SERVICE_CONNECTION_ID + SYSTEM_ACCESSTOKEN, see the azurerm docs).

Code and DevOps

#Azure DevOps Pipelines

Repo and PRs: 12 Azure DevOps · Terraform template: 12 → Pipeline template

#In the Portal

TaskWhere
Start a runPipelines → pipeline → Run pipeline → choose branch, set parameters
Run for another branchin Run pipeline switch Branch/tag at the top
Log of a stepopen run → job → click the step; top right View raw log
Retry a failed jobopen run → Rerun failed jobs
Grant an approvalrun shows "Waiting" → Review → Approve
Set up approvalsPipelines → Environments → environment → ⋯ → Approvals and checks
Variables/secretsPipelines → Library → variable group (optionally linked to Key Vault)
Build check in a PRRepos → Branches → Dev → Branch policies → Build validation
Pipeline permission on a service connectionProject settings → Service connections → connection → Security

#CLI

PowerShell
az devops configure --defaults organization=https://dev.azure.com/<org> project=<project>az pipelines list -o tableaz pipelines run --name <pipeline> --branch Devaz pipelines run --name <pipeline> --branch main --parameters environment=prod     # runtime parameteraz pipelines run --name <pipeline> --variables ImageTag=1.2.3az pipelines runs list --pipeline-ids <id> --top 10 `  --query "[].{id:id, branch:sourceBranch, status:status, result:result, start:startTime}" -o tableaz pipelines runs show --id <run-id> --open                                         # open in the browseraz pipelines runs list --branch Dev --result failed --top 5 -o tableaz pipelines variable-group list -o tableaz pipelines variable-group variable list --group-id <id> -o table

#Investigate a Failed Run

1. Open run → red job → red step → read the last red line, not the first. 2. Common causes:

MessageCauseFix
AuthorizationFailedservice connection has no role on the targetassign a role to the service connection's identity
No hosted parallelism has been purchased or grantedno agents free/enabledrequest parallel jobs or use a self-hosted agent
The pipeline is not valid … Could not find …wrong path/templatecheck the path relative to the repo root
Pipeline does not have permissions to use the referenced service connectionpermission missingopen run → Permit, or service connection → Security
Run stuck on "Waiting"approval in the environmentreviewer grants approval
The template … failed validationBicep/ARM errorrun az deployment group validate locally

3. More output: set variable system.debug = true and run again.

#Template: Bicep what-if in the PR, Deploy after Approval

pipelines/bicep.yml – what-if on every PR, deployment to Dev/main only after approval in the environment.

YAML
trigger:  branches:    include: [ Dev, main ]  paths:    include: [ infra/* ] pr:  branches:    include: [ Dev, main ] pool:  vmImage: ubuntu-latest variables:  serviceConnection: '<service-connection>'  rg: '<rg>'  ${{ if eq(variables['Build.SourceBranchName'], 'main') }}:    paramFile: 'infra/params/prod.bicepparam'    envName: 'prod'  ${{ else }}:    paramFile: 'infra/params/dev.bicepparam'    envName: 'dev' stages:- stage: Check  jobs:  - job: whatif    steps:    - task: AzureCLI@2      displayName: Bicep lint + what-if      inputs:        azureSubscription: $(serviceConnection)        scriptType: bash        scriptLocation: inlineScript        inlineScript: |          az bicep lint -f infra/main.bicep          az deployment group what-if -g $(rg) -f infra/main.bicep -p $(paramFile) \            --exclude-change-types NoChange Ignore - stage: Deploy  dependsOn: Check  condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))  jobs:  - deployment: deploy    environment: ${{ variables.envName }}   # configure approval in the environment    strategy:      runOnce:        deploy:          steps:          - checkout: self          - task: AzureCLI@2            displayName: Bicep deploy            inputs:              azureSubscription: $(serviceConnection)              scriptType: bash              scriptLocation: inlineScript              inlineScript: |                az deployment group create -g $(rg) -f infra/main.bicep -p $(paramFile) \                  -n "pipeline-$(Build.BuildId)"

Note: in a PR the check uses the Dev parameters (for PR runs the source branch is refs/pull/…). For PRs to main, attach a separate pipeline with prod.bicepparam as build validation on main if needed.

For the what-if to run in the PR: branch policy of Dev → Build validation → add this pipeline.

#Useful YAML Snippets

YAML
# Runtime parameter (choice at start)parameters:- name: environment  type: string  default: dev  values: [ dev, test, prod ] # Include a variable group (e.g. with Key Vault secrets)variables:- group: <variable-group> # Step only on main- script: echo "main only"  condition: eq(variables['Build.SourceBranch'], 'refs/heads/main') # Step even if a previous one failed (e.g. save logs)- script: echo "always"  condition: always() # Template from another reporesources:  repositories:  - repository: templates    type: git    name: <project>/<template-repo>    ref: refs/heads/mainsteps:- template: steps/bicep-deploy.yml@templates
Network and Firewall

#FortiGate CLI

Access: ssh <admin>@<mgmt-ip> · GUI https://<mgmt-ip> · in Azure also the serial console. Every config block ends with end – only then is it saved and synchronized in HA. abort discards.

Top 5: get system status · get router info routing-table all · diagnose sniffer packet any '<filter>' 4 20 · debug flow · get system ha status

#Using the CLI

FortiOS / Text
?                                      # help / possible input (Tab = complete)show                                   # configuration of the current section (changed values only)show full-configuration                # incl. defaultsget                                    # current values in the sectionshow | grep <text>show full-configuration | grep -f <text>         # matches with the enclosing config blockshow firewall policy | grep -i -A3 <name>config vdom ; edit root                          # with VDOMs enabledconfig global                                    # global settings with VDOMsabort                                            # leave a config block without savingdiagnose debug config-error-log read             # errors during apply/import

#System and License

FortiOS / Text
get system statusget system status | grep -i -E "version|serial|license"get system performance statusdiagnose sys top 5 20                                      # processes (q = quit)get hardware statusexecute vm-license <FortiFlex-token>                       # license via tokenexecute restore vmlicense tftp <file.lic> <server-ip>      # license file via TFTP (otherwise GUI upload)diagnose debug vm-print-license                            # VM license detailsdiagnose autoupdate versions | grep -A3 "AV Engine"execute update-now                                         # trigger FortiGuard updatediagnose debug crashlog readexecute disk listdiagnose sys logdisk statusexecute time ; execute dateexecute rebootexecute shutdownexecute factoryreset                                       # careful!

#Interfaces, Admin, DNS, NTP

FortiOS / Text
get system interface physicalshow system interface port1get system arpdiagnose hardware deviceinfo nic port1                     # link, error countersdiagnose netlink interface list port1config system interface    edit "port2"        set allowaccess ping https ssh probe-response    nextendshow system adminconfig system admin    edit "<admin>"        set trusthost1 <network> <mask>                    # restrict admin access    nextendconfig system global    set hostname "<name>"    set timezone "Europe/Vienna"    set admintimeout 30    set admin-sport 443endconfig system dns    set primary 168.63.129.16                              # Azure DNS (or your own resolver)endconfig system ntp    set ntpsync enable    set type fortiguardend

#HA (FGCP)

FortiOS / Text
get system ha statusdiagnose sys ha checksum cluster              # configuration in sync? (identical checksums)diagnose sys ha checksum show                 # checksums per area (find the difference)diagnose sys ha checksum recalculatediagnose sys ha history readshow full-configuration system haexecute ha synchronize start                  # trigger sync (on the secondary)execute ha manage <index> <admin>             # switch from primary to secondaryexecute ha failover set 1                     # test failover (temporary)execute ha failover unset 1                   # remove the flagdiagnose sys ha reset-uptime                  # override disable: give up the primary roleconfig system ha    set session-pickup enable    set override disableend

override disable: primary = longest HA uptime (5 min tolerance), then priority. No automatic failback.

#Azure SDN Connector

FortiOS / Text
diagnose sys sdn status                       # connector statusshow system sdn-connectordiagnose debug application azd -1             # debug the Azure daemondiagnose debug enable(wait / trigger failover)diagnose debug disable ; diagnose debug reset

With HA and SDN failover the FortiGate needs a managed identity with permissions on the affected public IPs/route tables.

#Routing

FortiOS / Text
get router info routing-table allget router info routing-table details <ip>    # which route applies to an IP?get router info routing-table database        # inactive routes tooget router info kernel                        # kernel routing (FIB)show router staticconfig router static    edit 0        set dst <network> <mask>        set gateway <gateway>        set device "port2"    nextend

Azure: the gateway of a subnet is always .1 of the subnet. Probe route 168.63.129.16/32 via the interface where the probe arrives.

#BGP

FortiOS / Text
get router info bgp summaryget router info bgp neighborsget router info bgp neighbors <ip> advertised-routesget router info bgp neighbors <ip> received-routes      # needs soft-reconfiguration enableget router info bgp networkexecute router clear bgp ip <ip> soft                   # re-exchange routesshow router bgp

#Addresses, Services, Policies

FortiOS / Text
config firewall address    edit "<name>"        set subnet <network> <mask>    next    edit "<fqdn-name>"        set type fqdn        set fqdn "<host.domain>"    nextendconfig firewall addrgrp    edit "<group>"        set member "<addr1>" "<addr2>"        append member "<addr3>"               # add instead of replace    nextendconfig firewall service custom    edit "TCP-8443"        set tcp-portrange 8443    nextendconfig firewall policy    edit 0        set name "<name>"        set srcintf "port2"        set dstintf "port1"        set srcaddr "<source>"        set dstaddr "all"        set action accept        set schedule "always"        set service "HTTPS" "DNS"        set nat enable        set logtraffic all        set utm-status enable        set ssl-ssh-profile "certificate-inspection"        set av-profile "default"        set webfilter-profile "default"        set ips-sensor "default"        set application-list "default"    nextendconfig firewall policy    move <id> before <id2>                    # change order    edit <id>        set status disable                    # policy off (enable = on)    next    delete <id>endshow firewall policyshow firewall policy <id>diagnose firewall iprope list 100004          # loaded policies (internal)diagnose firewall fqdn list                   # resolved FQDN addresses

Rule: policies are evaluated top to bottom, the first match wins.

#VIP (Port Forwarding)

FortiOS / Text
config firewall vip    edit "vip-<name>"        set extip <external-ip>        set extintf "port1"        set portforward enable        set mappedip "<internal-ip>"        set extport 443        set mappedport 443    nextendshow firewall vip

Use the VIP as dstaddr in the policy. Deleting: policy first, then VIP. In Azure extip is the private IP of port1 (the public IP sits on the LB/NIC), or the frontend IP with floating IP.

#IPsec VPN

FortiOS / Text
get vpn ipsec tunnel summary                          # overview up/downdiagnose vpn tunnel list name <phase1>                # SAs, counters, selectorsdiagnose vpn ike gateway list name <phase1>           # phase 1 statusdiagnose vpn tunnel up <phase2> <phase1>              # bring the tunnel updiagnose vpn ike gateway clear name <phase1>          # renegotiate phase 1show vpn ipsec phase1-interfaceshow vpn ipsec phase2-interface # IKE debug (filter syntax differs slightly between FortiOS versions)diagnose vpn ike log filter cleardiagnose vpn ike log filter rem-addr4 <peer-ip>diagnose debug application ike -1diagnose debug enable(bring the tunnel up)diagnose debug disable ; diagnose debug reset
MessageCause
no SA proposal chosenencryption/hash/DH group do not match
peer SA proposal not match local policyphase 2 selectors (networks) differ
authentication failed / probable pre-shared secret mismatchwrong PSK
negotiation timeoutpeer does not answer – UDP 500/4500 blocked or wrong IP

#Troubleshooting: Sniffer, Debug Flow, Sessions

FortiOS / Text
# Sniffer: filter like tcpdump, verbosity 4 = with interface, packet count (Ctrl+C stops)diagnose sniffer packet any 'host <ip> and port 443' 4 20diagnose sniffer packet any 'net 10.0.0.0/24' 4 50diagnose sniffer packet port2 'icmp' 4 10diagnose sniffer packet any 'host 168.63.129.16 and port 8008' 4 20    # Azure LB probesdiagnose sniffer packet any 'host <ip>' 4 0 l                          # 0 = endless, l = with timestamp # Debug flow: why is a packet dropped/allowed?diagnose debug resetdiagnose debug flow filter cleardiagnose debug flow filter addr <ip>diagnose debug flow filter dport <port>diagnose debug flow show function-name enablediagnose debug console timestamp enablediagnose debug flow trace start 10diagnose debug enable(generate traffic)diagnose debug disablediagnose debug reset # Sessionsdiagnose sys session filter cleardiagnose sys session filter dst <ip>diagnose sys session filter dport 443diagnose sys session listdiagnose sys session clear                     # delete filtered sessionsdiagnose sys session stat                      # number of sessions # Reachabilityexecute ping <ip>execute ping-options source <own-ip>execute ping-options resetexecute traceroute <ip>execute telnet <ip> <port>execute ping <fqdn>                           # tests name resolution toodiagnose test application dnsproxy 7          # DNS cachediagnose test application dnsproxy 1          # clear DNS cache

#Debug Flow Messages

MessageMeaning
Denied by forward policy check (policy 0)no matching policy (implicit deny)
reverse path check fail, dropno route back to the source (RPF)
find a route: flag=... gw-<ip> via <port>selected route – wrong port = route missing
Allowed by Policy-<id>: SNATallowed, source NAT active
Allowed by Policy-<id>: (without SNAT)allowed, without NAT
iprope_in_check() check failed, dropaccess to the FortiGate itself not allowed (allowaccess/local-in)
no session matchedreturn packet without session (asymmetry)
DNAT <ip>:<port> -> <ip>:<port>VIP applies

#Admin Profiles and Access

FortiOS / Text
config system accprofile    edit "readonly-network"        set netgrp read        set fwgrp read        set loggrp read        set sysgrp read        set vpngrp read    nextendconfig system admin    edit "<user>"        set accprofile "readonly-network"        set password <password>        set trusthost1 <mgmt-network> <mask>        set two-factor fortitoken               # optional, if FortiToken is available    nextendget system admin list                           # who is signed in right nowexecute disconnect-admin-session <index>show system accprofile

Built-in profiles: super_admin (everything), prof_admin (everything except system) – create your own profiles for read access.

#SD-WAN

FortiOS / Text
diagnose sys sdwan health-check                 # quality per link (latency, jitter, loss)diagnose sys sdwan member                       # members and statusdiagnose sys sdwan service                      # rules and selected link (older versions: service4)get router info routing-table all               # SD-WAN routesshow system sdwanconfig system sdwan    config health-check        edit "internet"            set server "8.8.8.8" "1.1.1.1"            set protocol ping            set members 0                       # 0 = all members        next    endend

#SSL VPN and Dial-up VPN

FortiOS / Text
get vpn ssl monitor                             # signed-in SSL VPN usersexecute vpn sslvpn listexecute vpn sslvpn del-tunnel <index>           # disconnect a sessiondiagnose debug application sslvpn -1diagnose debug enable(try to sign in)diagnose debug disable ; diagnose debug resetdiagnose vpn ike gateway list                   # IPsec dial-up clients (FortiClient)

Note: in newer FortiOS versions the SSL VPN tunnel mode has been removed on many models – the replacement is IPsec dial-up (IKEv2) with FortiClient or ZTNA. Check the release notes before upgrading.

#Firmware Upgrade (HA)

FortiOS / Text
get system status                               # current versiondiagnose sys ha checksum cluster                # in sync before?show full-configuration system ha | grep -i uninterruptibleexecute backup full-config tftp <file> <server-ip>execute restore image tftp <image.out> <server-ip>    # or GUI: System -> Firmware -> Upload# afterwardsget system statusget system ha statusdiagnose sys ha history read

Sequence in HA with uninterruptible-upgrade enable: the secondary is upgraded first, then failover, then the old primary. Check the upgrade path beforehand with the FortiOS Upgrade Path Tool (docs.fortinet.com). Checklist: 28 Checklists.

#FortiManager Connection

FortiOS / Text
config system central-management    set type fortimanager    set fmg "<fortimanager-ip-or-fqdn>"endget system central-managementdiagnose debug application fgfmd -1             # debug the connection to FortiManagerdiagnose debug enablediagnose debug disable ; diagnose debug resetexecute ping <fortimanager-ip>execute telnet <fortimanager-ip> 541            # port 541 (FGFM) must be open

Then in FortiManager: Device Manager → authorize the device. From then on make changes only via FortiManager, otherwise the next install will conflict.

#Logs

FortiOS / Text
execute log filter resetexecute log filter category 0                 # 0 = trafficexecute log filter field dstip <ip>execute log filter view-lines 50execute log display

#Backup

FortiOS / Text
execute backup config tftp <file> <server-ip>execute backup full-config ftp <file> <server-ip> <user> <pass>

Easier: GUI → admin menu (top right) → Configuration → Backup.

Network and Firewall

#Azure Network Basics

Commands: Azure Network

#Rules of Thumb

TopicRule
Reserved IPs5 per subnet: network, .1 gateway, .2/.3 Azure DNS, broadcast → first usable IP is .4
Peeringnot transitive; create both sides; Allow forwarded traffic for NVA traffic
UDR prioritylongest prefix wins; on a tie: UDR > BGP > system
Hub-and-spoke with NVAspokes: 0.0.0.0/0 → NVA/ILB, BGP propagation off; NVA subnets without 0/0 UDR
Spoke ↔ spokeonly goes via the NVA if both spokes have the UDR and the policy allows it
Asymmetryoutbound and return path must use the same firewall – otherwise drop; fix: UDR on both sides or SNAT
IP forwardingthe NVA's NIC needs IP forwarding = on (Azure) and forwarding in the OS
GatewaySubnet UDRonly needed with an Azure VPN/ER gateway: spoke prefixes → NVA, no 0/0
Default outbound accessoff = VMs without PIP/LB outbound/NAT GW cannot reach the internet (intended with an NVA)
Outbound path (priority)NAT Gateway > public IP on the NIC > LB outbound rule > default outbound
Health probessource 168.63.129.16, must be allowed and routed on the NVA
Azure DNS168.63.129.16 – only reachable from Azure, not "passed through" via VPN/peering
Floating IPon = destination stays the frontend IP (VIPs); off = destination = backend IP (services on the NVA, IPsec)
ICMP via LB outboundnot translated – ping to the internet fails, test with curl
Private endpoint + DNSlink the zone to the VNet that does the DNS resolution
Private endpoint + UDRreturn traffic from PEs uses UDRs only with network policies on the subnet = on
Zoneslogical zones differ per subscription – check az vm list-skus
NSG on App Gatewayallow GatewayManager 65200-65535 + AzureLoadBalancer
Bastion NSGfixed mandatory rules (443 Internet/GatewayManager, 8080/5701 VNet …)
Special subnetsfixed names: GatewaySubnet, AzureFirewallSubnet, AzureBastionSubnet, RouteServerSubnet

#Subnet Sizes

CIDRAddressesusable in Azuretypical for
/2983– (minimum)
/281611small NVA interfaces
/273227GatewaySubnet (recommended), mgmt
/266459AzureFirewallSubnet, AzureBastionSubnet
/25128123
/24256251App Gateway v2, workloads
/23512507
/2210241019whole spoke VNet

#Common Ports

PortService
22 / 3389SSH / RDP
53DNS (UDP+TCP)
80 / 443HTTP / HTTPS
500 / 4500 UDPIPsec IKE / NAT-T
179BGP
123 UDPNTP
1433 / 5432 / 3306SQL Server / PostgreSQL / MySQL
445SMB (Azure Files)
8008default FortiGate probe port in Azure templates (HTTP)
541 / 514FortiManager / Syslog
65200–65535App Gateway v2 infrastructure (GatewayManager)

#Order of Checks When "It Doesn't Work"

1. DNS – does the name resolve correctly (private/public)? nslookup / Resolve-DnsName 2. Route – where does the packet go? effective routes, show-next-hop 3. NSG – allowed? list-effective-nsg, test-ip-flow 4. Firewall – does it arrive? sniffer; why dropped? debug flow 5. Return path – back through the same firewall? (asymmetry, SNAT) 6. Target – is the service listening? ss -tlnp / Get-NetTCPConnection, OS firewall

Clients and Logs

#Linux

Top 5: ip a · ip r get <target> · ss -tlnp · curl -v · sudo tcpdump -ni eth0 host <ip>

#Network

Bash
ip a                                   # IP addressesip -br a                               # briefip r                                   # routesip r get <target>                      # which path to a targetip neigh                               # ARP tabless -tlnp                               # listening TCP ports with processss -tunp                               # active connectionsss -s                                  # statisticscurl -s ifconfig.me; echo              # own public (egress) IPcurl -sI https://<target> | head -1    # HTTP statuscurl -v -m 10 https://<target> -o /dev/null      # error reason (TLS, timeout)curl -k --resolve <host>:443:<ip> https://<host>/    # test a host against a specific IPcurl -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://<target>   # status + timenc -zv <ip> <port>                     # port reachable?ping -c 4 <ip>                         # ttl drops by 1 per routerping -M do -s 1372 <ip>                # test MTU (DF bit)tracepath <ip> ; traceroute <ip> ; mtr <ip>sudo tcpdump -ni eth0 host <ip> and port 443sudo tcpdump -ni any icmpsudo tcpdump -ni eth0 -w capture.pcap  # for Wiresharksysctl net.ipv4.ip_forward             # IP forwarding on?sudo netplan apply                     # Ubuntu: reload network

#DNS

Bash
nslookup <name> ; dig +short <name> ; dig @<dns> <name>dig <name> +traceresolvectl status                      # which DNS server per interfaceresolvectl flush-cachescat /etc/resolv.confgetent hosts <name>                    # how the system resolves (incl. /etc/hosts)

#Firewall and Services

Bash
sudo ufw status verbose ; sudo ufw allow 443/tcp ; sudo ufw delete allow 443/tcpsudo iptables -L -n -vsudo iptables -t nat -L -n -vsudo nft list rulesetsudo systemctl status <service> ; sudo systemctl restart <service> ; sudo systemctl enable --now <service>systemctl list-units --type=service --state=failedsudo journalctl -u <service> -fsudo journalctl -b -p errsudo journalctl --since "1 hour ago"

#System

Bash
hostnamectl ; uname -a ; cat /etc/os-releasedf -h ; du -sh * ; free -h ; top ; htoplsblk ; sudo blkiduptime ; who ; last -n 10sudo apt update && sudo apt upgrade -ysudo apt install -y curl dnsutils traceroute mtr-tools tcpdump nginxtail -f /var/log/syslogsudo cat /var/log/cloud-init-output.log        # Azure: custom data / cloud-initsudo waagent --version                         # Azure Linux agent

#Files and Text

Bash
grep -rin "<text>" <folder>            # search recursivelyfind . -name "*.tf" -mtime -1          # changed in the last 24 hsed -i 's/old/new/g' <file>tar czf package.tgz <folder> ; tar xzf package.tgzsha256sum <file>chmod 600 <file> ; chown <user>:<group> <file>

#Mini Tests

Bash
while true; do curl -s -m 2 ifconfig.me; echo " $(date +%T)"; sleep 1; done      # continuous failover testwhile true; do ping -c1 -W1 <ip> >/dev/null && echo "$(date +%T) OK" || echo "$(date +%T) FAIL"; sleep 1; donewhile true; do nc -zvw1 <ip> 443 2>&1 | tail -1; sleep 1; done                     # continuous port testsudo python3 -m http.server 80                                                   # quick test web serverecho "ok $(hostname)" | sudo tee /var/www/html/index.html                        # nginx test page with hostname
Clients and Logs

#Windows / PowerShell

Top 5: Test-NetConnection <ip> -Port 443 · Resolve-DnsName · ipconfig /all · curl.exe -v · Get-NetTCPConnection -State Listen

#Network

PowerShell
ipconfig /allGet-NetIPConfigurationGet-NetIPAddress -AddressFamily IPv4 | Format-Table InterfaceAlias, IPAddress, PrefixLengthGet-NetRoute -AddressFamily IPv4 | Sort DestinationPrefix | Format-Tableroute printFind-NetRoute -RemoteIPAddress <target>              # which path to a targetTest-NetConnection <ip> -Port 443                   # port reachable?Test-NetConnection <ip> -Port 443 -InformationLevel DetailedTest-NetConnection <ip> -TraceRoutetracert <ip> ; pathping <ip>ping -f -l 1372 <ip>                                # test MTU (DF bit)Get-NetTCPConnection -State Listen | Sort LocalPortnetstat -ano | findstr :443                         # port -> process IDGet-Process -Id <pid>arp -acurl.exe -v https://<target>                        # real curl (not the PowerShell alias)curl.exe -k --resolve <host>:443:<ip> https://<host>/(Invoke-RestMethod https://api.ipify.org)           # own public IPInvoke-WebRequest https://<target> -UseBasicParsing | Select StatusCode

#DNS

PowerShell
Resolve-DnsName <name> [-Server <dns>]Resolve-DnsName <name> -Type A -DnsOnly             # without hosts file/cachenslookup <name> <dns>ipconfig /flushdns ; Clear-DnsClientCacheGet-DnsClientServerAddress -AddressFamily IPv4Get-DnsClientCache | Where Entry -like "*<part>*"notepad C:\Windows\System32\drivers\etc\hosts       # as administrator

#Windows Firewall

PowerShell
Get-NetFirewallProfile | Select Name, EnabledNew-NetFirewallRule -DisplayName "Allow 443" -Direction Inbound -Protocol TCP -LocalPort 443 -Action AllowNew-NetFirewallRule -DisplayName "Allow ICMP" -Protocol ICMPv4 -IcmpType 8 -Direction Inbound -Action AllowGet-NetFirewallRule -DisplayName "*<part>*" | Format-Table DisplayName, Enabled, Direction, ActionRemove-NetFirewallRule -DisplayName "Allow 443"

#Files and Scripts

PowerShell
Set-ExecutionPolicy -Scope Process Bypass       # allow scripts for this session onlyGet-ChildItem -Recurse | Unblock-File           # remove the download blockGet-ChildItem -Recurse *.ps1 | Unblock-File     # scripts onlyExpand-Archive .\package.zip -DestinationPath .\packageCompress-Archive .\folder -DestinationPath .\package.zipCopy-Item <source> <target> ; Move-Item ; Remove-Item -RecurseGet-FileHash <file>                             # checksum (SHA256)Get-Content <file> -Tail 50 -Wait               # like tail -fSelect-String -Path *.tf -Pattern "<text>"      # like grepGet-ChildItem -Recurse -Filter *.tf | Select-String "<text>"     # recursiveGet-ChildItem -Recurse | Where LastWriteTime -gt (Get-Date).AddDays(-1)$env:PATH -split ';'[IO.File]::WriteAllText("<path>", $text, (New-Object Text.UTF8Encoding($false)))   # UTF-8 without BOM$PSVersionTable.PSVersionwinget install Microsoft.AzureCLI ; winget install Hashicorp.Terraform ; winget install Git.Git

#System and Events

PowerShell
hostname ; whoami ; whoami /groupsGet-ComputerInfo | Select OsName, OsVersion, CsNameGet-Service | Where Status -eq RunningRestart-Service <name>Get-WinEvent -LogName System -MaxEvents 50 | Format-Table TimeCreated, Id, LevelDisplayName, Message -WrapGet-WinEvent -FilterHashtable @{LogName='System'; Level=2; StartTime=(Get-Date).AddHours(-24)}   # errors in 24 hGet-ChildItem Cert:\LocalMachine\My | Format-Table Subject, NotAfter, ThumbprintRestart-Computer -Force

#Continuous Tests

PowerShell
while ($true) { "$((Get-Date).ToString('HH:mm:ss')) $(curl.exe -s -m 2 http://<ip>)"; Start-Sleep 1 }while ($true) { $r = Test-NetConnection <ip> -Port 443 -WarningAction SilentlyContinue; "$((Get-Date).ToString('HH:mm:ss')) $($r.TcpTestSucceeded)"; Start-Sleep 1 }
Clients and Logs

#SSH and Certificates (OpenSSL)

#Keys

PowerShell
ssh-keygen -t ed25519 -C "<comment>"                        # default (not for Azure DevOps)ssh-keygen -t rsa -b 4096 -f $HOME\.ssh\id_rsa              # RSA (Azure DevOps)Get-Content $HOME\.ssh\id_rsa.pub | Set-Clipboard           # copy the public keyGet-Content $HOME\.ssh\id_rsa -TotalCount 1                 # check format: -----BEGIN OPENSSH PRIVATE KEY-----ssh-keygen -y -f $HOME\.ssh\id_rsa                          # derive public key from private keyssh-keygen -p -f $HOME\.ssh\id_rsa                          # change passphrasessh-keygen -lf $HOME\.ssh\id_rsa.pub                        # fingerprintssh-keygen -R <host-or-ip>                                  # remove an old host keyRemove-Item $HOME\.ssh\config -ErrorAction SilentlyContinue   # remove a broken SSH configaz sshkey create -g <rg> -n <name>                          # key as an Azure resource

#Connect and Test

PowerShell
ssh <user>@<host>ssh -i <private-key> <user>@<host>ssh -p 2222 <user>@<host>ssh -L 8443:<target>:443 <user>@<jump>        # local port forwarding -> https://localhost:8443ssh -D 1080 <user>@<jump>                     # SOCKS proxy via the jump hostssh -J <user>@<jump> <user>@<target>          # via jump hostscp <file> <user>@<host>:/tmp/scp <user>@<host>:/var/log/syslog .ssh -T git@ssh.dev.azure.com                  # Azure DevOps: "Shell access is not supported" = OKssh -T git@github.comssh -vT <host>                                # troubleshooting

#~/.ssh/config

Windows: C:\Users\<user>\.ssh\config – file without extension.

Text
Host ssh.dev.azure.com    HostName ssh.dev.azure.com    User git    IdentityFile ~/.ssh/id_rsa_azdo    IdentitiesOnly yes Host fgt-a    HostName <mgmt-ip>    User <admin> Host vm-internal    HostName 10.0.1.4    User azureuser    ProxyJump jump

Then ssh fgt-a is enough.

#ssh-agent

Enter the passphrase only once.

PowerShell
# once as administratorGet-Service ssh-agent | Set-Service -StartupType AutomaticStart-Service ssh-agent# as userssh-add $HOME\.ssh\id_rsassh-add -lgit config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"   # Git uses Windows SSH

#Permissions

Error "UNPROTECTED PRIVATE KEY FILE / too open":

PowerShell
icacls $HOME\.ssh\id_rsa /inheritance:r /grant:r "$($env:USERNAME):(R)"
Bash
chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_rsa

#Certificates with OpenSSL

Bash
# Show a server's certificate (issuer, expiry, SAN)openssl s_client -connect <host>:443 -servername <host> </dev/null 2>/dev/null \  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName # Read a certificate fileopenssl x509 -in cert.pem -noout -textopenssl x509 -in cert.pem -noout -enddateopenssl x509 -in cert.cer -inform der -out cert.pem          # DER -> PEM # Split / build a PFXopenssl pkcs12 -in cert.pfx -nokeys -out cert.pemopenssl pkcs12 -in cert.pfx -nocerts -nodes -out key.pemopenssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem -certfile chain.pemopenssl pkcs12 -in old.pfx -legacy -nodes -out all.pem       # old PFX with OpenSSL 3 # Do key and certificate match? (outputs must be identical)openssl x509 -noout -modulus -in cert.pem | openssl sha256openssl rsa  -noout -modulus -in key.pem  | openssl sha256 # CSR and self-signed test certificateopenssl req -new -newkey rsa:2048 -nodes -keyout key.pem -out req.csr -subj "/CN=<fqdn>"openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 \  -subj "/CN=<fqdn>" -addext "subjectAltName=DNS:<fqdn>"

PowerShell:

PowerShell
Get-PfxCertificate .\cert.pfx | Format-List Subject, NotAfter, DnsNameListNew-SelfSignedCertificate -DnsName <fqdn> -CertStoreLocation Cert:\CurrentUser\My

App Gateway listeners need a PFX with private key and the full chain.

Clients and Logs

#Log Analytics (KQL)

Run: Portal → Log Analytics workspace → Logs, or az monitor log-analytics query

#Skeleton

KQL
<Table>| where TimeGenerated > ago(1h)| where <field> == "<value>"            // exact (case-sensitive)| where <field> =~ "<value>"            // exact, case-insensitive| where <field> has "<word>"            // whole word (fast)| where <field> contains "<part>"       // substring (slower)| project TimeGenerated, <fields>| order by TimeGenerated desc| take 100 // Count / group / chart| summarize Count = count() by <field>, bin(TimeGenerated, 5m)| render timechart // Which tables have data?search * | where TimeGenerated > ago(1h) | summarize count() by $table // Columns of a table<Table> | getschema

#Application Gateway and WAF

KQL
// WAF – blocked/detected requests (AzureDiagnostics mode)AzureDiagnostics| where Category == "ApplicationGatewayFirewallLog" and action_s in ("Blocked","Matched","Detected")| project TimeGenerated, hostname_s, requestUri_s, clientIp_s, ruleGroup_s, ruleId_s, Message, details_message_s| order by TimeGenerated desc // Most frequent WAF rules (false-positive candidates)AzureDiagnostics| where Category == "ApplicationGatewayFirewallLog"| summarize Count = count() by ruleId_s, ruleGroup_s, Message| order by Count desc // Access (AzureDiagnostics mode)AzureDiagnostics| where Category == "ApplicationGatewayAccessLog"| project TimeGenerated, host_s, requestUri_s, httpStatus_d, serverStatus_s, serverRouted_s, timeTaken_d // Requests with errors (resource-specific table)AGWAccessLogs| where TimeGenerated > ago(1h) and HttpStatus >= 400| project TimeGenerated, Host, RequestUri, HttpStatus, ServerStatus, ServerRouted, ClientIp| order by TimeGenerated desc // WAF (resource-specific table)AGWFirewallLogs| where TimeGenerated > ago(1h)| project TimeGenerated, Action, RuleId, Message, ClientIp, RequestUri, Hostname

Which variant applies is set in the diagnostic setting (Azure diagnostics vs. Resource specific).

#Azure Activity

KQL
// Who changed what?AzureActivity| where TimeGenerated > ago(1d) and ActivityStatusValue == "Success"| project TimeGenerated, Caller, OperationNameValue, ResourceGroup, _ResourceId // DeletionsAzureActivity| where OperationNameValue endswith "/DELETE" and ActivityStatusValue == "Success"| project TimeGenerated, Caller, _ResourceId // Failed deployments / policy denialsAzureActivity| where ActivityStatusValue == "Failure"| project TimeGenerated, Caller, OperationNameValue, Properties

#Network

KQL
// NSG flow logs / Traffic Analytics (old)AzureNetworkAnalytics_CL| where SubType_s == "FlowLog" and FlowStatus_s == "D"        // D = denied| project TimeGenerated, SrcIP_s, DestIP_s, DestPort_d, NSGRule_s // VNet flow logs / Traffic Analytics (new)NTANetAnalytics| where TimeGenerated > ago(1h)| take 50                                                      // check fields with getschema // Azure Firewall – denied (resource-specific tables)AZFWNetworkRule| where TimeGenerated > ago(1h) and Action == "Deny"| project TimeGenerated, SourceIp, DestinationIp, DestinationPort, Protocol

#FortiGate (Syslog/CEF)

KQL
CommonSecurityLog| where DeviceVendor == "Fortinet"| where TimeGenerated > ago(1h)| project TimeGenerated, SourceIP, DestinationIP, DestinationPort, DeviceAction, Activity| order by TimeGenerated desc // Denied connections by destinationCommonSecurityLog| where DeviceVendor == "Fortinet" and DeviceAction in ("deny","block")| summarize Count = count() by DestinationIP, DestinationPort| order by Count desc

#VMs

KQL
// Last heartbeat per VMHeartbeat | summarize last = max(TimeGenerated) by Computer | order by last asc // Linux syslog errorsSyslog | where SeverityLevel in ("err","crit") | project TimeGenerated, Computer, ProcessName, SyslogMessage // Windows eventsEvent | where EventLevelName == "Error" | project TimeGenerated, Computer, Source, RenderedDescription
Reference

#Error → Fix

Tip: search for the text of the error message with Ctrl+F.

#Azure General

SymptomCauseFix
RequestDisallowedByPolicyAzure Policyaz policy assignment list, exemption or policy team
AuthorizationFailedrole missing on this scopeaz role assignment list --assignee <x> --all
RoleAssignmentExists (409)role assigned twiceuse/import the existing assignment
PrincipalNotFoundidentity just created (replication)wait briefly, pass --assignee-principal-type
ResourceAvailabilityZonesCannotBeModifiedtwo resources with the same name, different zonename templates with a placeholder (unique per instance)
SKU "not available in zone X"zone not available in this subscriptionaz vm list-skus … locationInfo
OperationNotAllowed … quotavCPU quota exhaustedaz vm list-usage, request a quota increase
MarketplacePurchaseEligibilityFailed / termsMarketplace terms not acceptedaz vm image terms accept --urn …
ScopeLockedlock on RG/resourceaz lock list, remove the lock temporarily
InUseSubnetCannotBeDeletedsubnet still in use (NIC, PE, delegation)az network vnet subnet show … ipConfigurations
Forbidden on Key Vaultdata role or vault firewallrole Key Vault Secrets User, network rules

#Network

SymptomCauseFix
Portal: DisablePeeringRoute is not supportedpreview feature in the portalcreate the route table via CLI/Terraform
VM has no internetdefault outbound off, no route/policycheck NVA/NAT GW, UDR and firewall policy
Ping to the internet fails, curl worksLB outbound does not translate ICMPtest with curl
No traffic spoke ↔ spokepolicy missing / peering without forwarded trafficfirewall policy, peering settings
Peering Disconnectedother side deleted/missingrecreate peering on both sides
Peering RemoteNotInSyncaddress space changedaz network vnet peering sync
Connection drops after secondsasymmetric routingUDR on both sides, SNAT on the NVA
Private endpoint resolves publiclyzone not linked to the DNS VNetprivate DNS link, check the VNet's DNS servers
NVA does not forwardIP forwarding on the NIC offaz network nic update --ip-forwarding true
LB does not distributeprobe failsallow probe port, route to 168.63.129.16
VPN connection NotConnectedPSK/proposal/selectors, UDP 500/4500gateway logs, IPsec on the other side

#Application Gateway / WAF

SymptomCauseFix
Backend Timeoutwrong port/protocol, network pathFQDN + HTTPS for App Service, check path
Backend 403name resolves publiclylink the private DNS zone to the gateway VNet
Backend Cannot connectpeering/NSG/NVABackend health → Troubleshoot
Backend certificate CN mismatch / not trustedhost header/certificate does not matchpick host name from backend, trusted root cert
Browser 502all backends unhealthyfix backend health
Browser 403WAF rulefirewall log (KQL), exclusion/rule override
Gateway Failed after a changeKey Vault certificate not readableidentity role, versionless secret ID, KV firewall
Deployment hangs / subnet errorUDR 0/0 or NSG blocks GatewayManagerremove UDR, add NSG rules

#FortiGate

SymptomCauseFix
Sniffer shows only in, no outfirewall dropsdebug flow (policy/route)
Sniffer shows nothingtraffic does not arriveUDR/effective routes, NSG, peering
Denied by forward policy checkno matching policycreate policy / check order
reverse path check failroute to the source missingadd a static route
HA: secondary does not take the configend forgotten / out of syncend, diagnose sys ha checksum cluster, execute ha synchronize start
HA: switches back after unsetoverride disable, uptimediagnose sys ha reset-uptime
Failover: public IP/route does not moveSDN connector without permissionsmanaged identity + roles, diagnose debug application azd -1
GUI empty / no forwardingBYOL without licenseapply license or use PAYG
GUI not reachableallowaccess missing / trusthostset allowaccess https, check admin trusthost
IPsec no SA proposal chosenproposals differalign phase 1/2 on both sides

#Terraform

SymptomCauseFix
must be replacedname/zone/image changedcheck the plan, moved/ignore_changes if needed
State lockedaborted runterraform force-unlock <id> or break the blob lease
already exists – to be managed via Terraform this resource needs to be importedresource already existsimport block or terraform import
Inconsistent dependency lock fileprovider version changedterraform init -upgrade
subscription ID could not be determinedazurerm 4.x without subscriptionARM_SUBSCRIPTION_ID or subscription_id in the provider
403 … AuthorizationPermissionMismatch (backend)data role on the storage account missingStorage Blob Data Contributor
Error acquiring the state lock in a pipelineparallel runserialize pipelines, then force-unlock
429 Too Many Requeststhrottling-parallelism=5, retry later

#SSH and Git

SymptomCauseFix
Public key authentication failedwrong/no RSA keyRSA key, ssh -vT, ~/.ssh/config
Load key …: error in libcryptoPuTTY / wrongly encoded fileregenerate or PuTTYgen "Export OpenSSH key"
UNPROTECTED PRIVATE KEY FILEfile permissions too openset permissions
REMOTE HOST IDENTIFICATION HAS CHANGEDserver rebuiltssh-keygen -R <host>
post-quantum key exchange warninginfo onlyignore
Git: rejected … fetch firstremote has newer commitsgit pull (maybe --allow-unrelated-histories), then push
Git: refusing to merge unrelated historiestwo separate startsgit pull origin main --allow-unrelated-histories
Git: CONFLICT (content)same place changedfix the file → git add → git commit
Git: fatal: not a git repositorywrong foldercd into the repo, git status
Git: Authentication failed (HTTPS)PAT expired / old credential storednew PAT; Windows Credential Manager: delete entry git:https://dev.azure.com
DevOps: TF402455: Pushes to this branch are not permittedbranch policy, PR onlyfeature branch + PR
DevOps PR: "There are merge conflicts"target branch changed the same linesresolve the conflict locally
DevOps PR: Complete not possiblerequired reviewer missing / build check redadd reviewer, check policies on the right side of the PR
DevOps PR: approval gone after pushpolicy "Reset votes on new pushes"ask the reviewer to Approve again
Dev branch deleted after PR"Delete source branch after merging" was onrecreate the branch: Repos → Branches → New branch (based on main)
Reference

#Glossary

Terms from the portal, DevOps and networking – explained briefly. On the web page: just search at the top.

#Git and Azure DevOps

TermExplanation
Repository (repo)folder with files and their complete change history
Clonecopy a repo from the server to your own machine
Commitsaved state with a message, local
Push / Pullsend commits to the server / get them from there
Fetchget the server state without changing your own files
Branchseparate line of work, e.g. main, Dev, feature/x
Mergetake the changes of one branch into another
Rebase"replay" your own commits onto the latest state of another branch
Squashcombine several commits into one
Cherry-pickcopy one specific commit into another branch
Merge conflictthe same place was changed in both branches – you have to decide
Stashput uncommitted changes aside
HEADthe commit you are currently on
Origindefault name for the repo on the server
Pull request (PR)request to take a branch into another – with review
Reviewerreviews and approves the PR (Required = mandatory, Optional = voluntary)
Approvethe reviewer agrees
Completethe PR is merged
Abandonthe PR is closed without merging
Revertcounter-change to an earlier commit/PR
Branch policyprotection of a branch: PR only, minimum number of reviewers, build must be green
Build validationpipeline that runs automatically on every PR
Pipelineautomatic build/deploy, described in YAML
Stage / Job / Stepstructure of a pipeline
Agentmachine the pipeline runs on
Environmenttarget in DevOps with approvals
Service connectiona pipeline's access to Azure (usually via service principal / workload identity)
Variable groupshared variables/secrets for pipelines
PATpersonal access token – password replacement for Git over HTTPS / CLI

#Azure General

TermExplanation
Tenantone Entra ID instance, e.g. your company's
Management groupfolder for subscriptions, inherits policies and permissions
Subscriptionbilling and permission boundary, contains resource groups
Resource group (RG)logical container for resources with the same lifecycle
Resource IDunique path /subscriptions/…/resourceGroups/…/providers/…
Regiondata center location, e.g. West Europe
Availability zoneseparate data centers within a region
SKUsize/performance tier of a resource
Lockprevents deletion (CanNotDelete) or changes (ReadOnly)
Tagkey/value on resources, e.g. Owner
Deallocatestop a VM and end compute costs
Providere.g. Microsoft.Network – must be registered in the subscription
Activity logwho changed what in Azure and when
Diagnostic settingsends a resource's logs/metrics e.g. to Log Analytics
Log Analytics workspacecentral log database, queried with KQL
Action groupwho is notified on an alert
Landing zoneprepared subscription with network, policies, permissions

#Permissions and Identities

TermExplanation
Entra ID(formerly Azure AD) identity service: users, groups, apps
RBACrole-based access: who (principal) may do what (role) where (scope)
Scopelevel of a role/policy: MG, subscription, RG, resource; / = whole tenant
Role assignmentcombination of principal + role + scope
Principaluser, group, service principal or managed identity
Object IDID of an identity in Entra – needed for role assignments
App registrationdefinition of an application (app ID, secrets)
Service principal (SP)an app's "user account" in the tenant, gets roles
Client ID / App IDthe app's identifier for sign-in
Client secretan app's password – expires!
Managed identitypassword-less identity for Azure resources (system- or user-assigned)
Workload identity federationsign-in without a secret via token, e.g. from pipelines
PIMPrivileged Identity Management – activate roles only when needed and time-limited
Elevate accessGlobal Admin grants themselves User Access Administrator on /
Policyrule that allows/denies/audits resources (Deny, Audit, Modify)
Exemptiontime-limited exception from a policy
Key Vaultsecure store for secrets, keys, certificates
Soft delete / purge protectiondeleted items remain recoverable / may not be permanently deleted

#Network

TermExplanation
VNetyour own private network in Azure
Subnetpart of a VNet
Address space / CIDRe.g. 10.0.0.0/22 – /22 = 1024 addresses
Peeringdirect connection of two VNets, not transitive
Hub-and-spokecentral hub VNet (firewall, VPN) with connected spoke VNets
UDR / route tableyour own routes, e.g. 0.0.0.0/0 → firewall
Next hopwhere a packet is sent next
Effective routesall routes that actually apply to a NIC
NSGnetwork security group – simple firewall rules on subnet/NIC
Service tagplaceholder for IP ranges, e.g. AzureLoadBalancer
NVAnetwork virtual appliance, e.g. FortiGate as a VM
ILB / ELBinternal / external load balancer; ILB in front of the FortiGate for HA
Health probeLB checks whether a backend responds (source 168.63.129.16)
Floating IPdestination address stays the LB's frontend IP
HA portsone LB rule for all ports/protocols
SNAT / DNATsource / destination address is rewritten
Asymmetric routingreturn path does not use the same firewall → connection breaks
Private endpointprivate IP for a PaaS service in your own VNet
Private DNS zoneresolves e.g. *.privatelink.… to private IPs
Application Gatewaylayer 7 load balancer for HTTP(S), optionally with WAF
WAFweb application firewall (Detection = log only, Prevention = block)
Listenerport/host name on which the App Gateway accepts requests
Backend poolservers behind the App Gateway / LB
NAT Gatewayfixed outbound internet access for a subnet
Bastionsecure RDP/SSH access via the browser, without a public IP on the VM
VPN gateway / local network gatewayAzure side / representation of the remote side of an S2S VPN
BGProuting protocol for automatic exchange of routes

#FortiGate

TermExplanation
FGCPFortiGate Clustering Protocol – high availability active/passive
Primary / Secondaryactive / passive device in HA
Session pickupconnections survive a failover
SDN connectorconnection to Azure, rewrites routes/IPs on failover
Policyfirewall rule (top to bottom, first match wins)
VIPvirtual IP – port forwarding/DNAT to the inside
Address / address groupaddress object / group of addresses
Snifferpacket capture on the FortiGate
Debug flowshows why a packet is allowed or dropped
VDOMvirtual domain – separate FortiGate instances on one device
Phase 1 / Phase 2IPsec: building the management connection / the actual tunnel SAs
SD-WANdistributing traffic across several links by rules and quality
PAYG / BYOL / FortiFlexlicense models: usage-based via Azure / own license / flexible quota

#Infrastructure as Code

TermExplanation
IaCinfrastructure as code – resources are described in files instead of clicked
Bicep / ARMMicrosoft's IaC language / the JSON format underneath
.bicepparamparameter file for Bicep, usually one per environment
what-ifpreview of what a deployment would change
Deployment stackBicep deployment that manages its resources and can clean up
AVMAzure Verified Modules – reviewed Microsoft modules
TerraformIaC tool from HashiCorp, provider azurerm
StateTerraform's memory: which resource belongs to which code
Plan / Applypreview / execute in Terraform
Driftdifference between code and the actual resource (e.g. change in the portal)
Idempotentrunning it several times gives the same result
Reference

#Text Templates

Copy, fill in <…>, send. On the web page "Copy block" copies the whole template.

#Pull Request Description

text
## What<Short: what changes, e.g. "New parameters for spoke app-x in dev.bicepparam"> ## Why<Ticket / requirement / bug, e.g. "#1234 – connect app-x to the hub"> ## Impact- Environment: <dev / prod>- New: <resources>- Changed: <resources>- Deleted / replaced: <none / which> ## Checked- [ ] lint / validate without errors- [ ] what-if / plan reviewed (output below)- [ ] tested: <how> ## what-if / plan<paste output here>

#Change Request

text
Title: <short description> Description: <what will be done, in 2–3 sentences>Reason: <why, ticket>Affected systems: <subscriptions, resources, applications>Time: <date, from–to>Expected impact: <none / short interruption of approx. X minutes for Y>Risk: <low / medium / high> – <reason>Steps:  1. <step>  2. <step>Test afterwards: <how to check that everything works>Rollback plan: <how it is undone, how long that takes>Performed by: <name>Business contact: <name>

#Outage Notice

text
Subject: [OUTAGE] <system> – <short symptom> Since: <time>Affected: <who / what, e.g. "access to app-x from outside">Impact: <e.g. "application not reachable, internal use works">Status: under investigationNext update: <time, e.g. in 30 minutes>Contact: <name, phone>

All-clear:

text
Subject: [RESOLVED] <system> – <short symptom> Resolved since: <time>Duration: <from–to>Cause: <short, e.g. "missing route after a change to the route table">Action: <what was done>Follow-up: <if planned>

#Maintenance Announcement

text
Subject: [MAINTENANCE] <system> on <date>, <from>–<to> What: <e.g. firmware update of the FortiGate cluster in the hub>When: <date>, <from>–<to>Impact: <e.g. short interruption of existing connections (< 1 minute) during failover>What you need to do: <nothing / save sessions / …>Contact during maintenance: <name, phone>

#Handover to On-Call / Colleagues

text
Handover <date> – from <from> to <to> Open topics:- <topic> – status: <…> – next step: <…> Running changes / maintenance:- <…> Observations (keep an eye on):- <e.g. WAF reports increased blocks on app-x> Important links:- Codex, dashboards, tickets: <…>

#Commit Messages

text
<area>: <what> (<ticket>) Hub: add spoke app-x to GatewaySubnet UDR (#1234)FortiGate: add policy app-x -> internet (HTTPS)Params dev: adjust address space for spoke app-yFix: wrong probe path on the App Gateway

Imperative, short, area first – then you find it later with git log --grep "<area>".

No hits. Try fewer or different terms – all words must appear in the same line or in the title.