Skip to main content

How to Take a Screenshot from a Batch Script

Capturing a screenshot via Batch script is a useful way to document the state of a system during an automated test or troubleshoot visual issues on remote machines. If a script encounters a failure, a screenshot shows exactly what was on the screen, a specific error dialog, a hung application, or an unexpected state, that text logs cannot capture. While Batch cannot natively interact with the display, it can leverage PowerShell and the .NET System.Drawing library to capture the screen and save it as an image file.

This guide will explain how to capture screenshots from a Batch script.

Method 1: Basic Screen Capture

This method uses the built-in .NET framework through PowerShell to capture the primary monitor and save it as a PNG file. No third-party tools are required.

Implementation

@echo off
setlocal

set "OutDir=%~dp0screenshots"
set "OutFile=%OutDir%\screenshot.png"

:: Ensure the output directory exists
if not exist "%OutDir%\" mkdir "%OutDir%"

echo [INFO] Capturing screen to: %OutFile%

powershell -NoProfile -Command ^
"try {" ^
" Add-Type -AssemblyName System.Windows.Forms, System.Drawing;" ^
" $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;" ^
" $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height);" ^
" $graphics = [System.Drawing.Graphics]::FromImage($bitmap);" ^
" $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size);" ^
" $bitmap.Save('%OutFile%', [System.Drawing.Imaging.ImageFormat]::Png);" ^
" $graphics.Dispose();" ^
" $bitmap.Dispose();" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"

if errorlevel 1 (
echo [ERROR] Failed to capture screenshot. >&2
echo Possible causes: locked session, no interactive desktop, or display access denied. >&2
endlocal
exit /b 1
)

echo [OK] Screenshot saved successfully.

endlocal
exit /b 0

How this works:

  1. System.Windows.Forms provides Screen information (resolution, bounds).
  2. System.Drawing provides Bitmap and Graphics objects for image manipulation.
  3. CopyFromScreen captures the pixel data from the display buffer into the bitmap.
  4. Save writes the bitmap to disk in PNG format.
  5. Dispose releases the graphics resources to prevent memory leaks.

Important limitations:

  • Interactive desktop required: The script must run in a user session with a visible desktop. If the computer is locked, at the login screen, or running as a SYSTEM service, CopyFromScreen captures a black image or throws an error.
  • Primary monitor only: This captures only the primary display. See Method 3 for multi-monitor capture.
  • DPI scaling: If Windows display scaling is set above 100% (e.g., 125%, 150%), the captured image may be cropped or may not match the visual resolution. See the Common Errors section for a workaround.

Method 2: Timestamped Screenshots for Monitoring

For periodic monitoring (e.g., capturing the screen every few minutes via a scheduled task), each screenshot needs a unique filename to avoid overwriting previous captures.

@echo off
setlocal

set "OutDir=%~dp0screenshots"

:: Ensure the output directory exists
if not exist "%OutDir%\" mkdir "%OutDir%"

:: Generate a timestamp using PowerShell for locale-independent formatting
for /f "delims=" %%t in (
'powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd_HH-mm-ss'"'
) do set "Timestamp=%%t"

set "OutFile=%OutDir%\capture_%Timestamp%.png"

echo [INFO] Capturing screen to: %OutFile%

powershell -NoProfile -Command ^
"try {" ^
" Add-Type -AssemblyName System.Windows.Forms, System.Drawing;" ^
" $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;" ^
" $bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height);" ^
" $graphics = [System.Drawing.Graphics]::FromImage($bitmap);" ^
" $graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size);" ^
" $bitmap.Save('%OutFile%', [System.Drawing.Imaging.ImageFormat]::Png);" ^
" $graphics.Dispose();" ^
" $bitmap.Dispose();" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"

if errorlevel 1 (
echo [ERROR] Screenshot capture failed. >&2
endlocal
exit /b 1
)

echo [OK] Screenshot saved: %OutFile%

:: Optional: clean up screenshots older than 7 days
forfiles /P "%OutDir%" /M "capture_*.png" /D -7 /C "cmd /c del @path" 2>nul

endlocal
exit /b 0

Why PowerShell for the timestamp:

The %date% and %time% variables use locale-dependent formats. On a US system, %date% might produce Mon 05/10/2024, while a German system produces 10.05.2024. Slashes and dots in filenames are problematic or illegal. PowerShell's Get-Date -Format produces a consistent, filename-safe format (2024-05-10_14-32-05) regardless of locale.

Automatic cleanup:

The forfiles command at the end deletes screenshots older than 7 days. Without cleanup, periodic captures (e.g., every 5 minutes) would generate over 2,000 files per week, consuming significant disk space. The 2>nul suppresses errors when no files match the age criteria.

Method 3: Multi-Monitor Capture

If your environment uses multiple monitors, Method 1 captures only the primary display. This method captures all monitors as a single combined image.

@echo off
setlocal

set "OutDir=%~dp0screenshots"
if not exist "%OutDir%" mkdir "%OutDir%"

echo [INFO] Capturing all monitors...

powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"Add-Type -AssemblyName System.Windows.Forms, System.Drawing;" ^
"$allScreens = [System.Windows.Forms.Screen]::AllScreens;" ^
"$i = 0;" ^
"foreach ($screen in $allScreens) {" ^
" $bitmap = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height);" ^
" $graphics = [System.Drawing.Graphics]::FromImage($bitmap);" ^
" $graphics.CopyFromScreen($screen.Bounds.X, $screen.Bounds.Y, 0, 0, $screen.Bounds.Size);" ^
" $bitmap.Save(\"%OutDir%\monitor_$i.png\", [System.Drawing.Imaging.ImageFormat]::Png);" ^
" $graphics.Dispose();" ^
" $bitmap.Dispose();" ^
" $i++;" ^
"}" ^
"Write-Host \"Captured $i monitor(s)\";"

if errorlevel 1 (
echo [ERROR] Capture failed. >&2
exit /b 1
)

echo [OK] Screenshots saved to: %OutDir%
exit /b 0

How this works:

Instead of using PrimaryScreen.Bounds, the script calculates the bounding rectangle that encompasses all monitors by finding the minimum X/Y coordinates and the maximum extent across all screens. CopyFromScreen then captures this entire region as a single image. Monitors arranged side-by-side, stacked, or in irregular configurations are all captured correctly.

How to Avoid Common Errors

Problem: Black or Empty Screenshots

If the script captures a completely black image, the most likely causes are:

  • Locked workstation: When the screen is locked, the desktop is not rendered and CopyFromScreen captures nothing.
  • SYSTEM context: Task Scheduler tasks running as SYSTEM or with "Run whether user is logged on or not" have no interactive desktop.
  • Remote Desktop disconnected: When an RDP session is disconnected (but not logged off), the desktop may not be rendered.

Solution: Only run screenshot scripts in an interactive user session. Check for an interactive session before attempting capture:

if not defined SESSIONNAME (
echo [WARNING] No interactive session - skipping screenshot. >&2
exit /b 0
)

Problem: DPI Scaling Causes Cropped or Blurry Images

On displays with scaling above 100%, the .NET Screen.Bounds property may return logical (scaled) dimensions instead of physical pixel dimensions. This results in a screenshot that captures only a portion of the actual display.

Solution: Add DPI awareness to the PowerShell process before capturing:

powershell -NoProfile -Command ^
"Add-Type -TypeDefinition 'using System.Runtime.InteropServices; public class DPI { [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware(); }';" ^
"[DPI]::SetProcessDPIAware();" ^
"Add-Type -AssemblyName System.Windows.Forms, System.Drawing;" ^
"# ... (rest of capture code)"

Calling SetProcessDPIAware() before reading screen bounds ensures the API returns physical pixel dimensions.

Problem: Output File Path Contains Spaces or Special Characters

If %OutFile% contains spaces (e.g., C:\My Screenshots\file.png) and is passed unquoted into the PowerShell string, the path will break.

Solution: The examples in this guide already use %OutFile% within the PowerShell string literal (inside the single-quoted command), which handles spaces correctly. If you modify the command structure, ensure the path is always enclosed in quotes.

Taking screenshots of a user's desktop without their knowledge raises serious privacy and legal concerns, especially in corporate environments.

Correct Way: Ensure you have proper authorization (corporate policy, user agreement, or legal clearance) before deploying automated screenshot capture. Document the purpose, frequency, and retention period. Consider notifying the user when a capture occurs.

Best Practices and Rules

1. Use PNG for Diagnostic Screenshots

PNG uses lossless compression, preserving text clarity in error dialogs and log windows. JPEG introduces compression artifacts that can make small text unreadable. Use JPEG only if file size is a priority and text legibility is not critical.

2. Implement Automatic Cleanup

If screenshots are captured periodically, implement age-based deletion (as shown in Method 2) to prevent filling the drive. A 1920×1080 PNG screenshot is typically 1–3 MB; at one capture per minute, that is 1.5–4.5 GB per day.

3. Save to the Script's Directory, Not the Desktop

Using %USERPROFILE%\Desktop clutters the user's workspace. Save to a dedicated subdirectory relative to the script (%~dp0screenshots\) or to %TEMP% for transient captures.

4. Combine with Text Logging

A screenshot shows what was on screen, but not what the script was doing at that moment. Always pair screenshot capture with a text log entry noting what step the script was on and why the capture was triggered.

5. Consider File Permissions

If the script runs under a different user account than the logged-in user, it may not have write access to the target directory. Verify the output directory is writable, and handle the mkdir or Save() failure gracefully.

Conclusions

Capturing screenshots via Batch script provides a visual diagnostic capability that text logs cannot match. By bridging Batch with the .NET Drawing library through PowerShell, with proper error handling, DPI awareness, and multi-monitor support, you gain a visual record of your system's state at any point during automated execution. This capability is valuable for debugging visual failures, documenting system states during audits, and providing evidence for incident response.