为了捕获屏幕,您需要在用户会话中运行程序。那是因为没有用户,就无法关联桌面。
为了解决这个问题,你可以运行一个桌面应用程序来获取图像,这个应用程序可以在活动用户的会话中调用,这可以通过服务来完成。
下面的代码允许您调用桌面应用程序,使其在本地用户的桌面上运行。
如果您需要以特定用户身份执行,请查看文章Allow service to interact with desktop? Ouch. 中的代码。也可以考虑使用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;
}
}
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" 替换为您想要的工作目录。例如可以解压可执行路径的文件夹:
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),它可能是别的东西。