44

How can I capture the screen in Windows PowerShell? I need to be able to save the screen to disk.

Start-Automating
  • 8,067
  • 2
  • 28
  • 47

5 Answers5

57

You can also use .NET to take the screenshot programatically (which gives you finer control):

[Reflection.Assembly]::LoadWithPartialName("System.Drawing")
function screenshot([Drawing.Rectangle]$bounds, $path) {
   $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
   $graphics = [Drawing.Graphics]::FromImage($bmp)

   $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)

   $bmp.Save($path)

   $graphics.Dispose()
   $bmp.Dispose()
}

$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, 1000, 900)
screenshot $bounds "C:\screenshot.png"
Jeremy
  • 1,021
  • 9
  • 8
  • 1
    Great - how hard would it be to get this to capture the window of a specific application ? I am creating an IE instance like this : $ie = new-object -com "InternetExplorer.Application" - would it be possible to capture just the output of this window for instance ? – monojohnny Mar 14 '13 at 12:04
  • @jeremy tried on windows 10, doesn't work: output nothing. – user310291 Apr 15 '18 at 08:27
  • @user310291 the code itself still works, but you need to change the path from "C:\screenshot.png" to some location you can write to, e.g. your desktop. – Dreamer Jul 23 '18 at 08:00
15

For the sake of completion, this script allows you to take screenshots across multiple monitors.

The base code comes from Jeremy.

function screenshot($path)
{
    [void] [Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    [void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    $left = [Int32]::MaxValue
    $top = [Int32]::MaxValue
    $right = [Int32]::MinValue
    $bottom = [Int32]::MinValue

    foreach ($screen in [Windows.Forms.Screen]::AllScreens)
    {
        if ($screen.Bounds.X -lt $left)
        {
            $left = $screen.Bounds.X;
        }
        if ($screen.Bounds.Y -lt $top)
        {
            $top = $screen.Bounds.Y;
        }
        if ($screen.Bounds.X + $screen.Bounds.Width -gt $right)
        {
            $right = $screen.Bounds.X + $screen.Bounds.Width;
        }
        if ($screen.Bounds.Y + $screen.Bounds.Height -gt $bottom)
        {
            $bottom = $screen.Bounds.Y + $screen.Bounds.Height;
        }
    }

    $bounds = [Drawing.Rectangle]::FromLTRB($left, $top, $right, $bottom);
    $bmp = New-Object Drawing.Bitmap $bounds.Width, $bounds.Height;
    $graphics = [Drawing.Graphics]::FromImage($bmp);

    $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size);

    $bmp.Save($path);

    $graphics.Dispose();
    $bmp.Dispose();
}

It can be called with: screenshot "D:\screenshot.png"

halter73
  • 15,059
  • 3
  • 49
  • 60
Skami
  • 1,506
  • 1
  • 18
  • 29
  • This solution will be thrown off by DPI scaling. To avoid that, you'd p/invoke. And even then, getting per-monitor scaling information is tricky. See https://stackoverflow.com/questions/69786234/disable-high-dpi-scaling – halter73 Jun 28 '23 at 21:59
12

This PowerShell function will capture the screen in PowerShell and save it to an automatically numbered file. If the -OfWindow switch is used, then the current window will be captured.

This works by using the built in PRINTSCREEN / CTRL-PRINTSCREEEN tricks, and it uses a bitmap encoder to save the file to disk.

function Get-ScreenCapture
{
    param(
    [Switch]$OfWindow
    )


    begin {
        Add-Type -AssemblyName System.Drawing
        $jpegCodec = [Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
            Where-Object { $_.FormatDescription -eq "JPEG" }
    }
    process {
        Start-Sleep -Milliseconds 250
        if ($OfWindow) {
            [Windows.Forms.Sendkeys]::SendWait("%{PrtSc}")
        } else {
            [Windows.Forms.Sendkeys]::SendWait("{PrtSc}")
        }
        Start-Sleep -Milliseconds 250
        $bitmap = [Windows.Forms.Clipboard]::GetImage()
        $ep = New-Object Drawing.Imaging.EncoderParameters
        $ep.Param[0] = New-Object Drawing.Imaging.EncoderParameter ([System.Drawing.Imaging.Encoder]::Quality, [long]100)
        $screenCapturePathBase = "$pwd\ScreenCapture"
        $c = 0
        while (Test-Path "${screenCapturePathBase}${c}.jpg") {
            $c++
        }
        $bitmap.Save("${screenCapturePathBase}${c}.jpg", $jpegCodec, $ep)
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Start-Automating
  • 8,067
  • 2
  • 28
  • 47
  • Are there additional steps needed to get this to work on Windows 7 ? I'm getting the following error when running the function:"Unable to find type [Windows.Forms.Sendkeys]: make sure that the assembly containing this type is loaded." and then a further error "Unable to find type [Windows.Forms.Clipboard]: make sure that the assembly containing this type is loaded." , and then one more error (but I think because the previous two calls failed). – monojohnny Mar 21 '14 at 17:06
  • 1
    You probably haven't loaded Winforms. I'd recommend you simply download the module this answer is now in: [RoughDraft](http://gallery.technet.microsoft.com/RoughDraft-cfeb6e98) – Start-Automating Mar 24 '14 at 22:29
  • For future readers, adding `Add-Type -assembly System.Windows.Forms` before the function fixes the issue mentioned by @monojohnny – Ashutosh Jindal Jul 06 '23 at 20:17
8

Here is my solution for multi-monitor, which is a bit simpler than the current answer. It also will render screens properly if the user has a strange monitor config (stacked vertical, etc) without black bars.

Add-Type -AssemblyName System.Windows.Forms,System.Drawing

$screens = [Windows.Forms.Screen]::AllScreens

$top    = ($screens.Bounds.Top    | Measure-Object -Minimum).Minimum
$left   = ($screens.Bounds.Left   | Measure-Object -Minimum).Minimum
$width  = ($screens.Bounds.Right  | Measure-Object -Maximum).Maximum
$height = ($screens.Bounds.Bottom | Measure-Object -Maximum).Maximum

$bounds   = [Drawing.Rectangle]::FromLTRB($left, $top, $width, $height)
$bmp      = New-Object System.Drawing.Bitmap ([int]$bounds.width), ([int]$bounds.height)
$graphics = [Drawing.Graphics]::FromImage($bmp)

$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)

$bmp.Save("$env:USERPROFILE\test.png")

$graphics.Dispose()
$bmp.Dispose()
Jacob Colvin
  • 2,625
  • 1
  • 17
  • 36
  • What do you mean by *"the current answer"*? [The accepted answer](https://stackoverflow.com/questions/2969321/how-can-i-do-a-screen-capture-in-windows-powershell/2970339#2970339)? – Peter Mortensen May 21 '22 at 22:55
  • The currently most upvoted answer for multi-monitor: https://stackoverflow.com/a/44609221/4868262 – Jacob Colvin Jun 07 '22 at 13:45
6

Microsoft have a PowerShell script available here:

http://gallery.technet.microsoft.com/scriptcenter/eeff544a-f690-4f6b-a586-11eea6fc5eb8

I have just tried it on a Windows 7 machine and it to work, using the commandline example provided:

Take-ScreenShot -screen -file "C:\image.png" -imagetype png
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
monojohnny
  • 5,894
  • 16
  • 59
  • 83
  • Where do you place this .ps1 file so you don't have to type the entire network path in ? – Aaron Aug 27 '14 at 13:40
  • @Aaron, not quite sure how best to do this - but probably set it in Powershell profile (perhaps either by editing PATH variable, or using an alias) : this article may help :http://stackoverflow.com/questions/714877/setting-windows-powershell-path-variable – monojohnny Sep 02 '14 at 15:07
  • 2
    Open with... produces no outcome. Input in console then invoking it produces no input either. This is Windows 10 and this thread has been here for years without producing input. – Danilo J. Bonsignore Feb 23 '17 at 22:49
  • 1
    The link is (effectively) broken. It redirects to the ***unspecific*** URL `https://learn.microsoft.com/en-us/samples/browse/?redirectedfrom=TechNet-Gallery`. – Peter Mortensen May 13 '22 at 23:27