I’ve written books about Windows Server operating systems way back from the dark ages of Windows NT. I’ve also used these operating systems in a business environment and helped other IT professionals learn how to administer them properly and troubleshoot them when something goes wrong. One of the Windows Server features that I’ve had frequent occasion to work with is Remote Desktop Services (RDS), which has quite a history. As I mentioned in one of my books, way back in the mainframe age of computers, dumb terminals ruled the roost. Users keyed their data into a “green screen” terminal and it was sent over the wire to the mainframe. Once the user’s commands had been processed, the results were then transmitted back over the wire to the terminal, where they were displayed in bright green on black background. This client/server architecture of dumb terminals and terminal servers can still be seen in operation when you watch classic movies like "Billion Dollar Brain" with Michael Caine.
Of course, terminal servers have come a long way since then. Microsoft first introduced its own version of this architecture way back in 1996 with Windows NT 4.0 Terminal Server edition, and it provided users with session-based desktops they could access remotely as if they were logged on interactively. Then, as successive versions of Windows Server were released, Terminal Services was enhanced in many ways and rebranded as Remote Desktop Services. Today, RDS is a staple for organizations that want to deploy a wide range of remote work solutions including virtual desktops, RemoteApp programs, and session-based desktops. In fact, RDS can even enable users to securely run business applications over the Internet as if they were sitting at a computer on the company LAN. Naturally however — and especially with a juggernaut like Microsoft — nothing stands still or stays the same in the IT world. So with Microsoft’s big push towards the cloud under Satya Nadella, it’s only logical that RDS itself should continue to evolve with new scenarios like deploying an RDS farm in Microsoft Azure and similar exciting options. Many large enterprises, however, still have in-house RDS farms deployed for application and desktop delivery, and like many empowering technologies this one can often leave the harried sysadmin feeling powerless when something goes wrong. This article offers a few insights on how you might try and troubleshoot one particular type of frustrating problem occasionally inflicting RDS environments, namely those associated with Remote Desktop Services disconnects (intentional or not) of user sessions. The three stories I’ve shared below are based either on my own professional experiences or were gleaned from colleagues in the IT profession. As usual I’ve fictionalized the stories a bit to make them more interesting and instructive with archetypical IT pro Bob as the villain.
Remote Desktop Services disconnects: Goodbye and good riddance!
An RDS consultant named Bob told me about a customer who said that a customer he was working with was experiencing a problem with their RDS box booting off user sessions unexpectedly. When the user tried to reestablish the session, the RDS box prompted them for their credentials. When the user entered credentials and clicked Connect the connection dialog closed without the connection getting established. What the heck?
Bob began by capturing some logs using the Remote Desktop Services Diagnostic Tool to try and diagnose what might be the root cause of his customer’s Remote Desktop Services disconnects. Analyzing the trace logs captured by this tool showed that the logon attempt appeared to succeed even though the user immediately got kicked off the RDS server. Further investigation revealed the following series of entries in the log file:
 … [rdrpnpLib] rdrpnp_cpp655 CRdrPnpManager::OnRemoteDisconnect() - CRdrPnpManager::OnRemoteDisconnect : 2
 … [rdrpnpLib] rdrpnp_cpp567 CRdrPnpManager::OnRemoteConnect() - CRdrPnpManager::OnRemoteConnect : 2
 … [rdrpnpLib] rdrpnp_cpp580 CRdrPnpManager::OnRemoteConnect() - "GetSession FAILED". HR= 0x8007053d(ERROR_SERVER_DISABLED)
What these lines seem to indicate is that once the logon succeeded, the session terminated because Plug and Play (PnP) redirection failed on the server. What this actually means, however, is that drive redirection failed for the connection attempt. Drive redirection lets you specify which local devices and resources should be made available for remote sessions that users establish with the RDS server. Drive redirection can be managed on the server side with Group Policy using the policy setting Computer Configuration\Policies\Administrative Templates\Windows Components\Remote Desktop Services\Remote Desktop Session Host\Device and Resource Redirection. Drive redirection can also be enabled or disabled on the client side from the Local Resources tab of the Remote Desktop Connection (mstsc.exe) utility.
Further investigation of the customer’s Remote Desktop Services disconnects issues determined that there was an intermittent failure happening in the storage subsystem where user profiles were being saved. The take-home from this story is that careful analysis of log files can often be the fastest way to track down a related issue that is affecting your environment. It also shows that flaky hardware can cause software issues you wouldn’t expect.
OK, you can hang around a while
On another occasion, Bob fielded an unusual request from a customer who, for reasons that only management can understand, wanted the maximum time before a remote user would be kicked off the RDS server set to a whopping three weeks! Well, whatever you want I’ll do it for you if you’ll pay me, thought Bob.
Unfortunately, the Group Policy setting “Set time limit for active Terminal Services sessions” found under User Configuration\Policies\Administrative Templates\Windows Components\Terminal Services\Terminal Server\Session Time Limits in the policy tree could only be set to a maximum of five days on the version of Windows Server being used by the customer. Not to worry, Bob had an idea: What if he pushed out a registry key with a larger value for this setting using Group Policy Preferences (GPP)? A little digging around on TechNet and MSDN showed that the registry value that corresponded to this policy setting was a DWORD value named MaxDisconnectionTime, which was located at key HKCU\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services in the registry. Bob reasoned that perhaps the constriction to five days was an artifact of how the policy setting was exposed in the Group Policy Editor, so he created a preference that assigned a value of 1814400000 (21 days in milliseconds) to the MaxDisconnectionTime registry value and pushed it out to the targeted clients.
Well, what do you know, it worked! The customer was happy and so was Bob. The only downside to this story is that Bob had likely done something that Microsoft Support might consider “unsupported” should they ever be called by the customer for a support case. But a key takeaway of this story is that Group Policy can be tweaked in unanticipated ways if you try hard enough. Just be careful.
Say something in hex to me
Bob’s final story was a tip he learned from a support engineer at Microsoft. When Remote Desktop Services disconnects for some reason, it generates an error code that can be found in the logs captured. Unfortunately, the code is numeric, which isn’t much help when you’re trying to troubleshoot a problem. It’s like the universe generating the error code “42” when something goes wrong (that’s a takeoff from Douglas Adams, of course).
Anyway, it would be nice, wouldn’t it, if you could quickly convert an error code into a friendly name when troubleshooting Remote Desktop Services disconnects. Well, apparently there is a way using PowerShell, though I haven’t tried it myself. Here’s the script he got from a guy at Microsoft:
[parameter(Position=0,Mandatory=$true,HelpMessage="Enter the disconnect reason code in decimal from client side rds trace")]
#if it looks like a hex then try and convert to dec
$disconnectReason = [Convert]::ToInt32($disconnectReason, 16)
Write-Host "checking error code:$disconnectReason"
$mstsc = New-Object -ComObject MSTscAx.MsTscAx
write-host "description: $($mstsc.GetErrorDescription($disconnectReason,0))"
Hopefully, this may be of use to some of our readers!
Photo credit: Shutterstock