Last Update: Sep 04, 2024 | Published: Jun 25, 2015
During my recent PowerShell workshop in Finland, an attendee asked about Active Directory cmdlets from Microsoft in regards to remote desktop user settings. Although you can readily see the settings in Active Directory Users and Computers, Get-ADUser doesn’t retrieve them. I haven’t worked with Remote Desktop Services in quite a while, but I told him I’d look into this long-standing problem.
Let’s look at the problem. In Active Directory Users and Computers, you might have a setting like this:
I don’t have a Remote Desktop server, so I’m just improvising values. You would think that when you run Get-ADUser, you would see these properties somewhere.
Get-ADUser jdemo –properties *
But you don’t. However, if you happen to be using the ActiveRoles cmdlets from Dell (formerly part of Quest Software), then you can easily get these settings.
What’s going on here?
After a bit of research, I discovered this is a long-standing issue that seems to originate with a design decision made by the Terminal Services team. Instead of populating the user account with appropriate properties, they elected to store the information in a blob that’s part of the UserParameters property.
That’s unfortunate for us or at least those of us who don’t want to have to write complicated code to decode this data. I looked, and I found examples and thought about converting them to PowerShell, but it would be a ridiculously complicated process. Quest’s developers took the time to write the necessary code to extract the information. I don’t have that much time.
Instead, we can revert to some old-school techniques to get this information. This requires traditional LDAP connections to a domain controller. First, we need to create an ADSI object for the user account.
[ADSI]$user = "LDAP://CN=John Demo,OU=Development,DC=Globomantics,DC=Local"
By using the COM object, the terminal services properties are easily retrieved.
I can also easily change them.
$user.terminalServicesHomeDirectory = "Y:RDS"
$user.setinfo()
As you can imagine, this would be a lot of work to do manually, so I wrote a function to get remote desktop settings from the ADUC tab that I’ve been showing you.
Function Get-RDUserSetting { [cmdletbinding(DefaultParameterSetName="SAM")] Param( [Parameter(Position=0,Mandatory,HelpMessage="Enter a user's sAMAccountName", ValueFromPipeline,ParameterSetName="SAM")] [ValidateNotNullorEmpty()] [Alias("Name")] [string]$SAMAccountname, [Parameter(ParameterSetName="SAM")] [string]$SearchRoot, [Parameter(Mandatory,HelpMessage="Enter a user's distingished name", ValueFromPipelineByPropertyName,ParameterSetName="DN")] [ValidateNotNullorEmpty()] [Alias("DN")] [string]$DistinguishedName, [string]$Server ) Begin { Write-Verbose "Starting $($MyInvocation.MyCommand)" Write-Verbose ($PSBoundParameters | Out-String) #remote desktop properties $TSSettings = @("TerminalServicesProfilePath","TerminalServicesHomeDirectory","TerminalServicesHomeDrive") } #Begin Process { Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)" Switch ($PSCmdlet.ParameterSetName) { "SAM" { Write-Verbose "Retrieving distinguishedname for $samAccountname" $searcher = New-Object DirectoryServices.DirectorySearcher $searcher.Filter = "(&(objectcategory=person)(objectclass=user)(samAccountname=$sAMAccountname))" Write-Verbose $searcher.filter if ($SearchRoot) { Write-Verbose "Searching from $SearchRoot" if ($Server) { $searchPath = "LDAP://$server/$SearchRoot" } else { $searchPath = "LDAP://$SearchRoot" } $r = New-Object System.DirectoryServices.DirectoryEntry $SearchPath $searcher.SearchRoot = $r } $user = $searcher.FindOne().GetDirectoryEntry() } "DN" { Write-Verbose "Processing $DistinguishedName" if ($server) { Write-Verbose "Connecting to $Server" [ADSI]$User = "LDAP://$Server/$DistinguishedName" } else { [ADSI]$User = "LDAP://$DistinguishedName" } } } #close Switch if ($user.path) { #initialize a hashtable Try { $hash=[ordered]@{ DistinguishedName = $User.DistinguishedName.Value Name = $user.name.Value samAccountName = $user.samAccountName.value AllowLogon = $user.psbase.InvokeGet("AllowLogon") -as [Boolean] } foreach ($property in $TSSettings) { $hash.Add($property,$user.psbase.invokeGet($property)) } #foreach #create an object New-Object -TypeName PSObject -Property $hash } Catch { Write-Warning "Failed to retrieve remote desktop settings for $Distinguishedname. $($_.exception.message)" } } #if user found else { Write-Warning "Failed to find user $DistinguishedName. $($_.exception.message)" } } #Process End { Write-Verbose "Ending $($MyInvocation.MyCommand)" } #End } #end function
To use the command, you can either specify a samaccountname or a distinguishedname. I wrote it so that you could use it in conjunction with Get-ADUser.
I always encourage people to write tools that can take advantage of the pipeline.
get-aduser -filter "Name -like 'demouser10*'" | get-rdusersetting | where {-Not $_.AllowLogon}
The function doesn’t have much in the way of help, but you can specify a search path if searching by account name. You can also specify a domain controller.
Of course, you need a corresponding command to set these settings.
Function Set-RDUserSetting { [cmdletbinding(SupportsShouldProcess)] Param( [Parameter(Position=0,Mandatory,HelpMessage="Enter a user's sAMAccountName", ValueFromPipeline,ParameterSetName="SAM")] [ValidateNotNullorEmpty()] [Alias("Name")] [string]$SAMAccountname, [Parameter(ParameterSetName="SAM")] [string]$SearchRoot, [Parameter(Mandatory,HelpMessage="Enter a user's distingished name", ValueFromPipelineByPropertyName,ParameterSetName="DN")] [ValidateNotNullorEmpty()] [Alias("DN")] [string]$DistinguishedName, [boolean]$AllowLogon, [Alias("Profile")] [string]$TerminalServicesProfilePath, [Alias("HomeDirectory")] [string]$TerminalServicesHomeDirectory, [Alias("HomeDrive")] [string]$TerminalServicesHomeDrive, [string]$Server, [switch]$Passthru ) Begin { Write-Verbose "Starting $($MyInvocation.MyCommand)" Write-Verbose ($PSBoundParameters | out-string) #remote desktop properties $TSSettings = @("TerminalServicesProfilePath","TerminalServicesHomeDirectory","TerminalServicesHomeDrive") } #Begin Process { Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)" Switch ($PSCmdlet.ParameterSetName) { "SAM" { Write-Verbose "Retrieving distinguishedname for $samAccountname" $searcher = New-Object DirectoryServices.DirectorySearcher $searcher.Filter = "(&(objectcategory=person)(objectclass=user)(samAccountname=$sAMAccountname))" Write-Verbose $searcher.filter if ($SearchRoot) { Write-Verbose "Searching from $SearchRoot" if ($Server) { $searchPath = "LDAP://$server/$SearchRoot" } else { $searchPath = "LDAP://$SearchRoot" } $r = New-Object System.DirectoryServices.DirectoryEntry $SearchPath $searcher.SearchRoot = $r } $user = $searcher.FindOne().GetDirectoryEntry() } "DN" { Write-Verbose "Processing $DistinguishedName" if ($server) { Write-Verbose "Connecting to $Server" [ADSI]$User = "LDAP://$Server/$DistinguishedName" } else { [ADSI]$User = "LDAP://$DistinguishedName" } } } #close Switch if ($user.path) { if ($PSBoundParameters.ContainsKey("AllowLogon")) { Write-Verbose "Configuring AllowLogon" $user.psbase.invokeSet("AllowLogon",$AllowLogon -as [int]) } foreach ($property in $TSSettings) { if ($PSBoundParameters.ContainsKey($property)) { Write-Verbose "Setting $property = $($PSBoundParameters[$property])" $user.psbase.invokeSet($property,$PSBoundParameters[$property]) } } #commit changes if ($PSCmdlet.ShouldProcess($DistinguishedName)){ $user.setInfo() } #Whatif if ($Passthru) { $hash=[ordered]@{ DistinguishedName = $User.DistinguishedName.Value Name = $user.name.Value samAccountName = $user.samAccountName.value AllowLogon = $user.psbase.InvokeGet("AllowLogon") -as [Boolean] } foreach ($property in $TSSettings) { $hash.Add($property,$user.psbase.InvokeGet($property)) } #foreach #create an object New-Object -TypeName PSObject -Property $hash } } #if user found else { Write-Warning "Failed to find user $DistinguishedName" } } #Process End { Write-Verbose "Ending $($MyInvocation.MyCommand)" } #End } #end function
Normally I’m not a big fan of parameters that take Boolean values, but it was the simplest solution in this case. Now I can easily set remote desktop settings for multiple user accounts.
Get-ADUser -filter "Name -like 'demouser20*'" |
Set-RDUserSetting -AllowLogon $True -TerminalServicesProfilePath "\CHI-RDS01Profiles$($_.samaccountname)" -TerminalServicesHomeDirectory "Y:RDS" -Passthru
You might also need to configure settings from the Sessions tab. Those settings can be set the same way. You can either modify my functions or write your own. The property names are:
The time settings are in minutes.
I would recommend putting these functions into a module. This is definitely something you will want to test in a non-production setting.