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

10,285

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.

Share:
10,285
crankedrelic
Author by

crankedrelic

Updated on July 20, 2022

Comments

  • crankedrelic
    crankedrelic almost 2 years

    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.
    
    • pix
      pix over 6 years
      what did you try so far?
    • crankedrelic
      crankedrelic over 6 years
      I tried to use the MSTSC.lib to create an RdpClient as discribed here codeproject.com/Articles/43705/Remote-Desktop-using-C-NET, but that didn't help, besause it makes use of the AxHost, which is Windows Forms dependent.
    • Marcello B.
      Marcello B. over 6 years
      Please add what you have tried to your initial question. You may also want to take a look at this article
    • LorneCash
      LorneCash over 6 years
      @crankedrelic Did you ever find a solution to this? I need to do the exact same thing, would you be willing to share?
    • crankedrelic
      crankedrelic over 6 years
      @LorneCash Yes, take a look at this stackoverflow.com/questions/37310418/…. When i finish this project i will post a more detailed answer, but for now this will help you. Remember you still need to reference windows forms manually, and create a form on which you will assign the rdpclient. In my case i developed a windows service that implements the rdp connection, so no Forms get ever painted, and communicate with that via WCF.
    • crankedrelic
      crankedrelic almost 5 years
      @LorneCash the answer is finally posted, although i believe you have found your way around this by now
  • crankedrelic
    crankedrelic over 6 years
    Thanks for your answer, but that doesn't help. To understand the question better, imagine that i want to establish a Remote Desktop Connection, using a console application. This way I can't use the AxMSTSCLib, because it requires Windows Forms.
  • Amittai Shapira
    Amittai Shapira over 6 years
    Can you please edit your question with the clarification?
  • crankedrelic
    crankedrelic over 6 years
    You 're right about that, the reason I need to do this with Remote Desktop is that I want to have multiple users loged on simultaneously on a VM. And that's something I can accomplish with RD.
  • DWRoelands
    DWRoelands over 6 years
    I'm still a bit confused. A web service can handle requests from many clients simultaneously, and Windows Remote Desktop can also handle multiple users logging in simultaneously as well. I'm having trouble figuring out the problem you're trying to solve.
  • crankedrelic
    crankedrelic over 6 years
    Yes the thing is that i want to perform automation tasks on windows that require the windows UI to run. Tasks that may include OCR, clicking on Javascript buttons inside webpages and much more. That's the whole point. For now I'm satisfied with some invisible activeX controls on windows forms, but I am exploring the possibilities on a larger scale.
  • DWRoelands
    DWRoelands over 6 years
    Thanks for the elaboration. I still suggest you consider using a web service to allow for the "kickoff" of the automated tasks, then run them all locally on the server. The web service can queue requests as they come in. This will make the architecture more secure and easier to maintain.