DHCP Option 119 – DNS Search Suffix – PowerShell Array Builder

Although Microsoft clients may not support DHCP Option 119, it is nonetheless a very important option for Linux and OSX clients. However, configuring such an option is not exactly the friendliest thing in the world.

The RFC 3397 (http://tools.ietf.org/html/rfc3397) defines the standard. It requires that all options be presented as a byte array. One of the main benefits of the RFC though is the ability to use pointers to save duplication in the values that are transferred to the client. This again though makes the process of creating the correct array of values more prone to error.

For example, looking at the following list, it returns two domains, craig-tolley.co.uk and foobar.co.uk:

0x0C 0x63 0x72 0x61 0x69 0x67 0x2D 0x74 0x6F 0x6C 0x6C 0x65 0x79 0x02 0x63 0x6F 0x02 0x75 0x6B 0x00 0x06 0x66 0x6F 0x6F 0x62 0x61 0x72 0xC0 0x0D

Hardly readable is it?

I needed to do this for half a dozen scopes, each with 3 or 4 suffixes to be presented to the clients. Not wanting to waste a whole day setting up the array values, I spent a little time building a PowerShell function that can take a series of domain suffixes in order, and convert them into a byte array, utilising the pointers as much as possible.

How to Use It

Copy the entire function into a PowerShell window. This will create the function. Next, use it like this:

Make-HexDomainSearchSuffix -Domains craig-tolley.co.uk, foobar.co.uk

This will then print out a series of hex values ready for entering into the DHCP console.
However, this is still too much work for me, and still subject to errors creeping in. If your DHCP servers are running Windows 2012, then you have the PowerShell DHCP cmdlets at your disposal, and you can push the output straight into the option like this:

Set-DhcpServerv4OptionValue -ScopeId 192.68.10.0 -OptionId 119 -Value (Make-HexDomainSearchSuffix -Domains craig-tolley.co.uk, foobar.co.uk)

Easy.

Setting Up Microsoft Windows DHCP Server to Present Option 119

Microsoft DHCP servers do not make this option available by default. It has to be added as a Predefined Option before you can assign the option to a scope or to the server.
To do this:

  1. Open the DHCP Console and expand the server node.
  2. Right click IPv4 and select ‘Predefined Options and Values
  3. Ensure the Option Class is ‘DHCP Standard Options’
  4. Click Add and enter the values in the image below.DHCP Option 119
  5. Click OK

The option is now available to be added to scopes and servers.

The Script


# ----------------------------------------------------------------------------------------------------------
# PURPOSE:    Creates a byte array for use with DHCP Option 119
#
# VERSION     DATE         USER                DETAILS
# 1           07/03/2016   Craig Tolley        First version
# 1.1         08/03/2016   Craig Tolley        Fixed issue where if the whole domain matched the pointer was incorrect
# 1.2         08/03/2017   Craig Tolley        Fixed further issues where the whole domain was matched the pointers are incorrect
#                                              Modified outputs and tidied some formatting
#                                              Convert-StringToOpt119Hex option fixed to not return values for null/empty strings
# ----------------------------------------------------------------------------------------------------------

function Make-HexDomainSearchSuffix
{
    Param
    (
    [Parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    $Domains
    )

    # Helper function for converting whole strings to byte arrays.
    function Convert-StringToOpt119Hex
    {
        Param (
            [String]$SourceText
        )

        if ($SourceText -eq $null -or $SourceText.Trim().Length -eq 0) { return }
        $Hexed = @()
        $SourceText.Split(".") | ForEach { 
            $Hexed += [String]::Format("0x{0:X2}" -f [int]($_.ToCharArray().Count)); 
            $_.ToCharArray() | ForEach { 
                $Hexed += [String]::Format("0x{00:X2}" -f [int]$_)
                };
            }   
        return $Hexed
    }

    # Build the list of objects that we want to work with. 
    $DomListInt = @()
    ForEach ($Domain in $Domains)
    {
        $D = New-Object Object
        Add-Member -InputObject $D -MemberType NoteProperty -Name "DomainName" -Value $Domain
        Add-Member -InputObject $D -MemberType NoteProperty -Name "LinkedDomainIndex" -Value $null
        Add-Member -InputObject $D -MemberType NoteProperty -Name "LinkedDomainStartIndex" -Value $null
        Add-Member -InputObject $D -MemberType NoteProperty -Name "HexArray" -Value (New-object System.Collections.Arraylist)
        Add-Member -InputObject $D -MemberType NoteProperty -Name "HexLength" -Value 0
        $DomListInt += $D
    }

    # Work out if we can have any links
    ForEach ($Domain in $DomListInt)
                                                                                                                                                    {
    Write-Host "Current Domain: $($Domain.DomainName)"
    # Ignore the first domain, must be converted in full
    $DIndex = $DomListInt.IndexOf($Domain)
    if ($DIndex -eq 0)
    {
        #Write-Output "First Domain"
        $Domain.HexArray = Convert-StringToOpt119Hex -SourceText $Domain.DomainName -Pointer $Null
        continue
    }

    $Matched = $false
    $c = $($Domain.DomainName.Split(".").Count)
    Write-Host "Parts: $c"
    
    for ($i = 0; $i -lt $c; $i++)
    { 
        $DPart = [String]::Join(".", $Domain.DomainName.Split(".")[$i..$c])
        Write-Host "Comparing $DPart"
        # If the string can be found in a previous domain, then it can be linked. 
        $PartMatchDomain = ($DomListInt[0..($DIndex-1)] | Where { $_.DomainName -like "*$($DPart)"} | Select -First 1)
        if ($PartMatchDomain -ne $null)
        {
            Write-Host "Found in $($PartMatchDomain.DomainName)"
            Write-Host "Match Index: $($PartMatchDomain.DomainName.ToString().IndexOf($DPart))"
            $Domain.LinkedDomainIndex = $DomListInt.IndexOf($PartMatchDomain)
            $Domain.LinkedDomainStartIndex = $($PartMatchDomain.DomainName.ToString().IndexOf($DPart))
            $UniqueParts = if ($i -gt 0) { $([String]::Join(".",$Domain.DomainName.Split(".")[0..($i-1)])) } else { "" }
            
            Write-Host "Unique Parts: $UniqueParts"
            $Domain.HexArray += Convert-StringToOpt119Hex -SourceText $UniqueParts

            $i = $c # Causes the loop to stop
            $Matched = $true
        }
    }

    # If not matched, then the entry needs including in full
    if ($Matched -eq $false)
    {
        $Domain.HexArray = Convert-StringToOpt119Hex -SourceText $Domain.DomainName -Pointer $Null
    }
    }

    # And finally, lets put it all together
    $HexOutput = @()
    ForEach ($Domain in $DomListInt)
    {
        $HexOutput += $Domain.HexArray
    
        # If no linked domain, then null terminate
        if ($Domain.LinkedDomainIndex -eq $null)
        {
            $HexOutput += "0x00"
            $Domain.HexLength = $Domain.HexArray.Count + 1
        }

        # If linked domain index = 0 then, the start point is simply the start index
        elseif ($Domain.LinkedDomainIndex -eq 0)
        {
            $HexOutput += "0xC0" # Compression Link
            $HexOutput += [String]::Format("0x{0:X2}" -f [int]($Domain.LinkedDomainStartIndex))
            $Domain.HexLength = $Domain.HexArray.Count + 2
        }

        # If linked domain is not 0, then the start index needs to be calculated
        else
        {
            $HexOutput += "0xC0" # Compression Link
            $HexOutput += [String]::Format("0x{0:X2}" -f [int](($DomListInt[0..($Domain.LinkedDomainIndex-1)] | Measure -Sum HexLength).Sum + $Domain.LinkedDomainStartIndex))
            $Domain.HexLength = $Domain.HexArray.Count + 2
        }
    }

    Write-Output $HexOutput
}

4 people found this post useful.


19 thoughts on “DHCP Option 119 – DNS Search Suffix – PowerShell Array Builder

  1. This works with windows 10 client computers, 1809 LTSC. I am very surprised to see that it did. All these years later and this is still a very useful post.

  2. Love this solutions and I am trying to implement. Running into issues with PS against a 2012R2 DHCP server.

    The ‘Make-HexDomainSearchSuffix’ works great on its own, the output works when added by hand. Option 119 is set as a byte array. When trying to run with Set-DhcpServerv4OptionValue I get an error:

    [adc01]: PS C:\Users\a-cgagnon\Documents> Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -OptionId 119 -Value (Make-HexDomainSearchSuffix -Domains domain1, domain2)
    Current Domain: domain1
    Current Domain: domain2
    Parts: 3
    Comparing domain2
    Comparing domain1
    Found in domain1
    Match Index: 0
    Unique Parts: c
    Set-DhcpServerv4OptionValue : Failed to get option definition for option ID 119 on DHCP server ADC01.
    At line:1 char:1
    + Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 -OptionId 119 -Value (Make-HexDom …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (119:root/Microsoft/…erv4OptionValue) [Set-DhcpServerv4OptionValue], CimException
    + FullyQualifiedErrorId : DHCP 20010,Set-DhcpServerv4OptionValue

    Any ideas?

    1. Hi Charles, that error looks like the server has not had DHCP option 119 added to the list of available options. You would need to follow the section ‘Setting Up Microsoft Windows DHCP Server to Present Option 119’ above before running the script. Hope that helps.

    1. Thanks, yes that is an easier way, but as far as I can see that code does not compact the values as much as possible by using pointers. This makes it fine if you are only adding a single domain, but for anything where you are adding multiple domains where there are common sections in the FQDN it will not take advantage of the pointers and inflate the size of the option.

  3. Hi,
    I realize this is an old thread, but I am using this script and it looks like I can only add two domains in the search field.

    If I run Set-DhcpServerv4OptionValue -ScopeId 10.20.239.0 -OptionId 119 -Value (Make-HexDomainSearchSuffi
    x -Domains foo1.net, foo2.net,foo.net) I get a “paramaters for opton value tobe set for option ID 119 do not match with option definition on DHCP server.

    Using only two domains (which implies using only one comma) it will work.

    1. Hi, sorry about that, I updated the code a couple of weeks back and accidentally dropped in a ‘Write-Output’ instead of a ‘Write-Host’. Now fixed and tested.

  4. I sooooo wish this would work for me. It’s so convoluted to do it manually.

    Here’s what happens:
    PS C:\Users\username> Make-HexDomainSearchSuffix -Domains abc.def.com, xyz.def.com
    def.com
    System.Object
    Found in abc.def.com
    Match Index: 4
    Unique Parts: xyz
    0x03
    0x61
    0x62
    0x63
    0x03
    0x64
    0x65
    0x66
    0x03
    0x63
    0x6F
    0x6D
    0x00
    0x03
    0x78
    0x79
    0x7A
    0xC0
    0x04

    1. Sorry for the delay in responding. I’ve had a look and fixed a couple of minor issues in the script now.

      The output you show is roughly what I would expect, so what was the issue with it?

    1. The 0x0 is a null terminator and should be put at the end of the domain. The script does not put in any leading terminators.

      I don’t have a script to reverse the result, but you could certainly use [String]::Format to convert each of the bytes back to their representative character.

  5. Thanks for the write-up and the code! You can add the option definition in powershell as well.

    “Add-DhcpServerv4OptionDefinition -Name “Domain Search List” -Description “Domain Suffix Search Order” -OptionId 119 -Type Byte -MultiValued”

    1. Are you able to give me some more information? I have just tried it with the blah.blah.com example and the results are as I would expect. If you are seeing something different then it would be good to know so that I can fix it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.