Search code examples
c#winformswindows-servicesactivexrdp

Create Windows Session programmatically from Console or Windows Service


How can I programmatically log in to windows to create a Windows Logon Session? I need a way that works from a WinForms app, from a Console app, and (most important) from a Windows Service.

One other requirement is that I need it to work on a the local system that the program/service is running on and also for remote systems.

If there's a way to do this using pInvoke/Win32 API I am open to that too.

I found these similar questions/answers in my research:

Programmatically create and launch and RDP session (without gui)

The answer here says it's possible but and gives a link but the sample code from the link doesn't work

Create a Windows Session from a service via the Win32 API

No Solution to the question asked

Create Windows session programmatically

No Solution but the OP mentioned in a comment that http://freerdp.com worked for him.


Solution

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