3

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.
crankedrelic
  • 463
  • 1
  • 5
  • 14
  • 1
    what did you try so far? – pix Dec 11 '17 at 15:53
  • I tried to use the MSTSC.lib to create an RdpClient as discribed here https://www.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. – crankedrelic Dec 11 '17 at 16:06
  • Please add what you have tried to your initial question. You may also want to take a look at [this article](https://stackoverflow.com/help/how-to-ask) – Marcello B. Dec 11 '17 at 16:12
  • @crankedrelic Did you ever find a solution to this? I need to do the exact same thing, would you be willing to share? – LorneCash Jan 12 '18 at 04:01
  • 1
    @LorneCash Yes, take a look at this https://stackoverflow.com/questions/37310418/how-to-use-activex-component-in-classlibrary-without-winforms. 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 Jan 12 '18 at 07:24
  • 1
    @LorneCash the answer is finally posted, although i believe you have found your way around this by now – crankedrelic Jun 05 '19 at 16:22

4 Answers4

9

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.

crankedrelic
  • 463
  • 1
  • 5
  • 14
1

Here's how I'm calling @crankedrelic's solution from a .Net Core 6 console app.

static void Main(string[] args)
{
    RemoteDesktopApi.MessageLoopApartment.I.Run(() =>
    {
        var ca = new RemoteDesktopApi();
        ca.Connect(("USERNAME", "DOMAIN", "PASSWORD", "MACHINE"));
    }, CancellationToken.None);

    while (true)
    {
        System.Threading.Thread.Sleep(1000);
    }
}

You'll also need to modify your console app's config to use Windows Forms:

<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
StormRider01
  • 335
  • 5
  • 19
0

Can you please update your question related to the first comments :)

Then if I fully understand your question you can have a look to this MSD forum: https://social.msdn.microsoft.com/Forums/vstudio/en-US/6c8a2d19-a126-4b4b-aab7-0fa4c22671ed/hosting-remote-desktop-connection-in-wpf-app?forum=wpf

You can try something like this (this seems to based on your research) :

try
   {
    axMsRdpClient.Server = ServerName;

    axMsRdpClient.DesktopHeight = 768;
    axMsRdpClient.DesktopWidth = 1024;
    axMsRdpClient.Connect();
   }
   catch (Exception Ex)
   {
    MessageBox.Show(Ex.Message);
   }
pix
  • 1,264
  • 19
  • 32
  • 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. – crankedrelic Dec 11 '17 at 16:32
  • 1
    Can you please edit your question with the clarification? – Amittai Shapira Dec 11 '17 at 16:44
0

The problem you are trying solve sounds like a textbook case for a web service solution.

You should have an application running on the server that is a web service, waiting for requests.

Your client application (console application, whatever) sends calls to the web service to request that the server take some action.

The application on the server receives the request and performs the required tasks.

Is there some specific reason you want to be able to access the mouse, etc. on the server from the client?

DWRoelands
  • 4,878
  • 4
  • 29
  • 42
  • 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. – crankedrelic Dec 11 '17 at 20:43
  • 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. – DWRoelands Dec 11 '17 at 20:48
  • 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. – crankedrelic Dec 11 '17 at 21:09
  • 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. – DWRoelands Dec 12 '17 at 13:29