2021-08-08 , , ,

Windowsのコマンドラインからスクリーンショットを撮る(PowerShell)

ちょっと欲しくなったので調べてみました。

How can I do a screen capture in Windows PowerShell? - Stack Overflow の Jacob Colvin さんの回答が一番簡潔だったのでそれを元にいくつか用意してみました。

まずはマルチモニターを含めた全領域。

# screenshot-all.ps1
# From https://stackoverflow.com/questions/2969321/how-can-i-do-a-screen-capture-in-windows-powershell
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($Args[0])

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

次にプライマリースクリーンのみ。

# screenshot-primary.ps1
# From https://stackoverflow.com/questions/2969321/how-can-i-do-a-screen-capture-in-windows-powershell
Add-Type -AssemblyName System.Windows.Forms,System.Drawing

$screen = [Windows.Forms.Screen]::PrimaryScreen

$top    = $screen.Bounds.Top
$left   = $screen.Bounds.Left
$right  = $screen.Bounds.Right
$bottom = $screen.Bounds.Bottom

$bounds   = [Drawing.Rectangle]::FromLTRB($left, $top, $right, $bottom)
$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($Args[0])

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

最後にアクティブウィンドウのみ。

# screenshot-activewin.ps1
# Get Foreground Window's Rect
Add-Type -Type @'
using System;
using System.Runtime.InteropServices;
namespace Win32Util {
    public struct RECT {
        public int Left, Top, Right, Bottom;
    }
    public class Utils {
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();
        // [DllImport("user32.dll")]
        // public static extern int GetWindowRect(IntPtr hWnd, out RECT lpRect);
        [DllImport("dwmapi.dll")]
        public static extern int DwmGetWindowAttribute(IntPtr hWnd, uint dwAttribute, out RECT lpRect, int cbAttribute);

        public static uint DWMWA_EXTENDED_FRAME_BOUNDS = 0x09;

        public static RECT GetForegroundWindowRect(){
            IntPtr hwnd = GetForegroundWindow();
            // RECT rect = new RECT();
            // GetWindowRect(hwnd, out rect);
            RECT rect = new RECT();
            DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, out rect, Marshal.SizeOf(typeof(RECT)));

            return rect;
        }
    }
}
'@
$rect = [Win32Util.Utils]::GetForegroundWindowRect()

# https://stackoverflow.com/questions/2969321/how-can-i-do-a-screen-capture-in-windows-powershell
Add-Type -AssemblyName System.Windows.Forms,System.Drawing

$top    = $rect.Top
$left   = $rect.Left
$right  = $rect.Right
$bottom = $rect.Bottom

$bounds   = [Drawing.Rectangle]::FromLTRB($left, $top, $right, $bottom)
$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($Args[0])

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

実行は powershell screenshot-activewin.ps1 test.png のようにします。

それにしても久しぶりのWin32がまさかこんな形だとは。Windows10でGetWindowRectだと左右下に7ピクセルずつ余分に広い矩形を返してしまうだとか。影とかの効果の分でしょうか? なので今はDwmGetWindowAttributeを使うんだそうです。

あ、ちなみにEmacsのorg-downloadからを使うには次のように設定すればいいんです。使用頻度が少なそうなのでHydraが無いと覚えられる気がしませんね。まぁ、別に全部クリップボードからでもいいじゃんって思ったりもしますが。

(when (eq system-type 'windows-nt)
  (defun my-org-download-screenshot (script-name)
    (let ((org-download-screenshot-method
           (format
            "powershell %s %%s"
            (expand-file-name
             (format "<path-to-script-dir>/%s" script-name)))))
      (org-download-screenshot)))
  (defun my-org-download-screenshot-all ()
    (interactive)
    (my-org-download-screenshot "screenshot-all.ps1"))
  (defun my-org-download-screenshot-primary ()
    (interactive)
    (my-org-download-screenshot "screenshot-primary.ps1"))
  (defun my-org-download-screenshot-active-window ()
    (interactive)
    (my-org-download-screenshot "screenshot-activewin.ps1"))
  ;; Key bind help
  (when (featurep 'hydra)
    (define-key org-mode-map (kbd "C-c d")
      (defhydra my-org-download-hydra
        (:color red :exit t :hint nil)
        "
org-download copy from:
_c_: Clipboard
_y_: Full-path or URL on kill-ring
_a_: All monitors
_p_: Primary monitor
_f_: Foreground window
or drop from a local image file.
"
        ("c" org-download-clipboard)
        ("y" org-download-yank)
        ("a" my-org-download-screenshot-all)
        ("p" my-org-download-screenshot-primary)
        ("f" my-org-download-screenshot-active-window)))))

Pingback / Trackback

  • […] FileMakerのスクリプト内でPowerShellを動かしたくなったのですが、概ねうまく行ったのでノウハウを忘れないうちにブログにしておきます。もちろん、Windows版です。PowerShellで動かしたいのは画面ショットです。こちらのサイト「Windowsのコマンドラインからスクリーンショットを撮る(PowerShell)」にそのまま使えるスクリプトがあったので使わせてもらいました。ありがとうございます。また、FileMakerのスクリプトからシェルスクリプトを稼働する方法は、「Claris Community | Powershell script not working」に記載があります。 […]