capture screen even when workstation is locked

J

Jeremy Chapman

Is it possible to grab a screen shot of an application window even when the
workstation is locked? I can do it when it's not locked but just get a
black image when it is locked.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Jeremy,

No, it is not possible. If it was it would be a big security flaw. One of
the reason people lock their stations is because they don't want anyone to
be able to see what is on the screen.

Secondly, You can grab only what is visible on the screen. You cannot
capture anything that is not visible e.g. part of a window obscured by other
window. When the workstation is locked there is nothing on the screen, but
the dialog for unlocking the station.

Windows provides a message called WM_PRINT this message is meant to be used
to ask a window to draw itself to an arbitrary graphic device context. In
theory it can be used for capturing invisible parts of a window, but this
message needs to be handled and processed by the target application. I
remember playing with this message when I was doing Win32 native programming
and it worked with all standard windows' controls, but almost never worked
with third party controls and windows.
Once I tried to use this in a .NET application but there were some problem
with the Graphics object and I didn't have any success. However I gave up
pretty quick because I did it out of curiosity only, it wasn't important. It
might be possible to make it work with .NET application, I don't know.
 
J

Jeremy Chapman

Thanks for the info. After a little more searching with your direction, I
think I found a solution (only on xp and 2003) using the PrintWindow API.
Thought i'd share. I tried the WM_Print message but couldn't get it to work
at all with the window I wanted to capture. Doing a BitBlt also worked, but
not when the workstation was locked.

public class gdi32
{
private class Externs
{
[System.Runtime.InteropServices.DllImport(strGDI32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetDeviceCaps(int iHdc,int iIndex);
}

/// <summary>
///
/// </summary>
/// <param name="iHdc"></param>
/// <param name="eIndex"></param>
/// <returns></returns>
public static int GetDeviceCaps(int iHdc,enCapsIndex eIndex)
{
return Externs.GetDeviceCaps(iHdc,(int)eIndex);
}

/// <summary>
///
/// </summary>
public enum enCapsIndex
{
/// <summary>
/// The device driver version.
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device technology.
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Width, in millimeters, of the physical screen.
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Height, in millimeters, of the physical screen.
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Width, in pixels, of the screen.
/// </summary>
HORZRES = 8,
/// <summary>
/// Height, in raster lines, of the screen.
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of pixels per logical inch along the screen width. In a system
with multiple display monitors, this value is the same for all monitors.
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Number of pixels per logical inch along the screen height. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of adjacent color bits for each pixel.
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of color planes.
/// </summary>
PLANES = 14,
/// <summary>
/// Number of device-specific brushes.
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of device-specific pens.
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of device-specific markers.
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of device-specific fonts.
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of entries in the device's color table, if the device has a
color depth of no more than 8 bits per pixel. For devices with greater color
depths, 1 is returned.
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Reserved.
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Value that indicates the curve capabilities of the device, as shown
in the following table.
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Value that indicates the line capabilities of the device, as shown in
the following table:
/// </summary>
LINECAPS = 30,
/// <summary>
/// Value that indicates the polygon capabilities of the device, as shown
in the following table.
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Value that indicates the text capabilities of the device, as shown in
the following table.
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Flag that indicates the clipping capabilities of the device. If the
device can clip to a rectangle, it is 1. Otherwise, it is 0.
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Value that indicates the raster capabilities of the device, as shown
in the following table.
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Relative width of a device pixel used for line drawing.
/// </summary>
ASPECTX = 40,
/// <summary>
/// Relative height of a device pixel used for line drawing.
/// </summary>
ASPECTY = 42,
/// <summary>
/// Diagonal width of the device pixel used for line drawing.
/// </summary>
ASPECTXY = 44,
/// <summary>
/// For printing devices: the width of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-x11-inch paper
has a physical width value of 5100 device units. Note that the physical page
is almost always greater than the printable area of the page, and never
smaller.
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// For printing devices: the height of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-by-11-inch
paper has a physical height value of 6600 device units. Note that the
physical page is almost always greater than the printable area of the page,
and never smaller.
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// For printing devices: the distance from the left edge of the physical
page to the left edge of the printable area, in device units. For example, a
printer set to print at 600 dpi on 8.5-by-11-inch paper, that cannot print
on the leftmost 0.25-inch of paper, has a horizontal physical offset of 150
device units.
/// </summary>
PHYSICALOFFSETX = 112, // Physical Printable Area x margin
/// <summary>
/// For printing devices: the distance from the top edge of the physical
page to the top edge of the printable area, in device units. For example, a
printer set to print at 600 dpi on 8.5-by-11-inch paper, that cannot print
on the topmost 0.5-inch of paper, has a vertical physical offset of 300
device units.
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor for the x-axis of the printer.
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor for the y-axis of the printer.
/// </summary>
SCALINGFACTORY = 115

}
}

public class User32
{

const string strUSER32DLL = "user32.dll";

private class Externs
{
[System.Runtime.InteropServices.DllImport(strUSER32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int PrintWindow(int iHwnd, int hdcBlt, uint iFlags);
}

/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(int hWnd,
enPrintWindowFlags eFlags)
{
int iWidth = 0;
int iHeight = 0;
int hdcSrc = User32.GetWindowDC(hWnd);
try
{
iWidth = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.HORZRES);
iHeight = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.VERTRES);
}
finally
{
User32.ReleaseDC(hWnd,hdcSrc);
}

System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(iWidth,
iHeight);
System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(pImage);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
Externs.PrintWindow(hWnd, (int)hDC, (uint)eFlags);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}

public enum enPrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the entire
window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}

Stoitcho Goutsev (100) said:
Jeremy,

No, it is not possible. If it was it would be a big security flaw. One of
the reason people lock their stations is because they don't want anyone to
be able to see what is on the screen.

Secondly, You can grab only what is visible on the screen. You cannot
capture anything that is not visible e.g. part of a window obscured by
other window. When the workstation is locked there is nothing on the
screen, but the dialog for unlocking the station.

Windows provides a message called WM_PRINT this message is meant to be
used to ask a window to draw itself to an arbitrary graphic device
context. In theory it can be used for capturing invisible parts of a
window, but this message needs to be handled and processed by the target
application. I remember playing with this message when I was doing Win32
native programming and it worked with all standard windows' controls, but
almost never worked with third party controls and windows.
Once I tried to use this in a .NET application but there were some problem
with the Graphics object and I didn't have any success. However I gave up
pretty quick because I did it out of curiosity only, it wasn't important.
It might be possible to make it work with .NET application, I don't know.


--

Stoitcho Goutsev (100) [C# MVP]

Jeremy Chapman said:
Is it possible to grab a screen shot of an application window even when
the workstation is locked? I can do it when it's not locked but just get
a black image when it is locked.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Jeremy,

It is great that you decide to share with all of us.
I'm surprised though that this works on XP and 2003, but not on win2k let
say after all talk about security. I'd concider this as a security flaw. It
might be somehow connected to the remote dektop feature available on these
platforms.

It is funny that I new about this PrintWindow method, but I never tried
because the docs say that it is similar to sending WM_PRINT and the latter
didn't work

If you don't mind I'd like to give some suggestions:
1. When you declare import API methods for PInovke use IntPtr for handlers
(such as HWND and HDC) instead of *int*
2. Try to avoid as much as it's possible using non mamaged calls. For
example you use GetDeviceCaps to get the screen size. You can do that using
Screen class:

Jeremy Chapman said:
Thanks for the info. After a little more searching with your direction, I
think I found a solution (only on xp and 2003) using the PrintWindow API.
Thought i'd share. I tried the WM_Print message but couldn't get it to
work at all with the window I wanted to capture. Doing a BitBlt also
worked, but not when the workstation was locked.

public class gdi32
{
private class Externs
{
[System.Runtime.InteropServices.DllImport(strGDI32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetDeviceCaps(int iHdc,int iIndex);
}

/// <summary>
///
/// </summary>
/// <param name="iHdc"></param>
/// <param name="eIndex"></param>
/// <returns></returns>
public static int GetDeviceCaps(int iHdc,enCapsIndex eIndex)
{
return Externs.GetDeviceCaps(iHdc,(int)eIndex);
}

/// <summary>
///
/// </summary>
public enum enCapsIndex
{
/// <summary>
/// The device driver version.
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device technology.
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Width, in millimeters, of the physical screen.
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Height, in millimeters, of the physical screen.
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Width, in pixels, of the screen.
/// </summary>
HORZRES = 8,
/// <summary>
/// Height, in raster lines, of the screen.
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of pixels per logical inch along the screen width. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Number of pixels per logical inch along the screen height. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of adjacent color bits for each pixel.
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of color planes.
/// </summary>
PLANES = 14,
/// <summary>
/// Number of device-specific brushes.
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of device-specific pens.
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of device-specific markers.
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of device-specific fonts.
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of entries in the device's color table, if the device has a
color depth of no more than 8 bits per pixel. For devices with greater
color depths, 1 is returned.
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Reserved.
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Value that indicates the curve capabilities of the device, as shown
in the following table.
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Value that indicates the line capabilities of the device, as shown
in the following table:
/// </summary>
LINECAPS = 30,
/// <summary>
/// Value that indicates the polygon capabilities of the device, as
shown in the following table.
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Value that indicates the text capabilities of the device, as shown
in the following table.
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Flag that indicates the clipping capabilities of the device. If the
device can clip to a rectangle, it is 1. Otherwise, it is 0.
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Value that indicates the raster capabilities of the device, as shown
in the following table.
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Relative width of a device pixel used for line drawing.
/// </summary>
ASPECTX = 40,
/// <summary>
/// Relative height of a device pixel used for line drawing.
/// </summary>
ASPECTY = 42,
/// <summary>
/// Diagonal width of the device pixel used for line drawing.
/// </summary>
ASPECTXY = 44,
/// <summary>
/// For printing devices: the width of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-x11-inch
paper has a physical width value of 5100 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// For printing devices: the height of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-by-11-inch
paper has a physical height value of 6600 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// For printing devices: the distance from the left edge of the
physical page to the left edge of the printable area, in device units. For
example, a printer set to print at 600 dpi on 8.5-by-11-inch paper, that
cannot print on the leftmost 0.25-inch of paper, has a horizontal physical
offset of 150 device units.
/// </summary>
PHYSICALOFFSETX = 112, // Physical Printable Area x margin
/// <summary>
/// For printing devices: the distance from the top edge of the physical
page to the top edge of the printable area, in device units. For example,
a printer set to print at 600 dpi on 8.5-by-11-inch paper, that cannot
print on the topmost 0.5-inch of paper, has a vertical physical offset of
300 device units.
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor for the x-axis of the printer.
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor for the y-axis of the printer.
/// </summary>
SCALINGFACTORY = 115

}
}

public class User32
{

const string strUSER32DLL = "user32.dll";

private class Externs
{
[System.Runtime.InteropServices.DllImport(strUSER32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int PrintWindow(int iHwnd, int hdcBlt, uint
iFlags);
}

/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(int hWnd,
enPrintWindowFlags eFlags)
{
int iWidth = 0;
int iHeight = 0;
int hdcSrc = User32.GetWindowDC(hWnd);
try
{
iWidth = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.HORZRES);
iHeight = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.VERTRES);
}
finally
{
User32.ReleaseDC(hWnd,hdcSrc);
}

System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(iWidth,
iHeight);
System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(pImage);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
Externs.PrintWindow(hWnd, (int)hDC, (uint)eFlags);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}

public enum enPrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the entire
window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}

Stoitcho Goutsev (100) said:
Jeremy,

No, it is not possible. If it was it would be a big security flaw. One of
the reason people lock their stations is because they don't want anyone
to be able to see what is on the screen.

Secondly, You can grab only what is visible on the screen. You cannot
capture anything that is not visible e.g. part of a window obscured by
other window. When the workstation is locked there is nothing on the
screen, but the dialog for unlocking the station.

Windows provides a message called WM_PRINT this message is meant to be
used to ask a window to draw itself to an arbitrary graphic device
context. In theory it can be used for capturing invisible parts of a
window, but this message needs to be handled and processed by the target
application. I remember playing with this message when I was doing Win32
native programming and it worked with all standard windows' controls, but
almost never worked with third party controls and windows.
Once I tried to use this in a .NET application but there were some
problem with the Graphics object and I didn't have any success. However I
gave up pretty quick because I did it out of curiosity only, it wasn't
important. It might be possible to make it work with .NET application, I
don't know.


--

Stoitcho Goutsev (100) [C# MVP]

Jeremy Chapman said:
Is it possible to grab a screen shot of an application window even when
the workstation is locked? I can do it when it's not locked but just
get a black image when it is locked.
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Sorry about the previous post. I pressed wrong key combination :)

Jeremy,

It is great that you decide to share with all of us.
I'm surprised though that this works on XP and 2003, but not on win2k let
say after all talk about security. I'd concider this as a security flaw. It
might be somehow connected to the remote dektop feature available on these
platforms.

It is funny that I new about this PrintWindow method, but I never tried
because the docs say that it is similar to sending WM_PRINT and the latter
didn't work

If you don't mind I'd like to give some suggestions:
1. When you declare import API methods for PInovke use IntPtr for handlers
(such as HWND and HDC) instead of *int*
2. Try to avoid as much as it's possible using non mamaged calls. For
example you use GetDeviceCaps to get the screen size. You can do that using
Screen class:
screenBounds = Screen.FromHandle(hWnd).Bounds;
screenWidth = screenBounds.Width;
screenHeight = screenBounds.Height;
3. You accept handle to a window to capture, but the image you create is as
big as the whole screen, which makes small windows to appear in the corner
of an almost empty image.
I'd suggest to create the bitmap as big as the window is. If you capture
only client part if an window you can do it from managed code -
Graphics.VisibleClipBounds. For the wole window though you need to use
Pinvoke. For example you can use GetWindowDC and then create
Graphics.FormHdc, etc or you can use APIs such as GetWindowRect.

Here is little bit nodified version of your code. Not that it is better or
something, but it is little bit shorter. I hope you don't mind.

public class User32
{

const string strUSER32DLL = "user32.dll";

[System.Runtime.InteropServices.DllImport(strUSER32DLL)]
public static extern int PrintWindow(IntPtr iHwnd, IntPtr hdcBlt, uint
iFlags);
[System.Runtime.InteropServices.DllImport(strUSER32DLL)]
public static extern IntPtr GetWindowDC(IntPtr hWnd);


/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(IntPtr hWnd,
PrintWindowFlags flags)
{

//Rectangle screenBounds = Rectangle.Empty;
Rectangle visibleBounds = Rectangle.Empty;
System.Drawing.Bitmap image = null;

using(Graphics grfx = Graphics.FromHdc(GetWindowDC(hWnd)))
{

visibleBounds = Rectangle.Round(grfx.VisibleClipBounds);

}

image = new System.Drawing.Bitmap(
visibleBounds.Width,
visibleBounds.Height);

System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(image);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
User32.PrintWindow(hWnd, hDC, (uint)flags);
}
finally
{
graphics.ReleaseHdc(hDC);
}

return image;
}

public enum PrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the entire
/// window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}


--

Stoitcho Goutsev (100) [C# MVP]

Jeremy Chapman said:
Thanks for the info. After a little more searching with your direction, I
think I found a solution (only on xp and 2003) using the PrintWindow API.
Thought i'd share. I tried the WM_Print message but couldn't get it to
work at all with the window I wanted to capture. Doing a BitBlt also
worked, but not when the workstation was locked.

public class gdi32
{
private class Externs
{
[System.Runtime.InteropServices.DllImport(strGDI32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetDeviceCaps(int iHdc,int iIndex);
}

/// <summary>
///
/// </summary>
/// <param name="iHdc"></param>
/// <param name="eIndex"></param>
/// <returns></returns>
public static int GetDeviceCaps(int iHdc,enCapsIndex eIndex)
{
return Externs.GetDeviceCaps(iHdc,(int)eIndex);
}

/// <summary>
///
/// </summary>
public enum enCapsIndex
{
/// <summary>
/// The device driver version.
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device technology.
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Width, in millimeters, of the physical screen.
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Height, in millimeters, of the physical screen.
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Width, in pixels, of the screen.
/// </summary>
HORZRES = 8,
/// <summary>
/// Height, in raster lines, of the screen.
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of pixels per logical inch along the screen width. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Number of pixels per logical inch along the screen height. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of adjacent color bits for each pixel.
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of color planes.
/// </summary>
PLANES = 14,
/// <summary>
/// Number of device-specific brushes.
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of device-specific pens.
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of device-specific markers.
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of device-specific fonts.
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of entries in the device's color table, if the device has a
color depth of no more than 8 bits per pixel. For devices with greater
color depths, 1 is returned.
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Reserved.
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Value that indicates the curve capabilities of the device, as shown
in the following table.
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Value that indicates the line capabilities of the device, as shown
in the following table:
/// </summary>
LINECAPS = 30,
/// <summary>
/// Value that indicates the polygon capabilities of the device, as
shown in the following table.
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Value that indicates the text capabilities of the device, as shown
in the following table.
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Flag that indicates the clipping capabilities of the device. If the
device can clip to a rectangle, it is 1. Otherwise, it is 0.
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Value that indicates the raster capabilities of the device, as shown
in the following table.
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Relative width of a device pixel used for line drawing.
/// </summary>
ASPECTX = 40,
/// <summary>
/// Relative height of a device pixel used for line drawing.
/// </summary>
ASPECTY = 42,
/// <summary>
/// Diagonal width of the device pixel used for line drawing.
/// </summary>
ASPECTXY = 44,
/// <summary>
/// For printing devices: the width of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-x11-inch
paper has a physical width value of 5100 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// For printing devices: the height of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-by-11-inch
paper has a physical height value of 6600 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// For printing devices: the distance from the left edge of the
physical page to the left edge of the printable area, in device units. For
example, a printer set to print at 600 dpi on 8.5-by-11-inch paper, that
cannot print on the leftmost 0.25-inch of paper, has a horizontal physical
offset of 150 device units.
/// </summary>
PHYSICALOFFSETX = 112, // Physical Printable Area x margin
/// <summary>
/// For printing devices: the distance from the top edge of the physical
page to the top edge of the printable area, in device units. For example,
a printer set to print at 600 dpi on 8.5-by-11-inch paper, that cannot
print on the topmost 0.5-inch of paper, has a vertical physical offset of
300 device units.
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor for the x-axis of the printer.
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor for the y-axis of the printer.
/// </summary>
SCALINGFACTORY = 115

}
}

public class User32
{

const string strUSER32DLL = "user32.dll";

private class Externs
{
[System.Runtime.InteropServices.DllImport(strUSER32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int PrintWindow(int iHwnd, int hdcBlt, uint
iFlags);
}

/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(int hWnd,
enPrintWindowFlags eFlags)
{
int iWidth = 0;
int iHeight = 0;
int hdcSrc = User32.GetWindowDC(hWnd);
try
{
iWidth = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.HORZRES);
iHeight = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.VERTRES);
}
finally
{
User32.ReleaseDC(hWnd,hdcSrc);
}

System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(iWidth,
iHeight);
System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(pImage);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
Externs.PrintWindow(hWnd, (int)hDC, (uint)eFlags);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}

public enum enPrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the entire
window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}

Stoitcho Goutsev (100) said:
Jeremy,

No, it is not possible. If it was it would be a big security flaw. One of
the reason people lock their stations is because they don't want anyone
to be able to see what is on the screen.

Secondly, You can grab only what is visible on the screen. You cannot
capture anything that is not visible e.g. part of a window obscured by
other window. When the workstation is locked there is nothing on the
screen, but the dialog for unlocking the station.

Windows provides a message called WM_PRINT this message is meant to be
used to ask a window to draw itself to an arbitrary graphic device
context. In theory it can be used for capturing invisible parts of a
window, but this message needs to be handled and processed by the target
application. I remember playing with this message when I was doing Win32
native programming and it worked with all standard windows' controls, but
almost never worked with third party controls and windows.
Once I tried to use this in a .NET application but there were some
problem with the Graphics object and I didn't have any success. However I
gave up pretty quick because I did it out of curiosity only, it wasn't
important. It might be possible to make it work with .NET application, I
don't know.


--

Stoitcho Goutsev (100) [C# MVP]

Jeremy Chapman said:
Is it possible to grab a screen shot of an application window even when
the workstation is locked? I can do it when it's not locked but just
get a black image when it is locked.
 
J

Jeremy Chapman

Great feedback, thanks a lot!.


Stoitcho Goutsev (100) said:
Sorry about the previous post. I pressed wrong key combination :)

Jeremy,

It is great that you decide to share with all of us.
I'm surprised though that this works on XP and 2003, but not on win2k let
say after all talk about security. I'd concider this as a security flaw.
It
might be somehow connected to the remote dektop feature available on these
platforms.

It is funny that I new about this PrintWindow method, but I never tried
because the docs say that it is similar to sending WM_PRINT and the latter
didn't work

If you don't mind I'd like to give some suggestions:
1. When you declare import API methods for PInovke use IntPtr for handlers
(such as HWND and HDC) instead of *int*
2. Try to avoid as much as it's possible using non mamaged calls. For
example you use GetDeviceCaps to get the screen size. You can do that
using
Screen class:
screenBounds = Screen.FromHandle(hWnd).Bounds;
screenWidth = screenBounds.Width;
screenHeight = screenBounds.Height;
3. You accept handle to a window to capture, but the image you create is
as big as the whole screen, which makes small windows to appear in the
corner of an almost empty image.
I'd suggest to create the bitmap as big as the window is. If you capture
only client part if an window you can do it from managed code -
Graphics.VisibleClipBounds. For the wole window though you need to use
Pinvoke. For example you can use GetWindowDC and then create
Graphics.FormHdc, etc or you can use APIs such as GetWindowRect.

Here is little bit nodified version of your code. Not that it is better or
something, but it is little bit shorter. I hope you don't mind.

public class User32
{

const string strUSER32DLL = "user32.dll";

[System.Runtime.InteropServices.DllImport(strUSER32DLL)]
public static extern int PrintWindow(IntPtr iHwnd, IntPtr hdcBlt, uint
iFlags);
[System.Runtime.InteropServices.DllImport(strUSER32DLL)]
public static extern IntPtr GetWindowDC(IntPtr hWnd);


/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(IntPtr hWnd,
PrintWindowFlags flags)
{

//Rectangle screenBounds = Rectangle.Empty;
Rectangle visibleBounds = Rectangle.Empty;
System.Drawing.Bitmap image = null;

using(Graphics grfx = Graphics.FromHdc(GetWindowDC(hWnd)))
{

visibleBounds = Rectangle.Round(grfx.VisibleClipBounds);

}

image = new System.Drawing.Bitmap(
visibleBounds.Width,
visibleBounds.Height);

System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(image);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
User32.PrintWindow(hWnd, hDC, (uint)flags);
}
finally
{
graphics.ReleaseHdc(hDC);
}

return image;
}

public enum PrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the entire
/// window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}


--

Stoitcho Goutsev (100) [C# MVP]

Jeremy Chapman said:
Thanks for the info. After a little more searching with your direction,
I think I found a solution (only on xp and 2003) using the PrintWindow
API. Thought i'd share. I tried the WM_Print message but couldn't get it
to work at all with the window I wanted to capture. Doing a BitBlt also
worked, but not when the workstation was locked.

public class gdi32
{
private class Externs
{
[System.Runtime.InteropServices.DllImport(strGDI32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int GetDeviceCaps(int iHdc,int iIndex);
}

/// <summary>
///
/// </summary>
/// <param name="iHdc"></param>
/// <param name="eIndex"></param>
/// <returns></returns>
public static int GetDeviceCaps(int iHdc,enCapsIndex eIndex)
{
return Externs.GetDeviceCaps(iHdc,(int)eIndex);
}

/// <summary>
///
/// </summary>
public enum enCapsIndex
{
/// <summary>
/// The device driver version.
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device technology.
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Width, in millimeters, of the physical screen.
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Height, in millimeters, of the physical screen.
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Width, in pixels, of the screen.
/// </summary>
HORZRES = 8,
/// <summary>
/// Height, in raster lines, of the screen.
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of pixels per logical inch along the screen width. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Number of pixels per logical inch along the screen height. In a
system with multiple display monitors, this value is the same for all
monitors.
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of adjacent color bits for each pixel.
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of color planes.
/// </summary>
PLANES = 14,
/// <summary>
/// Number of device-specific brushes.
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of device-specific pens.
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of device-specific markers.
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of device-specific fonts.
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of entries in the device's color table, if the device has a
color depth of no more than 8 bits per pixel. For devices with greater
color depths, 1 is returned.
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Reserved.
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Value that indicates the curve capabilities of the device, as shown
in the following table.
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Value that indicates the line capabilities of the device, as shown
in the following table:
/// </summary>
LINECAPS = 30,
/// <summary>
/// Value that indicates the polygon capabilities of the device, as
shown in the following table.
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Value that indicates the text capabilities of the device, as shown
in the following table.
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Flag that indicates the clipping capabilities of the device. If the
device can clip to a rectangle, it is 1. Otherwise, it is 0.
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Value that indicates the raster capabilities of the device, as
shown in the following table.
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Relative width of a device pixel used for line drawing.
/// </summary>
ASPECTX = 40,
/// <summary>
/// Relative height of a device pixel used for line drawing.
/// </summary>
ASPECTY = 42,
/// <summary>
/// Diagonal width of the device pixel used for line drawing.
/// </summary>
ASPECTXY = 44,
/// <summary>
/// For printing devices: the width of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-x11-inch
paper has a physical width value of 5100 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// For printing devices: the height of the physical page, in device
units. For example, a printer set to print at 600 dpi on 8.5-by-11-inch
paper has a physical height value of 6600 device units. Note that the
physical page is almost always greater than the printable area of the
page, and never smaller.
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// For printing devices: the distance from the left edge of the
physical page to the left edge of the printable area, in device units.
For example, a printer set to print at 600 dpi on 8.5-by-11-inch paper,
that cannot print on the leftmost 0.25-inch of paper, has a horizontal
physical offset of 150 device units.
/// </summary>
PHYSICALOFFSETX = 112, // Physical Printable Area x margin
/// <summary>
/// For printing devices: the distance from the top edge of the
physical page to the top edge of the printable area, in device units. For
example, a printer set to print at 600 dpi on 8.5-by-11-inch paper, that
cannot print on the topmost 0.5-inch of paper, has a vertical physical
offset of 300 device units.
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor for the x-axis of the printer.
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor for the y-axis of the printer.
/// </summary>
SCALINGFACTORY = 115

}
}

public class User32
{

const string strUSER32DLL = "user32.dll";

private class Externs
{
[System.Runtime.InteropServices.DllImport(strUSER32DLL,
CharSet=CharSet.Auto, SetLastError=true)]
public static extern int PrintWindow(int iHwnd, int hdcBlt, uint
iFlags);
}

/// <summary>
///
/// </summary>
/// <param name="hWnd"></param>
/// <param name="eFlags"></param>
/// <returns></returns>
public static System.Drawing.Bitmap PrintWindow(int hWnd,
enPrintWindowFlags eFlags)
{
int iWidth = 0;
int iHeight = 0;
int hdcSrc = User32.GetWindowDC(hWnd);
try
{
iWidth = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.HORZRES);
iHeight = gdi32.GetDeviceCaps(hdcSrc,gdi32.enCapsIndex.VERTRES);
}
finally
{
User32.ReleaseDC(hWnd,hdcSrc);
}

System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(iWidth,
iHeight);
System.Drawing.Graphics graphics =
System.Drawing.Graphics.FromImage(pImage);

IntPtr hDC = graphics.GetHdc();
//paint control onto graphics using provided options
try
{
Externs.PrintWindow(hWnd, (int)hDC, (uint)eFlags);
}
finally
{
graphics.ReleaseHdc(hDC);
}
return pImage;
}

public enum enPrintWindowFlags : uint
{
/// <summary>
///
/// </summary>
PW_ALL = 0x00000000,
/// <summary>
/// Only the client area of the window is copied. By default, the
entire window is copied.
/// </summary>
PW_CLIENTONLY = 0x00000001
}
}

Stoitcho Goutsev (100) said:
Jeremy,

No, it is not possible. If it was it would be a big security flaw. One
of the reason people lock their stations is because they don't want
anyone to be able to see what is on the screen.

Secondly, You can grab only what is visible on the screen. You cannot
capture anything that is not visible e.g. part of a window obscured by
other window. When the workstation is locked there is nothing on the
screen, but the dialog for unlocking the station.

Windows provides a message called WM_PRINT this message is meant to be
used to ask a window to draw itself to an arbitrary graphic device
context. In theory it can be used for capturing invisible parts of a
window, but this message needs to be handled and processed by the target
application. I remember playing with this message when I was doing Win32
native programming and it worked with all standard windows' controls,
but almost never worked with third party controls and windows.
Once I tried to use this in a .NET application but there were some
problem with the Graphics object and I didn't have any success. However
I gave up pretty quick because I did it out of curiosity only, it wasn't
important. It might be possible to make it work with .NET application, I
don't know.


--

Stoitcho Goutsev (100) [C# MVP]

Is it possible to grab a screen shot of an application window even when
the workstation is locked? I can do it when it's not locked but just
get a black image when it is locked.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top