Public/Object/Get-PanDynamicAddressGroup.ps1
function Get-PanDynamicAddressGroup { <# .SYNOPSIS Retrieve PanDynamicAddressGroup with its dynamic members. .DESCRIPTION Retrieve PanDynamicAddressGroup populated with its dynamic members. The contents of dynamic address groups (DAG) can ony be known at runtime. Use this cmdlet to determine DAG members. A call to Get-PanAddressGroup (not dynamic one) will get all address groups, static and dynamic. Static groups have their static members included, dynamic group member property will be empty. This cmdlet is able to runtime fetch DAG membership. .NOTES The primary reason this cmdlet exists is because DAG's can be defined in Panorama but need to be runtime viewed on the firewalls. Get-PanAddressGroup (not dynamic one) can fetch all address group definitions (including static and dynamic) but membership is only available for static. After attempting to "hack-in" the ability to fetch DAG membership but it got too user-experience clumsy when the DAG was defined in Panorama but needed to see the runtime membership on the firewall. Alas, this cmdlet was born. Running this cmdlet against Panorama doesn't produce very useful output since Panorama itself does not populate DAG's This cmdlet should be run against firewalls. .INPUTS PanDevice[] You can pipe a PanDevice to this cmdlet PanAddress[] You can pipe a PanAddress to this cmdlet PanDynamicAddressGroup[] You can pipe a PanDynamicAddressGroup to this cmdlet .OUTPUTS PanDynamicAddressGroup .EXAMPLE $D = Get-PanDevice "fw.lab.local" Get-PanDynamicAddressGroup -Device $D -Name "MyDAG" .EXAMPLE Get-PanDevice "fw.lab.local" | Get-PanAddressGroup -Name "MyDAG" | Get-PanDynamicAddressGroup PowerShell one-liner .EXAMPLE $P = Get-PanDevice "panorama.lab.local" $D = Get-PanDevice "fw.lab.local" # Grab the address group defined in Panorama $AG = Get-PanAddress -Device $P -Location "Grandparent" -Name "MyDAG" # But query the firewall for runtime DAG members $DAG = Get-PanDynamicAddressGroup -Device $D -Name "MyDAG" This type of example is a primary reason why this cmdlet exists... DAG's can be defined in Panorama but need to be runtime viewed on the firewalls. Unfortunately, that specific use case can't be completed as a one-liner. #> [CmdletBinding(DefaultParameterSetName='Device')] param( [parameter(Mandatory=$true,ParameterSetName='Device',ValueFromPipeline=$true,HelpMessage='PanDevice to target')] [PanDevice[]] $Device, [parameter(ParameterSetName='Device',HelpMessage='Limit search to PanDevice locations (shared, vsys1, MyDeviceGroup)')] [String[]] $Location, [parameter(ParameterSetName='Device',HelpMessage='Exact match object name. Matched remotely (API)')] [String] $Name, [parameter(Mandatory=$true,Position=0,ParameterSetName='InputObject',ValueFromPipeline=$true,HelpMessage='Input object(s) to be retrieved')] [PanObject[]] $InputObject ) Begin { # Propagate -Verbose to this module function, https://tinyurl.com/y5dcbb34 if($PSBoundParameters.Verbose) { $VerbosePreference = 'Continue' } # Announce Write-Verbose ('{0}:' -f $MyInvocation.MyCommand.Name) } # Begin Block Process { # InputObject ParameterSetName if($PSCmdlet.ParameterSetName -eq 'InputObject') { foreach($InputObjectCur in $PSBoundParameters.InputObject) { Write-Verbose ('{0}: InputObject Device: {1} InputObject Name: {2} Type: {3}' -f $MyInvocation.MyCommand.Name,$InputObjectCur.Device.Name,$InputObjectCur.Name,$InputObjectCur.GetType()) # InputObject is of type PanObject which will accept more types than acceptable. Check. if($InputObjectCur -isnot [PanAddressGroup] -and $InputObjectCur -isnot [PanDynamicAddressgroup]) { Write-Error('InputObject must be type [PanAddressGroup] or [PanDynamicAddressGroup]') # Next iteration continue } $Cmd = '<show><object><dynamic-address-group><name>{0}</name></dynamic-address-group></object></show>' -f $InputObjectCur.Name $R = Invoke-PanXApi -Device $InputObjectCur.Device -Op -Cmd $Cmd # Check PanResponse if($R.Status -eq 'success') { # Ngfw and Panorama require different parsing if($InputObjectCur.Device.Type -eq [PanDeviceType]::Ngfw) { # Ngfw Response # <response cmd="status" status="success"> # <result> # <dyn-addr-grp> # <entry> # <vsys>vsys1</vsys> # <group-name>DAG-Infected</group-name> # <filter>'infected'</filter> # <member-list> # <entry name="1.2.2.2" type="registered-ip"/> # </member-list> # </entry> # <entry> # <vsys>vsys1</vsys> # <group-name>DAG-Quarantine</group-name> # <filter>'infected' or 'risky'</filter> # <member-list> # <entry name="H-1.2.3.4" type="address-object"/> # <entry name="1.2.2.2" type="registered-ip"/> # </member-list> # </entry> # <entry> # <group-name>DAG-Quarantine-Shared</group-name> # <filter>'infected' or 'risky'</filter> # <member-list> # <entry name="H-1.2.3.4" type="address-object"/> # <entry name="1.2.2.2" type="registered-ip"/> # </member-list> # </entry> # </dyn-addr-grp> # </result> # </response> $Entry = $R.Response.result.'dyn-addr-grp'.entry Write-Verbose ('{0}: API return entry count: {1}' -f $MyInvocation.MyCommand.Name,$Entry.Count) foreach($EntryCur in $Entry) { $XDoc = [System.Xml.XmlDocument]$EntryCur.OuterXml # Send to pipeline [PanDynamicAddressGroup]::new($InputObjectCur.Device,$XDoc) } } # if Ngfw elseif($InputObjectCur.Device.Type -eq [PanDeviceType]::Panorama) { # Panorama Response (odd, indeed) # Location is entry name attribute, but need to manually replicate entry Constructor call # DAG Name is address-group name attribute # # <response status="success"> # <result> # <device-groups> # <entry name="shared"> # <address-group name="DynamicGroupShared1"> # <filter>'black'</filter> # <member-list/> # </address-group> # <address-group name="DynamicGroupShared2"> # <filter>'black'</filter> # <member-list/> # </address-group> # </entry> # <entry name="Grandparent"> # <address-group name="DynamicGroupGrandParent1"> # <filter>'black'</filter> # <member-list/> # </address-group> # </entry> # </device-groups> # </result> # </response> Write-Warning ('{0}: Running this cmdlet against Panorama does not produce reliable output. DAG runtime state is avaiable on NGFW only' -f $MyInvocation.MyCommand.Name) $DgEntry = $R.Response.result.'device-groups'.entry Write-Verbose ('{0}: API return device-group entry count: {1}' -f $MyInvocation.MyCommand.Name,$DgEntry.Count) foreach($DgEntryCur in $DgEntry) { foreach($AgCur in $DgEntryCur.'address-group') { Write-Verbose ('{0}: API return device-group: {1} address-group entry count: {2}' -f $MyInvocation.MyCommand.Name,$DgEntryCur.Name,$DgEntryCur.'address-group'.Count) # Create a new entry with name attribute representing the location $S = '<entry name="{0}"></entry>' -f $DgEntryCur.name $XDoc = [System.Xml.XmlDocument]$S # Import the inner <address-group> with deep-copy $ImportedNode = $XDoc.ImportNode($AgCur,$true) # Append $XDoc.Item('entry').AppendChild($ImportedNode) | Out-Null # Send to pipeline [PanDynamicAddressGroup]::new($InputObjectCur.Device,$XDoc) } } } # elseif Panorama } else { Write-Error ('Error retrieving InputObject {0} on {1} Status: {2} Code: {3} Message: {4}' -f $InputObjectCur.Name,$InputObjectCur.Device.Name,$R.Status,$R.Code,$R.Message) } } # foreach InputObject } # InputObject ParameterSetName # Device ParameterSetName elseif($PSCmdlet.ParameterSetName -eq 'Device') { foreach($DeviceCur in $PSBoundParameters.Device) { Write-Verbose ('{0}: Device: {1}' -f $MyInvocation.MyCommand.Name,$DeviceCur.Name) # Update Location if past due if($DeviceCur.LocationUpdated.AddSeconds($Global:PanDeviceLocRefSec) -lt (Get-Date)) { Update-PanDeviceLocation -Device $DeviceCur } # If PanDevice Location(s) are missing, move on. Should not happen under normal circumstances but could by accident if users # are assigning Locations manually. Splash a nice warning and move on. if(-not ($DeviceCur.Location.Count -ge 1)) { Write-Warning ('{0}: Device: {1} Location(s) are missing. Manually run Update-PanDeviceLocation' -f $MyInvocation.MyCommand.Name,$DeviceCur.Name) # Jump to the next DeviceCur as there is nothing we can do for this DeviceCur continue } # XML API does not provide the ability to server-side search for location, so we will simulate it if(-not [String]::IsNullOrEmpty($PSBoundParameters.Name)) { $Cmd = '<show><object><dynamic-address-group><name>{0}</name></dynamic-address-group></object></show>' -f $PSBoundParameters.Name } else { $Cmd = '<show><object><dynamic-address-group><all></all></dynamic-address-group></object></show>' } $R = Invoke-PanXApi -Device $DeviceCur -Op -Cmd $Cmd # Check PanResponse if($R.Status -eq 'success') { # Ngfw and Panorama require different parsing # Ngfw if($DeviceCur.Type -eq [PanDeviceType]::Ngfw) { if(-not [String]::IsNullOrEmpty($PSBoundParameters.Location)) { # <vsys>vsys1</vsys>, etc $Entry = $R.Response.result.'dyn-addr-grp'.entry | Where-Object {$_.vsys -in $PSBoundParameters.Location} # Special condition for shared, <vsys> will be missing, that's how shared is indicated if('shared' -in $PSBoundParameters.Location) { $Entry += $R.Response.result.'dyn-addr-grp'.entry | Where-Object {[String]::IsNullOrEmpty($_.vsys)} } } else { # else Entry stays the same as Location is not defined $Entry = $R.Response.result.'dyn-addr-grp'.entry } Write-Verbose ('{0}: API return entry count: {1} Post-Location Filter count: {2}' -f $MyInvocation.MyCommand.Name,$R.Response.result.'dyn-addr-grp'.entry.Count,$Entry.Count) foreach($EntryCur in $Entry) { [System.Xml.XmlDocument]$XDoc = $EntryCur.OuterXml # Send to pipeline [PanDynamicAddressGroup]::new($DeviceCur,$XDoc) } } # Panorama elseif($DeviceCur.Type -eq [PanDeviceType]::Panorama) { Write-Warning ('{0}: Running this cmdlet against Panorama does not produce reliable output. DAG runtime state is avaiable on NGFW only' -f $MyInvocation.MyCommand.Name) if(-not [String]::IsNullOrEmpty($PSBoundParameters.Location)) { # Filter based on Location parameter $DgEntry = $R.Response.result.'device-groups'.entry | Where-Object {$_.name -in $PSBoundParameters.Location} } else { # else DgEntry stays the same as Location is not defined $DgEntry = $R.Response.result.'device-groups'.entry } Write-Verbose ('{0}: API return device-group entry count: {1} Post-Location Filter count: {2}' -f $MyInvocation.MyCommand.Name,$R.Response.result.'device-groups'.entry,$DgEntry.Count) foreach($DgEntryCur in $DgEntry) { foreach($AgCur in $DgEntryCur.'address-group') { Write-Verbose ('{0}: API return device-group: {1} address-group entry count: {2}' -f $MyInvocation.MyCommand.Name,$DgEntryCur.Name,$DgEntryCur.'address-group'.Count) # Create a new entry with name attribute representing the location $S = '<entry name="{0}"></entry>' -f $DgEntryCur.name $XDoc = [System.Xml.XmlDocument]$S # Import the inner <address-group> with deep-copy $ImportedNode = $XDoc.ImportNode($AgCur,$true) # Append $XDoc.Item('entry').AppendChild($ImportedNode) | Out-Null # Send to pipeline [PanDynamicAddressGroup]::new($DeviceCur,$XDoc) } } } } # API call not successful else { Write-Error ('Error retrieving PanDynamicAddressGroup Cmd: {0} on {1} Status: {2} Code: {3} Message: {4}' -f $Cmd,$DeviceCur.Name,$R.Status,$R.Code,$R.Message) } } # foreach Device } # ParameterSetName } # Process block End { } # End block } # Function |