sskodje / ScreenRecorderLib

A .NET library for screen recording in Windows, using native Microsoft Media Foundation for realtime encoding to h264 video or PNG images.
MIT License
414 stars 94 forks source link

The library is crashing when used under Windows 8.1 64Bit #212

Closed hAbd0u closed 2 years ago

hAbd0u commented 2 years ago

Hi there, The library is crashing while using it in windows service application, my current testing environment: Windows 8.1 64BIT - VMware player guest. VS 2022 - .NET framework 6.0. This is the error I am getting from windows events:

Application : RdpMonitoringService.exe
Version du Framework : v4.0.30319
Description : le processus a été arrêté en raison d'une exception non gérée.
Informations sur l'exception : System.AccessViolationException
Pile :
   à <Module>.GetMainOutput(IDXGIOutput**)
   à <Module>.GetMainOutput(IDXGIOutput**)
   à ScreenRecorderLib.SourceOptions.get_MainMonitor()
   à ScreenRecorderLib.Recorder..ctor(ScreenRecorderLib.RecorderOptions)
   à ScreenRecorderLib.Recorder.CreateRecorder()
   à RdpMonitoringService.RdpMonitoringService.OnSessionChange(System.ServiceProcess.SessionChangeDescription)
   à System.ServiceProcess.ServiceBase.DeferredSessionChange(Int32, Int32)
   à System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr, System.Object[], System.Object, System.Object[] ByRef)
   à System.Runtime.Remoting.Messaging.StackBuilderSink.AsyncProcessMessage(System.Runtime.Remoting.Messaging.IMessage, System.Runtime.Remoting.Messaging.IMessageSink)
   à System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   à System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   à System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   à System.Threading.ThreadPoolWorkQueue.Dispatch()

My windows service code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceProcess;
using ScreenRecorderLib;

namespace RdpMonitoringService
{
    struct RdpSession
    {
        public int sessionId;
        public string userName;
        public string ipAddress;
        public string sessionDateTime;
        public string recordFileName;
        public Recorder sessionRecorder;

        public string generateRecordFileName()
        {
            string dirName = "RecordedSessions";
            string path = AppDomain.CurrentDomain.BaseDirectory + "\\" + dirName;
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\" + dirName + "\\" + userName + " - " + sessionDateTime + ".mp4";
            return filepath;
        }

        public override string ToString()
        {
            return "SessionId: " + sessionId + ", userName: " + userName; 
        }
    }
    public partial class RdpMonitoringService : ServiceBase
    {
        RdpSession onRdpSession;
        Dictionary<int, RdpSession> rdpSessionList;
        public RdpMonitoringService()
        {
            InitializeComponent();

            this.CanPauseAndContinue = true;
            this.CanHandleSessionChangeEvent = true;

            rdpSessionList = new Dictionary<int, RdpSession>();
        }
        protected override void OnStart(string[] args)
        {
            WriteToFile("Service is started at: ============ " + DateTime.Now);
            WriteToFile("this.CanHandleSessionChangeEvent: " + this.CanHandleSessionChangeEvent);
        }
        protected override void OnStop()
        {
            WriteToFile("Service is stopped at " + DateTime.Now);
        }

        protected override void OnSessionChange(SessionChangeDescription sessionChangeDescription)
        {
            try { 
                var userInfo = TermServicesManager.GetSessionInfo(Dns.GetHostEntry("").HostName, sessionChangeDescription.SessionId);

                    IPAddress ipAddress = new IPAddress(userInfo.ClientAddress.Address.Skip(2).Take(4).ToArray());
                    if (!rdpSessionList.ContainsKey(sessionChangeDescription.SessionId))
                    {
                        onRdpSession.sessionId = sessionChangeDescription.SessionId;
                        onRdpSession.ipAddress = ipAddress.ToString();
                        onRdpSession.userName = userInfo.UserName;
                        onRdpSession.sessionDateTime = DateTime.Now.ToString("yyyy'-'MM'-'dd'T'HH'-'mm'-'ss");
                        onRdpSession.recordFileName = onRdpSession.generateRecordFileName();
                        onRdpSession.sessionRecorder = Recorder.CreateRecorder();       // <<<<<<<<< Crash happening here
                        onRdpSession.sessionRecorder.Record(onRdpSession.generateRecordFileName());
                        rdpSessionList.Add(sessionChangeDescription.SessionId, onRdpSession);
                    }
                    else
                    {
                        onRdpSession = rdpSessionList[sessionChangeDescription.SessionId];
                    }

                    WriteToFile("SessionChange event");

                    switch (sessionChangeDescription.Reason)
                    {
                        case SessionChangeReason.SessionLogon:
                            WriteToFile("SessionChange" + DateTime.Now.ToLongTimeString() + ", SessionUnlock|RemoteConnect|SessionLogon [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());
                            onRdpSession.sessionRecorder = Recorder.CreateRecorder();
                            onRdpSession.sessionRecorder.Record(onRdpSession.recordFileName);
                            break;
                        case SessionChangeReason.RemoteConnect:
                            WriteToFile("SessionChange" + DateTime.Now.ToLongTimeString() + ", SessionUnlock|RemoteConnect|SessionLogon [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());
                            onRdpSession.sessionRecorder = Recorder.CreateRecorder();
                            onRdpSession.sessionRecorder.Record(onRdpSession.recordFileName);
                            break;
                        case SessionChangeReason.SessionUnlock:
                            WriteToFile("SessionChange" + DateTime.Now.ToLongTimeString() + ", SessionUnlock|RemoteConnect|SessionLogon [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());
                            onRdpSession.sessionRecorder = Recorder.CreateRecorder();
                            onRdpSession.sessionRecorder.Record(onRdpSession.recordFileName);
                            break;
                        case SessionChangeReason.SessionLock:
                        WriteToFile("SessionChange: " + onRdpSession.ToString() + ", " + DateTime.Now.ToLongTimeString() + " RemoteDisconnect|SessionLogoff|SessionLock [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());

                        onRdpSession.sessionRecorder.Stop();
                        break;
                    case SessionChangeReason.SessionLogoff:
                        WriteToFile("SessionChange: " + onRdpSession.ToString() + ", " + DateTime.Now.ToLongTimeString() + " RemoteDisconnect|SessionLogoff|SessionLock [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());

                        onRdpSession.sessionRecorder.Stop();
                        break;
                    case SessionChangeReason.RemoteDisconnect:
                            WriteToFile("SessionChange: " + onRdpSession.ToString() + ", " + DateTime.Now.ToLongTimeString() + " RemoteDisconnect|SessionLogoff|SessionLock [" + sessionChangeDescription.SessionId.ToString() + "]" + ", User: " + userInfo.UserName + ", Connect state: " + userInfo.ConnectState.ToString() + ", Client address: " + ipAddress.ToString() + ", user: " + userInfo.UserName + ", WinStationName: " + userInfo.WinStationName.ToString());

                            onRdpSession.sessionRecorder.Stop();
                            break;
                        default:
                            break;
                    }

            } 
            catch(Exception ex)
            {
                WriteToFile("SessionChange exception: " + ex.Message + " || " + sessionChangeDescription.SessionId.ToString() + " || " + onRdpSession.sessionId.ToString());

            }
        }

        public void WriteToFile(string Message)
        {
            string path = AppDomain.CurrentDomain.BaseDirectory + "\\Logs";
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\Logs\\ServiceLog_" + DateTime.Now.Date.ToShortDateString().Replace('/', '_') + ".txt";
            if (!File.Exists(filepath))
            {
                // Create a file to write to.   
                using (StreamWriter sw = File.CreateText(filepath))
                {
                    sw.WriteLine(Message);
                }
            }
            else
            {
                using (StreamWriter sw = File.AppendText(filepath))
                {
                    sw.WriteLine(Message);
                }
            }
        }

    }
}
sskodje commented 2 years ago

Hello!

To record, you need to launch some form of application that does it on the desktop, you cannot record from a service. This is because Microsoft have made it impossible to access the desktop or graphics output from a service, for security reasons.

hAbd0u commented 2 years ago

Hello, That makes sense now, do you any suggestion to my problem here?

sskodje commented 2 years ago

I think what people often do is to leverage the task scheduler when having for example an update service that needs to interact with the desktop. I have not tested this myself but this project may help to launch e.g. a console application from the service onto the desktop.

https://github.com/dahall/TaskScheduler