4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ステップ記録ツール(psr.exe)をコマンドラインから使いやすくしてみる(PowerShell・Register-ArgumentCompleter)

Last updated at Posted at 2019-12-30

概要

PowerShell で ステップ記録ツール(psr.exe) のコマンドラインオプションの補完ができるようにしてみた。

ステップ記録ツール(psr.exe) とは?

ステップ記録ツール(psr.exe)は、最近の Windows であれば標準で含まれているプログラム。
マウスなどで操作をする度に、スクリーンショットの取得&操作した UI 要素の情報を記録し、保存できる。
(UI 要素の情報にはウィンドウのクラス名なども含まれており、使えるツールが限定される場面でも地味に役立ったりする)

プログラム自体は GUI ではあるが、コマンドラインオプションを指定することで、いくつか設定をカスタマイズできる。
しかし、一般的な CLI プログラムのようにpsr.exe /?などでヘルプを表示できないため、その場でオプションを確認、というのが難しい。

その解決のために、PowerShell の ArgumentCompleter の機能を使って入力補完を表示できるようにした。

ArgumentCompleter とは?

PowerShell のタブ補完や IntelliSense の候補を列挙する機能。

通常は PowerShell 関数の作成時に作成者が設定するものだが、
Register-ArgumentCompleterコマンドレットを使用することで、ネイティブコマンド(.exe など)に後付けで入力補完を設定できる。

この記事では、Register-ArgumentCompleterコマンドレットを使用して、psr.exeに入力補完を設定した。

参考:psr.exe のコマンドラインオプション

以下のページの内容を元に作成。

Problems Step Recorder (PSR.exe) Command Line Options

スイッチ 指定する値 備考(参考ページの内容&独自調査の結果)
/start 「ステップ記録ツール」を起動し、記録を開始します。/outputスイッチも指定する必要があります。
/stop 「ステップ記録ツール」を停止します。実行中の「ステップ記録ツール」のインスタンスがない場合は何も起きません。/stopスイッチを指定したとき、他のスイッチは指定できません。
/sc (0|1) 1(On) のとき、各ステップ毎にスクリーンショットを撮影します。0(Off) または 1(On) を指定します。既定値は 1(On) です。
/maxsc <value> 記録されるスクリーンショットの最大数を指定します。
/maxlogsize <value> 圧縮前のログファイルの容量を MB 単位で指定します。
/gui (0|1) 1(On) のとき、「ステップ記録ツール」のウィンドウを表示します。0(Off) または 1(On) を指定します。既定値は 1(On) です。0(Off) を指定する場合、/startスイッチも指定する必要があります。
/arcetl (0|1) 1(On) のとき、出力ファイルに etw ファイルを含めます。0(Off) または 1(On) を指定します。既定値は 0(Off) です。
/arcxml (0|1) 1(On) のとき、出力ファイルに xml ファイルを含めます。0(Off) または 1(On) を指定します。既定値は 0(Off) です。
/recordpid <pid> 指定されたプロセス ID に関連した操作のみが記録されます。
/sketch (0|1) 1(On) のとき、スクリーンショットの代わりにスケッチを保存します。0(Off) または 1(On) を指定します。既定値は 0(Off) です。/scスイッチに 0 が指定されている必要があります。
/slides (0|1) 1(On) のとき、出力される mht ファイルにスライドショーを設定します。0(Off) または 1(On) を指定します。既定値は 1(On) です。
/output <filepath> 出力先のファイルパスを指定します。拡張子は .zip である必要があります。
/stopevent <eventname> ログファイルが出力された後イベントを発生させます。

psr.exe に入力補完を設定するコード

以下のコードを実行すると、その PowerShell インスタンス内では psr.exe について入力補完が表示されるようになります。

最新版(GitHub) : NativeCommandCompleter/psr.ps1 at master · imihito/NativeCommandCompleter
リポジトリ全体をモジュールとして使用する事が前提。

psr.ps1
using namespace System
using namespace System.Collections.Specialized
using namespace System.Management.Automation
using namespace System.Management.Automation.Language

# [ステップ記録ツール(psr.exe)をコマンドラインから使いやすくしてみる(PowerShell・Register-ArgumentCompleter) - Qiita](https://qiita.com/nukie_53/items/58a5d0e4f33fb58a8bab "ステップ記録ツール(psr.exe)をコマンドラインから使いやすくしてみる(PowerShell・Register-ArgumentCompleter) - Qiita")
# Register-ArgumentCompleter に指定するスクリプトブロックを定義。
# 入力中のコマンド構文木を元に、入力候補をパイプライン出力する。
[scriptblock]$psrCompleter = {
    [CmdletBinding(HelpUri = 'https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/register-argumentcompleter')]
    [OutputType([System.Management.Automation.CompletionResult])]
    param (
        # 入力中のスイッチ。
        [Parameter(Position = 0)]
        [string]$commandName
        ,
        # 入力中のコマンド構文木。
        [Parameter(Position = 1)]
        [CommandAst]$wordToComplete
        ,
        # 補完を開始したカーソルの位置。
        [Parameter(Position = 2)]
        [int]$cursorPosition
    )
    # カーソルまでの CommandElementAst(includes cursor position ast)。
    # ○○.exe があるため最低でも1要素はある。
    [CommandElementAst[]]$astsOfBeforeCursor = @($wordToComplete.CommandElements |
        Where-Object -FilterScript {
            $_.Extent.StartOffset -le $cursorPosition
        }
    )
    
    [string[]]$allSwitchs = @(
        '/start'
        '/stop'
        '/sc'
        '/maxsc'
        '/maxlogsize'
        '/gui'
        '/arcetl'
        '/arcxml'
        '/recordpid'
        '/sketch'
        '/slides'
        '/output'
        '/stopevent'
    )
    
    # ツールチップの情報を取得
    # see also:https://social.technet.microsoft.com/Forums/office/en-US/b78253b1-6e38-4563-9efa-4973414e9a75/problems-step-recorder-psrexe-command-line-options?forum=w7itprogeneral
    [hashtable]$tooltipInfo = @{
        # For psr.exe ja-JP ToolTips
        # 日本語用ツールチップ文字列。
        # see also : https://social.technet.microsoft.com/Forums/office/en-US/b78253b1-6e38-4563-9efa-4973414e9a75/problems-step-recorder-psrexe-command-line-options?forum=w7itprogeneral
        '/start'      = '/start:「ステップ記録ツール」を起動し、記録を開始します。/output スイッチも指定する必要があります。'
        '/stop'       = '/stop:「ステップ記録ツール」を停止します。実行中の「ステップ記録ツール」のインスタンスがない場合は何も起きません。/stop スイッチを指定したとき、他のスイッチは指定できません。'
        '/sc'         = '/sc (0|1):1(On) のとき、各ステップ毎にスクリーンショットを撮影します。0(Off) または 1(On) を指定します。既定値は 1(On) です。'
        '/maxsc'      = '/maxsc <value>:記録されるスクリーンショットの最大数を指定します。'
        '/maxlogsize' = '/maxlogsize <value>:圧縮前のログファイルの容量を MB 単位で指定します。'
        '/gui'        = '/gui (0|1):1(On) のとき、「ステップ記録ツール」のウィンドウを表示します。0(Off) または 1(On) を指定します。既定値は 1(On) です。0(Off) を指定する場合、/start スイッチも指定する必要があります。'
        '/arcetl'     = '/arcetl (0|1):1(On) のとき、出力ファイルに etw ファイルを含めます。0(Off) または 1(On) を指定します。既定値は 0(Off) です。'
        '/arcxml'     = '/arcxml (0|1):1(On) のとき、出力ファイルに xml ファイルを含めます。0(Off) または 1(On) を指定します。既定値は 0(Off) です。'
        '/recordpid'  = '/recordpid <pid>:指定されたプロセス ID に関連した操作のみが記録されます。'
        '/sketch'     = '/sketch (0|1):1(On) のとき、スクリーンショットの代わりにスケッチを保存します。0(Off) または 1(On) を指定します。既定値は 0(Off) です。/sc スイッチに 0 が指定されている必要があります。'
        '/slides'     = '/slides (0|1):1(On) のとき、出力される mht ファイルにスライドショーを設定します。0(Off) または 1(On) を指定します。既定値は 1(On) です。'
        '/output'     = '/output <filepath>:出力先のファイルパスを指定します。拡張子は .zip である必要があります。'
        '/stopevent'  = '/stopevent <eventname>:ログファイルが出力された後イベントを発生させます。'

        # for /sc|gui|arcetl|arcxml|sketch|slides Switch.
        '0'           = '指定したスイッチを無効にします。'
        '1'           = '指定したスイッチを有効にします。'
    }
    
    if ($astsOfBeforeCursor.Extent.Text -icontains '/stop') {
        # /stop スイッチが指定されているとき、他のスイッチは全て無効のため、補完を停止。
        return
    }
    
    # 最後から1個目、2個目の情報を取得。
    # 指定しているスイッチと、その値を想定。
    [string]$last1Token = $astsOfBeforeCursor[-1].Extent.Text
    [string]$last2Token = if ($astsOfBeforeCursor.Length -ge 2) {
        $astsOfBeforeCursor[-2].Extent.Text
    } else {
        [string]::Empty
    }
    # 指定したパラメータが入力中かどうか。
    [scriptblock]$swtichIsInputting = {
        param ([string[]]$Switches)
        return (
            # カーソル前の最後の要素がスイッチかつ、カーソルがスイッチの後ろ(これから入力しようとしている場合)。
            [string]::IsNullOrEmpty($commandName) -and 
            ($last1Token -iin $Switches)
        ) -or (
            # 入力中の値を変更しようとしている場合。
            -not [string]::IsNullOrEmpty($commandName) -and 
            ($last2Token -iin $Switches)
        )
    }
    
    [string[]]$disableCompletionParameters = @(
        '/maxsc', '/maxlogsize', '/output', '/stopevent'
    )
    
    if (& $swtichIsInputting $disableCompletionParameters) {
        # カーソルの直前が任意入力のスイッチだったら、補完を停止。
        return
    }

    [string[]]$onOffParameters = @(
        '/sc', '/gui', '/arcetl', '/arcxml', '/sketch', '/slides'
    )
    if (& $swtichIsInputting $onOffParameters) {
        # カーソルの直前が 0, 1 を指定するスイッチだったら、0, 1 を表示。
        [CompletionResult]::new('0', '0 : Off', [CompletionResultType]::ParameterValue, $tooltipInfo['0'])
        [CompletionResult]::new('1', '1 : On' , [CompletionResultType]::ParameterValue, $tooltipInfo['1'])
        return
    }
    if (& $swtichIsInputting '/recordpid') {
        # カーソルの直前が /recordpid だったら、ウィンドウを持っているプロセスの ID をリストアップ。
        Get-Process |
            Where-Object -Property MainWindowHandle -NE -Value ([IntPtr]::Zero) |
            ForEach-Object -Process {
                [string]$procInfo = '{0} : {1} - {2}' -f $_.Id, $_.Name, $_.MainWindowTitle
                [CompletionResult]::new($_.Id, $procInfo, [CompletionResultType]::ParameterValue, $procInfo)
            }
        return
    }

    
    # 今の位置のスイッチ or 入力中の文字列にマッチするスイッチを取得。
    [string[]]$assginedParams = $wordToComplete.CommandElements.Extent.Text
    [string[]]$showSwitchs = @(
        # 補完開始位置にすでにスイッチがあればそれを優先。
        if ($tooltipInfo.Contains($commandName)) {
            $commandName
        }
        [string[]]$tmpFilterdSwitch = $allSwitchs
        [string[]]$exclusiveSwitch = @('/start', '/stop')
        # any contains.
        if ($exclusiveSwitch.Where({$assginedParams -icontains $_}).Count -ne 0) {
            $tmpFilterdSwitch = $allSwitchs.Where({$_ -inotin $exclusiveSwitch})
        }
        $tmpFilterdSwitch.Where({
            # まだ指定されていないスイッチかつ、入力中の文字列に一致するものを探す。
            ($assginedParams -inotcontains $_) -and
            $_ -imatch [regex]::Unescape($commandName)    
        })
    )
    if ($showSwitchs.Length -eq 0) {
        # 入力中の文字列が無かったり、マッチするスイッチが無かった場合は、指定していないスイッチ全部。
        $showSwitchs = $showSwitchs = $tmpFilterdSwitch.Where({$assginedParams -inotcontains $_}).ForEach({$_.ToString()})
    }
    $showSwitchs | 
        Select-Object -Unique | 
        ForEach-Object -Process {
            [CompletionResult]::new($_, $_, [CompletionResultType]::ParameterName, $tooltipInfo[$_])
        }
    return
}

# 定義したスクリプトブロックを psr.exe に紐付けて登録。
Register-ArgumentCompleter -CommandName psr.exe -Native -ScriptBlock $psrCompleter

動作イメージ

Windows PowerShell ISE 上での動作イメージ。
コンソール上ではPSReadlineがインストールされていれば、アイコン以外は表示される。
Visual Studio Code ではツールチップ以外は表示される。

psr.gif

ハマったところ

入力中の文字列の最後に空白があっても$wordToCompleteには、空白が含まれないっぽい挙動のため、スイッチの後の値指定の箇所で手間取った。

特に/outputスイッチの対応がうまくいかなかった。
/outputスイッチの後ろはファイルパスを指定したいため、自前の補完ではなくPowerShell側の補完機能を使いたい。
そのためには、自前の補完を切る(候補を出さない)必要があるが、補完を復活させるタイミングの制御が難しい。

191231時点の実装では、パスの後ろにスペースを空けて/などを入力すれば、補完が表示されるようになっている。

上記の問題は、$wordToCompleteの型をstringだと誤認していたことが原因。
実際の型はSystem.Management.Automation.LanguageCommandAstであり、この型を使うことでより詳細な情報を取得できる。

参考ページ

Windows ステップ記録ツール(psr)のススメ - Qiita

psr.exe のラッパーバッチや、psr.exe そのものの罠についても記載されているため、こちらの記事も一度読んでおくといいと思います。

問題ステップ記録ツール (PSR.exe) について - yaimairiの備忘録
Problems Step Recorder (PSR.exe) Command Line Options

コマンドラインオプションの情報はこちらから確認しました。

問題を再現する手順の記録 - Windows ヘルプ

MS公式の説明(GUI 部分のみ)。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?