Jump to content

Maintain Active Desktop after RDP disconnect


Recommended Posts

This allows you to easily redirect your existing RDP session to the console so it will maintain an active Desktop.  

This code has a UI to perform the redirect, but to actually use it with your automation, you would need to incorporate it into your code as once it's run it will kick you out of the RDP session since it was redirected to the console.  When I was using this, I had another automation running in the background so I ran this to redirect to console and my other automation script ran after hours when it was schedule.

 

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Outfile_type=a3x
#AutoIt3Wrapper_Outfile=RedirectToConsole.a3x
#AutoIt3Wrapper_UseX64=N
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#cs
    Make a remote connection not loose access to the desktop after disconnecting.

    https://support.smartbear.com/testcomplete/docs/testing-with/running/via-rdp/keeping-computer-unlocked.html
    https://support.microsoft.com/en-us/help/302801/the-use-of-tscon-exe-can-leave-a-previously-locked-console-unlocked

        for /f "skip=1 tokens=3" %%s in ('query user %USERNAME%') do (%windir%\System32\tscon.exe %%s /dest:console)
        "C:\<path to dc64.exe>\dc64.exe" -width=1920 -height=1200 -depth=32

        for /f "skip=1 tokens=3" %%s in ('query user %USERNAME%') do (
          %windir%\System32\tscon.exe %%s /dest:console
        )

    Must be run under x64 not 32, use this to run the execute under 64bit
        _WinAPI_Wow64EnableWow64FsRedirection(True)
        ShellExecute(@WindowsDir & '\System32\tscon.exe', '1 /dest:console', '', 'runas')
        _WinAPI_Wow64EnableWow64FsRedirection(False)

    Once we have been redirected to console, ensure we are using the largest display available
      can't tell what the available displays are until we have gone into the console at least once...
        Create an .ini for use on that computer after to load the dropdown?
        Default to the last one used, Largest if nothing else was used yet.

    This doesn't work within Citrix as its a totally different connection type


    Create a log file to track who initiated the redirect and where they connected in from
#ce

#include <WinAPIFiles.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <Array.au3>
#include <ColorConstants.au3>
#include <GuiComboBox.au3>

Opt("GUIOnEventMode", 1)            ;Use Event mode to call functions directly

$sLogFile = @ScriptDir & "\redirect.log"
$sSeperator = "---------------------------------------------------------------------------------------------"
;=====================================================================================
;=== Used to figure out what SessionID we are logged in with and if its a remote connection
;=====================================================================================
;Options for the $SessionID
    Global Static $WTS_CURRENT_SESSION = -1

;Options for the $WTSInfoClass
    Global Static $WTSInitialProgram = 0
    Global Static $WTSApplicationName = 1
    Global Static $WTSWorkingDirectory = 2
    Global Static $WTSOEMId = 3
    Global Static $WTSSessionId = 4
    Global Static $WTSUserName = 5
    Global Static $WTSWinStationName = 6
    Global Static $WTSDomainName = 7
    Global Static $WTSConnectState = 8
    Global Static $WTSClientBuildNumber = 9
    Global Static $WTSClientName = 10
    Global Static $WTSClientDirectory = 11
    Global Static $WTSClientProductId = 12
    Global Static $WTSClientHardwareId = 13
    Global Static $WTSClientAddress = 14
    Global Static $WTSClientDisplay = 15
    Global Static $WTSClientProtocolType = 16
    Global Static $WTSIdleTime = 17
    Global Static $WTSLogonTime = 18
    Global Static $WTSIncomingBytes = 19
    Global Static $WTSOutgoingBytes = 20
    Global Static $WTSIncomingFrames = 21
    Global Static $WTSOutgoingFrames = 22
    Global Static $WTSClientInfo = 23
    Global Static $WTSSessionInfo = 24
    Global Static $WTSSessionInfoEx = 25
    Global Static $WTSConfigInfo = 26
    Global Static $WTSValidationInfo = 27
    Global Static $WTSSessionAddressV4 = 28
    Global Static $WTSIsRemoteSession = 29

Global $sINIfile = @ScriptDir & "\redirect.ini"

$iGuiW = 500
$iGuiH = 200
$hGUI = GUICreate("RDP Desktop Redirect", $iGuiW, $iGuiH)
GUISetOnEvent($GUI_EVENT_CLOSE, "_ExitApp")

GUICtrlCreateLabel("This will redirect your current session to Console." & @CRLF & "This desktop will remain active, but you will be disconnected.", 0, 13, $iGuiW, 40, $SS_CENTER)
GUICtrlSetFont(-1, 10, 600)

GUICtrlCreateLabel("Select the desktop Display Settings to use after the redirect", 0, 70, $iGuiW, 17, $SS_CENTER)
$hDisplaySetting = GUICtrlCreateCombo("<Max>", 40, 90, 240, 20) ;This will be loaded from .ini if found

$hBtnRedirect = GUICtrlCreateButton("Redirect and Disconnect", 40, $iGuiH - 50, 160, 25)
    GUICtrlSetOnEvent($hBtnRedirect, "_RedirectNow")
GUICtrlCreateButton("Exit", $iGuiW - 140, $iGuiH - 50, 100, 25)
    GUICtrlSetOnEvent(-1, "_ExitApp")


;Disable the Redirect button if this is not an RDP session
;   Create a red note next to redirect to say Not an RDP session.
$sConnType = _WTSQuerySessionInformation($WTS_CURRENT_SESSION, $WTSClientProtocolType) ;What type of connection was used for this session 2 = RDP
ConsoleWrite("$WTSClientProtocolType  " & $sConnType & @CRLF)
If $sConnType <> 2 Then ;2 = RDP connection
    GUICtrlCreateLabel("Not an RDP connection", 205, $iGuiH - 45, 140, 17)
    GUICtrlSetColor(-1, $COLOR_RED)
    GUICtrlSetState($hBtnRedirect, $GUI_DISABLE)
EndIf

;Read the .ini if exists and populate the combobox
$aDisplayOptions = IniReadSection($sINIfile, "Display")
If @error Then
    GUICtrlCreateLabel("List available after the first redirect", 285, 93, $iGuiW - 285, 17)
Else
    ;load the returned array into the combobox
    For $i = 1 to $aDisplayOptions[0][0]
        _GUICtrlComboBox_AddString($hDisplaySetting, $aDisplayOptions[$i][1])
    Next
EndIf

GUISetState(@SW_SHOW, $hGUI)

While 1
    sleep(100)
WEnd




Func _RedirectNow()

    FileWriteLine($sLogFile, $sSeperator & @CRLF & " " & @MON & "/" & @MDAY & "/" & @YEAR & " " & @HOUR & ":" & @MIN & ":" & @SEC & "." & @MSEC)

    $id = _WTSQuerySessionInformation($WTS_CURRENT_SESSION, $WTSSessionId) ;Get the current users SessionId
    ConsoleWrite("My Session ID:  " & $id & @CRLF)
    $Username = _WTSQuerySessionInformation($WTS_CURRENT_SESSION, $WTSUserName) ;Get the username for the specified session
    ConsoleWrite("My Username:  " & $Username & @CRLF)
    $sConnFrom = _WTSQuerySessionInformation($WTS_CURRENT_SESSION, $WTSClientName) ;Where the connected user is from (computername)
    ConsoleWrite("Connected From  " & $sConnFrom & @CRLF)

    If $sConnType = 2 Then  ; 2 = RDP

        Local $aRes[4]  ;Store the resolution details needed to update res after switching to console
        $sDisplaySelection = GUICtrlRead($hDisplaySetting)  ;what did the user pick from the dropdown?

        ;ConsoleWrite("About to redirect to Console:  " & @HOUR & ":" & @MIN & ":" & @SEC & "." & @MSEC & @CRLF)
        FileWriteLine($sLogFile, " RDP Session " & $id & " Redirect to console session 0 initiated by " & $Username & " from " & $sConnFrom)

        _RedirectToConsole($id)     ;Redirect the logged in RDP session to the console to maintain an active desktop for automation

        While _WTSQuerySessionInformation($WTS_CURRENT_SESSION, $WTSClientProtocolType) = 2 ;Sleep until the Session type is no longer RDP
            sleep(200)
        WEnd

        Sleep(2000)

    ;Pull every available graphic option from the console and save to the .ini file for selection on the next run
        IniDelete($sINIfile, "Display", "")         ;Delete any existing display settings
        $aAllOptions = _EnumDisplaySettingsEx()     ;Retrieve array of all valid display settings
        For $i = 0 to UBound($aAllOptions) - 1
            IniWrite($sINIfile, "Display", $i, $aAllOptions[$i][4])
        Next
        FileWriteLine($sLogFile, " - " & UBound($aAllOptions) & " valid display settings were found for console.  Saved to .ini for next run")

    ;If something besides <max> has been selected, then choose that
        If $sDisplaySelection = "<Max>" Then

            $aRes[0] = $aAllOptions[0][0]
            $aRes[1] = $aAllOptions[0][1]
            $aRes[2] = $aAllOptions[0][2]
            $aRes[3] = $aAllOptions[0][3]

        Else

            ;Find the selected display in the [][4] and use that
            FileWriteLine($sLogFile, " - " & $sDisplaySelection & " was specified instead of opting for <Max>")
            For $f = 0 to UBound($aAllOptions) - 1
                If $aAllOptions[$f][4] = $sDisplaySelection Then
                    $aRes[0] = $aAllOptions[$f][0]
                    $aRes[1] = $aAllOptions[$f][1]
                    $aRes[2] = $aAllOptions[$f][2]
                    $aRes[3] = $aAllOptions[$f][3]
                    ExitLoop
                EndIf
            Next

        EndIf

        If $aRes[0] = "" or $aRes[1] = "" or $aRes[2] = "" or $aRes[3] = "" Then
            FileWriteLine($sLogFile, " - Custom Resolution was not found in Console, Left at default:  " & @DesktopWidth & "x" & @DesktopHeight & " at " & @DesktopDepth & "dpi with " & @DesktopRefresh & "Hz refresh rate")
            Exit
        EndIf

        _ChangeScreenResEx(1, $aRes[0], $aRes[1], $aRes[2], $aRes[3])   ;Set the display to the max value
        Sleep(2000)
        FileWriteLine($sLogFile, " - Screen resolution set to " & @DesktopWidth & "x" & @DesktopHeight & " at " & @DesktopDepth & "dpi with " & @DesktopRefresh & "Hz refresh rate")

    ;----------------------------------------------------------------------------------------------------------
    ;This is where your automation code would go, take screenshots, interact with Desktop, etc...
    ;----------------------------------------------------------------------------------------------------------


        Exit

    Else
        MsgBox(0, "Redirect - Disconnect", "This must be launched via a Remote Desktop Connection")
    EndIf

    Exit

EndFunc




Func _RedirectToConsole($iSessionID)
    _WinAPI_Wow64EnableWow64FsRedirection(True)
    ShellExecute(@WindowsDir & '\System32\tscon.exe', $iSessionID & ' /dest:console', '', 'runas')
    _WinAPI_Wow64EnableWow64FsRedirection(False)
EndFunc



;Mod of argumentum code at https://www.autoitscript.com/forum/topic/134679-get-hostname-of-the-client-connected-to-the-terminalserver-session/
Func _WTSQuerySessionInformation($SessionId = -1, $WTSInfoClass = 10, $iReturnAsIs = 0)
    Local $aResult = DllCall("Wtsapi32.dll", "int", "WTSQuerySessionInformation", "Ptr", 0, "int", $SessionId, "int", $WTSInfoClass, "ptr*", 0, "dword*", 0)
    If @error Or $aResult[0] = 0 Then Return SetError(1, 0, "")
    Local $ip = DllStructGetData(DllStructCreate("byte[" & $aResult[5] & "]", $aResult[4]), 1)
    DllCall("Wtsapi32.dll", "int", "WTSFreeMemory", "ptr", $aResult[4])
    If $iReturnAsIs Then Return $ip
    Switch $WTSInfoClass
        Case 4 ; We want the WTSSessionId
            Return Int('0x' & StringTrimRight(StringReverse($ip), 3))

        Case 29 ; 1 = Its remote, 0 = physical connection $WTSIsRemoteSession
            Return Int('0x' & StringTrimRight(StringReverse($ip), 3))

        case 16 ; $WTSClientProtocolType
            Return Int('0x' & StringTrimRight(StringReverse($ip), 3))

        Case 14 ; We want the WTSClientAddress
            If Not (Int(StringLeft($ip, 4)) = 2) Then ; IPv4
                $ip = ""
            Else
                $ip = Dec(StringMid($ip, 15, 2)) & '.' & Dec(StringMid($ip, 17, 2)) & '.' & Dec(StringMid($ip, 19, 2)) & '.' & Dec(StringMid($ip, 21, 2))
            EndIf

    EndSwitch
    Return StringReplace(BinaryToString($ip), Chr(0), "")
EndFunc   ;==>_GetWTSClientName




;==============================================================
;===  Return array containing all valid display resolutions
;===    [#][0] = Width
;===    [#][1] = Height
;===    [#][2] = dpi
;===    [#][3] = Refresh Rate
;===    [#][4] = All values in a user readable string
;===
;===    Sorted Largest to smallest, dups removed
;===  Origionally from rasim
;===    Modified by: BigDaddyO
;==============================================================
Func _EnumDisplaySettingsEx()
Local $DEVMODE, $DllRet = 0, $enum = 0

local $aAll[1000][5]

    ;Create a structure to hold all the items that are returned from the EnumDisplaySettinsEx call
    $DEVMODE = DllStructCreate("char dmDeviceName[32];ushort dmSpecVersion;ushort dmDriverVersion;short dmSize;" & _
                               "ushort dmDriverExtra;dword dmFields;short dmOrientation;short dmPaperSize;short dmPaperLength;" & _
                               "short dmPaperWidth;short dmScale;short dmCopies;short dmDefaultSource;short dmPrintQuality;" & _
                               "short dmColor;short dmDuplex;short dmYResolution;short dmTTOption;short dmCollate;" & _
                               "byte dmFormName[32];dword dmBitsPerPel;int dmPelsWidth;dword dmPelsHeight;" & _
                               "dword dmDisplayFlags;dword dmDisplayFrequency")

    DllStructSetData($DEVMODE, "dmSize", DllStructGetSize($DEVMODE))
    $enum = 0   ;Used to track how many display settings have been added to the $aList
    Do
        $DllRet = DllCall("user32.dll", "int", "EnumDisplaySettingsEx", "ptr", 0, "dword", $enum, _
                          "ptr", DllStructGetPtr($DEVMODE), "dword", 0)
        $DllRet = $DllRet[0]

        ;Need to retrieve everything into a 2D array, Sort by the Width in Descending order.
        ; Then loop through writing them into a single string making sure there are no duplicates and returning as a 1D array
        $aAll[$enum][0] = Int(DllStructGetData($DEVMODE, "dmPelsWidth"))
        $aAll[$enum][1] = Int(DllStructGetData($DEVMODE, "dmPelsHeight"))
        $aAll[$enum][2] = Int(DllStructGetData($DEVMODE, "dmBitsPerPel"))
        $aAll[$enum][3] = Int(DllStructGetData($DEVMODE, "dmDisplayFrequency"))
        $aAll[$enum][4] = $aAll[$enum][0] & "x" & $aAll[$enum][1] & ", " & $aAll[$enum][2] & "dpi, " & $aAll[$enum][3] & "Hz"
        ConsoleWrite("Display Setting = " & $aAll[$enum][4] & @CRLF)
        $enum += 1

    Until $DllRet = 0

    $DEVMODE = 0            ;Clear out the DLL Struct before we leave the Func
    ReDim $aAll[$enum][5]   ;Resize to the number of items we actually retrieved
    _ArraySort($aAll, 1)    ;Sort the array so its in the order we want

;Move through the array to get rid of any dups
    Local $aList[$enum][5]      ;This will hold the display settigns without any dups
    Local $iAdd = 0             ;Keep track of the number of unique records
    For $d = 0 to $enum - 1     ;Loop through every record returned

        For $f = 0 to $iAdd     ;Loop through only those we already verified as not dups
            If $aList[$f][4] = $aAll[$d][4] Then
                ContinueLoop(2)     ;We found it, so just skip to the next record
            EndIf
        Next

        ;We will only get to this part of the loop if we didn't find a dup
        $aList[$iAdd][0] = $aAll[$d][0]
        $aList[$iAdd][1] = $aAll[$d][1]
        $aList[$iAdd][2] = $aAll[$d][2]
        $aList[$iAdd][3] = $aAll[$d][3]
        $aList[$iAdd][4] = $aAll[$d][4]
        $iAdd += 1

    Next

    ReDim $aList[$iAdd][5]
    Return $aList

EndFunc


;===============================================================================
; Function Name:   _ChangeScreenResEx()
; Description:     Changes the current screen geometry, colour and refresh rate.
; Version:         1.0.0.0
; Parameter(s):    $i_DisplayNum - Display to change, starting at 1
;                  $i_Width - Width of the desktop screen in pixels. (horizontal resolution)
;                  $i_Height - Height of the desktop screen in pixels. (vertical resolution)
;                  $i_BitsPP - Depth of the desktop screen in bits per pixel.
;                  $i_RefreshRate - Refresh rate of the desktop screen in hertz.
; Return Value(s): On Success - Screen is adjusted, @ERROR = 0
;                  On Failure - sets @ERROR = 1
; Forum(s):
; Author(s):       Original code - psandu.ro, PartyPooper
;                  Modifications - bobchernow
;===============================================================================
Func _ChangeScreenResEx($i_DisplayNum = 1, $i_Width = @DesktopWidth, $i_Height = @DesktopHeight, $i_BitsPP = @DesktopDepth, $i_RefreshRate = @DesktopRefresh)
    Local Const $DM_PELSWIDTH = 0x00080000
    Local Const $DM_PELSHEIGHT = 0x00100000
    Local Const $DM_BITSPERPEL = 0x00040000
    Local Const $DM_DISPLAYFREQUENCY = 0x00400000
    Local Const $CDS_TEST = 0x00000002
    Local Const $CDS_UPDATEREGISTRY = 0x00000001
    Local Const $DISP_CHANGE_RESTART = 1
    Local Const $DISP_CHANGE_SUCCESSFUL = 0
    Local Const $HWND_BROADCAST = 0xffff
    Local Const $WM_DISPLAYCHANGE = 0x007E
    If $i_Width = "" Or $i_Width = -1 Then $i_Width = @DesktopWidth; default to current setting
    If $i_Height = "" Or $i_Height = -1 Then $i_Height = @DesktopHeight; default to current setting
    If $i_BitsPP = "" Or $i_BitsPP = -1 Then $i_BitsPP = @DesktopDepth; default to current setting
    If $i_RefreshRate = "" Or $i_RefreshRate = -1 Then $i_RefreshRate = @DesktopRefresh; default to current setting
    Local $DEVMODE = DllStructCreate("byte[32];int[10];byte[32];int[6]")
    Local $s_Display

    $s_Display = "\\.\Display" & $i_DisplayNum

    Local $B = DllCall("user32.dll", "int", "EnumDisplaySettings", "ptr", 0, "int", 0, "ptr", DllStructGetPtr($DEVMODE))

    If @error Then
        $B = 0
        SetError(1)
        Return $B
    Else
        $B = $B[0]
    EndIf
    If $B <> 0 Then
        DllStructSetData($DEVMODE, 2, BitOR($DM_PELSWIDTH, $DM_PELSHEIGHT, $DM_BITSPERPEL, $DM_DISPLAYFREQUENCY), 5)
        DllStructSetData($DEVMODE, 4, $i_Width, 2)
        DllStructSetData($DEVMODE, 4, $i_Height, 3)
        DllStructSetData($DEVMODE, 4, $i_BitsPP, 1)
        DllStructSetData($DEVMODE, 4, $i_RefreshRate, 5)

        $B = DllCall("user32.dll", "int", "ChangeDisplaySettingsEx","str", $s_Display, "ptr", DllStructGetPtr($DEVMODE), "hwnd", 0, "dword", $CDS_TEST, "lparam", 0)
        If @error Then
            $B = -1
        Else
            $B = $B[0]
        EndIf
        Select
            Case $B = $DISP_CHANGE_RESTART
                $DEVMODE = ""
                Return 2
            Case $B = $DISP_CHANGE_SUCCESSFUL
                DllCall("user32.dll", "int", "ChangeDisplaySettingsEx","str", $s_Display, "ptr", DllStructGetPtr($DEVMODE), "hwnd", 0, "dword", $CDS_UPDATEREGISTRY, "lparam", 0)
                DllCall("user32.dll", "int", "SendMessage", "hwnd", $HWND_BROADCAST, "int", $WM_DISPLAYCHANGE, _
                        "int", $i_BitsPP, "int", $i_Height * 2 ^ 16 + $i_Width)
                $DEVMODE = ""
                Return 1
            Case Else
                $DEVMODE = ""
                SetError(1)
                Return $B
        EndSelect
    EndIf
EndFunc;==>_ChangeScreenResEx




Func _ExitApp()
    Exit
EndFunc

 

Link to post
Share on other sites
  • 1 month later...
  • Moderators
1 hour ago, JohnTWI said:

Can you point me additional info on the console?

Yes, the help file. Do a search on console and you will find plenty of material to read.

"Profanity is the last vestige of the feeble mind. For the man who cannot express himself forcibly through intellect must do so through shock and awe" - Spencer W. Kimball

How to get your question answered on this forum!

Link to post
Share on other sites
  • 2 weeks later...

BigDaddyO

Question when I login back on to the Remote server just to monitor how my job is doing then disconnect from the remote session. My AutoIt job dies, is this expected behavior or am not exiting from the remote session properly?

Thanks again for your help on this.

Link to post
Share on other sites

That is expected behavior.  you must launch the redirect to Console script and that will disconnect your RDP session.

if you plan on logging in to review, then you will need to have the redirect to console script separate from your normal automation script.

1.  launch your automation script

2. launch the Redirect to Console script which will disconnect your RDP session.

 

Link to post
Share on other sites
  • 8 months later...
On 2/14/2020 at 5:01 PM, BigDaddyO said:

That is expected behavior.  you must launch the redirect to Console script and that will disconnect your RDP session.

if you plan on logging in to review, then you will need to have the redirect to console script separate from your normal automation script.

1.  launch your automation script

2. launch the Redirect to Console script which will disconnect your RDP session.

 

Sorry for this noobish question but do I just have to post your code in the editor and save it so the RDP stays open?
Sorry again, I'm new with this :(

Link to post
Share on other sites

It depends on your automation. 

If your automation is going to sit there and wait for something to happen then you can use this redirect as a separate application.  Launch your automation, then launch this separate redirect app which will close your RDP session and let your automation continue to run.

 

If your automation starts doing stuff over and over as soon as it's launched, then you will need to look into this redirect code and build it into your automation so when you launch it will disconnect RDP and start the actual automation.  This will not be a quick plug and play process.

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...