私PowerShellだけどスクリーンショットを撮影したい


# 3行で要約

- PowerShellスクリプト(ps1ファイル)でスクリーンショットを取得する方法の紹介と解説

- PowerShellからDirectX使う方式でのスクリーンショット取得は難しい

- ライブラリを使うと楽に実現可能


:::note info
マウスカーソルも保存したい場合は続編のこちらの記事を参照ください
[私PowerShellだけどマウスカーソル付きのスクリーンショットを撮影したい](https://qiita.com/magiclib/items/c867525aa83db5a00ebb)
:::

## はじめに

仕事でスクリーンショットや画面の動画を撮ることが結構あります。


- スクリーンショット：Windowsキー＋PrintScreenキーでファイル保存
- 動画：Windowsキー＋Gキーで、Game Barを表示して動画保存

というWindowsの標準機能を私は使っているのですが、

- 撮影ファイル名のカスタムをしたい
- 数秒間の撮影が目的であるのにGame Barは高機能すぎる

という課題があったのでPowerShellで実現する方法を調査しました。


## 方式の検討

Windowsでスクリーンショットを取得する方法にはいくつか方法があります。
私が知っているのは以下の３つ

 1. PrintScreenキーを押下してクリップボードから画像を取り出す
      伝統的手法ですが、さすがに現代では使いたくない
      
 1. GDIのPrintWindow API
     [PrintWindow](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-printwindow) APIはウィンドウ単位でキャプチャできるので便利な場合もあるのですがPowerShellからGDIの画像保存とかしたくない

3. DirectXのWindows.Graphics.Capture
    [Windows.Graphics.Capture](https://learn.microsoft.com/ja-jp/uwp/api/windows.graphics.capture?view=winrt-22621) APIは高速にスクリーンショット取得できるのですが他の候補と比べるとインターフェースが複雑


他の方式があるか検索したところ[Desktop Duplication API](https://learn.microsoft.com/ja-jp/windows/win32/direct3ddxgi/desktop-dup-api)というものもあるらしい。Windows.Graphics.Captureより機能が少ないようですので今回の対象外。


今回はWindows.Graphics.Capture使って保存する方式で調べてみました。


## 問題点：PowerShellからのDirectX呼び出しが困難

MS公式の[解説](https://learn.microsoft.com/ja-jp/windows/uwp/audio-video-camera/screen-capture)を見ながらPowerShell＋C#で実装してみたのですが、スクリーンショットのID3D11Texture2Dがうまく取得できない。
取得するインターフェースか構造体の定義が間違っているのだと思うがPowerShellからデバッグする方法がわからない。


## ライブラリを探してみよう

DirectX11を直接実行するのが困難なのでライブラリを探してみました。
[ScreenCapturer](https://github.com/0x2E757/ScreenCapturer)というライブラリがDirectXを使用して画像が取得できるので使いやすそう。

ScreenCapturerは[SharpDX](https://github.com/sharpdx/SharpDX)に依存してるようですので、必要なDLLの入手はnugetに頼りましょう。


### ScreenCapturerの依存関係

面倒なのでnugetで依存関係DLLをまとめて入手します。

:::note info
nugetコマンドがインストールされていない場合は、[こちら](https://www.nuget.org/downloads)からnuget.exeを入手してください。実行ファイルだけダウンロードできるのでインストーラーとかは不要です。
:::

以下のコマンドを実行してダウンロードとコピーしてください。

```batchfile

>nuget install ScreenCapturer

(略)
Executing nuget actions took 578 ms

>dir /B
nuget.exe
ScreenCapturer.3.0.1
SharpDX.4.2.0
SharpDX.Direct3D11.4.2.0
SharpDX.DXGI.4.2.0
System.Drawing.Common.7.0.0


>copy /B ".\ScreenCapturer.3.0.1\lib\netstandard2.0\ScreenCapturer.dll" .\
        1 個のファイルをコピーしました。

>copy /B ".\SharpDX.4.2.0\lib\netstandard1.1\SharpDX.dll" .\
        1 個のファイルをコピーしました。

>copy /B ".\SharpDX.Direct3D11.4.2.0\lib\netstandard1.1\SharpDX.Direct3D11.dll"  .\
        1 個のファイルをコピーしました。

>copy /B ".\SharpDX.DXGI.4.2.0\lib\netstandard1.1\SharpDX.DXGI.dll" .\
        1 個のファイルをコピーしました。

>copy /B "./System.Drawing.Common.7.0.0\lib\net462\System.Drawing.Common.dll" .\
        1 個のファイルをコピーしました。


```

### ソースコード

DLLと同じフォルダに以下のps1ファイルを配置して実行することでスクリーンショットが撮影できます。

```powershell:screen_shot.ps1

$Refs = @(
"netstandard",
"System.Drawing",
".\System.Drawing.Common.dll",
".\ScreenCapturer.dll"
)

$Source = @"
using System;
using System.Drawing;
using ScreenCapturerNS;

public static class ScreenShot
{
    static Int32 count = 0;
    static Int32 displayIndex = 0;
    static Int32 adapterIndex = 0;

    public static void Start()
    {
        Console.WriteLine("Start");        
        ScreenCapturer.StartCapture(((System.Drawing.Bitmap bmp) => { 
                ScreenShot.count++; 
                bmp.Save("screenshot" + ScreenShot.count + ".png");            
            }),
            ScreenShot.displayIndex ,
            ScreenShot.adapterIndex );
    }

    public static void Stop()
    {
        Console.WriteLine("Stop");
        ScreenCapturer.StopCapture();
    }
}
"@

Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Refs -Debug:$false 
$null = [System.Reflection.Assembly]::LoadFrom("./ScreenCapturer.dll")

# 開始
[ScreenShot]::Start()

# 1 秒スリープ
Start-Sleep -Seconds 1

# 停止
[ScreenShot]::Stop()

```

PowerShellというよりはC#で実現してる感じです。
Bitmapが取得できるのでクロップなどの編集もできます。
インターバルや撮影回数を指定したい場合もC#で書いてください。


## おわりに

もう少し部品としての使いやすさを詰めたいけど、撮影できたので今回はこれで終わりにします。


## 参考情報

https://qiita.com/i_saint/items/ad5b0545873d0cff4604


# 私PowerShellだけど…シリーズ

[私PowerShellだけど、君のタスクトレイで暮らしたい](https://qiita.com/magiclib/items/cc2de9169c781642e52d)
[私PowerShellだけど「送る」からファイルを受け取りたい（コンテキストメニュー登録もあるよ）](https://qiita.com/magiclib/items/7529557f756af9c8d21b)
[私powershellだけどタスクトレイの片隅でアイを叫ぶ](https://qiita.com/magiclib/items/9d0cd35814b6fcff2e44)
[私PowerShellだけど子を持つ親になるのはいろいろ大変そう](https://qiita.com/magiclib/items/ef0ab5c1fd8e0260b39d)
[私PowerShellだけどあなたにトーストを届けたい（プログレスバー付）](https://qiita.com/magiclib/items/12e2a9e1e1e823a7fa5c)
[私Powershellだけど日付とサイズでログを切り替えたい(log4net)](https://qiita.com/magiclib/items/4fe5bab3e879dd8de951)
[私PowerShellだけどスクリプトだけでサービス登録したい](https://qiita.com/magiclib/items/b4e2e919f02af1b04b58)
[私PowerShellだけどマウスカーソル付きのスクリーンショットを撮影したい](https://qiita.com/magiclib/items/c867525aa83db5a00ebb)
[私PowerShellだけどスリープ抑制したい](https://qiita.com/magiclib/items/352e46f86d3d0e2e9c7b#%E7%A7%81powershell%E3%81%A0%E3%81%91%E3%81%A9%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E6%8A%91%E5%88%B6%E3%81%97%E3%81%9F%E3%81%84)

[サンプルソース置き場](https://github.com/part-time-penguin/qiita_powershell_samples)