RDP Session Keep Alive






4.93/5 (10 votes)
Keep those RDP sessions alive and unlocked
Introduction
Ever wanted to keep those Microsoft RDP sessions active and unlocked (both mstsc.exe (normal Microsoft RDP client) and rdcman.exe (Microsoft Remote Desktop Connection Manager)? This will take care of it for you.
Using the Code
Create a console application project in Visual Studio (I used VS 2015 and set it to .NET 4, but I'm sure it should work with others) and replace the code with the code below. You can leave it as a console application and uncomment the Console.Writeline
entries if you like but I personally leave them commented out and go back into the project and change it from "Console Application" to "Windows Forms Application" so that it runs without any sort of window.
Then, if you want to have it start each time you log on, you can just create a registry entry in "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" with a type of String
Value (REG_SZ
) and name it whatever you like and then put the fully qualified file path in the Value
data field (be sure to put quotes around it).
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Threading
Module Module1
' Change PollIntervalSeconds to however many seconds you want between wakup events
Const PollIntervalSeconds As Integer = 50
Public BoolMouseLoc As Boolean = False
Public BoolNeedToRestore As Boolean
Private Const WmSetfocus As Integer = &H7
Private Const WmMousemove As Integer = &H200
Private Const ALT As Integer = &Ha4
Private Const EXTENDEDKEY As Integer = &H1
Private Const KEYUP As Integer = &H2
' ----------------------------------------------------
' This is all of the User32 magic that lets you do things like find the windows
' handles, titles, class names, dimensions etc...
' I'm a relative novice programmer so I was mostly reusing code examples from Microsoft
' for a lot of this high level stuff. You can search and find these User32 functions
' very easy to see what they specifically do.
' ----------------------------------------------------
Delegate Function EnumWindowDelegate(hWnd As IntPtr, lparam As IntPtr) As Boolean
Private ReadOnly Callback As EnumWindowDelegate = New EnumWindowDelegate(AddressOf EnumWindowProc)
Delegate Function EnumWindowDelegate2(hWnd As IntPtr, lparam As IntPtr) As Boolean
Private ReadOnly Callback2 As EnumWindowDelegate2 = _
New EnumWindowDelegate2(AddressOf EnumWindowProc2)
Private Declare Function EnumWindows Lib "User32.dll" _
(wndenumproc As EnumWindowDelegate, lparam As IntPtr) As Boolean
Private Declare Auto Function EnumChildWindows Lib "User32.dll" _
(windowHandle As IntPtr, wndenumproc2 As EnumWindowDelegate2, lParam As IntPtr) As Boolean
Private Declare Auto Function GetWindowText Lib "User32.dll" _
(hwnd As IntPtr, txt As Byte(), lng As Integer) As Integer
Private Declare Function IsWindowVisible Lib "User32.dll" (hwnd As IntPtr) As Boolean
Private Declare Function IsIconic Lib "User32.dll" (hwnd As IntPtr) As Boolean
Private Declare Function GetWindowTextLengthA Lib "User32.dll" (hwnd As IntPtr) As Integer
Private Declare Auto Function ShowWindow Lib "User32.dll" (hwnd As IntPtr, lng As Integer) _
As Integer
Private Declare Function GetForegroundWindow Lib "User32.dll" () As IntPtr
Private Declare Function SetForegroundWindow Lib "User32.dll" (hwnd As IntPtr) As Integer
Declare Auto Function SendMessage Lib "user32.dll" _
(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
Private Declare Function GetClientRect Lib "User32.dll" _
(hWnd As IntPtr, ByRef lpRect As Rect) As Boolean
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Private Sub GetClassName(hWnd As IntPtr, lpClassName As StringBuilder, nMaxCount As Integer)
End Sub
' ----------------------------------------------------
<DllImport("user32.dll")> _
Private Sub keybd_event(bVk As Byte, bScan As Byte, dwFlags As UInteger, dwExtraInfo As Integer)
End Sub
' Makes a structure to store the windows dimensions in.
<StructLayout(LayoutKind.Sequential)>
Public Structure Rect
Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer
Public Overrides Function ToString() As String
Return String.Format("{0},{1},{2},{3}", Left, Top, Right, Bottom)
End Function
End Structure
Sub Main()
Dim storeCurrenthWnd As IntPtr
Do
BoolNeedToRestore = False
' Switches up the mouse move location each poll
BoolMouseLoc = Not BoolMouseLoc
' Stores the handle of the current window you have in focus.
' It will only use this to change focus back if one of your RDP
' windows needed to be unminimized.
storeCurrenthWnd = GetForegroundWindow()
' Look through all windows and determine which ones are
' RDP related and keep them awake.
EnumWindows(Callback, IntPtr.Zero)
' If the program needed to unminimize one or more of your
' RDP windows this will set the focus back on the window
' you were on prior to that.
If BoolNeedToRestore Then
ShowWindow(storeCurrenthWnd, 5)
' Needed to reliably set the foreground window
keybd_event(CByte(ALT), &H45, EXTENDEDKEY Or 0, 0)
keybd_event(CByte(ALT), &H45, EXTENDEDKEY Or KEYUP, 0)
SetForegroundWindow(storeCurrenthWnd)
End If
'Force garbage collection.
GC.Collect()
GC.WaitForPendingFinalizers()
' Wait for number of seconds specified in
' the PollIntervalSeconds variable.
Thread.Sleep(PollIntervalSeconds * 1000)
Loop
End Sub
' This function looks at the titles of the windows you currently
' have open and only looks at those that have "remote desktop" in them.
' It then determines if the RDP session windows are minimized, if they are
' it will un-minmize them. This is needed in order to keep them awake.
'
' It then sets focus (in the background without it affecting the users currently
' focused window) on each RDP session and then determines where the center of that
' window where the mouse SendMessage should be sent to (this is probably not necessary
' but I figured why not). I'm also having it switch mouse locations each poll.
' Near the end of the function it then makes a call to all child windows to keep
' them awake in the same way that this function does with the exception of not
' needing to perform the un-minimizing steps (The child windows are for keeping
' each of the RDP sessions in the Remote Desktop Connection Manager active).
Private Function EnumWindowProc(hWnd As IntPtr, lparam As IntPtr) As Boolean
Dim myRect As Rect
Dim arrRect(4) As String
Dim coord As Integer
If IsWindowVisible(hWnd) Then
' Gather the window titles
Dim theLength As Integer = GetWindowTextLengthA(hWnd)
Dim theReturn(theLength * 2) As Byte '2x the size of the Max length
GetWindowText(hWnd, theReturn, theLength + 1)
Dim theText = ""
For x = 0 To (theLength - 1) * 2
If theReturn(x) <> 0 Then
theText &= Chr(theReturn(x))
End If
Next
' Narrow it to RDP sessions.
If theText.ToLower Like "*remote desktop*" Then
'Console.WriteLine(theText & " - " & CStr(hWnd))
' Un-minimize the RDP sessions that are minimized
If IsIconic(hWnd) Then
ShowWindow(hWnd, 5)
ShowWindow(hWnd, 9)
' Since it had to keep at least one RDP session awake
' Make sure it knows to set focus back to the window
' the user was on.
BoolNeedToRestore = True
End If
' Set focus on the RDP session (without affecting the users currently
' focused window).
SendMessage(hWnd, WmSetfocus, 0, 0)
' Find the RDP window dimensions and figure out the center of it
GetClientRect(hWnd, myRect)
arrRect = Split(myRect.ToString(), ",")
'Console.WriteLine("Client Rect : " & myRect.ToString())
'Console.WriteLine("Click Location: " & CInt(CInt(arrRect(2)) / 2) & " X " & _
'CInt(CInt(arrRect(3)) / 2))
coord = CInt(CInt(arrRect(2)) / 2) * &H10000 + CInt(CInt(arrRect(3)) / 2)
' Send the mouse move on the RDP session in the background (without
' affecting the users currently focused window). Swapping the location
' each poll.
If BoolMouseLoc Then
SendMessage(hWnd, WmMousemove, 0, 0)
Else
SendMessage(hWnd, WmMousemove, 0, coord)
End If
' Look through all of the child windows as well in order
' to keep Remote Desktop Connection Manager RDP sessions
' active.
EnumChildWindows(hWnd, Callback2, IntPtr.Zero)
'Console.WriteLine("")
End If
End If
Return True
End Function
' This does the same thing that the EnumWindowsProc function does but does
' it for child windows. The only thing that is different in this one is that
' it doesn't un-minimize anything (that is done for the parent window) and
' it gathers the window class so that we can skip several window class types
' that that cause issues or don't need to be woken up.
Private Function EnumWindowProc2(hWnd As IntPtr, lparam As IntPtr) As Boolean
Dim myRect As Rect
Dim arrRect(4) As String
Dim coord As Integer
Dim myClassName As String
' Gather the window titles
Dim theLength As Integer = GetWindowTextLengthA(hWnd)
Dim theReturn(theLength * 2) As Byte '2x the size of the Max length
GetWindowText(hWnd, theReturn, theLength + 1)
Dim theText = ""
For x = 0 To (theLength - 1) * 2
If theReturn(x) <> 0 Then
theText &= Chr(theReturn(x))
End If
Next
' Get the window class name
myClassName = GetWindowClass(hWnd)
' Ignore child windows that don't need to be woken up.
If Not theText.Trim().Length = 0 And Not theText.ToLower.MultiContains("disconnected") And _
Not myClassName.ToLower.MultiContains("atl:", ".button.", "uicontainerclass", _
".systreeview32.", ".scrollbar.", "opwindowclass", "opcontainerclass") Then
'Console.WriteLine(vbTab & theText & " - Child - " & CStr(hWnd) & " - " & myClassName)
' Set focus on the RDP session (without affecting the users currently
' focused window).
'SendMessage(hWnd, WmSetfocus, 0, 0)
' Find the RDP window dimensions and figure out the center of it
GetClientRect(hWnd, myRect)
arrRect = Split(myRect.ToString(), ",")
'Console.WriteLine("Child Client Rect : " & myRect.ToString())
'Console.WriteLine("Child Click Location: " & CInt(CInt(arrRect(2)) / 2) & " X " & _
'CInt(CInt(arrRect(3)) / 2))
coord = CInt(CInt(arrRect(2)) / 2) * &H10000 + CInt(CInt(arrRect(3)) / 2)
' Send the mouse move on the RDP session in the background (without
' affecting the users currently focused window). Swapping the location
' each poll.
If BoolMouseLoc Then
SendMessage(hWnd, WmMousemove, 0, 0)
Else
SendMessage(hWnd, WmMousemove, 0, coord)
End If
End If
Return True
End Function
' Gathers the windows class names
Public Function GetWindowClass(hwnd As Long) As String
Dim sClassName As New StringBuilder("", 256)
Call GetClassName(hwnd, sClassName, 256)
Return sClassName.ToString
End Function
<Extension>
Public Function MultiContains(str As String, ParamArray values() As String) As Boolean
Return values.Any(Function(val) str.Contains(val))
End Function
End Module
Points of Interest
I am not a professional programmer... so if you see anything that needs to be improved, please let me know.
History
- 3/5/2017
- First version posted
- 3/9/2017
- Added in line comments to the code
- 3/17/2017
- Changed the previous window restoral to only happening when RDP sessions were un-minimized
- Added garbage collection Skipping more windows that don't need to be touched
- 10/21/2017
- Fixed an issue where the focus would not return to the proper window if you had RDP sessions minimized and it needed to un-minimize them.
- Fixed issue that caused a focus problem while using Remote Desktop Connection Manager. Focus would inadvertently go on the server name on the left pane instead of the remote connection window itself from time to time.
- 10/22/2017
- Made small code change based on a user tip relating to the moust location rotation.