Network Security Groups (NSGs) are a key security feature in Azure, acting as virtual firewalls to control network traffic to and from your Virtual Machines (VMs). Without NSGs attached to either a VM’s network interface or its subnet, your VM can be exposed to unwanted traffic and potential security risks.
To help Azure administrators and cloud engineers identify such risks, I created AzVM-NSGSecurityAudit — a simple PowerShell script that scans your Azure subscriptions and lists all VMs not protected by any NSG.
Contents
🚨 Why This Script Matters
In complex Azure environments, it’s easy to overlook whether every VM has proper NSG protection. While Azure provides tools to configure and enforce NSGs, misconfigurations happen. This script helps:
- Identify VMs without any NSG attached either at the network interface or subnet level
- Provide quick visibility across all subscriptions in your tenant
- Support security reviews, compliance checks, or manual audits
🤖 What AzVM-NSGSecurityAudit Does
The script:
- Connects to your Azure account and enumerates all subscriptions
- Retrieves all Network Security Groups (NSGs) and collects their associated network interfaces and subnets
- Retrieves all VMs and inspects their primary network interface and subnet
- Flags VMs which have neither the NIC nor subnet protected by an NSG
- Outputs a simple list of unprotected VMs, including VM name, resource group, and subscription name
⚙️ How to Use It
- Ensure you have the Azure PowerShell module installed and are logged in (Connect-AzAccount).
- Run the script locally or integrate it into automation runbooks.
- Review the output in the console or extend it to export as CSV or JSON as needed.
📋 Script
Link: https://github.com/simon-vedder/powershell/scripts/audits/AzVM-NSGSecurityAudit.ps1
<#
.TITLE
AzVM-NSGSecurityAudit
.SYNOPSIS
Find all Virtual Machines which are not protected by an NSG.
.DESCRIPTION
This PowerShell script will find every Virtual Machine within your tenant to find resources without an assigned Network Security Group.
It will not review if the rules within the NSG are secure.
.TAGS
PowerShell, Diagnostic, Security
.MINROLE
Reader
.PERMISSIONS
Microsoft.Resources/subscriptions/read
Microsoft.Network/networkSecurityGroups/read
Microsoft.Compute/virtualMachines/read
.AUTHOR
Simon Vedder
.VERSION
1.0
.CHANGELOG
1.0 - Initial release
.LASTUPDATE
2025-06-21
.NOTES
.USAGE
- Run locally or use it in your runbook while changing Connect-AzAccount to use a non-interactive authentication
#>
$unprotectedVMs = @()
$protectedVMs = @() #optional
Write-Host "AzVM-NSGSecurityAudit"
Write-Host "- by Simon Vedder -"
Write-Host "---------------------"
Write-Host (Get-Date)
#LOGIN
Connect-AzAccount | Out-Null
# Get all Subscriptions
try {
Write-Host "Retrieve Azure Subscriptions"
$subscriptions = Get-AzSubscription -ErrorAction Stop
Write-Host "Subscriptions successfully retrieved" -ForegroundColor Green
}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
# Run code in each subscription
foreach($sub in $subscriptions)
{
# Change the subscription
Set-AzContext -SubscriptionId $sub.Id | Out-Null
Write-Host ""
Write-Host "SubscriptionName: $($sub.Name)"
# Get all NSGs within the subscription
try {
Write-Host "1. Get all NSGs"
$NSGs = Get-AzNetworkSecurityGroup -ErrorAction Stop
}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
if (-not $NSGs) {
Write-Warning "No Azure Network Security Groups found in the current context."
return
}
else {
Write-Host "NetworkSecurityGroups successfully retrieved" -ForegroundColor Green
}
# Set variables with NSG Ids that are assigned to NICs and Subnets
$protectedNICs = $NSGs.NetworkInterfaces.Id
$protectedSubnets = $NSGs.Subnets.Id
# Get all VMs within the Subscription
try {
Write-Host "2. Get all VMs"
$VMs = Get-AzVM
}
catch {
Write-Error "Error: $($_.Exception.Message)"
}
if (-not $VMs) {
Write-Warning "No Azure Virtual Machines found in the current context."
return
}
else {
Write-Host "VirtualMachines successfully retrieved" -ForegroundColor Green
}
#Begin the check
Write-Host "- Start protection check: "
foreach ($VM in $VMs)
{
# Set variables with the VMs NIC and SubnetId
$NIC = Get-AzNetworkInterface -Name ($VM.NetworkProfile.NetworkInterfaces[0].Id.Split('/')[-1]) -ResourceGroupName $VM.ResourceGroupName
$subnetId = $NIC.IpConfigurations[0].Subnet.Id
# Check if the the protected NICs and protectedSubnets contain the VMs resources (NIC & Subnet)
if ($protectedNICs -notcontains $NIC.Id -and $protectedSubnets -notcontains $subnetId)
{
Write-Host "VM found: $($VM.Name)" -ForegroundColor Cyan
$unprotectedVMs += [PSCustomObject]@{
UnprotectedVM = $VM.Name
ResourceGroup = $VM.ResourceGroupName
Subscription = $sub.Name
}
}
else {
$protectedVMs += [PSCustomObject]@{
ProtectedVM = $VM.Name
ResourceGroup = $VM.ResourceGroupName
Subscription = $sub.Name
}
}
}
}
#Output
if ($unprotectedVMs.Count -gt 0)
{
Write-Host ""
Write-Host "--- Overview ---"
$unprotectedVMs | Format-Table -AutoSize
$protectedVMs | Format-Table -AutoSize #uncomment if you want to see protected VMS
}
else {
Write-Host "Every VM is protected by a NSG!" -ForegroundColor Green
}
✅ Output

💡 Conclusion
AzVM-NSGSecurityAudit is a straightforward, effective tool to improve your Azure security posture by ensuring all your VMs have NSG protection. It’s a great first step in cloud security hygiene, especially useful for administrators managing multiple subscriptions.
Feel free to use and modify the script to fit your environment and extend it as needed.