Class/PanAddressGroup.ps1

class PanAddressGroup : PanObject, ICloneable {
   # Defined in base class
   # [PanDevice] $Device
   # [String] $XPath
   # [System.Xml.XmlDocument] $XDoc

   # Constructor accepting XML content with call to base class to do assignment
   PanAddressGroup([PanDevice] $Device, [String] $XPath, [System.Xml.XmlDocument] $XDoc) : base() {
      $this.Device = $Device
      $this.XPath = $XPath
      $this.XDoc = $XDoc
   } # End constructor

   # Constructor for building a basic shell from non-XML content. Build/assign XML content in this constructor.
   # Handy for creating shell objects. Goal is to build out a basic shell .XDoc, .XPath, and assign the .Device
   PanAddressGroup([PanDevice] $Device, [String] $Location, [String] $Name) : base() {
      $Suffix = "/address-group/entry[@name='{0}']" -f $Name
      $XPath =  "{0}{1}" -f $Device.Location.($Location),$Suffix
      # Build a minimum viable XDoc/Api Element with obvious non-common values
      $Xml = "<entry name='{0}'><static></static></entry>" -f $Name
      $XDoc = [System.Xml.XmlDocument]$Xml

      $this.Device = $Device
      $this.XPath = $XPath
      $this.XDoc = $XDoc
   } # End constructor

   # Static constructor for creating ScriptProperty properties using Update-TypeData
   # Update-TypeData in static contructor is PREFERRED to Add-Member in regular contructor
   # Update-TypeData within static constructor runs ONLY ONCE the first time the type is used is the PowerShell session
   # Contrast with Add-Member within regular constructor runs EVERY TIME a new object is created from the class
   # Be careful choosing where to use linebreaks in the middle of the Update-TypeData cmdlet call. Using linebreaks for getter/setter readability
   static PanAddressGroup() {
      # Call base class static methods to add properties via Update-Type. Do not need to redefine them.
      $TypeName = 'PanAddressGroup'
      [PanObject]::AddName($TypeName)
      [PanObject]::AddTag($TypeName)
      [PanObject]::AddDescription($TypeName)
      [PanObject]::AddDisableOverride($TypeName)
      [PanObject]::AddLocation($TypeName)
      [PanObject]::AddOverrides($TypeName)

      # Define what is unique to derived class only

      # Type ScriptProperty linked to $XDoc.entry.static or $XDoc.entry.dynamic, etc.
      Update-TypeData -TypeName $TypeName -MemberName Type -MemberType ScriptProperty -Value {
      # Getter
         if($this.XDoc.Item('entry').Item('static')) { return 'static' }
         elseif($this.XDoc.Item('entry').Item('dynamic')) { return 'dynamic' }
      } -SecondValue {
      # Setter
         param($Set)
         if($this.Type -eq $Set) {
            # Do nothing if setting to same type
         }
         else {
            # Clear out the current <static> or <dynamic> element and rebuild based on $Set
            # No need to deep copy since <static> and <dynamic> have entirely different children
            $OldChild = $this.Item('entry').Item($this.Type)
            $OldChild.ParentNode.RemoveChild($OldChild)

            $NewChild = $this.XDoc.CreateElement($Set)
            $this.XDoc.Item('entry').AppendChild($NewChild)
         }
      } -Force
      
      # Member ScriptProperty linked to $XDoc.entry.static.member It's also an array, watch out.
      # <static><member>A-1</member><member>A-2</member><static>
      Update-TypeData -TypeName $TypeName -MemberName Member -MemberType ScriptProperty -Value {
      # Getter
         return $this.XDoc.Item('entry').Item('static').GetElementsByTagName('member').InnerText
      } -SecondValue {
      # Setter
         param($Set)
         # If <static> is already present
         if($this.XDoc.Item('entry').Item('static')) {
            # Clear all <member> (and rebuild later)
            $this.XDoc.Item('entry').Item('static').RemoveAll()
         }
         # Else <static> is not present
         else {
            # Build and add <static>
            $XStatic = $this.XDoc.CreateElement('static')
            $this.XDoc.Item('entry').AppendChild($XStatic)
         }
         # Build inner <member>'s
         foreach($MemberCur in $Set) {
            $XMember = $this.XDoc.CreateElement('member')
            $XMember.InnerText = $MemberCur
            $this.XDoc.Item('entry').Item('static').AppendChild($XMember)
         }
      } -Force
 
      # Filter ScriptProperty linked to $XDoc.entry.dynamic.filter.InnerText
      Update-TypeData -TypeName $TypeName -MemberName Filter -MemberType ScriptProperty -Value {
      # Getter
         return $this.XDoc.Item('entry').Item('dynamic').Item('filter').InnerText
      } -SecondValue {
      # Setter
         param($Set) 
         # If <dynamic> is not already present
         if(-not $this.XDoc.Item('entry').Item('dynamic')) {
            # Build and add <dynamic>
            $XDynamic = $this.XDoc.CreateElement('dynamic')
            $this.XDoc.Item('entry').AppendChild($XDynamic)         
         }
         # If <filter> element exists
         if($this.XDoc.Item('entry').Item('dynamic').Item('filter')) {
            if([String]::IsNullOrEmpty($Set)) {
               # Remove the <filter> element entirely
               $XFilter = $this.XDoc.Item('entry').Item('dynamic').Item('filter')
               $this.XDoc.Item('entry').RemoveChild($XFilter)
            }
            else {
               $this.XDoc.Item('entry').Item('dynamic').Item('filter').InnerText = $Set
            }
         }
         # No existing <filter>
         else {
            # Build a new <filter> element
            $XFilter = $this.XDoc.CreateElement('filter')
            $XFilter.InnerText = $Set
            $this.XDoc.Item('entry').Item('dynamic').AppendChild($XFilter)
         }
      } -Force
   } # End static constructor

   # Clone() method as part of ICloneable interface
   [Object] Clone() {
      return [PanAddressGroup]::new(
         $this.Device,
         $this.XPath.Clone(),
         $this.XDoc.Clone()
      )
   } # End method

   # ToString() Method
   [String] ToString() {
      return $this.Name
   } # End method

} # End class