首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在服务器桌面会话上捕获屏幕

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

Stack Overflow用户
提问于 2011-03-05 07:17:21
回答 6查看 16.2K关注 0票数 19

我已经开发了一个GUI测试框架,可以定期对我们公司的网站进行集成测试。当某些东西失败时,它会截取桌面的截图,以及其他一些东西。它在专用Windows Server 2008上的登录用户上以无人值守方式运行。

The problem是在我断开了远程桌面会话的桌面上截图。我得到了以下异常:

代码语言:javascript
复制
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

TakeScreenshot()方法如下所示:

代码语言:javascript
复制
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);
            }
        }

我已确保屏幕保护程序设置为“无”,没有超时。我还实现了一段代码,它对发送鼠标移动执行了几个pinvokes,希望它能生成一个桌面图形句柄。但不是。

代码语言:javascript
复制
IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
    SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);

任何建议都是值得感谢的。

EN

回答 6

Stack Overflow用户

发布于 2012-10-12 10:02:16

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

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

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

如果需要以特定用户身份执行,请查看文章Allow service to interact with desktop? Ouch.中的代码。您还可以考虑使用函数LogonUser

代码:

代码语言:javascript
复制
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;
    }
}

代码语言:javascript
复制
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;
    }
}

此代码是在文章Allow service to interact with desktop? Ouch. (必须阅读)中找到的代码的修改

附录:

上面的代码允许在本地登录到机器的用户的桌面上执行程序。此方法特定于当前本地用户,但也可以为任何用户执行此操作。请查看文章Allow service to interact with desktop? Ouch.中的代码以获取示例。

这个方法的核心是函数CreateProcessAsUser,你可以在MSDN上找到更多关于它的信息。

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

代码语言:javascript
复制
    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;
    }

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

票数 8
EN

Stack Overflow用户

发布于 2012-10-11 10:46:14

为了解决这个问题,我调用了tscon.exe,告诉它在截图之前将会话重定向回控制台。它是这样的(注意,这段代码是未经测试的):

代码语言:javascript
复制
public static void TakeScreenshot(string path) {
    try {
        InternalTakeScreenshot(path);
    } catch(Win32Exception) {
        var winDir = System.Environment.GetEnvironmentVariable("WINDIR");
        Process.Start(
            Path.Combine(winDir, "system32", "tscon.exe"),
            String.Format("{0} /dest:console", GetTerminalServicesSessionId()))
        .WaitForExit();

        InternalTakeScreenshot(path);
    }
}

static void InternalTakeScreenshot(string path) {
    var point = new System.Drawing.Point(0,0);
    var bounds = System.Windows.Forms.Screen.GetBounds(point);

    var size = new System.Drawing.Size(bounds.Width, bounds.Height);
    var screenshot = new System.Drawing.Bitmap(bounds.Width, bounds.Height);
    var g = System.Drawing.Graphics.FromImage(screenshot)
    g.CopyFromScreen(0,0,0,0,size);

    screenshot.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg); 
}

[DllImport("kernel32.dll")]
static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId);

static uint GetTerminalServicesSessionId()
{
    var proc = Process.GetCurrentProcess();
    var pid = proc.Id;

    var sessionId = 0U;
    if(ProcessIdToSessionId((uint)pid, out sessionId))
        return sessionId;
    return 1U; // fallback, the console session is session 1
}
票数 4
EN

Stack Overflow用户

发布于 2012-10-10 05:36:31

这不是一个受支持的功能,它确实可以在XP和windows server 2003中工作,但是这被视为安全缺陷。

为防止出现这种情况,请改用%windir%\system32\tscon.exe 0 /dest:console,而不要使用'x‘来关闭远程连接。(这将确保屏幕不被锁定)。-- Nicolas Voron

确实,如果你以这种方式断开与服务器的连接,“屏幕”将不会被锁定,以确保它处于解锁状态,你需要确保你关闭了屏幕保护程序,因为一旦启动,它将自动锁定你的屏幕。

有相当多的人做同样的事情,甚至在堆栈溢出这里,下面的帖子建议您创建一个在实际用户帐户下运行的windows应用程序,该应用程序通过IPC将屏幕截图发送到正在运行的服务。

获得与服务一起工作的自定义图形用户界面的正确方法是将它们分成两个进程,并进行某种进程间通信。因此,服务将在机器启动时启动,并且GUI应用程序将在用户会话中启动。在这种情况下,GUI可以创建屏幕截图,将其发送到服务,服务可以对其执行任何您喜欢的操作。- Screenshot of process under Windows Service

我已经整理了一些我在网上找到的策略,它们可能会给你一些想法。

第三方软件

有很多程序可以制作网站的屏幕截图,比如http://www.websitescreenshots.com/,它们有一个用户界面和命令行工具。但是,如果您使用的是一些测试框架,这可能不会起作用,因为它会发出一个新的请求来获取所有资产并绘制页面。

WebBrowser控件

我不确定您正在使用什么浏览器来测试您的公司网站,但是,如果您不担心是哪种浏览器,您可以使用WebBrowser控件并使用DrawToBitmap method

虚拟化

我见过一个系统,在这个系统中,开发人员通过他们选择的浏览器使用虚拟环境,并进行了所有设置,以确保机器不会锁定,如果锁定,它将重新启动。

也可以将selenium与selenium-webdriver和headless结合使用,如果您的测试使用selenium,那么这种方法可能是最好的。headless是由leonid-shevtsov开发的红宝石。Selenium本身支持在他们可用的they驱动程序上捕获屏幕。

当然,所有这些都取决于你的测试框架使用的是什么,如果你能分享一些关于你的设置的细节,我们将能够给你一个更好的答案。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5200341

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档