c# - teclado - Captura de pantalla en la sesión de escritorio del servidor
como hacer captura de pantalla en pc sin teclado (6)
He desarrollado un marco de prueba GUI que realiza la prueba de integración del sitio web de nuestra empresa de forma programada. Cuando algo falla, tomará una captura de pantalla del escritorio, entre otras cosas. Esto se ejecuta sin supervisión en un usuario que ha iniciado sesión en un Windows Server 2008 dedicado.
El problema es que tomar una captura de pantalla en un escritorio del que he desconectado mi sesión de escritorio remoto. Me sale la siguiente excepción:
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
El método TakeScreenshot () se ve así:
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);
}
}
Me he asegurado de que el protector de pantalla esté configurado en "Ninguno" sin tiempo de espera. También he implementado un fragmento de código que hace un par de pinvokes para enviar un movimiento del mouse , con la esperanza de que genere un controlador de gráficos de escritorio ... pero no.
IntPtr hWnd = GetForegroundWindow();
if (hWnd != IntPtr.Zero)
SendMessage(hWnd, 0x200, IntPtr.Zero, IntPtr.Zero);
Cualquier consejo es apreciado.
Creo que el problema puede ser que estás en la WindowStation equivocada. Echa un vistazo a estos artículos;
¿Por qué la pantalla de impresión en un Servicio de Windows devuelve una imagen en negro?
Captura de pantalla del servicio de windows
Podría ser que tu estación de ganancias esté desapareciendo cuando te desconectes. ¿Está ejecutando la aplicación cuando inicia sesión y luego intenta dejarla en ejecución cuando se desconecta?
Si es así, ¿todavía lo hace si te conectas con " mstsc /admin
"? En otras palabras, ¿conectarse y ejecutarse en la sesión de la consola? Si no, esto podría ser una solución.
El problema parece ser que al cerrar la conexión remota, la pantalla se bloquea y evita que el sistema realice operaciones gráficas como su g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
Para evitar esto, no use la ''x'' para cerrar la conexión remota, en su lugar use %windir%/system32/tscon.exe 0 /dest:console
. (Eso asegurará que la pantalla no esté bloqueada).
Lea esta publicación para obtener más información (en VBA, pero c # -comprensible ;-))
EDITAR Si desea hacerlo directamente en c #, intente algo como esto:
Process p = new Process();
p.StartInfo.FileName = "tscon";
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.StartInfo.Arguments = "0 /dest:console";
p.Start();
Encontré una pregunta similar Captura de pantalla con problemas de C # y Escritorio remoto . Espero que te ayude a resolver el problema.
Aquí está el código de esa respuesta:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle, ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY);
// restore selection
GDI32.SelectObject(hdcDest, hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
return img;
}
Esta no es una característica compatible. Es cierto que funciona en XP y Windows Server 2003, sin embargo, esto se considera un defecto de seguridad.
Para evitar esto, no use la ''x'' para cerrar la conexión remota, en su lugar use% windir% / system32 / tscon.exe 0 / dest: console. (Eso asegurará que la pantalla no esté bloqueada). - Nicolas Voron
Es cierto que si se desconecta del servidor de esta manera, la "pantalla" no se bloqueará para garantizar que permanezca desbloqueada, debe asegurarse de desactivar el protector de pantalla, ya que tan pronto como se inicie, se bloqueará automáticamente la pantalla.
Hay bastantes ejemplos, solo de personas que hacen lo mismo, incluso aquí en el desbordamiento de pila, la publicación a continuación sugiere que cree una aplicación de Windows que se ejecute con una cuenta de usuario real que envíe capturas de pantalla a través del IPC al servicio en ejecución.
La forma correcta de obtener una GUI personalizada que funcione con un servicio es separarlos en dos procesos y hacer algún tipo de IPC (comunicación entre procesos). Por lo tanto, el servicio se iniciará cuando se active la máquina y se iniciará una aplicación GUI en la sesión del usuario. En ese caso, la GUI puede crear una captura de pantalla, enviarla al servicio y el servicio puede hacerlo con lo que desee. - Captura de pantalla del proceso bajo el servicio de Windows.
He recopilado algunas estrategias que he encontrado en línea que pueden darle algunas ideas.
Software de terceros
Hay una gran cantidad de programas que hacen capturas de pantalla de sitios web como http://www.websitescreenshots.com/ que tienen una interfaz de usuario y una herramienta de línea de comandos. Pero si está utilizando algún marco de prueba, esto podría no funcionar ya que hará una nueva solicitud para recuperar todos los activos y dibujar la página.
Control webBrowser
No estoy seguro de qué navegador está utilizando para probar el sitio web de su empresa; sin embargo, si no le preocupa qué navegador es, puede usar un control WebBrowser y usar el método DrawToBitmap .
Virtualización
He visto un sistema en el que los desarrolladores utilizaban entornos virtuales con el navegador de su elección con todas las configuraciones para asegurarse de que la máquina no se bloqueara y, si lo hiciera, se reiniciaría.
Selenio
También es posible utilizar selenio con el controlador de web de selenio y una gema de rubí sin cabeza desarrollada por leonid-shevtsov Si su prueba está en selenio, este enfoque podría ser el mejor. Selenium admite la captura de pantalla en los controladores web que tienen disponibles.
Por supuesto, todo esto depende de lo que esté utilizando para su marco de prueba. Si puede compartir algunos detalles sobre su configuración, podremos darle una mejor respuesta.
Lo que hice para resolver esto es llamar a tscon.exe y decirle que redirija la sesión a la consola justo antes de tomar la captura de pantalla . Funciona así (nota, este código exacto no está probado):
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
}
Para capturar la pantalla necesita ejecutar un programa en la sesión de un usuario. Eso es porque sin el usuario no hay manera de tener un escritorio asociado.
Para resolver esto, puede ejecutar una aplicación de escritorio para tomar la imagen, esta aplicación puede invocarse en la sesión del usuario activo, esto puede hacerse desde un servicio.
El siguiente código le permite invocar una aplicación de escritorio de tal manera que se ejecute en el escritorio del usuario local.
Si necesita ejecutar como un usuario en particular, verifique el código en el artículo ¿ Permitir que el servicio interactúe con el escritorio? Ay. . También puede considerar el uso de la función LogonUser .
El código:
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;
}
}
Este código es una modificación del que se encuentra en el artículo ¿ Permitir que el servicio interactúe con el escritorio? Ay. (DEBE LEER)
Apéndice:
El código anterior permite ejecutar un programa en el escritorio del usuario registrado localmente en la máquina. Este método es específico para el usuario local actual, pero es posible hacerlo para cualquier usuario. Verifique el código en el artículo ¿ Permitir que el servicio interactúe con el escritorio? Ay. para un ejemplo.
El núcleo de este método es la función CreateProcessAsUser , que puede encontrar más información en CreateProcessAsUser .
Reemplace "Executable Path"
con la ruta del ejecutable que se ejecutará. Reemplace "Command Line"
con la cadena pasada como argumentos de ejecución, y reemplace "Working Directory"
con el directorio de trabajo que desee. Por ejemplo, puede extraer la carpeta de la ruta ejecutable:
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;
}
Si tiene un servicio, puede usar este código en el servicio para invocar una aplicación de escritorio. Esa aplicación de escritorio también puede ser el ejecutable del servicio ... para eso puede usar Assembly.GetExecutingAssembly().Location
como la ruta ejecutable. Luego puede usar System.Environment.UserInteractive
para detectar si el ejecutable no se está ejecutando como un servicio y pasar como argumentos de ejecución la información sobre la tarea que debe hacer. En el contexto de esta respuesta que es capturar la pantalla (por ejemplo, con CopyFromScreen
), podría ser otra cosa.