もろもろ

もろもろ書いていきます。気軽にコメントください^_^

VSCode の Task を PowerShell スクリプトで実装する

VSCode の Task を PowerShell スクリプトで実装する方法です。
tasks.json に直接 PowerShell コードを記述することも出来るのですが、セミコロン区切り、多重エスケープなど、中々面倒です。 それよりも、tasks.ps1 というように PowerShell スクリプト化して tasks.json から呼び出す形の方が圧倒的に楽です。

ちなみに僕のところだと、リモートにソースコードやテストスクリプトを同期するために "SFTP" 拡張機能を使っているのですが、この設定ファイルを簡単に構成するためのタスクを PowerShell スクリプト化して用意しています。("Remote Development" 拡張機能Linux 32bit が非サポートな関係で使用を断念)
ここではこれを簡略化したサンプルコードを使って要点を押さえていきましょう。

では、早速サンプルコードです。

.vscode/tasks.json

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "type": "shell",
    "options": {
        "shell": {
            "executable": "powershell.exe"
        }
    },
    "presentation": {
        "echo": false,
        "reveal": "always",
        "focus": true,
        "panel": "shared",
        "showReuseMessage": true,
        "clear": true
    },
    "problemMatcher": [],
    "inputs": [
        {
            "id": "hostAddress",
            "type": "promptString",
            "description": "Host Name or IP Address",
        }
    ],
    "tasks": [
        {
            "label": "Configure SFTP",
            "command": [
                "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process;",
                ". .vscode/tasks.ps1;",
                "ConfigureSFTP ${input:hostAddress};"
            ],
        },
    ],
}

.vscode/tasks.ps1

$ErrorActionPreference = "Stop"

function ConfigureSFTP {
    param(
        [string]$hostAddress
    )
    
    $Host.UI.WriteLine("[Configure SFTP]")
    $config = [PSCustomObject]@{
        name         = "My Server";
        host         = $hostAddress;
        protocol     = "sftp";
        port         = 22;
        username     = ${env:username};
        remotePath   = "/";
        uploadOnSave = $true;
        ignore       = @(".git");
    }
    $json = (ConvertTo-Json $config).Replace("`r`n", "`n")
    [IO.File]::WriteAllText(".vscode/sftp.json", $json)
    $Host.UI.WriteLine("Configured: .vscode/sftp.json")
    
    $Host.UI.WriteLine("Completed.")
    $Host.UI.WriteLine()
    $Host.UI.WriteLine("Please apply the config with the following steps.")
    $Host.UI.WriteLine("1. Execute `"sftp: config`" on the command palette.")
    $Host.UI.WriteLine("2. Re-save the opened `"sftp.config`" file.")
}

tasks.json について

トップレベルの設定

タスクを主に PowerShell で実装していくつもりなら、このサンプルのように "type" や "options" を tasks.json のトップレベルで宣言してしまいましょう。各タスクの中で設定をオーバーライドすることもできるので、PowerShell 以外で実装するタスクを共存させたい場合も問題ありません。

"presentation" の詳細はここではあまり触れません。詳細はこちらをご参照ください。
僕は実行コマンドのエコーや過去の出力履歴を邪魔に感じたので、"echo" を false、"clear" を true に設定しています。あと、タスク内から ssh コマンドや scp コマンドを実行すると初回接続時に入力を求められたりするので、"focus" も予め true にしています。

タスクの入力パラメータ

"inputs" はタスクの入力パラメータ宣言です。ここでは hostAddress という名前の入力パラメータを宣言しています。タスク内で ${input:hostAddress} というように使用することができ、そのタスクの実行時にユーザー入力が求められるようになります。
詳細はこちらをご参照ください。

PowerShell スクリプトの呼び出し

ここからが肝です。
タスク実行時に tasks.ps1 の ConfigureSFTP 関数が実行されるよう、"command" に PowerShell コードを記述していきます。
"command" は文字列もしくは文字列の配列で指定できるのですが、配列で指定した場合でも全て一行に繋げられて実行されてしまうため、文末にセミコロンが必要になることに気を付けてください。

まず、タスク実行時にのみスクリプト実行を自動で一時的に許可するよう Add-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process; を実行します。
PowerShell はデフォルトだとスクリプトの実行が許可されていません。この設定は管理者権限で Set-ExecutionPolicy コマンドを実行することで恒久的に変更することができますが、開発メンバー全員に予め設定変更しておいてもらうというのはいくつかの理由から (特にセキュリティの観点で) 面倒です。
この方法であれば、事前設定や管理者権限、そしてセキュリティの考慮も不要です。タスク実行時にのみ反映され、他には一切影響を与えません。

続いて、tasks.ps1 を実行します。タスク実行時のカレントディレクトリは Workspace フォルダ、スクリプト内の関数のロードが目的なのでドットソースで実行、となりますので . .vscode/tasks.ps1; という形で実行します。

最期に、ロードされた PowerShell 関数を実行します。ここでは ConfigureSFTP 関数を、第一引数にタスクの入力パラメータ "hostAddress" を指定して実行する必要がありますので、ConfigureSFTP ${input:hostAddress}; という形で実行します。

tasks.ps1 について

$ErrorActionPreference

関数実行中に例外が発生した場合には処理をエラー終了させたいので、$ErrorActionPreference に "Stop" を設定しておきます。

関数

PowerShell スクリプトファイルをタスク毎に用意するのは嫌なので、関数化して tasks.ps1 に全てのタスクを集約します。各タスクは tasks.ps1 をドットスコープでロードして、対応する関数を個別に実行します。

$Host.UI

$Host.UI を使用することで VSCode のターミナルに出力することができます。WriteLine()、WriteWarningLine()、WriteErrorLine() を使い分けましょう。(WriteWarningLine() だけは、行頭に "警告: " とか "WARNING: " を勝手に付加するのでご注意ください。)
入力を受け付けることもできますが、基本的にはタスクの入力パラメータで充分なはずです。

ファイル選択ダイアログ

サンプルコードには含んでいませんが、下記のサンプルコードのように OpenFileDialog クラスを使ってファイル選択ダイアログを利用することも可能です。

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$dialog = New-Object System.Windows.Forms.OpenFileDialog
$dialog.Filter = "json files (*.json)|*.json"
if ($dialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) {
    $Host.UI.WriteErrorLine("Cancelled.")
    return
}
$contents = Get-Content $dialog.FileName
$Host.UI.WriteLine($contents)

さいごに

これで皆さんも PowerShellVSCode タスクを作成することができるようになったと思います。
機会がありましたら是非ご活用ください。