ScreenshotNeo

BlogHow-to

How to Take a Screenshot of a Website in PowerShell

Capture a website manually with Edge or automate full-page screenshots from PowerShell using Playwright, with waits, troubleshooting, and API options.

By the ScreenshotNeo team1 October 20267 min read

For a one-off screenshot, use Microsoft Edge Web Capture. Open the page in Edge, press Ctrl+Shift+S, choose Capture full page or Capture area, then save or copy the image. For repeatable jobs, call a real browser engine from PowerShell. Playwright is a practical default because it can run headless, wait for the page to render, and save viewport, clipped, or full-page images.

Choose the right PowerShell method

Method Best for Tradeoffs
Edge Web Capture Human, one-off captures Manual and unsuitable for unattended jobs
Playwright Scheduled scripts, CI, full-page captures, multiple browsers Requires Node.js, Playwright, and browser binaries or a compatible installed browser
WebDriver/Selenium Teams that already use WebDriver tests and user-event simulation Requires a matching Edge WebDriver and a WebDriver framework
Puppeteer/CDP Chromium-focused automation already using that ecosystem Separate JavaScript dependency

Rendered screenshots need a browser engine. A PowerShell command by itself cannot execute a page’s JavaScript, apply layout, or load content below the fold.

One-off capture with Microsoft Edge

  1. Open the target page in Microsoft Edge.
  2. Press Ctrl+Shift+S, or open the Edge menu and choose Web capture.
  3. Select Capture full page for the entire scrollable document, or Capture area for a region.
  4. Use the save or copy action in the Web Capture toolbar.

This is the quickest route when a person is present. It does not provide a repeatable URL-to-file command for a scheduled PowerShell task.

Automate screenshots from PowerShell with Playwright

1. Install Node.js and Playwright

Install a current Node.js release, then create a working directory and install Playwright:

mkdir website-capture
cd website-capture
npm init -y
npm install playwright
npx playwright install

Playwright launches headless browsers by default. If your organization manages Microsoft Edge, you can use the installed browser with the msedge channel.

2. Create the browser script

Save this as capture.js. It accepts a URL and an optional output path, waits for network idle, and writes a full-page PNG.

const { chromium } = require('playwright');

const url = process.argv[2];
const output = process.argv[3] || 'website.png';

if (!url) {
  console.error('Usage: node capture.js <url> [output.png]');
  process.exit(1);
}

(async () => {
  const browser = await chromium.launch({ headless: true });
  try {
    const page = await browser.newPage({
      viewport: { width: 1440, height: 900 },
      deviceScaleFactor: 1
    });

    await page.goto(url, { waitUntil: 'networkidle' });
    await page.screenshot({ path: output, fullPage: true });
    console.log(`Saved ${output}`);
  } finally {
    await browser.close();
  }
})();

3. Call it from PowerShell

node .\capture.js 'https://example.com' '.\example.png'

Use an absolute output path when a scheduled task runs with a different working directory:

$output = Join-Path $PWD 'captures\example.png'
New-Item -ItemType Directory -Force (Split-Path $output) | Out-Null
node .\capture.js 'https://example.com' $output
if ($LASTEXITCODE -ne 0) { throw "Capture failed with exit code $LASTEXITCODE" }

Make the PowerShell workflow reusable

This function validates the URL, creates the destination directory, and returns a failing exit status to CI or Task Scheduler.

function Save-WebsiteScreenshot {
    param(
        [Parameter(Mandatory)] [uri] $Url,
        [string] $OutputPath = (Join-Path $PWD 'website.png')
    )

    $directory = Split-Path -Parent $OutputPath
    if ($directory) {
        New-Item -ItemType Directory -Force $directory | Out-Null
    }

    node (Join-Path $PSScriptRoot 'capture.js') $Url.AbsoluteUri $OutputPath
    if ($LASTEXITCODE -ne 0) {
        throw "Playwright capture failed for $($Url.AbsoluteUri)"
    }

    Get-Item $OutputPath
}

Save-WebsiteScreenshot -Url 'https://example.com' -OutputPath '.\captures\example.png'

Viewport, full-page, and clipped screenshots

Choose the capture scope before writing the job:

  • Viewport: captures what a user sees without scrolling.
  • Full page: captures the complete scrollable document, including content below the fold.
  • Clip: captures a known rectangle, useful for a chart or component.

Playwright’s screenshot API supports fullPage, clip, and scale. Add a selector-based clip when the target element has a stable bounding box:

const locator = page.locator('#pricing');
await locator.screenshot({ path: 'pricing.png' });

For CSS-pixel output, use scale: 'css'; use scale: 'device' when you need device pixels. A larger viewport can change responsive layout, so set it explicitly for every automated capture.

Wait for JavaScript, lazy content, and animations

networkidle is useful for pages that finish loading, but it can wait indefinitely on pages with analytics, polling, or WebSockets. Use a targeted condition when possible:

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.locator('[data-ready="true"]').waitFor({ state: 'visible' });
await page.waitForTimeout(500);
await page.screenshot({ path: output, fullPage: true });

For lazy-loaded images, scroll before capture so intersection observers run:

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.evaluate(async () => {
  await new Promise(resolve => {
    let y = 0;
    const step = () => {
      window.scrollBy(0, 800);
      y += 800;
      if (y >= document.body.scrollHeight) return resolve();
      setTimeout(step, 50);
    };
    step();
  });
});
await page.waitForTimeout(300);
await page.screenshot({ path: output, fullPage: true });

Disable animations when visual consistency matters:

await page.addStyleTag({ content: `*, *::before, *::after {
  animation: none !important;
  transition: none !important;
  caret-color: transparent !important;
}` });

Authentication, cookies, and custom browser settings

Private pages require a browser context with the correct state. You can load a saved Playwright storage state, set cookies, or add headers before navigation. Keep credentials out of scripts and source control.

const context = await browser.newContext({
  viewport: { width: 1440, height: 900 },
  storageState: 'auth-state.json',
  locale: 'en-US',
  timezoneId: 'UTC'
});
const page = await context.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });

For sites that vary by user agent, timezone, geolocation, or viewport, define those values explicitly so two runs produce comparable images.

PowerShell scheduling and reliability checklist

  • Use an absolute path to node.exe and the script in Task Scheduler.
  • Run with a dedicated working directory and write logs beside the output.
  • Set a timeout for navigation and fail the job instead of saving a partial image.
  • Retry transient DNS, connection, and 5xx failures with a small capped delay.
  • Close the browser in a finally block so failed runs do not leave processes behind.
  • Pin your Playwright version and install browser binaries on the worker image.
  • Check enterprise policies if headless Edge behaves differently from an interactive session.

Troubleshooting common errors

Error or symptom Cause Fix
playwright cannot find a browser Browser binaries were not installed Run npx playwright install, or configure a compatible installed browser channel.
Navigation timeout The page has slow resources, long polling, or a blocked request Use domcontentloaded plus a specific readiness selector; set a deliberate timeout and retry transient failures.
Screenshot is only above the fold fullPage: true was omitted Set fullPage: true, or capture a locator/clip intentionally.
Blank or incomplete lazy images Images load only after scrolling into view Scroll the page, wait for image completion, then capture.
Cookie banner covers content The page requires consent before revealing the layout Automate the consent button, preload the correct cookie state, or use a service that handles consent before capture.
Different output on each run Animations, ads, time-dependent data, or responsive breakpoints Freeze animations, set viewport/timezone, block or mock unstable resources, and wait for a deterministic selector.
Edge WebDriver session fails Driver and browser versions do not match Install a compatible Edge WebDriver and verify enterprise policies before unattended deployment.
PowerShell reports a nonzero exit code Node or the script failed, but the wrapper ignored it Check $LASTEXITCODE and throw so the scheduler or CI system records the failure.

Performance, reliability, and cost considerations

Launching a browser for every URL is simple but expensive in time and memory. For batches, keep one browser process alive and create a fresh context per site. Reuse contexts only when their cookies and local storage are intentionally shared. Limit concurrency to what the worker can handle; too many pages increase memory pressure and make rendering less deterministic.

Full-page images use more memory than viewport captures, especially on long documents or high device scale factors. Use PNG for lossless diagrams, JPEG for photographic pages, and a deliberate viewport and scale for predictable file sizes. Cache results when the source is unchanged, and record the URL, timestamp, viewport, browser version, and wait condition with each artifact.

Self-hosted Playwright costs your compute, browser maintenance, storage, and engineering time. A screenshot API can move those operational tasks to a single request.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. The request below returns an image; see the ScreenshotNeo API documentation for 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
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)
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 and consent banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.

FAQ

Can PowerShell take a screenshot without installing Node.js?

Edge Web Capture works manually. Automated browser captures need a browser automation stack such as WebDriver/Selenium or another installed engine; the Playwright workflow shown here uses Node.js.

How do I capture content below the fold?

Use Playwright’s fullPage: true, and scroll first when the page uses lazy loading.

Should I use Edge Web Capture or Playwright?

Use Web Capture for a quick human action. Use Playwright when a PowerShell script must run repeatedly, unattended, or in CI.

Why does a page still look different from a normal browser visit?

Responsive breakpoints, consent state, authentication, animations, ads, and timing can all change the render. Set those inputs explicitly and wait for a page-specific ready signal.