Active Directory Powershell to manage Sites and Subnets – Part 2 (New-XADSubnet)

In an earlier post “Active Directory Powershell to manage sites – Part 1 (New-XADSite)” Jairo explained in detail about how to create a Site in Active Directory using AD Powershell. In today’s post I am going to discuss about how to create Subnets using AD Powershell.

Before going into details of creating a subnet object, first let us understand what is a Site and Subnet.

The following definition is from: https://technet.microsoft.com/en-us/library/cc782048(WS.10).aspx

In Active Directory, a site is a set of computers well-connected by a high-speed network, such as a local area network (LAN). All computers within the same site typically reside in the same building, or on the same campus network. A single site consists of one or more Internet Protocol (IP) subnets. Subnets are subdivisions of an IP network, with each subnet possessing its own unique network address. A subnet object in AD DS groups neighboring computers in much the same way that postal codes group neighboring postal addresses .

Sites and subnets are represented in Active Directory by site and subnet objects. Each site object is associated with one or more subnet objects. By associating a site with one or more subnets, you assign a set of IP addresses to the site.

NOTE: The term "subnet" in AD DS does not have the strict networking definition of the set of all addresses behind a single router. The only requirement for an AD DS subnet is that the address prefix conforms to the IP version 4 (IPv4) or IP version 6 (IPv6) format.

A subnet object (objectClass=subnet) in AD DS stores the prefix information in its name and the site information in attribute siteObject. It should be created under “CN=Subnets,CN=Sites,<Configuration-NC>”. For documentation on “how to determine the prefix of a subnet” bing for the phrase “Entering Address Prefixes”.

I have written a function below called New-XADSubnet that creates a new subnet. It accepts the subnet-prefix, site name, description and location of the subnet as input. The function also does some validation to provide a reliable experience, for example: it validates whether the specified subnet prefix length is correct or not. If a prefix length is not specified then this function will auto-generate the prefix length based on the number of trailing zero bits found in the prefix-ip-address.

NOTE: The script below uses Test-XADObject function published in a previous post.

    1:  #
    2:  # Advanced Function to create a new subnet.
    3:  #
    4:  function New-XADSubnet() {
    5:     [CmdletBinding(ConfirmImpact="Low")]
    6:     Param (
    7:        [Parameter(Mandatory=$true,
    8:                   Position=0,
    9:                   ValueFromPipeline=$true,
   10:                   HelpMessage="Prefix of the subnet to be created. This should be a combination of IP Address followed by / and Prefix length. IPv4 example: 157.54.208.0/20  IPv6 example: 3FFE:FFFF:0:C000::/64 . NOTE: If the prefix length is not specified then this cmdlet will auto-generate the prefix length based on the number of trailing zero bits. For example: If supplied Prefix is 157.54.208.0 then the subnet created is 157.54.208.0/20. Another example: If supplied Prefix is 3FFE:FFFF:0:C000:: then the subnet created is 3FFE:FFFF:0:C000::/34"
   11:                  )]
   12:        [String] $Prefix,
   13:        [Parameter(Mandatory=$false,
   14:                   Position=1,
   15:                   ValueFromPipeline=$false,
   16:                   HelpMessage="Site to which the subnet will be applied. Accepts Site name, Guid, DN or ADObject representing the site"
   17:                  )]
   18:        [Object] $Site,
   19:        [Parameter(Mandatory=$false,
   20:                   ValueFromPipeline=$false,
   21:                   HelpMessage="Description"
   22:                  )]
   23:        [String] $Description,
   24:        [Parameter(Mandatory=$false,
   25:                   ValueFromPipeline=$false,
   26:                   HelpMessage="Location"
   27:                  )]
   28:        [String] $Location
   29:     )
   30:     PROCESS {
   31:   
   32:        if ([String]::IsNullOrEmpty($Prefix)) {
   33:           throw New-Object System.Management.Automation.PSArgumentException("Prefix name cannot be an empty string, please try again.")
   34:        }
   35:        
   36:        $newSubnetName = $Prefix
   37:        
   38:        if ($Prefix.Contains("/")) {
   39:            $subnetIPAddressStr,$prefixLengthStr = $newSubnetName.Split("/")
   40:            $subnetIPAddress = [System.Net.IPAddress]::Parse($subnetIPAddressStr)
   41:            $specifiedPrefixLength = [int]::Parse($prefixLengthStr)
   42:            
   43:            $ipAddressPrefixLength = GetIPAddressPrefixLength $subnetIPAddress
   44:            if ($ipAddressPrefixLength -gt $specifiedPrefixLength) {
   45:                throw New-Object System.Management.Automation.PSArgumentException("The subnet prefix length you specified is incorrect. Please check the prefix and try again.")
   46:            }
   47:            
   48:        } else {
   49:            $subnetIPAddress = [System.Net.IPAddress]::Parse($newSubnetName)
   50:            $prefixLength = GetIPAddressPrefixLength $subnetIPAddress
   51:            $newSubnetName = $newSubnetName + "/" + $prefixLength
   52:        }
   53:   
   54:        # Get the configuration partition DN, the sites container and build the new site DN
   55:        $configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
   56:        $subnetContainerDN = ("CN=Subnets,CN=Sites," + $configNCDN)
   57:        $newSubnetDN = ("CN=" + $newSubnetName +"," + $subnetContainerDN)
   58:        $siteDN = $null
   59:        if ($Site -ne $null) {
   60:            $siteDN = (Get-XADSite $Site).DistinguishedName
   61:        }
   62:   
   63:        # Verify if the subnet already exists
   64:        $subnetExists = Test-XADObject -Identity $newSubnetDN
   65:        if ($subnetExists) {
   66:           throw New-Object System.Management.Automation.PSArgumentException("Subnet already exists. Please check the name and try again.")
   67:        }
   68:        
   69:        [Hashtable] $ht = new-object -type hashtable
   70:        if ($siteDN -ne $null) {
   71:            $ht.Add("siteObject", $siteDN)
   72:        }
   73:        if (-not [String]::IsNullOrEmpty($Description)) {
   74:            $ht.Add("description", $Description)
   75:        }
   76:        if (-not [String]::IsNullOrEmpty($Location)) {
   77:            $ht.Add("location", $Location)
   78:        }
   79:   
   80:   
   81:        # Create subnet object 
   82:        if ($ht.Count -eq 0) {
   83:            New-ADObject -Name $newSubnetName -Path $subnetContainerDN -Type subnet 
   84:        } else {
   85:            New-ADObject -Name $newSubnetName -Path $subnetContainerDN -Type subnet -OtherAttributes $ht
   86:        }
   87:   
   88:        # Fetch the subnet object
   89:        Get-ADObject $newSubnetDN -properties "siteObject", "description", "location"
   90:   
   91:      }
   92:  }
   93:   
   94:   
   95:   
   96:  #
   97:  # Internal utility function
   98:  # This function returns the number of trailing zeroes in the input byte
   99:  #
  100:  function GetNumberOfTrailingZeroes {
  101:  Param ([byte] $x)
  102:  $numOfTrailingZeroes = 0;
  103:  if ( $x -eq 0) {
  104:     return 8
  105:  }
  106:  if ( $x % 2 -eq 0) {
  107:     $numOfTrailingZeroes ++;
  108:     $numOfTrailingZeroes +=  GetNumberOfTrailingZeroes($x/2);
  109:  }
  110:  return $numOfTrailingZeroes
  111:  }
  112:   
  113:   
  114:   
  115:   
  116:  #
  117:  # Internal utility function
  118:  # This function returns the number of non-zero bits in an ip-address
  119:  #
  120:  function GetIPAddressPrefixLength {
  121:  Param ([System.Net.IPAddress] $ipAddress)
  122:  $byteArray = $ipAddress.GetAddressBytes()
  123:  $numOfTrailingZeroes = 0;
  124:  for ($i = $byteArray.Length - 1; $i -ge 0; $i--) {
  125:      $numOfZeroesInByte = GetNumberOfTrailingZeroes($byteArray[$i]);
  126:      if ($numOfZeroesInByte -eq 0) {
  127:         break;
  128:      }
  129:      $numOfTrailingZeroes += $numOfZeroesInByte;
  130:  }
  131:  (($byteArray.Length * 8) - $numOfTrailingZeroes)
  132:  }

Sample usage in my test environment:

PS AD:\>
PS AD:\> New-XADSubnet "10.10.0.0/16"  -Location "Redmond,WA" -Description "Redmond Subnet"

Description       : Redmond Subnet
DistinguishedName : CN=10.10.0.0/16,CN=Subnets,CN=Sites,CN=Configuration,DC=dsw
                    amipat-w7-vm1,DC=nttest,DC=microsoft,DC=com
location          : Redmond,WA
Name              : 10.10.0.0/16
ObjectClass       : subnet
ObjectGUID        : e4c924ff-ee39-47e3-8b92-71a8600188af

Cheers,

Swami

--

Swaminathan Pattabiraman

Developer – Active Directory Powershell Team