Private/Object/Address/Set-PanAddressHelper.ps1

function Set-PanAddressHelper {
   <#
   .SYNOPSIS
   .DESCRIPTION
   .NOTES
   .INPUTS
   None
   .OUTPUTS
   PanResponse
   .EXAMPLE
   #>

   [CmdletBinding()]
   param(
      [parameter( Mandatory=$true, HelpMessage='PanAddress object to be applied to candidate configuration')]
      [PanAddress] $Address
   )

   # 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 + ':')

   # Build XPath based on Address location property
   if($PSBoundParameters.Address.Location -match '^panorama\/*$') {
      Write-Error $($MyInvocation.MyCommand.Name + ': Unable to create or update Panorama pushed object ' +
         $PSBoundParameters.Address.Name + ' on firewall directly. Update on Panorama or override on firewall.')
      return
   }
   elseif($PSBoundParameters.Address.Location -match '^local\/shared$') {
      $XPath = "/config/shared/address/entry[@name='{0}']" -f $PSBoundParameters.Address.Name
   }
   elseif($PSBoundParameters.Address.Location -match '^local\/(?<cap1>[\w]+)$') {
      $XPath = "/config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='{0}']/address/entry[@name='{1}']" -f
         $Matches['cap1'],
         $PSBoundParameters.Address.Name
   }
   else {
      Write-Error $($MyInvocation.MyCommand.Name + ': No object location specified')
      return
   }

   # Build Element contents
   $Element = ""

   # Value is based on Type
   switch($PSBoundParameters.Address.Type) {
      # Using enum inside of switch statement requires wrapping in parentheses. Odd.
      # https://powershell.org/forums/topic/why-cant-my-enum-values-be-specified-as-conditions-for-switch-statement/
      ([PanAddressType]::IpNetmask) { $Element += "<ip-netmask>{0}</ip-netmask>" -f [System.Net.WebUtility]::HtmlEncode($PSBoundParameters.Address.Value); break }
      ([PanAddressType]::IpRange) { $Element += "<ip-range>{0}</ip-range>" -f [System.Net.WebUtility]::HtmlEncode($PSBoundParameters.Address.Value); break }
      ([PanAddressType]::IpWildcard) { $Element += "<ip-wildcard>{0}</ip-wildcard>" -f [System.Net.WebUtility]::HtmlEncode($PSBoundParameters.Address.Value); break }
      ([PanAddressType]::Fqdn) { $Element += "<fqdn>{0}</fqdn>" -f [System.Net.WebUtility]::HtmlEncode($PSBoundParameters.Address.Value); break }
   }

   # Description
   if(-not [String]::IsNullOrEmpty($PSBoundParameters.Address.Description)) {
      $Element += "<description>{0}</description>" -f [System.Net.WebUtility]::HtmlEncode($PSBoundParameters.Address.Description)
   }

   # Tag(s)
   if(-not [String]::IsNullOrEmpty($PSBoundParameters.Address.Tag)) {
      $Element += "<tag>"
      foreach($TagCur in $PSBoundParameters.Address.Tag) {
         $Element += "<member>{0}</member>" -f [System.Net.WebUtility]::HtmlEncode($TagCur)
      }
      $Element += "</tag>"
   }

   Write-Debug $($MyInvocation.MyCommand.Name + ': Device: ' + $PSBoundParameters.Address.Device.Name )
   Write-Debug $($MyInvocation.MyCommand.Name + ': XPath: ' + $XPath )
   Write-Debug $($MyInvocation.MyCommand.Name + ': Element: ' + $Element )

   # Using PAN-OS XML-API action=edit to replace at this node (and not merge). action=set would merge config. We must be able
   # to remove config (like tags and descriptions), so action=edit is required.
   # When using action=edit, the XPath and the Element both contain a reference to the entry itself. Odd but true.
   # Wrap the Element up to this point in another <entry> tag. Would not be needed if using action=set
   $Element = ("<entry name='" + $PSBoundParameters.Address.Name + "'>{0}</entry>") -f $Element

   # Build Invoke-PanXApi call
   return Invoke-PanXApi -Device $PSBoundParameters.Address.Device -Config -Edit -XPath $XPath -Element $Element
}