Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

RDP Session Keep Alive

4.93/5 (10 votes)
22 Oct 2017CPOL2 min read 40.1K  
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).

VB.NET
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)