BlogScreenshots on your device
How to Take Desktop Screenshots in PowerShell
Capture a Windows desktop, display, or screen region in PowerShell with .NET, save it as PNG, and handle scheduled tasks and common failures.

To take a desktop screenshot in Windows PowerShell, create a .NET bitmap, copy screen pixels into it with System.Drawing.Graphics.CopyFromScreen, then save the bitmap as a PNG. The example below captures the primary display. Run it in a logged-on interactive Windows session; a scheduled task running without a logged-on desktop cannot capture that user’s screen using this method.
1. Capture the primary display
Save this as Capture-Screenshot.ps1 and run it from Windows PowerShell:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
try {
$graphics.CopyFromScreen(
$bounds.Location,
[System.Drawing.Point]::Empty,
$bounds.Size
)
$path = Join-Path $env:TEMP 'screenshot.png'
$bitmap.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
Write-Output $path
}
finally {
$graphics.Dispose()
$bitmap.Dispose()
}
The script prints the saved path, typically under your temporary directory. CopyFromScreen copies color data for a screen-pixel rectangle to a graphics drawing surface; here the bitmap is that surface and the primary screen’s bounds specify the rectangle. See Microsoft’s CopyFromScreen API reference.
The finally block disposes both .NET objects whether saving succeeds or an error occurs. The sample uses PNG so the screenshot is lossless. Change the destination path to a directory you can write to, and keep the format argument aligned with the filename extension.
2. Choose an output path and capture repeatedly
For automation, a fixed filename overwrites the previous capture. A timestamp makes each output distinct:
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$path = Join-Path $env:USERPROFILE "Pictures\screenshot-$stamp.png"
Insert those lines where the first example sets $path. Ensure the destination folder exists before saving:
$folder = Join-Path $env:USERPROFILE 'Pictures\Captures'
New-Item -ItemType Directory -Path $folder -Force | Out-Null
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$path = Join-Path $folder "screenshot-$stamp.png"
To run the capture script from a command prompt, use the PowerShell executable and pass the script path:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Capture-Screenshot.ps1
-ExecutionPolicy Bypass applies to that process invocation; follow your organization’s script policy where it differs. The capture itself still needs access to the interactive desktop.
3. Capture a selected screen region
CopyFromScreen takes a source location, destination location, and size. Use the screen coordinates of the region you want. This example captures a rectangle starting at screen coordinate (100, 100), 800 pixels wide and 600 pixels tall:
Add-Type -AssemblyName System.Drawing
$x = 100
$y = 100
$width = 800
$height = 600
$bitmap = New-Object System.Drawing.Bitmap $width, $height
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
try {
$source = New-Object System.Drawing.Point $x, $y
$destination = [System.Drawing.Point]::Empty
$size = New-Object System.Drawing.Size $width, $height
$graphics.CopyFromScreen($source, $destination, $size)
$bitmap.Save(
(Join-Path $env:TEMP 'region.png'),
[System.Drawing.Imaging.ImageFormat]::Png
)
}
finally {
$graphics.Dispose()
$bitmap.Dispose()
}
Coordinates are desktop screen coordinates, not coordinates relative to a window. The API copies a rectangle, so keep the requested area within the display region you intend to capture. A region that crosses displays or lies outside available screen pixels may fail or produce an unexpected result depending on the host configuration.
4. Capture the full virtual desktop
For a multi-monitor setup, the primary screen bounds cover only the primary display. To request a rectangle covering the virtual desktop, use the virtual screen bounds instead:

$bounds = [System.Windows.Forms.SystemInformation]::VirtualScreen
Use this line in place of $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds in the first example. The bitmap will use the virtual rectangle’s width and height, and $bounds.Location supplies its origin. Multi-monitor layouts can include negative coordinates when a secondary display sits to the left or above the primary. Verify the resulting bounds and behavior on the target machine, especially with mixed display scaling; the API documentation does not establish a universal DPI behavior for every setup.
You can also inspect each display’s bounds before choosing a capture target:
[System.Windows.Forms.Screen]::AllScreens | ForEach-Object {
[pscustomobject]@{
DeviceName = $_.DeviceName
Primary = $_.Primary
Bounds = $_.Bounds
}
}
5. Schedule a screenshot
Use Windows Task Scheduler for periodic capture, but configure the task to run only when the user is logged on. The cited Microsoft Q&A answer notes that a noninteractive task, such as one running as SYSTEM without a desktop, has no logged-on desktop screen for this method to capture. This is community guidance rather than a guarantee covering every Windows configuration.
- Save the script to a stable path and make sure its output directory exists and is writable.
- Create a task with a trigger for the desired interval.
- Choose the option to run only when the user is logged on.
- Set the action to start
powershell.exewith arguments such as-NoProfile -File "C:\Scripts\Capture-Screenshot.ps1". - Run the task manually while logged in and inspect the output file before relying on the schedule.
For unattended server-side capture, desktop pixels may not exist in the task’s execution context. Use a browser screenshot service for web pages instead of expecting a background task to see an interactive user’s desktop.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
CopyFromScreen throws a Win32Exception |
The screen rectangle could not be copied in the current desktop or graphics context. | Run in the logged-on desktop session, check the source coordinates and dimensions, and retry with the primary screen bounds. The API documents Win32Exception as a possible failure. |
| The scheduled task creates no useful screenshot | The task is running without an interactive logged-on desktop. | Configure it to run only while the user is logged on. Check Task Scheduler’s last-run result and the output directory. |
| The file is missing | The chosen folder does not exist or the account cannot write there. | Use a known writable folder such as $env:TEMP, or create the target directory before saving. |
| The screenshot contains only one monitor | The script uses PrimaryScreen.Bounds. |
Use SystemInformation.VirtualScreen for a virtual-desktop rectangle, or enumerate AllScreens and capture a specific display. |
| The capture is clipped or shifted | Coordinates or dimensions do not match the screen layout, or display scaling affects expectations. | Print the screen bounds, adjust the source rectangle, and validate on the target display arrangement. Mixed-DPI behavior is not established for every configuration by the cited sources. |
Add-Type cannot load a drawing or forms assembly |
The PowerShell host or runtime does not provide the Windows desktop assemblies expected by this example. | Run on a Windows desktop host and verify compatibility for the installed PowerShell and .NET version. Do not assume the Windows desktop example works on non-Windows PowerShell. |
| A protected video area appears black or absent | Protected content can restrict what desktop capture returns. | Capture non-protected content and check the application’s capture restrictions. Behavior can vary by application and system policy. |
7. Options, reliability, and performance
Capture scope
Pick the smallest rectangle that meets the need. A primary display capture is straightforward; a region reduces bitmap dimensions; the virtual screen covers a multi-display layout if its bounds are suitable. Microsoft documents overloads that accept two points and a size, as well as integer coordinates and a size. Another overload accepts a CopyPixelOperation value to control how source and destination colors are combined; ordinary screenshots generally use the default copy behavior shown above.
Image format and file size
PNG preserves pixels without lossy compression and is a practical default for UI captures. The .NET bitmap can also be saved with another supported image encoder, but choose the matching ImageFormat and extension. JPEG can reduce file size for photographic content but introduces compression artifacts around text and sharp edges. Saving may fail if the destination is locked, invalid, or unwritable.
Memory and repeat captures
A bitmap’s memory use grows with its pixel dimensions and color depth. Large virtual desktops therefore need more memory than a single display. Dispose of each bitmap and graphics object after use, especially in a loop. For a recurring job, catch and log failures around each capture so one missed interval does not silently stop later work; keep logs free of screenshot contents if those images may contain sensitive information.
Reliability limits
This method copies pixels available to the desktop graphics context. It is not a browser renderer and does not navigate to a URL. The research sources do not establish consistent behavior for secure desktops, cursor inclusion, protected content, non-Windows systems, or all PowerShell/.NET combinations. Validate those requirements on the exact target host rather than treating a successful interactive example as a platform-wide guarantee.
8. Or skip the browser setup
PowerShell’s CopyFromScreen captures a desktop. If your actual task is to capture a web page, ScreenshotNeo returns a page screenshot from one GET request. Its API documentation covers the available options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month with no card.
9. Frequently asked questions
Can PowerShell take a screenshot without installing a separate capture tool?
On a compatible Windows desktop host, the example uses built-in .NET drawing and Windows Forms types. Runtime and platform compatibility still depend on the PowerShell host.
Does this capture the mouse pointer?
The basic CopyFromScreen example copies screen pixels and does not explicitly draw a cursor. The research sources do not confirm cursor capture behavior across configurations.
Can I capture a website with this script?
Not by URL alone. This method captures pixels already shown on the desktop; a web page screenshot service can load and render a URL for you.
Will it work when the computer is locked?
The cited sources do not establish behavior for locked or secure desktops. The dependable setup described for scheduled captures is a task running while the user is logged on; verify any locked-session requirement on the target machine.


