Programmatically Create and Launch and Rdp Session (Without Gui)

Programmatically create and launch and RDP session (without gui)

You can use the Remote Desktop ActiveX control to connect, you would need to host it in a Form but the form wouldn't need to be visible. For an example see http://www.codeproject.com/KB/cs/RemoteDesktop_CSharpNET.aspx

Create Windows Session programmatically from Console or Windows Service

I've created a simple utility that I believe meets all the requirements in the question. You'll need to add a COM reference to Microsoft Terminal Services Active Client 1.0 Type Library (ActiveX).

I thought it might not work for creating a session on the local machine but I tested in in 2012R2 running as a Service and it actually can. The same exact method can be called from a WinForms app or from a Console app. When launched from a WinForms or Console app, the a form is shown for a few seconds so I made sure to set the control to enabled = false so it can't be interacted with.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using AxMSTSCLib;

namespace Utility.RemoteDesktop
{
public class Client
{
private int LogonErrorCode { get; set; }

public void CreateRdpConnection(string server, string user, string domain, string password)
{
void ProcessTaskThread()
{
var form = new Form();
form.Load += (sender, args) =>
{
var rdpConnection = new AxMSTSCLib.AxMsRdpClient9NotSafeForScripting();
form.Controls.Add(rdpConnection);
rdpConnection.Server = server;
rdpConnection.Domain = domain;
rdpConnection.UserName = user;
rdpConnection.AdvancedSettings9.ClearTextPassword = password;
rdpConnection.AdvancedSettings9.EnableCredSspSupport = true;
if (true)
{
rdpConnection.OnDisconnected += RdpConnectionOnOnDisconnected;
rdpConnection.OnLoginComplete += RdpConnectionOnOnLoginComplete;
rdpConnection.OnLogonError += RdpConnectionOnOnLogonError;
}
rdpConnection.Connect();
rdpConnection.Enabled = false;
rdpConnection.Dock = DockStyle.Fill;
Application.Run(form);
};
form.Show();
}

var rdpClientThread = new Thread(ProcessTaskThread) { IsBackground = true };
rdpClientThread.SetApartmentState(ApartmentState.STA);
rdpClientThread.Start();
while (rdpClientThread.IsAlive)
{
Task.Delay(500).GetAwaiter().GetResult();
}
}

private void RdpConnectionOnOnLogonError(object sender, IMsTscAxEvents_OnLogonErrorEvent e)
{
LogonErrorCode = e.lError;
}
private void RdpConnectionOnOnLoginComplete(object sender, EventArgs e)
{
if (LogonErrorCode == -2)
{
Debug.WriteLine($" ## New Session Detected ##");
Task.Delay(10000).GetAwaiter().GetResult();
}
var rdpSession = (AxMsRdpClient9NotSafeForScripting)sender;
rdpSession.Disconnect();
}
private void RdpConnectionOnOnDisconnected(object sender, IMsTscAxEvents_OnDisconnectedEvent e)
{
Environment.Exit(0);
}
}
}

On a side note I found this question that says there may be a way to use the ActiveX control (for RDP) without using a windows form at all. I saw the example they gave and I was unsure hot to use their code for this situation.

ActiveX control without a form

If there's anyone out there who understands how to do this without hosting the ActiveX control on a Form please post an example.

Creating a Remote Desktop Client Application without using Windows Forms (C#)

Finally, I'm posting the answer to this.
This is the wrapper for the remote control library, together with the WinForms-like message loop. You still have to reference the windows forms dll and create a form to host the rdpclient, but this now can run from a console app, a windows service, or whatever.

using AxMSTSCLib;

public class RemoteDesktopApi
{

#region Methods

public void Connect((string username, string domain, string password, string machineName) credentials)
{
try
{
var form = new Form();
var remoteDesktopClient = new AxMsRdpClient6NotSafeForScripting();
form.Controls.Add(remoteDesktopClient);
form.Show();

remoteDesktopClient.AdvancedSettings7.AuthenticationLevel = 0;
remoteDesktopClient.AdvancedSettings7.EnableCredSspSupport = true;
remoteDesktopClient.Server = credentials.machineName;
remoteDesktopClient.Domain = credentials.domain;
remoteDesktopClient.UserName = credentials.username;
remoteDesktopClient.AdvancedSettings7.ClearTextPassword = credentials.password;
remoteDesktopClient.Connect();
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}

#endregion

#region Nested type: MessageLoopApartment

public class MessageLoopApartment : IDisposable
{
#region Fields/Consts

private static readonly Lazy<MessageLoopApartment> Instance = new Lazy<MessageLoopApartment>(() => new MessageLoopApartment());
private TaskScheduler _taskScheduler;
private Thread _thread;

#endregion

#region Properties

public static MessageLoopApartment I => Instance.Value;

#endregion

private MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();

_thread = new Thread(startArg =>
{
void IdleHandler(object s, EventArgs e)
{
Application.Idle -= IdleHandler;
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
}

Application.Idle += IdleHandler;
Application.Run();
});

_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
_taskScheduler = tcs.Task.Result;
}

#region IDisposable Implementation

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

#endregion

#region Methods

public Task Run(Action action, CancellationToken token)
{
return Task.Factory.StartNew(() =>
{
try
{
action();
}
catch (Exception)
{
// ignored
}
}, token, TaskCreationOptions.LongRunning, _taskScheduler);
}

protected virtual void Dispose(bool disposing)
{
if (_taskScheduler == null) return;

var taskScheduler = _taskScheduler;
_taskScheduler = null;
Task.Factory.StartNew(
Application.ExitThread,
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler)
.Wait();
_thread.Join();
_thread = null;
}

#endregion
}

#endregion
}

and this is how I call the Connect method

public void ConnectToRemoteDesktop((string username, string domain, string password, string machineName) credentials)
{
RemoteDesktopApi.MessageLoopApartment.I.Run(() =>
{
var ca = new RemoteDesktopApi();
ca.Connect(credentials);
}, CancellationToken.None);
}

This may also be useful with other types ActiveX controls.

Can RDP clients launch remote applications and not desktops

Using an RDP connection file you can set the alternate shell to be your application; the file syntax is like

alternate shell:s:c:\winnt\system32\notepad.exe

and you pass that as a command-line argument to mstsc.exe; this similar to chrissr's solution, but without affecting every RDP session you launch. A fuller summary of settings here.

How to start a new Windows logon session (RDP or console) programmatically

I got SendInput to work on the logon desktop (and, as it turns out, the UAC secure desktop). SetThreadDesktop must not give you the same privileges as if you'd initially started the process in the target desktop.

So when I detected a desktop change, instead of calling SetThreadDesktop, I launched yet another process in the new desktop with CreateProcessAsUser. Then I signaled for the viewer to switch and closed the current process.

Edit (years later): I ended up being wrong about this. You just need ensure your current thread doesn't have any open windows or hooks in the current desktop. And since this only sets the desktop for the calling thread (not the process), subsequent threads will need to call this as well.



Related Topics



Leave a reply



Submit