Use Delivery Optimization with DHCP Option on Pre-Windows 10 version 1803

The new Windows 10 Peer 2 Peer feature Delivery Optimization was enhanced by the setting to query DHCP option ID 234 to get a Group ID (DOGroupIdSource). It was implemented into the latest Windows 10 version 1803 based on my feedback. I’m a little proud that the idea was well received and my product feedback was implemented as new option within six month.

It was officially announced with Windows Insider Preview 17063. It can be read here: https://blogs.windows.com/windowsexperience/2017/12/19/announcing-windows-10-insider-preview-build-17063-pc/#zluZyU1FMlLMzU6h.97 in the section New features for IT Pros in Delivery Optimization.

I’ve written a blog post how Delivery Optimization works and how to use this feature with new Windows versions 1803+, see here: Configure Delivery Optimization with Intune for Windows Update for Business.  It’s really great to have it with Windows 10 version 1803+ but I need to provide this functionality to older Windows 10 versions (1703 and 1709) also. I want to use this very good and flexible grouping capability from now on for all devices even pre-version 1803.

Therefore I have designed a solution to provide this functionality to older versions of Windows 10 and switch over to the native Windows 10 implemented solution as soon as the device gets upgraded to 1803.

First problem is to get the DHCP option from the DHCP server. Luckily I’ve written a small C++ program to do that in the past. I’ve written it to even work with a WinPE environment. The small binary can be used to send the DHCP option ID x request. When executed the binary expects a result as a string value. Below an example:

DhcpOption.exe <OptionID>
DhcpOptionExeExample

It’s even possible to specify a debug switch

DhcpOption.exe <OptionID> [debug]

to get debug output for troubleshooting:

DhcpOptionExeDebugExample

The C++ solution can be found on my GitHub here:
https://github.com/okieselbach/Helpers/tree/master/DhcpOption

The compiled x64 binary DhcpOption.exe can be found here:
https://github.com/okieselbach/Intune/tree/master/ManagementExtension-Samples/DOScript

With this little helper we can use the Intune Management Extension and design a PowerShell script to:

  1. query DHCP server for the Option ID 234
  2. write result (Group ID) to the registry

Prerequisite is an available Option ID 234 on the DHCP server. See Configure Delivery Optimization with Intune for Windows Update for Business how to configure the DHCP server for this.

To support travelling users we need to make sure the device will query the DHCP server from time to time to get the group ID belonging to the current DHCP scope the client is using. For this we schedule the script as a scheduled task and run it at logon and on every unlock of the workstation.

The Delivery Optimization service will query the registry value for every new request and this makes sure a client uses the Group ID delivered by DHCP from the particular site.

To make sure we do not interfere with the native implementation starting with 1803 I implemented a logic to disable the scheduled task and remove the registry key as soon as Windows 10 version 1803, a build greater than 16299 is found.

The DhcpOption.exe will be provided from an Azure Blob Storage account. We need to create a Storage account as type Blob storage and a container to store the DhcpOption.exe. Then we use the provided download link in the script to get the binary during install. A guide how to create the Azure storage account is included in my article Gather Windows 10 AutoPilot info in Azure Blob Storage during wipe and reload. You don’t need the SAS token, just the container and upload DhcpOption.exe. The SAS token is only needed to support POSTS methods for uploading new files to the blob storage. The DhcpOption.exe is uploaded once and then provided for download only.

With this solution we can use the DHCP option for all Windows 10 version until 1803 and as soon as we get upgraded we disable and remove our self and the native implementation takes over. To make sure the native implementation is used as soon 1803 is found we need to implement the new DO settings (DOGroupIdSource). Follow my guide in my previous post for this Configure Delivery Optimization with Intune for Windows Update for Business.

Source Code of the full custom DO install script Register-DOScript.ps1 can be found on my GitHub Gist here:

<#
Version: 1.1
Author: Oliver Kieselbach
Script: Register-DOScript.ps1
Description:
Register a PS script as scheduled task to query DHCP for option 234 to get Group ID GUID
Release notes:
Version 1.0: Original published version.
Version 1.1: Fix, removed null terminator at the end of DHCP option value provided by DHCPOption.exe
The script is provided "AS IS" with no warranties.
#>
$exitCode = 0
if (![System.Environment]::Is64BitProcess) {
# start new PowerShell as x64 bit process, wait for it and gather exit code and standard error output
$sysNativePowerShell = "$($PSHOME.ToLower().Replace("syswow64", "sysnative"))\powershell.exe"
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $sysNativePowerShell
$pinfo.Arguments = "-ex bypass -file `"$PSCommandPath`""
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.CreateNoWindow = $true
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$exitCode = $p.ExitCode
$stderr = $p.StandardError.ReadToEnd()
if ($stderr) { Write-Error -Message $stderr }
}
else {
# start logging to TEMP in file "scriptname".log
Start-Transcript -Path "$env:TEMP\$($(Split-Path $PSCommandPath -Leaf).ToLower().Replace(".ps1",".log"))" | Out-Null
# definition of PS script to query DHCP option ID 234 and write result to registry
$scriptContent = @'
# With Windows 10 version 1803+ DOGroupIDSource can be set by MDM and Option ID 234 is queried natively by Windows!
# Due to this a version check is done to disable the solution when Windows 10 version 1803 is found.
# REMEMBER: as soon as 1803 is used the DOGroupIDSource must be set!
#
# https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-deliveryoptimization#deliveryoptimization-dogroupidsource
# DOGroupIDSource = 3 (DHCP Option ID)
$registryPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization"
if ([int][Environment]::OSVersion.Version.Build -gt 16299) {
Disable-ScheduledTask -TaskName "RunCustomDOScript" | Out-Null
if (Test-Path $registryPath) {
Remove-Item -Path $registryPath -Force -Confirm:$false
}
}
else {
$dhcpOptionTool = "DhcpOption.exe"
$customScriptsPath = $(Join-Path $env:ProgramData CustomScripts)
$filePath = "$customScriptsPath\$dhcpOptionTool"
$optionId = 234
$optionIdValue = Invoke-Expression -Command "$filePath $optionId"
if (-not [string]::IsNullOrWhiteSpace($optionIdValue)) {
if (!(Test-Path $registryPath)) {
New-Item -Type String -Path $registryPath | Out-Null
}
$Name = "DOGroupId"
$value = $optionIdValue.SubString(0, $optionIdValue.Length - 1)
New-ItemProperty -Path $registryPath -Name $name -Value $value -PropertyType string -Force | Out-Null
}
}
'@
# Scheduled task XMl definition. Trigger on Logon and Workstation unlock
$xmlTask = @'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"&gt;
<RegistrationInfo>
<Author>Admin</Author>
<Description>This script receives DO Group ID from DHCP Option ID 234 and writes value to registry.</Description>
<URI>\RunCustomDOScript</URI>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
<SessionStateChangeTrigger>
<Enabled>true</Enabled>
<StateChange>SessionUnlock</StateChange>
</SessionStateChangeTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-ex bypass -file "C:\ProgramData\CustomScripts\DOScript.ps1"</Arguments>
</Exec>
</Actions>
</Task>
'@
# we register the script only on pre 1803 Windows 10 versions.
if ([int][Environment]::OSVersion.Version.Build -le 16299) {
# create custom script folder and write PS script and dhcp option helper binary
$customScriptsPath = $(Join-Path $env:ProgramData CustomScripts)
if (!(Test-Path $customScriptsPath)) {
New-Item -Path $customScriptsPath -ItemType Directory -Force -Confirm:$false
}
Out-File -FilePath "$customScriptsPath\DOScript.ps1" -InputObject $scriptContent -Encoding unicode -Force -Confirm:$false
$dhcpOptionTool = "DhcpOption.exe"
$dhcpOptionToolPath = "$customScriptsPath\$dhcpOptionTool"
if (Test-Path $dhcpOptionToolPath) {
Remove-Item -Path $dhcpOptionToolPath -Force -Confirm:$false
}
try {
# get DhcpOption.exe from Azure Blob storage
$url = "https://REPLACE-ME-WITH-YOUR-ACCOUNT.blob.core.windows.net/REPLACE-ME-WITH-YOUR-CONTAINER/DhcpOption.exe&quot;
$ProgressPreference = 0
Invoke-WebRequest $url -OutFile $dhcpOptionToolPath -UseBasicParsing
$taskName = "RunCustomDOScript"
Register-ScheduledTask -TaskName $taskName -Xml $xmlTask -Force
Start-ScheduledTask -TaskName $taskName | Out-Null
}
catch {
Write-Error -Message "Could not write regsitry value" -Category OperationStopped
}
}
Stop-Transcript | Out-Null
}
exit $exitCode

Make sure to replace the Azure blob storage URL with your own one!

The uninstall script Unregister-DOScript.ps1 is also available via my GitHub Gist:

<#
Version: 1.0
Author: Oliver Kieselbach
Script: Unregister-DOScript.ps1
Description:
Unregister the scheduled task and delete DO registry key
Release notes:
Version 1.0: Original published version.
The script is provided "AS IS" with no warranties.
#>
$exitCode = 0
if (![System.Environment]::Is64BitProcess) {
# start new PowerShell as x64 bit process, wait for it and gather exit code and standard error output
$sysNativePowerShell = "$($PSHOME.ToLower().Replace("syswow64", "sysnative"))\powershell.exe"
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $sysNativePowerShell
$pinfo.Arguments = "-ex bypass -file `"$PSCommandPath`""
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.CreateNoWindow = $true
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$exitCode = $p.ExitCode
$stderr = $p.StandardError.ReadToEnd()
if ($stderr) { Write-Error -Message $stderr }
}
else {
# start logging to TEMP in file "scriptname".log
Start-Transcript -Path "$env:TEMP\$($(Split-Path $PSCommandPath -Leaf).ToLower().Replace(".ps1",".log"))" | Out-Null
$taskName = "RunCustomDOScript"
Stop-ScheduledTask -TaskName $taskName | Out-Null
if (Get-ScheduledTask -TaskName $taskName) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
$customScriptsPath = $(Join-Path $env:ProgramData CustomScripts)
$dhcpOptionPath = "$customScriptsPath\DhcpOption.exe"
$doScriptPath = "$customScriptsPath\DOScript.ps1"
$registryPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization"
if (Test-Path $dhcpOptionPath) {
Remove-Item -Path $dhcpOptionPath -Force -Confirm:$false
}
if (Test-Path $doScriptPath) {
Remove-Item -Path $doScriptPath -Force -Confirm:$false
}
if (Test-Path $registryPath) {
Remove-Item -Path $registryPath -Force -Confirm:$false
}
Stop-Transcript | Out-Null
}
exit $exitCode

If we combine the script with Intune Management Extension we can easily deploy this solution to our Azure AD joined modern managed Windows 10 pre-version 1803 devices.

We will get the files for scheduling at C:\ProgramData\CustomScripts

DOScriptProgramDataFiles

and the scheduled task RunCustomDOscript

DOScriptScheduledTask

with task trigger logon or unlock of workstation

DOScriptScheduledTaskTrigger

when executed we will get the Group ID from the DHCP server

MD DHCP Group ID Option in Scope

and the received string is written to the registry

DOGroupIdRegistry

for me this bridges the time until all devices are upgraded to Windows 10 version 1803 and gives me enough flexibility to group my devices to achieve maximum Delivery Optimization benefits.

Happy caching!