In response to my recent set of posts someone pointed me to a new PowerShell 3.0 feature of PowerShell sessions – RunAs… I had seen people use PowerShell session configurations in the past but I had never really seen a need to use them myself that is until now.  With the challenges of constrained delegation and some of the limitations of CredSSP the ability to define an account on your Hyper-V hosts for which all commands will run in the context of can be very powerful and that’s exactly what this provides.

Basic Configuration

GPOFor example let’s say I defined a new domain user “HyperVRemoteCommands” and I restrict the account as I would any other service account – no interactive login, no remote desktop login etc…  and I add it to the local Hyper-V Administrators group on my Windows 8 Hyper-V servers which will give that account permissions to all of Hyper-V (creating VM’s, deleting VM’s, creating switches etc…)  If I’m using SMB storage, which I am, I would also add that account to the security group that had permissions on my SMB storage.
Now I’ve got a user that can do all of my Hyper-V administration but has no login rights – basically just like an SCVMM service account or a SQL account.  Here’s the cool part you can use the Register-PSSessionConfiguration cmdlet which with PowerShell 3.0 has a new parameter RunAsCredential to create a new WSMan session that will run as that user account.  Here’s an Example:
Register-PSSessionConfiguration -Name HyperVRemoteAdmin -SessionType DefaultRemoteShell -AccessMode Remote -RunAsCredential (Get-Credential "hyper-v\HyperVRemoteCommands")

Now from a remote machine we can connect to that session and execute commands just as we normally would – however they are being run in the context of the RunAs account.
$session = New-PSSession -ComputerName 37-4611K2615L -ConfigurationName HyperVRemoteAdmin
Invoke-Command -Session $session -ScriptBlock {New-VM –Name Demo -Path \\hv-w8-beta-smb\VirtualMachines\Demo}

Advanced Security

The question that remains is who has access to connect to this session and use these permissions?  By default access is same for the new session as the default WSMan instance – which is the local administrators group and interactive users, this can be overridden very easily with custom permissions.  If you specify the ShowSecurityDescriptorUI flag the cmdlet will prompt you for the credentials requires to this configuration.  There’s another parameter you can specify as well –SecurityDescriptorSddl which is much more script friendly but does require some work as it takes an Sddl string. 

So I wrote a function to generate one – it takes the default WSMan SSDL – removes all of the permissions (local administrators group and interactive users) and then adds a domain security group with full permissions.

Function New-SSDLForADGroup
{
param(
    [String]$GroupName = [String]::Empty
    )
    try {
    #retreave the default SSDL as a base
    $defaultSSDL = (Get-Item WsMan:\localhost\service\rootSDDL).Value
    $isContainer = $false
    $isDS = $false
    $SecurityDescriptor = New-Object -TypeName `
        Security.AccessControl.CommonSecurityDescriptor `
        $isContainer, $isDS, $defaultSSDL

    #remove the default groups (Administrators and Interactive User)
    while($SecurityDescriptor.DiscretionaryAcl.Count -gt 0)
    {
        $ssdl = $SecurityDescriptor.DiscretionaryAcl[0]
        $SecurityDescriptor.DiscretionaryAcl.RemoveAccess(
            [System.Security.AccessControl.AccessControlType]::Allow,
            $ssdl.SecurityIdentifier,
            $ssdl.AccessMask,
            $ssdl.InheritanceFlags,
            $ssdl.PropagationFlags) | Out-Null
    }

    #get the SID for the specified Group and add it to the SSDL
    $AdminGroup = New-Object Security.Principal.NTAccount $GroupName
    $AdminGroupSid = $AdminGroup.Translate(`
        [Security.Principal.SecurityIdentifier]).Value

    $SecurityDescriptor.DiscretionaryAcl.AddAccess(
        [System.Security.AccessControl.AccessControlType]::Allow,
        $AdminGroupSid,
        268435456, #full control all operations
        [System.Security.AccessControl.InheritanceFlags]::None,
        [System.Security.AccessControl.PropagationFlags]::None) | Out-Null

    return $SecurityDescriptor.GetSddlForm("All")
    }
    catch [Exception] {
        Write-Error -Message "Failed To Generate SSDL (review inner exception):`n $_.Message" `
            -Exception $_.Exception
    }
}

Now if we combine that with some active directory scripting to grab all of the Hyper-V Servers in my security group and run the register command on them then all of my Hyper-V hosts will be setup to run delegated commands for any user in my Hyper-V administrators group.  You may notice that I specify –NoServiceRestart in the first command and then restart the service in the second – the reason for this is to ensure that if the first command failed I would get full exception handling.

$HyperVServersGroup = "hyper-v\hv-hosts"
$HyperVAdminsGroup = "hyper-v\hv-admins"
$HyperVRemoteAdminRunAs = Get-Credential "hyper-v\HyperVRemoteCommands"

$HvServersAD = Get-ADGroupMember $HyperVServersGroup
$ssdlForSession = New-SSDLForADGroup -GroupName "hyper-v\hv-admins"

$remoteScriptBlock = {Register-PSSessionConfiguration -Name "HyperVRemoteAdmin" `
                        -SessionType DefaultRemoteShell -AccessMode Remote `
                        -NoServiceRestart -Force -SecurityDescriptorSddl `
                        $args[0] -RunAsCredential $args[1]}

foreach ($computer in $HvServersAD)
{
    Invoke-Command -ComputerName $computer.Name -ScriptBlock $remoteScriptBlock `
        -ArgumentList @($ssdlForSession, $HyperVRemoteAdminRunAs)

    Invoke-Command -ComputerName $computer.Name `
        -ScriptBlock {Restart-Service -Name WinRM -Force} -ErrorAction Ignore
}

Taylor Brown
Hyper-V Enterprise Deployment Team
taylorb@microsoft.com
http://blogs.msdn.com/taylorb

WS08R2-HyperV_v_rgb