Public/RegisteredIp/Remove-PanRegisteredIp.ps1

function Remove-PanRegisteredIp {
   <#
   .SYNOPSIS
   Unregister PAN-OS registered-ip(s) and tag(s).
   .DESCRIPTION
   Unregister PAN-OS registered-ip(s) and tag(s).
   Unregister with -Ip and -Tag Strings (simultaneously or exclusively) or using a -PanRegisteredIp object (from New-PanRegisteredIp).
 
   If a single -Ip is passed exclusively, all tags for the IP are unregistered, forcing complete unregistration of the IP.
   If a single -Tag is passed exclusively, the tag is unregistered from all tagged IP's. If an active registered-ip has multiple tags, the other tags will remain registered.
   If both -Ip and -Tag are passed simultaneously, the tag is unregistered from the specified IP only.
   If multiple -Ip and multiple -Tag are passed (via arrays) simultaneously (arrays for both parameters) or exclusively (array for one parameter), the specified tags are removed from the specified IP's.
 
   If a single -RegisteredIp is passed, the tag(s) within the PanRegisteredIp object are removed from the IP within the PanRegisteredIp object.
   If multiple -RegisteredIp are passed (via array), each PanRegisteredIp object is treated as an individual action (different behavior from passing multiple -Ip and -Tag).
 
   If multiple -Device are passed (via array), the registered-ip and tags unregistration logic is applied to each device individually.
   .NOTES
   PAN-OS registered-ip is not added to a Dynamic Address Group (DAG) directly. Instead, a PAN-OS registered-ip is registered with tag(s). Multiple tags can be registered to a single registered-ip.
   The tag(s) against which a registered-ip can be registered can be defined in Objects > Tags OR arbitrary string values that do not exist in Objects > Tags.
   DAG match criteria is based on tag(s). After registering a registered-ip with tag(s), PAN-OS then dynamically computes to which DAG(s) the registered-ip is added.
   .INPUTS
   .OUTPUTS
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -Ip "1.1.1.1" -Tag "MyTag"
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -Ip "1.1.1.1","2.2.2.2"
   All tags are unregistered from both 1.1.1.1 and 2.2.2.2 registered-ip's.
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -Tag "HerTag","HisTag"
   "HerTag" and "HisTag" are unregistered from every registered-ip.
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -Ip "1.1.1.1","2.2.2.2" -Tag "HerTag","HisTag"
   "HerTag" and "HisTag" are both unregistered from both 1.1.1.1 and 2.2.2.2 registered-ip's.
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -RegisteredIp $(New-PanRegisteredIp -Ip "1.1.1.1" -Tag "HerTag","HisTag")
   "HerTag" and "HisTag" are both removed from 1.1.1.1 registered-ip.
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -RegisteredIp $(New-PanRegisteredIp -Ip "1.1.1.1" -Tag "HerTag","HisTag"),$(New-PanRegisteredIp -Ip "2.2.2.2" -Tag "HerTag")
   "HerTag" and "HisTag" are both unregistered from 1.1.1.1 registered-ip. "HerTag" is unregistered from 2.2.2.2 registered-ip.
   .EXAMPLE
   Remove-PanRegisteredIp -Device $Device -All
   All tags are unregistered from all registered-ip's.
   #>

   [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')]
   param(
      [parameter(
         Mandatory=$true,
         ValueFromPipeline=$true,
         HelpMessage='PanDevice against which IP unregistration and untagging will take place.')]
      [PanDevice[]] $Device,
      [parameter(
         Mandatory=$false,
         ParameterSetName='UnregisterWithStrings',
         HelpMessage='IP address to unregister.')]
      [String[]] $Ip,
      [parameter(
         Mandatory=$false,
         ParameterSetName='UnregisterWithStrings',
         HelpMessage='Tag(s) to unregister from IP address.')]
      [String[]] $Tag,
      [parameter(
         Mandatory=$true,
         ParameterSetName='UnregisterWithPanRegisteredIp',
         HelpMessage='PanRegisteredIp object to unregister.')]
      [PanRegisteredIp[]] $RegisteredIp,
      [parameter(
         Mandatory=$true,
         ParameterSetName='UnregisterAll',
         HelpMessage='Switch parameter to unregister all tags from all IP addresses.')]
      [Switch] $All
   )

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

   Process {
      foreach($DeviceCur in $Device) {
         # For -RegisteredIp, -Ip/-Tag, -Ip, and -Tag Cases, $RegisteredIpAgg is blueprint for unregistrations.
         # Build and hold the collection of [PanRegisteredIp] that need to be removed.
         $RegisteredIpAgg = [System.Collections.Generic.List[PanRegisteredIp]]@()

         # -All switch parameter indicating all registered-ip's are to be unregistered.
         if($PSCmdlet.ParameterSetName -eq 'UnregisterAll') {
            if($PSCmdlet.ShouldProcess($Device.Name,'Unregister ALL IP:TAG registration(s)')) {
               Write-Debug ($MyInvocation.MyCommand.Name + ': Unregister ALL IP:TAG registration(s)')
               return Clear-PanRegisteredIp -Device $Device
            }
         }
         # -RegisteredIp providing the exact set of unregistration actions.
         elseif($PSCmdlet.ParameterSetName -eq 'UnregisterWithPanRegisteredIp') {
            Write-Debug ($MyInvocation.MyCommand.Name + ': Unregistering IP:TAG registration(s) specified by PanRegisteredIp object(s)')
            $RegisteredIpAgg.Add($RegisteredIp)
         }
         # With Parameter set UnregisterWithStrings, -Ip and -Tag can be used exclusively or simultaneously to achieve different goals.
         elseif($PSCmdlet.ParameterSetName -eq 'UnregisterWithStrings') {
            # -Ip and -Tag parameters simultaneously
            if(-not [String]::IsNullOrEmpty($Ip) -and -not [String]::IsNullOrEmpty($Tag) ) {
               Write-Debug ($MyInvocation.MyCommand.Name + ': Unregistering IP:TAG registration(s) matching IP && TAG')
               # While Remove-PanRegisteredIp will accept array on -Ip parameter,
               # Get-PanRegisteredIp cannot accept array on its -Ip parameter. Must iterate.
               foreach($IpCur in $Ip) {
                  Write-Debug ($MyInvocation.MyCommand.Name + ": Searching device $($DeviceCur.Name) for registered-ip $IpCur")
                  # $RegisteredIpResult will be a single [PanRegisteredIp], but the IP MAY have MULTIPLE tag registrations.
                  $RegisteredIpResult = $null
                  $RegisteredIpResult = Get-PanRegisteredIp -Device $DeviceCur -Ip $IpCur
                  if(-not [String]::IsNullOrEmpty($RegisteredIpResult) ) {
                     Write-Debug ($MyInvocation.MyCommand.Name + ": registered-ip $IpCur FOUND on device $($DeviceCur.Name)")
                     # Remove-PanRegisteredIp with -Ip and -Tag simultaneously will ONLY unregister tags specified in -Tag parameter, must iterate.
                     # $TagAgg to hold the (sub)set of tags that must be unregistered.
                     $TagAgg = [System.Collections.Generic.List[String]]@()
                     foreach($TagCur in $Tag) {
                        Write-Debug ($MyInvocation.MyCommand.Name + ": Searching for tag $TagCur on registered-ip $IpCur")
                        if($RegisteredIpResult.Tag -contains $TagCur) {
                           Write-Debug ($MyInvocation.MyCommand.Name + ": tag $TagCur FOUND on registered-ip $IpCur")
                           $TagAgg.Add($TagCur)
                        }
                     }
                     # If tags need to be unregistered, build new [PanRegisteredIp] with ONLY necessary tags to unregister.
                     # Add new [PanRegisteredIp] to the blueprint to be unregistered later.
                     if(-not [String]::IsNullOrEmpty($TagAgg) ) {
                        $RegisteredIpAgg.Add( (New-PanRegisteredIp -Ip $IpCur -Tag $TagAgg) )
                     }
                  } # if $RegisteredIpResult
               } # foreach $IpCur in $Ip
            } # -Ip and -Tag parameters simultaneously

            # -Ip parameter exlusively
            elseif(-not [String]::IsNullOrEmpty($Ip) ) {
               Write-Debug ($MyInvocation.MyCommand.Name + ': Unregistering IP:TAG registration(s) matching IP only')
               # While Remove-PanRegisteredIp will accept array on -Ip parameter,
               # Get-PanRegisteredIp cannot accept array on its -Ip parameter. Must iterate.
               foreach($IpCur in $Ip) {
                  Write-Debug ($MyInvocation.MyCommand.Name + ": Searching device $($DeviceCur.Name) for registered-ip $IpCur")
                  # $RegisteredIpResult will be a single [PanRegisteredIp] with all tag registrations.
                  $RegisteredIpResult = $null
                  $RegisteredIpResult = Get-PanRegisteredIp -Device $DeviceCur -Ip $IpCur
                  if(-not [String]::IsNullOrEmpty($RegisteredIpResult) ) {
                     Write-Debug ($MyInvocation.MyCommand.Name + ": registered-ip $IpCur FOUND on device $($DeviceCur.Name)")
                     # Add the [PanRegisteredIp] to the blueprint to be unregistered later.
                     $RegisteredIpAgg.Add($RegisteredIpResult)
                  } # if $RegisteredIpResult
               } # foreach $IpCur in $Ip
            } # -Ip parameter exclusively

            # -Tag parameter exclusively
            elseif(-not [String]::IsNullOrEmpty($Tag) ) {
               Write-Debug ($MyInvocation.MyCommand.Name + ': Unregistering IP:TAG registration(s) matching Tag only')
               # $HashTableAgg placeholder data structure to store @{"10.10.10.10" = @("HisTag","HerTag"); "10.20.20.20" = @("HerTag") }
               # string representations of IP:TAG mappings temporarily. Easy to insert and manipulate.
               $HashTableAgg = @{}
               # While Remove-PanRegisteredIp will accept array on -Tag parameter,
               # Get-PanRegisteredIp cannot accept array on its -Tag parameter. Must iterate.
               foreach($TagCur in $Tag) {
                  Write-Debug ($MyInvocation.MyCommand.Name + ": Searching device $($DeviceCur.Name) for registered-ip's with tag $TagCur")
                  # $RegisteredIpResult will be ZERO OR MORE [PanRegisteredIp] (array), where each [PanRegisteredIp] IP MAY have MULTIPLE tag registrations.
                  $RegisteredIpResult = $null
                  $RegisteredIpResult = @(Get-PanRegisteredIp -Device $DeviceCur -Tag $TagCur)
                  if(-not [String]::IsNullOrEmpty($RegisteredIpResult) ) {
                     foreach($RegisteredIpResultCur in $RegisteredIpResult) {
                        Write-Debug ($MyInvocation.MyCommand.Name + ": registered-ip $($RegisteredIpResultCur.Ip) with tag $TagCur FOUND on device $($DeviceCur.Name)")
                        # IP already a key in hash table, add the tag to the existing array of tags.
                        if($HashTableAgg.Contains($RegisteredIpResultCur.Ip) ) {
                           $HashTableAgg[$RegisteredIpResultCur.Ip] = $HashTableAgg[$RegisteredIpResultCur.Ip] + $TagCur
                        }
                        # IP not a key in hash table, add the new k->v. Value must be added as array despite being a single tag at this point.
                        else {
                           $HashTableAgg.Add($RegisteredIpResultCur.Ip, @($TagCur))
                        }
                     } # foreach $RegisteredIpResultCur in $RegisteredIpResult
                  } # if $RegisteredIpResult
               } # foreach $TagCur in $Tag

               # Iterate through the $HashTableAgg "converting" to the blueprint $RegisteredIpAgg
               # Hashtable keys are IP addresses, values are an array of tags.
               if(-not [String]::IsNullOrEmpty($HashTableAgg)) {
                  foreach($Entry in $HashTableAgg.GetEnumerator()) {
                     $RegisteredIpAgg.Add( (New-PanRegisteredIp -Ip $Entry.Name -Tag $Entry.Value) )
                  }
               }
            } # -Tag parameter simultaneously
         } # elseif UnregisterWithStrings

         # Convert $RegisteredIpAgg blueprint to XML uid-message and send API request to unregister.
         # For -RegisteredIp, -Ip/-Tag, -Ip, and -Tag Cases, $RegisteredIpAgg is blueprint for unregistrations.
         if([String]::IsNullOrEmpty($RegisteredIpAgg) ) {
            Write-Debug ($MyInvocation.MyCommand.Name + ": Searches resulted in ZERO unregistrations to process")
         }
         else {
            Write-Debug ($MyInvocation.MyCommand.Name + ": Searches resulted in ONE OR MORE unregistrations to process")
            Write-Debug ($MyInvocation.MyCommand.Name + ": Device: $($DeviceCur.Name)")
            # Seed a herestring with XML-API "uid-message" beginning elements. Rebuild for every new device iteration.
            $CmdCur = @'
<uid-message>
 <version>2.0</version>
 <type>update</type>
 <payload>
  <unregister>
 
'@

            foreach($RegisteredIpCur in $RegisteredIpAgg) {
               Write-Debug ($MyInvocation.MyCommand.Name + ": registered-ip: $($RegisteredIpCur.Ip)")
               $CmdCur += " <entry ip=`"$($RegisteredIpCur.Ip)`">`n"
               $CmdCur += " <tag>`n"

               foreach($TagCur in $RegisteredIpCur.Tag) {
                  Write-Debug ($MyInvocation.MyCommand.Name + ": tag: $($TagCur)")
                  $CmdCur += " <member>$TagCur</member>`n"
               }

               $CmdCur += " </tag>`n"
               $CmdCur += " </entry>`n"
            }
            # Complete the XML-API "uid-message" ending elements.
            $CmdCur += @'
  </unregister>
 </payload>
</uid-message>
 
'@

            if($PSCmdlet.ShouldProcess($DeviceCur.Name,'Unregister IP:TAG registrations ')) {
               Write-Debug ($MyInvocation.MyCommand.Name + ': Device: ' + $DeviceCur.Name)
               Write-Debug ($MyInvocation.MyCommand.Name + ': Prepared uid-message: ' + $CmdCur)
               $PanResponse = Invoke-PanXApi -Device $DeviceCur -Uid -Cmd $CmdCur
               Write-Debug ($MyInvocation.MyCommand.Name + ': PanResponseStatus: ' + $PanResponse.Status)
               Write-Debug ($MyInvocation.MyCommand.Name + ': PanResponseMsg: ' + $PanResponse.Message)
            }
         } # else Convert $RegisteredIpAgg blueprint to XML uid-message and send API request to unregister
      } # foreach Device
   } # Process block

   End {
   } # End block
} # Function