前回、スクリーンショットを保存するファイルを指定できるようにした。しかし、そのままだと実際には結構使いにくいところがある。今回は何が問題なのかと、その改善方法について説明する。

前回の成果物を確認

Webページのスクリーンショットを撮るPowerShellスクリプト「getss.ps1」は、前回までの取り組みでほぼ完成した。これで特定のWebページのスクリーンショットを自動的に取って利用する、といったスクリプトを運用することができる。

作ったgetss.ps1は次の通りだ。

#!/usr/bin/env pwsh

#========================================================================
# スクリーンショットを取得する
#========================================================================

#========================================================================
# 引数を処理
#   -URI uri        スクリーンショットを取得するリソースのURI
#   -Width width    スクリーンショットの幅
#   -Height height  スクリーンショットの高さ
#   -OutputFilePath path スクリーンショットを保存するファイル
#   -Agent      Webサーバへ送るUser-Agent文字列を指定
#========================================================================
Param(
    [Parameter(Mandatory=$false)][String]$URI = "desktop:",
    [Int]$Width = 1200,
    [Int]$Height = 800,
    [String]$OutputFilePath = "${env:HOME}/ss.png",
    [String]$Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
)

#========================================================================
# スクリーンショットの取得に利用するアプリケーションほか
#========================================================================
$msedge='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$snippingtool='SnippingTool.exe'
$curl='C:\Windows\System32\curl.exe'

#========================================================================
# どの方法でスクリーンショットを取得するか判断
#========================================================================
switch  -Wildcard ($URI)
{
    'http*'
    {
        #========================================================
        # Microsoft Edgeを使って取得
        #========================================================
        $method='msedge'
        break
    }

    'desktop:*'
    {
        #========================================================
        # Snipping Toolを使って取得
        #========================================================
        $method='snippingtool'
        break
    }

    default
    {
        $method='none'
    }
}

#========================================================================
# スクリーンショットを撮るリソース種類の特定
#========================================================================
$contenttype='none'
switch  -Wildcard ($URI)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    'http*.html'
    {
        $contenttype='text/html'
        break
    }
    'http*.htm'
    {
        $contenttype='text/html'
        break
    }
    'http*.shtml'
    {
        $contenttype='text/html'
        break
    }
    'http*/'
    {
        $contenttype='text/html'
        break
    }
    'http*.txt'
    {
        $contenttype='text/plain'
        break
    }
    'http*.csv'
    {
        $contenttype='text/csv'
        break
    }
    'http*csv=1'
    {
        $contenttype='text/csv'
        break
    }
    'http*.pdf'
    {
        $contenttype='application/pdf'
        break
    }
    'http*.zip'
    {
        $contenttype='application/zip'
        break
    }
    default
    {
        $contenttype=(  & $curl     --location      `
                        -A $Agent       `
                        -Ss -I          `
                        $URI            |
                Select-String   "^Content-Type:"    )
        break
    }
}

#========================================================================
# スクリーンショットが取れないWebリソースの場合、代わりに
# http://example.com/ のスクリーンショットを取っておく
#========================================================================
switch  -Wildcard ($contenttype)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    '*text/html*'
    {
        break
    }
    '*text/plain*'
    {
        break
    }
    '*/*'
    {
        $URI='http://example.com/'
        break
    }
}

#========================================================================
# スクリーンショットを取得
#========================================================================
switch  ($method)
{
    #================================================================
    # Microsoft Edgeを使って取得
    #================================================================
    'msedge'
    {
        $o1='--headless'
        $o2='--screenshot="' + $OutputFilePath + '"'
        $o3="--window-size=$Width,$Height"
        $o4='--user-agent="$Agent"'
        $o5='--hide-scrollbars'

        Start-Process   -FilePath $msedge           `
                -ArgumentList $o1,$o2,$o3,$o4,$o5,$URI  `
                -Wait
        break
    }

    #================================================================
    # Snipping Toolを使って取得
    #================================================================
    'snippingtool'
    {
        Start-Process   -FilePath $snippingtool 
        # ※ -Waitは指定しても機能しない
        break
    }
}

結構スクリプトが長くなってきたが、処理自体はシンプルなことしか行っていない。このように必要な機能だけまとめたスクリプトを見通しの良い状態に保っておくことが、PowerShellスクリプトを利用していく上でポイントの一つだ。複雑にしてしまうと将来の自分を困らせることになる。

実際に使うと……?

getss.ps1を実際に使っていくと、ある問題に遭遇すると思う。次のようにコマンドを実行しても、指定したファイルにスクリーンショットが保存されないのだ。

PS C:\Users\daichi> getss.ps1 -OutputFilePath .\Desktop\ss.png https://news.mynavi.jp/techplus/
PS C:\Users\daichi>

これは、指定されたパスをそのままMicrosoft Edgeに渡していることが原因だ。上記コマンドラインでは、スクリーンショットを保存するファイルのパスを相対パスで指定している。しかし、Microsoft Edgeのヘッドレスモードは相対パスで指定されたパスを扱わない。ファイルパスは絶対パスで指定しないと機能してくれないのだ。

コマンドラインから使う場合、パスを絶対パスで指定するというのは面倒だ。スクリプトから使う場合はパスを明確にした方がよいのだが、自分だけが使うようなスクリプトならカレントディレクトリにそのまま生成して使ってしまいたくもなる。相対パスで指定できないと面倒なのだ。

そこで、今回はスクリーンショットを保存するファイルを相対パスで指定できるように書き換える。

相対パスを絶対パスに変換する

要するに、OutputFilePathパラメータに相対パスが指定された場合、それをスクリプトの中で絶対パスに変換して使えばよいわけだ。

PowerShellで相対パスを絶対パスに変換するには、Convert-Pathコマンドレットを使う。これで今回の問題は解決すると思いきや、実はうまくいかない。実際にConvert-Pathコマンドレットで相対パスを絶対パスに変換しようとすると、次のようにエラーが発生する。

PS C:\Users\daichi> Convert-Path .\Desktop\ss.png
Convert-Path: Cannot find path 'C:\Users\daichi\Desktop\ss.png' because it does not exist.
PS C:\Users\daichi>

これはConvert-Pathコマンドレットに指定した「.\Desktop\ss.png」が、ファイルとして存在していないためだ。Convert-Pathコマンドレットは、実在するファイルに対してしか機能しない。つまり、今回のケースでは使用できないのである。

実在しないファイルパスを絶対パスに変換する方法はいくつかある。指定されたパスを解析して自分で絶対パスに変換するコードを組み上げてもよい。

ただ、今回はスクリプトをシンプルな状態にしておきたいので、ここでは.NETの「Path.GetFullPathメソッド(System.IO)」を使う。この機能を使えば、Convert-Pathコマンドレットを使うくらいの手軽さで似たようなことを実現できる。

実際に実行してみよう。次のように動作する。

PS C:\Users\daichi> [System.IO.Path]::GetFullPath('Desktop\ss.png')
C:\Users\daichi\Desktop\ss.png
PS C:\Users\daichi>

これで基本となる機能はわかった。なお、参考までにPath.GetFullPathメソッド(System.IO)の使い方をもう一つ紹介しておこう。この機能は次のようにベースとなるディレクトリを指定することができる。上記と同じ結果を得る場合には次のように実行する。

PS C:\Users\daichi> [System.IO.Path]::GetFullPath('Desktop\ss.png', 'C:\Users\daichi')
C:\Users\daichi\Desktop\ss.png
PS C:\Users\daichi>

カレントディレクトリをベースに指定するなら、次のようになる。

PS C:\Users\daichi> [System.IO.Path]::GetFullPath('Desktop\ss.png', (Get-Location))
C:\Users\daichi\Desktop\ss.png
PS C:\Users\daichi>

どれを使うかは好み次第だろう。パスをコードの上で明確にしておきたいなら、上記のようにベースディレクトリを指定する方法を使えばよいし、シンプルにしておきたいなら引数が1つの書き方にしておけばよい。

相対パスか絶対パスかを判定する

もう一つ、OutputFilePathパラメータに指定されたパスが相対パスか絶対パスかを判断する方法も使う。実際にはこの判定を使わなくても実装できるのだが、ロジック的には「指定されたパスが相対パスだった場合に、絶対パスに変換する」という記述にしておくと何がしたかったのかが明確になる。

この判定には、Split-PathコマンドレットのIsAbsoluteパラメータを使う。実行すると次のようになる。

PS C:\Users\daichi> Split-Path -IsAbsolute 'Desktop\ss.png'
False
PS C:\Users\daichi> Split-Path -IsAbsolute 'C:\Users\daichi\Desktop\ss.png'
True
PS C:\Users\daichi>

これで、今回の処理に必要な機能は明らかになった。早速、今回必要な機能を実装しよう。

相対パスを絶対パスに変換する処理

相対パスを絶対パスに変換する処理をスクリプトにまとめると次のようになる。

#========================================================================
# スクリーンショットを保存するファイルのパスを絶対パスへ変換
#========================================================================
if      (-not (Split-Path -IsAbsolute $OutputFilePath))
{
        $OutputFilePath = [System.IO.Path]::GetFullPath($OutputFilePath)
        Write-Warning                                                   `
                "スクリーンショット保存先として $OutputFilePath を使用。"
}

これで今回の問題は解決だ。この処理をgetss.ps1に取り込むと次のようになる。

#!/usr/bin/env pwsh

#========================================================================
# スクリーンショットを取得する
#========================================================================

#========================================================================
# 引数を処理
#   -URI uri        スクリーンショットを取得するリソースのURI
#   -Width width    スクリーンショットの幅
#   -Height height  スクリーンショットの高さ
#   -OutputFilePath path スクリーンショットを保存するファイル
#   -Agent      Webサーバへ送るUser-Agent文字列を指定
#========================================================================
Param(
    [Parameter(Mandatory=$false)][String]$URI = "desktop:",
    [Int]$Width = 1200,
    [Int]$Height = 800,
    [String]$OutputFilePath = "${env:HOME}/ss.png",
    [String]$Agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
)

#========================================================================
# スクリーンショットの取得に利用するアプリケーションほか
#========================================================================
$msedge='C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
$snippingtool='SnippingTool.exe'
$curl='C:\Windows\System32\curl.exe'

#========================================================================
# スクリーンショットを保存するファイルのパスを絶対パスへ変換
#========================================================================
if  (-not (Split-Path -IsAbsolute $OutputFilePath))
{
    $OutputFilePath = [System.IO.Path]::GetFullPath($OutputFilePath)
    Write-Warning                           `
        "スクリーンショット保存先として $OutputFilePath を使用。"
}

#========================================================================
# どの方法でスクリーンショットを取得するか判断
#========================================================================
switch  -Wildcard ($URI)
{
    'http*'
    {
        #========================================================
        # Microsoft Edgeを使って取得
        #========================================================
        $method='msedge'
        break
    }

    'desktop:*'
    {
        #========================================================
        # Snipping Toolを使って取得
        #========================================================
        $method='snippingtool'
        break
    }

    default
    {
        $method='none'
    }
}

#========================================================================
# スクリーンショットを撮るリソース種類の特定
#========================================================================
$contenttype='none'
switch  -Wildcard ($URI)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    'http*.html'
    {
        $contenttype='text/html'
        break
    }
    'http*.htm'
    {
        $contenttype='text/html'
        break
    }
    'http*.shtml'
    {
        $contenttype='text/html'
        break
    }
    'http*/'
    {
        $contenttype='text/html'
        break
    }
    'http*.txt'
    {
        $contenttype='text/plain'
        break
    }
    'http*.csv'
    {
        $contenttype='text/csv'
        break
    }
    'http*csv=1'
    {
        $contenttype='text/csv'
        break
    }
    'http*.pdf'
    {
        $contenttype='application/pdf'
        break
    }
    'http*.zip'
    {
        $contenttype='application/zip'
        break
    }
    default
    {
        $contenttype=(  & $curl     --location      `
                        -A $Agent       `
                        -Ss -I          `
                        $URI            |
                Select-String   "^Content-Type:"    )
        break
    }
}

#========================================================================
# スクリーンショットが取れないWebリソースの場合、代わりに
# http://example.com/ のスクリーンショットを取っておく
#========================================================================
switch  -Wildcard ($contenttype)
{
    #================================================================
    # コンテンツの種類を特定
    #================================================================
    '*text/html*'
    {
        break
    }
    '*text/plain*'
    {
        break
    }
    '*/*'
    {
        $URI='http://example.com/'
        break
    }
}

#========================================================================
# スクリーンショットを取得
#========================================================================
switch  ($method)
{
    #================================================================
    # Microsoft Edgeを使って取得
    #================================================================
    'msedge'
    {
        $o1='--headless'
        $o2='--screenshot="' + $OutputFilePath + '"'
        $o3="--window-size=$Width,$Height"
        $o4='--user-agent="$Agent"'
        $o5='--hide-scrollbars'

        Start-Process   -FilePath $msedge           `
                -ArgumentList $o1,$o2,$o3,$o4,$o5,$URI  `
                -Wait
        break
    }

    #================================================================
    # Snipping Toolを使って取得
    #================================================================
    'snippingtool'
    {
        Start-Process   -FilePath $snippingtool 
        # ※ -Waitは指定しても機能しない
        break
    }
}

いちいち絶対パスで指定しなければならないのは思いの外ストレスになる。また、相対パスで指定されてもMicrosoft Edgeのヘッドレスモードは何のエラーも出力しないので、保存されていないことに気づけない可能性もある。このように、パスを絶対パスに変換するようにするだけでも実用度がアップする。

実行して動作を確認する

作成したgetss.ps1を実行して動作を確認してみよう。OutputFilePathパラメータに相対パスを指定すると、次のように動作する。

PS C:\Users\daichi> getss.ps1 -OutputFilePath .\Desktop\ss.png https://news.mynavi.jp/techplus/
WARNING: スクリーンショット保存先として C:\Users\daichi\Desktop\ss.png を使用。
PS C:\Users\daichi>

これで次のようなスクリーンショットが取得できる。

  • 取得されたスクリーンショット

    取得されたスクリーンショット

今回欲しかった機能はこれで完成だ。

これまで何度も取り上げているように、PowerShellスクリプトの利点は簡単に書き換えができる点にある。問題が出ればすぐにそこを改良する。そうして小さな改善をコツコツ繰り返していくことが、PowerShellスクリプトを便利に使っていく上でのポイントだ。

日々の業務はたくさんあるわけで、自分が使うツールの整理に長い時間がかけられるとは限らない。だからこそ、Powershellスクリプトの細かい改善を繰り返す。これにより、本当に自分の業務に役立つスクリプトが出来上がるのだ。