This blog post shows how you can configure NSG Flow Logs for all network security groups (NSGs) in an Azure Subscription with the use of an Azure PowerShell script.
NSG flow logs is a feature of Network Watcher which enables you to capture information about IP traffic flowing in and out of NSGs. Afterwards you can use those logs to check for anomalies or to get more insight into suspected breaches.
If you want to read some more about NSG flow logs, you can do so via the following Microsoft Docs link: NSG flow logs documentation
To automate the configuration process of the NSG flow logs for all NSGs in a specific subscription, I wrote the below Azure PowerShell script which does all of the following:
- Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
- Suppress breaking change warning messages.
- Store the specified set of tags in a hash table.
- Register Insights provider (Microsoft.Insights) in order for flow logging to work, if not already registered. Registration may take up to 10 minutes.
- Create a resource group for the storage account which will store the flow logs, if it not already exists
- Create a general purpose v2 storage account for storing the flow logs with specific configuration settings (like minimum TLS version set to 1.2, allow public access set to disabled), if it not already exists. Also apply the necessary tags to this storage account.
- Enable NSG Flow logs and Traffic Analytics* for all NSGs.
*Traffic analytics is a fully cloud-based solution, which provides you with visibility into network activities from users and applications inside your Azure VNets. If you want to read some more about Traffic analytics, you can do so via the following Microsoft Docs link: Traffic analytics documentation
To use the script copy and save it as Configure-NSG-Flow-Logs-for-all-NSGs-in-an-Azure-Subscription.ps1 or download it from GitHub. Then before using the script, adjust all variables to your use and then run the customized script from Windows Terminal, Visual Studio Code, or Windows PowerShell. Or you can simply run it from Cloud Shell.
Prerequisites
- An Azure subscription.
- An Azure Administrator account with the necessary RBAC roles.
- An existing Network Watcher.
- An existing VNet(s) with one or more subnets associated with different NSGs.
- An existing Log Analytics workspace.
- At least Azure Az PowerShell module version 5.9.0
- Change all the variables in the script where needed to fit your needs (you can find an adjusted example in the screenshot below).





Azure PowerShell script
If you are not running the script from Cloud Shell, don’t forget to sign in with the Connect-AzAccount cmdlet to connect your Azure account. And if you are using multiple Azure subscriptions, select the proper subscription with the Set-AzContext cmdlet before running the script.

<#
.SYNOPSIS
A script used to configure NSG FLog logs for all NSG's used in an Azure Subscription.
.DESCRIPTION
A script used to configure NSG FLog logs for all NSG's used in an Azure Subscription.
The script will do all of the following:
Check if the PowerShell window is running as Administrator (when not running from Cloud Shell), otherwise the Azure PowerShell script will be exited.
Suppress breaking change warning messages.
Store the specified set of tags in a hash table.
Register Insights provider (Microsoft.Insights) in order for flow logging to work, if not already registered. Registration may take up to 10 minutes.
Create a resource group for the storage account which will store the flow logs, if it not already exists
Create a general purpose v2 storage account for storing the flow logs with specific configuration settings, if it not already exists. Also apply the necessary tags to this storage account.
Enable NSG Flow logs (Version 2) and Traffic Analytics for all NSG's.
.NOTES
Filename: Configure-NSG-Flow-Logs-for-all-NSGs-in-an-Azure-Subscription.ps1
Created: 18/08/2022
Last modified: 18/08/2022
Author: Wim Matthyssen
Version: 1.0
PowerShell: Azure PowerShell and Azure Cloud Shell
Requires: PowerShell Az (v5.9.0) and Az.Network (v4.16.0)
Action: Change variables were needed to fit your needs
Disclaimer: This script is provided "As Is" with no warranties.
.EXAMPLE
Connect-AzAccount
Get-AzTenant (if not using the default tenant)
Set-AzContext -tenantID "<xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx>" (if not using the default tenant)
Set-AzContext -Subscription "<SubscriptionName>" (if not using the default subscription)
.\Configure-NSG-Flow-Logs-for-all-NSGs-in-an-Azure-Subscription.ps1
.LINK
https://wmatthyssen.com/2022/08/18/azure-networking-configure-nsg-flow-logs-for-all-nsgs-in-an-azure-subscription-with-an-azure-powershell-script/
#>
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Variables
$abbraviationLog = "log"
$region = "westeurope"
$rgNameStorage = #<your storage resource group name here> The name of new or existing storage resource group in which the storage account holing the flow logs exists. Example: "rg-prd-myh-storage-01"
$rgNameNetworkWatcher = #<your Network Watcher resource group name here> The name of the Network Watcher. Example: "rg-prd-myh-networkwatcher-01"
$networkWatcherName = #<your Network Watcher name here> The name of the Network Watcher. Example: "nw-prd-myh-we-01"
$logAnalyticsWorkspaceName = #<your Log Analytics workspace name here> The name of your existing Log Analytics workspace. Example: "law-hub-myh-01"
$storageAccountName = #<your Storage Account name here> The name of the storage account used to store your flow logs. Example: "stprdmyhlog01"
$storageAccountSkuName = "Standard_LRS"
$storageAccountType = "StorageV2"
$storageMinimumTlsVersion = "TLS1_2"
$nsgFlowLogsRetention = "90"
$trafficAnalyticsInterval = "60"
$tagSpokeName = #<your environment tag name here> The environment tag name you want to use. Example: "Env"
$tagSpokeValue = "$($spoke[0].ToString().ToUpper())$($spoke.SubString(1))"
$tagCostCenterName = #<your costCenter tag name here> The costCenter tag name you want to use. Example: "CostCenter"
$tagCostCenterValue = #<your costCenter tag value here> The costCenter tag value you want to use. Example: "23"
$tagCriticalityName = #<your businessCriticality tag name here> The businessCriticality tag name you want to use. Example: "Criticality"
$tagCriticalityValue = #<your businessCriticality tag value here> The businessCriticality tag value you want to use. Example:"High"
$tagPurposeName = #<your purpose tag name here> The purpose tag name you want to use. Example: "Purpose"
$tagPurposeValue = (Get-Culture).TextInfo.ToTitleCase($abbraviationLog.ToLower())
$tagSkuName = #<your SKU tag name here> The SKU tag name you want to use. Example: "Sku"
$tagSkuValue = $storageAccountSkuName
$global:currenttime= Set-PSBreakpoint -Variable currenttime -Mode Read -Action {$global:currenttime= Get-Date -UFormat "%A %m/%d/%Y %R"}
$foregroundColor1 = "Red"
$foregroundColor2 = "Yellow"
$writeEmptyLine = "`n"
$writeSeperatorSpaces = " - "
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Check if PowerShell runs as Administrator (when not running from Cloud Shell), otherwise exit the script
if ($PSVersionTable.Platform -eq "Unix") {
Write-Host ($writeEmptyLine + "# Running in Cloud Shell" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## Start script execution
Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
} else {
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdministrator = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
## Check if running as Administrator, otherwise exit the script
if ($isAdministrator -eq $false) {
Write-Host ($writeEmptyLine + "# Please run PowerShell as Administrator" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
Start-Sleep -s 3
exit
}
else {
## If running as Administrator, start script execution
Write-Host ($writeEmptyLine + "# Script started. Without any errors, it will need around 1 minute to complete" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
}
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Save Log Analytics workspace from the managment subscription in a variable
# ! Delete this part if the Log Analytics is in the same subscription as the current one or change the -like search parameter if it is stored in another subscription !
$subNameCurrent = (Get-AzContext).Subscription
$subNameManagement = Get-Azsubscription | Where-Object {$_.Name -like "*management*"}
Set-AzContext -subscriptionId $subNameManagement.subscriptionId | Out-Null
$workSpace = Get-AzOperationalInsightsWorkspace | Where-Object Name -Match $logAnalyticsWorkspaceName
Set-AzContext -subscriptionId $subNameCurrent | Out-Null
Write-Host ($writeEmptyLine + "# Log Analytics workspace variable created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Store the specified set of tags in a hash table
$tags = @{$tagSpokeName=$tagSpokeValue;$tagCostCenterName=$tagCostCenterValue;$tagCriticalityName=$tagCriticalityValue}
Write-Host ($writeEmptyLine + "# Specified set of tags available to add" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Register Insights provider (Microsoft.Insights) in order for flow logging to work, if not already registered. Registration may take up to 10 minutes
# Register Microsoft.Insights resource provider
Register-AzResourceProvider -ProviderNamespace Microsoft.Insights | Out-Null
Write-Host ($writeEmptyLine + "# Microsoft.Insights resource provider currently registering or already registerd" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
# ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create a resource group for the storage account which will store the flow logs, if it not already exists
try {
Get-AzResourceGroup -Name $rgNameStorage -ErrorAction Stop | Out-Null
} catch {
New-AzResourceGroup -Name $rgNameStorage -Location $region -Force | Out-Null
}
# Save variable tags in a new variable to add tags
$tagsResourceGroup = $tags
# Add Purpose tag to tagsResourceGroup
$tagsResourceGroup += @{$tagPurposeName = "Storage"}
# Set tags rg storage
Set-AzResourceGroup -Name $rgNameStorage -Tag $tagsResourceGroup | Out-Null
Write-Host ($writeEmptyLine + "# Resource group $rgNameStorage available" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Create a general purpose v2 storage account for storing the flow logs with specific configuration settings, if it not already exists. Also apply the necessary tags to this storage account.
try {
Get-AzStorageAccount -ResourceGroupName $rgNameStorage -Name $storageAccountName -ErrorAction Stop | Out-Null
} catch {
New-AzStorageAccount -ResourceGroupName $rgNameStorage -Name $storageAccountName -SkuName $storageAccountSkuName -Location $region -Kind $storageAccountType `
-AllowBlobPublicAccess $false -MinimumTlsVersion $storageMinimumTlsVersion | Out-Null
}
# Save variable tags in a new variable to add tags
$tagsStorageAccount = $tags
# Add Purpose tag to tagsStorageAccount
$tagsStorageAccount += @{$tagPurposeName = $tagPurposeValue}
# Add Sku tag to tagsStorageAccount
$tagsStorageAccount += @{$tagSkuName = $tagSkuValue}
# Set tags storage account
Set-AzStorageAccount -ResourceGroupName $rgNameStorage -Name $storageAccountName -Tag $tagsStorageAccount | Out-Null
Write-Host ($writeEmptyLine + "# Storage account $storageAccountName created" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor2 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Enable NSG Flow logs (Version 2) and Traffic Analytics for all NSG's
$networkWatcher = Get-AzNetworkWatcher -Name $networkWatcherName -ResourceGroupName $rgNameNetworkWatcher
$storageAccount = Get-AzStorageAccount -ResourceGroupName $rgNameStorage -Name $storageAccountName
$nsgs = Get-AzNetworkSecurityGroup
Foreach ($nsg in $nsgs) {
# Configure Flow log and Traffic Analytics
Set-AzNetworkWatcherFlowLog -Name ($nsg.Name + "-flow-log") -NetworkWatcher $networkWatcher -TargetResourceId $nsg.Id -StorageId $storageAccount.Id -Enabled $true -FormatType Json `
-FormatVersion 2 -EnableTrafficAnalytics -TrafficAnalyticsWorkspaceId ($workSpace.ResourceId) -TrafficAnalyticsInterval $trafficAnalyticsInterval -EnableRetention $true `
-RetentionPolicyDays $nsgFlowLogsRetention -Tag $tags -Force | Out-Null
Write-Host ($writeEmptyLine + "# NSG FLow logs and Traffic Analytics for $($nsg.Name) enabled" + $writeSeperatorSpaces + $currentTime) -foregroundcolor $foregroundColor2 $writeEmptyLine
}
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Query Flow Log Status
# Get-AzNetworkWatcherFlowLogStatus -NetworkWatcher $networkWatcher -TargetResourceId $nsg1.Id | Out-Null
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
## Write script completed
Write-Host ($writeEmptyLine + "# Script completed" + $writeSeperatorSpaces + $currentTime)`
-foregroundcolor $foregroundColor1 $writeEmptyLine
## ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------








Audit NSG Flow Logs existence with Azure Policy
After you ran the script, you can also use the built-in policy “Flow log should be configured for every network security group” to audit all NSGs in a given scope, and to check for the existence of linked Flow Logs.



I hope this Azure PowerShell script is useful for you whenever you want to start using NSG flow logs into your Azure environment.
If you have any questions or recommendations about it, feel free to contact me through my Twitter handle (@wmatthyssen) or to just leave a comment.
Pingback: Configure NSG Waft Logs for all NSGs in an Azure Subscription with an Azure PowerShell script – Wim Matthyssen - Firnco
Pingback: Azure Networking: Identify NSGs without NSG Flow Logs and Traffic Analytics configured with Azure Monitor Network Insights – Wim Matthyssen