Get-AzureCloudNetworkSecurityGroupRule.ps1

<#
.SYNOPSIS
    This function will retrieve Network Security Group Rules in Azure Cloud
.DESCRIPTION
    Use this function as needed, view Network Security Group rules in mass, or even export the rules to a file
.NOTES
   .NOTES
    Version: V.1.0.0
    Date Written: 01/04/2024
    Written By: Nik Chikersal
    CopyRight: (c) Nik Chikersal
    
    Change Log:
    N/A
    v.1.0.0 - 01/04/2024 - Nik Chikersal - Initial Version

.LINK
https://www.powershellgallery.com/packages/AzureCloud
.EXAMPLE
Get-AzureCloudNetworkSecurityGroupRule -Name "NSG-Name"
    This example will retrieve Security Group Rules for a specific Network Security Group in Azure Cloud

Get-AzureCloudNetworkSecurityGroupRule -All
    This example will retrieve Security Group Rules for all Network Security Groups in Azure Cloud

Get-AzureCloudNetworkSecurityGroupRule -All -ExportToFile $True
    This example will retrieve Security Group Rules for all Network Security Groups Azure Cloud and export the output to a .TXT file

Get-AzureCloudNetworkSecurityGroupRule -All -ExportToFile $True -FIleType "csv"
    This example will retrieve Security Group Rules for all Network Security Groups Azure Cloud and export the output to a .CSV file

Get-AzureCloudNetworkSecurityGroupRule -All -ExportToFile $True -FIleType "html"
    This example will retrieve Security Group Rules for all Network Security Groups Azure Cloud and export the output to a .HTML file
#>

function Get-AzureCloudNetworkSecurityGroupRule {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [switch]$All,
        [Parameter(Mandatory = $false, Position = 2, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [boolean]$ExportToFile,
        [Parameter(Mandatory = $false, Position = 3, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()][ValidateSet('html', 'csv')]
        [string]$FileType
    )

    Connect-Azure -CheckIfConnected
    
    #param validation to ensure -All OR -Name is used
    if (! $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("All") -and 
    (! $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Name"))) {
        Write-Error "Script cannot continue, it needs at least one of the following parameters"
        Write-Host "Use the "  -BackgroundColor "Yellow" -ForegroundColor "Red" -NoNewline; Write-Host " -Name" -BackgroundColor "Cyan" -ForegroundColor "Red" -NoNewline; Write-Host " Parameter to display all Resources"  -BackgroundColor "Yellow" -ForegroundColor "Red"
        Write-Host "Use the "  -BackgroundColor "Yellow" -ForegroundColor "Red" -NoNewline; Write-Host " -All" -BackgroundColor "Cyan" -ForegroundColor "Red" -NoNewline; Write-Host " Parameter to display all Resources"  -BackgroundColor "Yellow" -ForegroundColor "Red"
        break
    }

    #If the -FileType Param exists, check if the -ExportToFile Param exists
    if ($FileType) {
        if (!$PSCmdlet.MyInvocation.BoundParameters.ContainsKey(("ExportToFile"))) {
           Write-Error "Script cannot continue, The -FileType Parameter requires the -ExportToFile Parameter"
           break
        }
        #If the -FileType Param exists, check if the -ExportToFile Param is set to $true and NOT $false
        elseif($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("ExportToFile") -and -not ([boolean]::Equals($true, $ExportToFile)) -and ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("FileType"))) {
            Write-Error "Script cannot continue, the -ExportToFile Parameter has to be set to $true when using the -FileType Parameter"
            break
        }
    }
    #Azure subscription validation to ensure subscriptions can be queried with existing permissions
    $global:Results = [System.Collections.ArrayList]::new()
    $Subscriptions = (Get-AzSubscription).Name
     if ([string]::IsNullOrEmpty($Subscriptions)) { 
        Write-Error "Could not find any Subscriptions in this Azure Tenant with current permissions"
        break
    }
    elseif (![string]::IsNullOrEmpty($Subscriptions)) {
     foreach ($Subscription in $Subscriptions) {
        [void](Set-AzContext -Subscription $Subscription)
  try {
    if (! (Get-Command -Name Get-AzNetworkSecurityGroup -ErrorAction SilentlyContinue)) {
        Write-Warning "The required command doesn't exist on this machine. Please re-install the AzureCloud Module to Automatically Install the required AZ Modules"
        break
    }   #If -Name is used, retrieve one NSG record, if -All is used, retrieve all NSG records
        switch ($PSCmdlet.MyInvocation.BoundParameters.Keys) {
            'Name' {
                $NSGs = Get-AzNetworkSecurityGroup -Name $Name
                  if ([string]::IsNullOrEmpty($NSGs)) { 
                     Write-Warning "Could not find the following Network Security Group(s): $($Name)"
                     break
                   }
                }
            'All' {
                $NSGs = Get-AzNetworkSecurityGroup
                  if ([string]::IsNullOrEmpty($NSGs)) { 
                    Write-Warning "Could not find any Network Security Group(s) in the Tenant $(Get-AzDomain).DefaultDomain"
                    break
                   }
                }
            }
        }
        catch {
           return [PSCustomObject][Ordered]@{
                Error   = $global:Error.Exception.Message[0]
                Failure = $global:Error.Failure[0]
            }
        }
        #Convert from JSON and store results into a custom object after the loop
        foreach ($NSG in $NSGs) {
            if ([string]::IsNullOrEmpty($NSGs)) { 
                Write-Warning "Could not find the following Network Security Group(s): $($NSGs)"
                break
            }
            elseif (![string]::IsNullOrEmpty($NSGs)) {

                  [void][array]$global:Results.Add([PSCustomObject][Ordered]@{
                      Name        = $NSG.Name
                      AzureSub    = $Subscription
                      Action      = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).Access)
                      RuleName    = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).Name)
                      Protocol    = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).Protocol)
                      Source      = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).SourceAddressPrefix).Replace("*", "All IP Addresses")
                      Dest        = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).DestinationAddressPrefix).Replace("*", "All IP Addresses")
                      SourceRange = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).SourcePortRange)
                      DestRange   = [string]::Join(", ", ($NSG.SecurityRulesText | ConvertFrom-Json).DestinationPortRange)
                   })
                }
            }
        }
    }
    #Nested switch statement to handle the -ExportToFile Param
    switch ($PSCmdlet.MyInvocation.BoundParameters.Keys) {
        'ExportToFile' {
            if (! $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("FileType")) {
             [array]$global:Results | Out-File -FilePath "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.Txt" 
                    if ((Test-Path -Path "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.txt")) {
                        Write-Host "Exported File to Path: $([System.IO.Directory]::GetCurrentDirectory())\" -ForegroundColor Yellow -NoNewline ; Write-Host "$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.txt" -ForegroundColor Green                    
                    }
                }
            #Nested switch statement to handle the -FileType Param
            switch ($FileType) {
                'csv'  { 
                    [array]$global:Results | Export-Csv -Path "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)" -NoTypeInformation
                    if ((Test-Path -Path "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)")) {
                        Write-Host "Exported File to Path: $([System.IO.Directory]::GetCurrentDirectory())\" -ForegroundColor Yellow -NoNewline ; Write-Host "$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)" -ForegroundColor Green
                        [string]$OpenFileExcel = Read-Host "Would you like to open the File in Excel? [Y/N]"   
                          while ($OpenFileExcel -ne "Y" -and $OpenFileExcel -ne "N") {
                            Write-Warning "Invalid Input. Please enter 'Y' or 'N'"
                              [string]$OpenFileExcel = Read-Host "Would you like to open the File in Excel? [Y/N]"
                            }
                            #switch statement to track output and open the exported results as a CSV if chosen
                            switch ($OpenFileExcel) {
                                'Y' { 
                                    try {
                                        if ((Get-Process -Name 'Excel' -ErrorAction SilentlyContinue)) {
                                               Stop-Process -Name 'Excel' -Force
                                        }
                                        [System.Diagnostics.Process]::Start("excel.exe", "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)")
                                    }
                                    catch {
                                        return [PSCustomObject]@{
                                            Error   = $global:Error[0].Exception.Message[0]
                                            Failure = $global:Error[0].Failure
                                        }
                                    }
                                 }
                              'N' { Exit 1 }
                            }
                        }
                    } #If html is chosen on the -FileType Parameter, open HTML file in browser if open
                     'html' { 
                     [array]$global:Results | ConvertTo-Html -Fragment -As 'Table' | Out-File -FilePath "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)"
                     if ((Test-Path -Path "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)")) {
                       Write-Host "Exported File to Path: $([System.IO.Directory]::GetCurrentDirectory())\" -ForegroundColor Yellow -NoNewline ; Write-Host "$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)" -ForegroundColor Green
                       [string]$OpenHTMLFile = Read-Host "Would you like to open the HTML File? [Y/N]"   
                          while ($OpenHTMLFile -ne "Y" -and $OpenHTMLFile -ne "N") {
                            Write-Warning "Invalid Input. Please enter 'Y' or 'N'"
                              [string]$OpenFileExcel = Read-Host "Would you like to open the HTML File? [Y/N]"
                          }
                          switch ($OpenHTMLFile) {
                            'Y' {
                                try {
                                  [System.Diagnostics.Process]::Start("C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", "$([System.IO.Directory]::GetCurrentDirectory())\$([System.DateTime]::get_Now().ToString().Replace("/", "_").Split(" ")[0])_NSGReport.$($FileType)") 
                                }
                                catch {
                                      return [PSCustomObject]@{
                                          Error   = $global:Error[0].Exception.Message[0]
                                          Failure = $global:Error[0].Failure
                                        }
                                    }
                                }
                           'N' {Exit 1} 
                        }
                    }
                }
            }
        }
#If -All parameter is used, check if the -ExportFile parameter is emunerated. void the global var to avoid results outputting while the file exports
        'All' {
          if ($ExportToFile) {
              [void]$global:Results 
        }
        
        else {
         return [array]$global:Results #If -All parameter is used alone, return the custom object with the NSG results
            } 
        }
    }
}