Search code examples
c#visual-studioremote-desktopterminal-services

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


I need to build a Remote Desktop Client application with C#, which establishes a connection to a remote Windows Server, and then programmatically starts some services to the remote PC.

It's important that, when I logon, the Desktop Environment on the Server side exists, because the services I want to start make use of it, but on the client side I don't want any Windows Forms container, because I want to create these sessions dynamically.

To understand the question better, imagine that i want to establish a Remote Desktop Connection, using a console application. The point is, in the client side I don't need any GUI, but the services on the Host side need the windows, mouse, internet explorer etc UI handles.

So far I tried to use the MSTSClib to create an RdpClient as discribed here, but that didn't help, because it makes use of the AxHost, which is Windows Forms dependent.

Any ideas on if that's possible, and how can I achieve that?

UPDATE:

Tried this:

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

namespace RDConsole
{
    class Program
    {
        static void Main(string[] args)
        {

            var thread = new Thread(() =>
                {
                    var rdp = new AxMsRdpClient9NotSafeForScripting();
                    rdp.CreateControl();
                    rdp.OnConnecting += (s, e) => { Console.WriteLine("connecting"); };
                    rdp.Server = "xxx.xxx.xxx.xxx";
                    rdp.UserName = "Administrator";
                    rdp.AdvancedSettings9.AuthenticationLevel = 2;
                    rdp.AdvancedSettings9.ClearTextPassword = "xxxxxxxxxx";
                    rdp.Connect();
                    Console.ReadKey();
                });
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            Console.ReadKey();
        }


    }
}

but i get a null reference exception

"Object reference not set to an instance of an object.

Solution

  • 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.