Free PowerShell Script: Quickly Configure Constrained Delegation for Hyper-V

Do you need to Live Migrate a Hyper-V guest from one unclustered host to another? Would you like to give your Hyper-V host the ability to directly mount ISO files from a file server or VMM library system? Do you ever need to do these things when you’re not logged on directly to the host that needs to perform these operations? Constrained delegation is a necessity.

Updated on February 6, 2017. Notes below the script and in the PowerShell help.

Briefly, delegation is the ability for one Active Directory computer to capture credentials from a currently-logged-on user and present them to a second computer. It’s similar to what CredSSP does except that it’s more secure and credentials can continue to be propagated as far as the allowed delegation chain extends. This is a powerful capability and is not to be used lightly. In order to restrict the impact of any potential compromise, constrained delegation allows you to select specific services that a computer can present credentials for. Unlike CredSSP, delegation requires a bit of effort on your part. The script included herein allows you to Configure Constrained Delegation for Hyper-V, find out the explanation below.

The GUI Way

The GUI, and some would say simple, way is to access the computer object in Active Directory Users and Computers and configure hosts using the Delegation tab. The following is taken from one of my Hyper-V hosts:

Constrained Delegation in ADUC

Constrained Delegation in ADUC

The host listed here (SVHV1) can present credentials to SVSTORE when performing SMB operations. It can present credentials to SVHV2 when performing unclustered Live Migration or SMB operations.

For just one or two hosts, doing this via ADUC isn’t too bad. More than a few can quickly get tiring, even for the most dedicated GUI-Only Clickmins. If you’re tempted to take the reduced-security shortcut of selecting Trust this computer for delegation to any service (Keberos only), I’ve been told that it doesn’t work for Live Migrations. I have not tested this myself because reducing security just because you don’t want to click is even worse than not using PowerShell because of whatever excuses people still have for not using PowerShell.

The Script Way

The sort of tedium and error-prone activity that is exemplified by clicking through constrained delegation screens is exactly what scripting is good for. Unfortunately, the Active Directory properties involved are not conducive to easy discovery or manipulation. So, I built a quick script that can take care of all the heavy lifting for you. All you have to do is save it to a .ps1 file (I assumed Set-VMHostADDelegation.ps1, but whatever you like), then dot-source it in to your environment.

function Set-VMHostADDelegation
{
	<#
	.SYNOPSIS
		Sets Active Directory delegation on specified hosts for SMB transfers and/or Live Migration.
	.DESCRIPTION
		Sets Active Directory delegation on specified hosts for SMB transfers and/or Live Migration.
	.PARAMETER TargetHost
		One or more Active Directory computer object names that will be added to the allowed delegate list of the SourceHost.
	.PARAMETER TargetHost
		One or more Active Directory computers that will be added to the allowed delegate list of the source host.
		Can be supplied as a string array or an array of Microsoft.ActiveDirectory.Management.ADComputer.
	.PARAMETER SourceHostName
		One or Active Directory computers that will allow delegation to the target computer account(s).
		Can be supplied as a string array or an array of Microsoft.ActiveDirectory.Management.ADComputer.
	.PARAMETER SourceHostObject
		One or Active Directory computer objects that will allow delegation to the target computer account(s).
	.PARAMETER LiveMigration
		If set, the specified source system(s) will allow delegation for the 'Microsoft Virtual System Migration Service' to the designated target(s).
	.PARAMETER SMB
		If set, the specified source system(s) will allow delegation for the 'CIFS' service to the designated target(s).
	.PARAMETER Reciprocate
		If set, all items in the source and target lists will be used as sources and targets for all the other items in both lists.
		If this parameter is set, neither SourceHost or SourceHostObject need to be specified.
	.PARAMETER CompatibilityMode
		Choose '2012' to limit the delegated protocol to Kerberos only.
		Choose '2016' to allow delegation to any protocol.
	.PARAMETER NamingMode
		Choose 'DNS' to delegate to the fully-qualified domain name of the target host(s).
		Choose 'NetBIOS' to delegate to the short name of the target host(s).
		The default is 'DNS'.
	.INPUTS
		System.String
		Microsoft.ActiveDirectory.Management.ADComputer
	.OUTPUTS
		None
	.NOTES
		Set-VMHostADDelegation.ps1
		Version 1.1
		February 6, 2017
		Author: Eric Siron
		(c) 2017 Altaro Software

		----------
		1.1
		Added "CompatibilityMode" parameter. Choose 2012 to delegate to Kerberos only. Choose 2016 to delegate to any protocol.
		Added "NamingMode" parameter. Choose DNS to delegate to the FQDN of the target(s). Choose NetBIOS to delegate to the short name(s). Default is DNS.

	.EXAMPLE
		Set-VMHostADDelegation -SourceHost hyperv1 -TargetHost hyperv2 -LiveMigration

		Gives the host named 'hyperv1' the ability to present credentials to 'hyperv2' for Kerberos-based Live Migrations
	.EXAMPLE
		Set-VMHostADDelegation -SourceHost hyperv1 -TargetHost hyperv2 -LiveMigration -Reciprocate

		Gives the hosts named 'hyperv1' and 'hyperv2' the ability to present credentials to each other for Kerberos-based Live Migrations.
	.EXAMPLE
		Set-VMHostADDelegation -TargetHost 'hyperv1', 'hyperv2' -Reciprocate

		Exactly the same outcome as example 2.
	.EXAMPLE
		Set-VMHostADDelegation -TargetHost (Get-ADComputer -Filter 'Name -like "hyper*"') -SMB -LiveMigration -Reciprocate

		Gives every computer in the domain whose name starts with "hyper" the ability to present credentials to every other computer in the domain whose name starts with "hyper" for SMB operations and Kerberos-based Live Migrations.
	.EXAMPLE
		Set-VMHostADDelegation -TargetHost (Get-ADComputer -Filter 'Name -like "hyper*"') -SMB -LiveMigration -Reciprocate

		Gives every computer in the domain whose name starts with "hyper" the ability to present credentials to every other computer in the domain whose name starts with "hyper" for SMB operations and Kerberos-based Live Migrations.
	#>
	#requires -Module ActiveDirectory

	[CmdletBinding(DefaultParameterSetName='By Names', SupportsShouldProcess)]
	param
	(
		[Parameter(Mandatory=$true, Position=1)][PSObject[]]$TargetHost,
		[Parameter(Position=2)][PSObject[]]$SourceHost = @(),
		[Parameter()][Switch]$LiveMigration,
		[Parameter()][Switch]$SMB,
		[Parameter()][Switch]$Reciprocate,
		[Parameter()][ValidateSet('2012', '2016')][String]$CompatibilityMode = '2012',
		[Parameter()][ValidateSet('DNS', 'NetBIOS')][String]$NamingMode = 'DNS'
	)

	Write-Verbose 'Verifying input...'
	if($SourceHost.Count -eq 0 -and $SourceHostObject.Count -eq 0 -and -not $Reciprocate)
	{
		throw 'If no source host is specified, the Reciprocate switch must be set.'
	}

	if(-not($SMB.ToBool() -bor $LiveMigration.ToBool()))
	{
		throw('You must select the SMB switch, the LiveMigration switch, or both.')
	}

	$DelegationServices = @()
	if($SMB)
	{
		$DelegationServices += 'cifs'
	}
	if($LiveMigration)
	{
		$DelegationServices += 'Microsoft Virtual System Migration Service'
	}

	Write-Verbose -Message 'Extracting fully-qualified domain names...'
	$TargetHostGroup = @()
	$SourceHostGroup = @()
	$TargetHostType = $TargetHost[0].GetType()
	$SourceHostType = $null
	if($TargetHostType.FullName -eq 'System.String')
	{
		Write-Verbose -Message 'Targets supplied as strings. Verifying Active Directory accounts...'
		foreach($HostName in $TargetHost)
		{
			Write-Verbose -Message ('Retrieving computer object for {0}...' -f $HostName)
			try
			{
				$Computer = Get-ADComputer -Identity $HostName -ErrorAction Stop
				if($NamingMode -eq 'DNS')
				{
					$TargetName = $Computer.DNSHostName
				}
				else
				{
					$TargetName = $Computer.Name
				}
				Write-Verbose -Message ('Adding {0} to the target list...' -f $TargetName)
				$TargetHostGroup += $TargetName
				if($Reciprocate)
				{
					Write-Verbose -Message ('Reciprocate flag set, copying {0} to the source list...' -f $Computer.DNSHostName)
					$SourceHostGroup += $Computer
				}
			}
			catch
			{
				Write-Error -Message $_.Message # this try/catch just prevents entering blank lines in the target host group
			}
		}
	}
	elseif($TargetHostType.FullName -eq 'Microsoft.ActiveDirectory.Management.ADComputer')
	{
		Write-Verbose -Message 'Targets supplied as strings. Extracting fully-qualified domain names...'
		foreach($Computer in $TargetHost)
		{
			if($NamingMode -eq 'DNS')
			{
				$TargetName = $Computer.DNSHostName
			}
			else
			{
				$TargetName = $Computer.Name
			}
			Write-Verbose -Message ('Adding {0} to the target list...' -f $TargetName)
			$TargetHostGroup += $TargetName
			Write-Verbose -Message ('Reciprocate flag set, copying {0} to the source list...' -f $Computer.DNSHostName)
			$SourceHostGroup += $Computer
		}
	}
	else
	{
		$Arg = New-Object System.Management.Automation.PSArgumentException(('Type {0} is invalid for the TargetHost parameter. Only String and ADComputer objects are accepted.' -f $TargetHostType), 'TargetHost')
		throw($Arg)
	}

	if($SourceHost.Count)
	{
		$SourceHostType = $SourceHost[0].GetType()
		if($SourceHostType.FullName -eq 'System.String')
		{
			foreach($HostName in $SourceHost)
			{
				Write-Verbose -Message 'Sources supplied as strings. Retrieving Active Directory objects...'
				try
				{
					$Computer = Get-ADComputer -Identity $HostName -ErrorAction Stop
					if($SourceHostGroup -notcontains $Computer) # might have been populated by reciprocate action
					{
						Write-Verbose -Message ('Adding {0} to the source list...' -f $Computer.DNSHostName)
						$SourceHostGroup += $Computer
						if($Reciprocate)
						{
							if($TargetHostGroup -notcontains $Computer.DNSHostName)
							{
								if($NamingMode -eq 'DNS')
								{
									$TargetName = $Computer.DNSHostName
								}
								else
								{
									$TargetName = $Computer.Name
								}
								Write-Verbose -Message ('Reciprocate flag set, copying {0} to the target list...' -f $TargetName)
								$TargetHostGroup += $Computer.Name
							}
						}
					}
				}
				catch
				{
					Write-Error -Message $_.Message # this try/catch just prevents entering empty objects to the source host group
				}
			}
		}
		elseif($SourceHostType.FullName -eq 'Microsoft.ActiveDirectory.Management.ADComputer')
		{
			$SourceHostGroup = $SourceHost
			if($Reciprocate)
			{
				if($TargetHostGroup -notcontains $Computer.DNSHostName)
				{
					if($NamingMode -eq 'DNS')
					{
						$TargetName = $Computer.DNSHostName
					}
					else
					{
						$TargetName = $Computer.Name
					}
					Write-Verbose -Message ('Reciprocate flag set, copying {0} to the target list...' -f $TargetName)
					$TargetHostGroup += $Computer.Name
				}
			}
		}
		else
		{
			$Arg = New-Object System.Management.Automation.PSArgumentException(('Type {0} is invalid for the SourceHost parameter. Only String and ADComputer objects are accepted.' -f $SourceHostType), 'SourceHost')
			throw($Arg)
		}
	}

	switch($CompatibilityMode)
	{
		'2012' {
			$AuthModeParameter = @{TrustedForDelegation = $true}
		}
		'2016' {
			$AuthModeParameter = @{TrustedToAuthForDelegation = $true}
		}
	}

	foreach($Computer in $SourceHostGroup)
	{
		foreach($Service in $DelegationServices)
		{
			foreach($TargetComputerName in $TargetHostGroup)
			{
				if($Computer.DNSHostName.ToLower() -ne $TargetComputerName.ToLower())
				{
					if($PSCmdlet.ShouldProcess($Computer.DNSHostName.ToLower(), ('Present credentials to {0} for {1} operations' -f $TargetComputerName, $Service)))
					{
						Set-ADObject -Identity $Computer -Add @{'msDS-AllowedToDelegateTo' = ('{0}/{1}' -f $Service, $TargetComputerName) }
						Set-ADAccountControl -Identity $Computer @AuthModeParameter
					}
				}
			}
		}
	}
}

The script has full Get-Help support, including examples.

Script Explanation

There is only one parameter set, and this how it looks:

SYNTAX
    Set-VMHostADDelegation [-TargetHost] <PSObject[]> [[-SourceHost] <PSObject[]>] [-LiveMigration] [-SMB]
    [-Reciprocate] [-CompatibilityMode <String>] [-NamingMode <String>] [-WhatIf] [-Confirm] [<CommonParameters>]

The basic idea is that you’ll give the script one or more source computers, one or more destination computers, and tell it if you want to configure for SMB, Live Migration, or both.

As a point of clarification, the “Source” host is the one that you would open the ADUC dialog for; the “Target” is the computer that you would add to that computer’s dialog.

Because of the way that the ActiveDirectory PowerShell module auto-loads, I couldn’t just use any of its data types as input parameters. So, I set this script up to accept any object for a source and/or target. However, it expects that the object will be one or more Active Directory computer objects or one or more names of Active Directory computer objects. If it turns out that the supplied object is neither, the script will become upset and stop functioning until it gets what it wants. It will take short names or DNS names — really, anything that it can feed to Get-ADComputer’s Identity parameter.

Working from the assumption that you’d want all Hyper-V hosts that you supply to be able to perform operations on each other, I provided a Reciprocate parameter. If you use it, all items in both the source and target lists will be granted the specified delegation right on all other items in both lists. Because of this, I didn’t see any value in forcing you to specify a source and target list with the Reciprocate parameter. The script will accept the input if you do, but you can just put them all in TargetHost and not supply SourceHost at all.

If you’re not entirely certain what you’re about to do, don’t forget about the WhatIf parameter. I set it to explicitly show what will be changed by the script.

Changes in Version 1.1

I made a couple of substantial, but non-breaking changes in version 1.1.

First, Windows/Hyper-V Server 2016 does not work with constrained delegation set to Kerberos only. You can read John Slack’s article about it on TechNet. The claim is made that delegating to “any” protocol isn’t less safe than delegating to Kerberos only. On the surface, that doesn’t seem to hold a lot of water. I’m sure it’s fine if you’re only using delegation for its intended purpose. I’m not so sure it’s fine when someone is attempting to use delegation for more nefarious goals. I do not have the hacking chops to make that determination, though. All I know is that this is the new normal.

To address this change, I have added a parameter called “CompatibilityMode”. You can choose ‘2012’ or ‘2016’ for this parameter. It knows what the options are, so you can use tab completion and not type. If you do not enter this parameter, it will choose ‘2012’, which is delegation to Kerberos only. If you’d like, you can change the default. Just go to line 80 and swap in ‘2016’ where you see ‘2012’.

Second, I originally wrote the script to delegate to the complete DNS name of the target host(s). Some people said that didn’t work for them. They used the short names instead. The version of the script that I published has a parameter named “NamingMode” that allows you to choose between ‘DNS’ and ‘NetBIOS’. It will delegate accordingly. The default is ‘DNS’. You can change that default on line 81. I first tried to rewrite the script to allow delegation to both the short name and the DNS name, but it always knocks out any DNS names in favor of short names, if a short name is specified. That’s why the script doesn’t allow both.

Script Requirements

I am not certain exactly what permissions are required for configuring constrained delegation on an object, but just having Full Control on the object, its OU, and the target object is insufficient. I didn’t spend any time digging very deeply, but it appears that you really need to be a domain administrator.

The ActiveDirectory PowerShell module must be loaded. All of my domain controllers are running Server Core and I never log on to them directly. Instead, I have Remote Server Administration Tools configured on a couple of management systems. The PowerShell module is listed like this in 2012 R2 (it’s similar in all RSAT displays):

Active Directory PowerShell Module in RSAT

Active Directory PowerShell Module in RSAT

The ActiveDirectory PowerShell module sometimes behaves oddly when run in a remote PowerShell session, which, for me, means that it usually doesn’t work at all. I confess that I haven’t put a lot of effort into getting it to work. I built a management machine for a reason. So, if you use my script in a remote session and get strange errors, see if you can use Get-ADComputer in the same session. If not, that’s probably the root of the problem.

 

Altaro Hyper-V Backup
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

9 thoughts on "Free PowerShell Script: Quickly Configure Constrained Delegation for Hyper-V"

  • Flo says:

    Awesome work 🙂

  • John says:

    Thanks for the script and article.

    Unfortunately it did not work 100% for me as the netbios names were missing from the delegation.

  • John says:

    Thanks for the script and article.

    Unfortunately it did not work 100% for me as the netbios names were missing from the delegation.

  • Niklas Ljung says:

    Thank you for the script Set-VMHostADDelegation.ps1 !

    To further improve the script, I would like the inclusion of “Microsoft Virtual Console Service” and “Hyper-V Replica Service”. I am too much of a noob to make this myself…

  • Frank says:

    HI,

    Thanks for this, although I can’t use the actual script, using it as a reference for my own script has been very helpful.

    I found that in server 2016 the only way I could get it to work was to add the servers using the FQDN then tick the ‘Expand’, but I can’t work out how to do Expand via PowerShell. Could you point me in the right direction?

    Thanks

    • Eric Siron says:

      The “Expanded” checkbox just shifts the dialog box view to include FQDN items, so there is no PS equivalent because PowerShell allows any text that you supply.
      But the script does have a -NamingMode switch so you can tell it to use FQDNs instead of NetBIOS names.

Leave a comment or ask a question

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

Your email address will not be published.

Notify me of follow-up replies via email

Yes, I would like to receive new blog posts by email

What is the color of grass?

Please note: If you’re not already a member on the Dojo Forums you will create a new account and receive an activation email.