Public/Device/Get-PanDevice.ps1

function Get-PanDevice {
<#
.SYNOPSIS
Retrieve PanDevice(s) from the PanDeviceDb
.DESCRIPTION
Retrieve PanDevice(s) from the PanDeviceDb
.NOTES
Multiple -Label (via array) is a logical AND match with NO regular expression support.
Multiple -Name (via array) is a logical OR match with regular expression support.

When -Session, -Label, -Name are used together, specific parameter AND/OR match behavior
still applies, but each parameter in use must match (AND across all in use parameters)

:: Location Property Update ::
Each PanDevice has a Location property holding shared, vsys, and device group "Name to XPath"
mappings for the device. Location property is *not* persisted to disk or preserved across
PowerShell sessions and is updated dynamically during the PowerShell session.

Get-PanDevice is *also* the principal trigger for updating the PanDevice.Location property.

Performing the update requires XML API calls to the device retrieve its current shared, vsys1,
vsys2, device group, etc. layout.

Why does this matter? Imagine:
   - 10 PanDevice's persisting across PowerShell sessions
   - 8 them are network reachable by Name/IP from your current workstation
   - 2 of them are NOT network reachable by Name/IP from your current workstation
   - A call to "Get-PanDevice -All" in a *new* PowerShell session will attempt to refresh all 10
   - 8 device's Location properties will update very quickly
   - 2 unreachable devices will hang for many seconds while attempting to reach their API's

Recommendation:
   - Use "Get-PanDevice -All -NoLocation" to "see" all your PanDevice's without updating Location
   - When wanting to get a device for actual use, scope Get-PanDevice with -Name or -Label
.INPUTS
None
.OUTPUTS
PanDevice[]
.EXAMPLE
Get-PanDevice -Name "firewall-01.acme.local"

Returns PanDevice with case-insensitive match on -Name
.EXAMPLE
Get-PanDevice -Name "firewall-01.acme.local","firewall-02.acme.local"

Returns PanDevice with case-insensitive match on -Name.
Multiple -Name (via array) are logical OR match to facilitate precise multi-select based on PanDevice Name.
For more complex match logic use Get-PanDevice -All | Where-Object ...
.EXAMPLE
Get-PanDevice -Session

Returns PanDevice(s) created in current PowerShell session.
.EXAMPLE
Get-PanDevice -Label "east-us-1","PCI"

Returns PanDevice(s) with "east-us-1" AND "PCI" Labels. Multiple -Label (via array) are an AND match.
For more complex match logic use Get-PanDevice -All | Where-Object ...
.EXAMPLE
Get-PanDevice -All

Returns all PanDevice(s) within the PanDeviceDb.
Complex match criteria can be crafted using Get-PanDevice -All | Where-Object ...
.EXAMPLE
Get-PanDevice -Session -Name "firewall-01.acme.local"

Matches -Session AND -Name parameters, both parameters must result in match.
.EXAMPLE
Get-PanDevice -Name "firewall-01.acme.local" -Label "PCI"

Matches -Name AND -Label parameters, both parameters must result in match.
.EXAMPLE
Get-PanDevice

With no parameters, returns the "default" PanDevice(s) based on $Global:PanDeviceLabelDefault.
If $Global:PanDeviceLabelDefault is empty, returns PanDevice(s) created in current PowerShell session.
#>

   [CmdletBinding(DefaultParameterSetName='Empty')]
   param(
      [parameter(Position=0,Mandatory=$false,ParameterSetName='Filter',HelpMessage='Case-insensitive exact match for Name. Multiple Name is logical OR match')]
      [String[]] $Name,
      [parameter(Mandatory=$false,ParameterSetName='Filter',HelpMessage='Switch parameter for PanDevice created in CURRENT PowerShell session')]
      [Switch] $Session,
      [parameter(Mandatory=$false,ParameterSetName='Filter',HelpMessage='Case-insensitive exact match for Label. Multiple Label is logical AND match')]
      [String[]] $Label,
      [parameter(Mandatory=$true,ParameterSetName='All',HelpMessage='Switch parameter for ALL PanDevice')]
      [Switch] $All,
      [parameter(Mandatory=$false,HelpMessage='Do not update PanDevice Location map')]
      [Switch] $NoLocation
   )

   # Propagate -Debug and -Verbose to this module function, https://tinyurl.com/y5dcbb34
   if($PSBoundParameters.Debug) { $DebugPreference = 'Continue' }
   if($PSBoundParameters.Verbose) { $VerbosePreference = 'Continue' }
   # Announce
   Write-Debug ($MyInvocation.MyCommand.Name + ':')

   # Initialize PanDeviceDb
   InitializePanDeviceDb

   # Fetch PanSessionGuid to be used throughout function. Avoids littering Debug logs with excessive calls to GetPanSessionGuid
   $SessionGuid = GetPanSessionGuid
   # Fetch PanDeviceLabelDefault to be used throughout function. Avoids littering Debug logs with excessive calls to Get-PanDeviceLabelDefaul
   $LabelDefault = Get-PanDeviceLabelDefault

   # If the PanDeviceDb is NOT populated, no need to continue evaluating ParameterSets, answer is always empty
   if([String]::IsNullOrEmpty($Global:PanDeviceDb)) {
      Write-Debug ($MyInvocation.MyCommand.Name + ': PanDeviceDb empty')
      # .NET Generic List provides under-the-hood efficiency during add/remove compared to PowerShell native arrays or ArrayList.
      $DeviceAgg = [System.Collections.Generic.List[PanDevice]]@()
   }

   # ParameterSetName 'Empty'
   # Peference (most to least) is PanDeviceLabelDefault, then session- label.
   elseif($PSCmdlet.ParameterSetName -eq 'Empty') {
      Write-Debug ($MyInvocation.MyCommand.Name + ': Empty ParameterSetName')
      # .NET Generic List provides under-the-hood efficiency during add/remove compared to PowerShell native arrays or ArrayList.
      $DeviceAgg = [System.Collections.Generic.List[PanDevice]]@()

      # If PanDeviceLabelDefault is only the session- label (see function calls above to populate these variables), then there are no PanDeviceLabelDefault(s)
      # Send back only the session- matches. More common scenario and thus evaluated first.
      if($LabelDefault -eq "session-$SessionGuid") {
         Write-Debug ($MyInvocation.MyCommand.Name + ': No PanDeviceLabelDefault(s) found. Using session-' + $SessionGuid )

         foreach($DeviceCur in ($Global:PanDeviceDb | Where-Object { $_.Label -contains "session-$SessionGuid"})) {
            $DeviceAgg.Add($DeviceCur)
         }
      }
      # PanDeviceLabelDefault has content
      else {
         Write-Debug ($MyInvocation.MyCommand.Name + ': Using PanDeviceLabelDefault(s)')

         # For each PanDevice, prime Verdict as $true. Iterate through PanDeviceLabelDefault(s) and look for matches one at a time.
         # If no match, $Verdict becomes false and the PanDevice will not be added to the aggregate to be returned.
         # If all PanDeviceLabelDefault(s) are found, $Verdict stays $true and PanDevice will be added to aggregate to be returned.
         foreach($DeviceCur in $Global:PanDeviceDb) {
            $Verdict = $true
            foreach($LabelCur in $LabelDefault) {
               if($DeviceCur.Label -notcontains $LabelCur) {
                  $Verdict = $false
                  break
               }
            }
            if($Verdict) {
               $DeviceAgg.Add($DeviceCur)
            }
         } # foreach $DeviceCur
      }
   } # elseif ParameterSetName 'Empty'

   # ParameterSetName 'Filter'
   # -Session, -Label, -Name can be used to filter individually or together/simultaneously
   #
   # Multiple -Label (via array) is a logical AND hit (all) with NO regular expression support.
   # Multiple -Name (via array) is a logical OR hit (at least one) with regular expression support.
   #
   # When -Session, -Label, -Name are used together, specific behavior (above) still applies, but each in use must hit.
   # Simultaneous use is logical AND (all) for a hit.
   elseif($PSCmdlet.ParameterSetName -eq 'Filter') {
      Write-Debug ($MyInvocation.MyCommand.Name + ': Filter ParameterSetName')
      # .NET Generic List provides under-the-hood efficiency during add/remove compared to PowerShell native arrays or ArrayList.
      $DeviceAgg = [System.Collections.Generic.List[PanDevice]]@()
      # Iterate through each PanDevice in PanDeviceDb
      foreach($DeviceCur in $Global:PanDeviceDb) {
         # Prime the Verdict
         $Verdict = $true
         # If Session filter is enabled and no session match, current PanDevice is not a filter match, break outer loop immediately
         if($PSBoundParameters.Session.IsPresent -and $DeviceCur.Label -notcontains "session-$SessionGuid") {
            $Verdict = $false
            continue
         }
         # If Label filter is enabled and every Label is not a match, current PanDevice is not a filter match, break outer loop immediately
         if(-not [String]::IsNullOrEmpty($PSBoundParameters.Label)) {
            foreach($LabelCur in $PSBoundParameters.Label) {
               if($DeviceCur.Label -notcontains $LabelCur) {
                  $Verdict = $false
                  break
               }
            }
            if(-not $Verdict) {
               continue
            }
         }
         # If Name filter is enabled and at least one Name is not a match, current PanDevice is not a filter match, break outer loop immediately
         if(-not [String]::IsNullOrEmpty($PSBoundParameters.Name)) {
            $Verdict = $false
            foreach($NameCur in $PSBoundParameters.Name) {
               if($DeviceCur.Name -imatch "^$NameCur`$") {
                  $Verdict = $true
               }
            }
            if(-not $Verdict) {
               continue
            }
         }

         # Process the verdict
         if($Verdict) {
            $DeviceAgg.Add($DeviceCur)
         }
      }
   } # ParameterSetName 'Filter'

   # ParameterSetName 'All'
   elseif($PSCmdlet.ParameterSetName -eq 'All') {
      Write-Debug ($MyInvocation.MyCommand.Name + ': All ParameterSetName')
      $DeviceAgg = $Global:PanDeviceDb
   } # ParameterSetName 'All'

   # Ensure Location map is up to date for PanDevice's being returned
   if(-not $PSBoundParameters.NoLocation.IsPresent) {
      foreach($DeviceCur in $DeviceAgg) {
         Update-PanDeviceLocation -Device $DeviceCur
      }
   }

   return $DeviceAgg
} # Function