概要
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 のコマンドラインオプション
以下のページの内容を元に作成。
スイッチ | 指定する値 | 備考(参考ページの内容&独自調査の結果) |
---|---|---|
/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
リポジトリ全体をモジュールとして使用する事が前提。
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 ではツールチップ以外は表示される。
ハマったところ
入力中の文字列の最後に空白があっても$wordToComplete
には、空白が含まれないっぽい挙動のため、スイッチの後の値指定の箇所で手間取った。
特に/output
スイッチの対応がうまくいかなかった。
/output
スイッチの後ろはファイルパスを指定したいため、自前の補完ではなくPowerShell側の補完機能を使いたい。
そのためには、自前の補完を切る(候補を出さない)必要があるが、補完を復活させるタイミングの制御が難しい。
191231時点の実装では、パスの後ろにスペースを空けて/
などを入力すれば、補完が表示されるようになっている。
上記の問題は、$wordToComplete
の型をstring
だと誤認していたことが原因。
実際の型はSystem.Management.Automation.LanguageCommandAst
であり、この型を使うことでより詳細な情報を取得できる。
参考ページ
psr.exe のラッパーバッチや、psr.exe そのものの罠についても記載されているため、こちらの記事も一度読んでおくといいと思います。
問題ステップ記録ツール (PSR.exe) について - yaimairiの備忘録
Problems Step Recorder (PSR.exe) Command Line Options
コマンドラインオプションの情報はこちらから確認しました。
MS公式の説明(GUI 部分のみ)。