在服务器桌面会话上捕获屏幕

Sil*_*sen 19 .net c# pinvoke system.drawing windows-server-2008

我开发了一个GUI测试框架,可以按计划对我们公司网站进行集成测试.当某些内容失败时,它会截取桌面的屏幕截图等.这在专用Windows Server 2008上的登录用户上无人值守.

问题是在我已断开远程桌面会话的桌面上截取屏幕截图.我得到以下异常:

System.ComponentModel.Win32Exception (0x80004005): The handle is invalid     
at System.Drawing.Graphics.CopyFromScreen(Int32 sourceX, Int32 sourceY, Int32 destinationX, Int32 destinationY, Size blockRegionSize, CopyPixelOperation copyPixelOperation)     
at System.Drawing.Graphics.CopyFromScreen(Point upperLeftSource, Point upperLeftDestination, Size blockRegionSize)     
at IntegrationTester.TestCaseRunner.TakeScreenshot(String name) in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 144     
at IntegrationTester.TestCaseRunner.StartTest() in C:\VS2010\IntegrationTester\IntegrationTester\Config\TestCaseRunner.cs:line 96
Run Code Online (Sandbox Code Playgroud)

TakeScreenshot()方法是这样的:

public static void TakeScreenshot(string name)
        {
            var bounds = Screen.GetBounds(Point.Empty);
            using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
            {
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
                }
                bitmap.Save("someFileName", ImageFormat.Jpeg);
            }
        }
Run Code Online (Sandbox Code Playgroud)

我确保屏幕保护程序设置为"无",没有超时.我还实现了一段代码,它可以执行一些pinvokes来发送鼠标移动,希望它能生成桌面图形处理..但是没有.

IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
    SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);
Run Code Online (Sandbox Code Playgroud)

任何建议表示赞赏.

The*_*aot 7

为了捕获屏幕,您需要在用户会话中运行一个程序。这是因为没有用户就无法关联桌面。

为了解决这个问题,你可以运行一个桌面应用程序来获取图像,这个应用程序可以在活动用户的会话中调用,这可以从服务中完成。

下面的代码允许您以在本地用户桌面上运行的方式调用桌面应用程序。

如果您需要以特定用户身份执行,请查看文章允许服务与桌面交互中的代码哎哟。. 您还可以考虑使用函数LogonUser

编码:

public void Execute()
{
    IntPtr sessionTokenHandle = IntPtr.Zero;
    try
    {
        sessionTokenHandle = SessionFinder.GetLocalInteractiveSession();
        if (sessionTokenHandle != IntPtr.Zero)
        {
            ProcessLauncher.StartProcessAsUser("Executable Path", "Command Line", "Working Directory", sessionTokenHandle);
        }
    }
    catch
    {
        //What are we gonna do?
    }
    finally
    {
        if (sessionTokenHandle != IntPtr.Zero)
        {
            NativeMethods.CloseHandle(sessionTokenHandle);
        }
    }
}

internal static class SessionFinder
{
    private const int INT_ConsoleSession = -1;

    internal static IntPtr GetLocalInteractiveSession()
    {
        IntPtr tokenHandle = IntPtr.Zero;
        int sessionID = NativeMethods.WTSGetActiveConsoleSessionId();
        if (sessionID != INT_ConsoleSession)
        {
            if (!NativeMethods.WTSQueryUserToken(sessionID, out tokenHandle))
            {
                throw new System.ComponentModel.Win32Exception();
            }
        }
        return tokenHandle;
    }
}
Run Code Online (Sandbox Code Playgroud)
internal static class ProcessLauncher
{
    internal static void StartProcessAsUser(string executablePath, string commandline, string workingDirectory, IntPtr sessionTokenHandle)
    {
        var processInformation = new NativeMethods.PROCESS_INFORMATION();
        try
        {
            var startupInformation = new NativeMethods.STARTUPINFO();
            startupInformation.length = Marshal.SizeOf(startupInformation);
            startupInformation.desktop = string.Empty;
            bool result = NativeMethods.CreateProcessAsUser
            (
                sessionTokenHandle,
                executablePath,
                commandline,
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                0,
                IntPtr.Zero,
                workingDirectory,
                ref startupInformation,
                ref processInformation
            );
            if (!result)
            {
                int error = Marshal.GetLastWin32Error();
                string message = string.Format("CreateProcessAsUser Error: {0}", error);
                throw new ApplicationException(message);
            }
        }
        finally
        {
            if (processInformation.processHandle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(processInformation.processHandle);
            }
            if (processInformation.threadHandle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(processInformation.threadHandle);
            }
            if (sessionTokenHandle != IntPtr.Zero)
            {
                NativeMethods.CloseHandle(sessionTokenHandle);
            }
        }
    }
}

internal static class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    internal static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool CreateProcessAsUser(IntPtr tokenHandle, string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandle, int creationFlags, IntPtr envrionment, string currentDirectory, ref STARTUPINFO startupInfo, ref PROCESS_INFORMATION processInformation);

    [DllImport("Kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId")]
    internal static extern int WTSGetActiveConsoleSessionId();

    [DllImport("WtsApi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool WTSQueryUserToken(int SessionId, out IntPtr phToken);

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    {
        public IntPtr processHandle;
        public IntPtr threadHandle;
        public int processID;
        public int threadID;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct STARTUPINFO
    {
        public int length;
        public string reserved;
        public string desktop;
        public string title;
        public int x;
        public int y;
        public int width;
        public int height;
        public int consoleColumns;
        public int consoleRows;
        public int consoleFillAttribute;
        public int flags;
        public short showWindow;
        public short reserverd2;
        public IntPtr reserved3;
        public IntPtr stdInputHandle;
        public IntPtr stdOutputHandle;
        public IntPtr stdErrorHandle;
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码是对文章允许服务与桌面交互?哎哟。(必读)


附录:

上面的代码允许在本地登录到机器的用户的桌面上执行程序。此方法特定于当前本地用户,但可以为任何用户执行此操作。检查文章允许服务与桌面交互中的代码哎哟。举个例子。

该方法的核心是函数CreateProcessAsUser,您可以在MSDN 上找到更多信息。

替换"Executable Path"为要运行的可执行文件的路径。替换"Command Line"为作为执行参数传递的字符串,并替换"Working Directory"为您想要的工作目录。例如,您可以提取可执行路径的文件夹:

    internal static string GetFolder(string path)
    {
        var folder = System.IO.Directory.GetParent(path).FullName;
        if (!folder.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
        {
            folder += System.IO.Path.DirectorySeparatorChar;
        }
        return folder;
    }
Run Code Online (Sandbox Code Playgroud)

如果您有服务,则可以在服务中使用此代码来调用桌面应用程序。该桌面应用程序也可能是服务可执行文件...因此您可以将其Assembly.GetExecutingAssembly().Location用作可执行文件路径。然后,您可以使用System.Environment.UserInteractive来检测可执行文件是否未作为服务运行,并将有关需要执行的任务的信息作为执行参数传递。在此答案的上下文中,即捕获屏幕(例如使用CopyFromScreen),它可能是其他内容。


Nic*_*ron 1

问题似乎是当您关闭远程连接时,屏幕会进入锁定状态,这会阻止系统执行像您这样的图形操作g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);

为了防止这种情况,不要使用“x”来关闭远程连接,而是使用“x”来关闭远程连接%windir%\system32\tscon.exe 0 /dest:console。(这将确保屏幕未锁定)。

阅读这篇文章以获取更多信息(使用 VBA,但可以理解 c# ;-))

编辑 如果你想直接在 c# 中执行此操作,请尝试如下操作:

Process p = new Process();

p.StartInfo.FileName = "tscon";
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

p.StartInfo.Arguments = "0 /dest:console";
p.Start();
Run Code Online (Sandbox Code Playgroud)