PoweShellの開発環境を整える

PoweShellの開発環境を整える

PowerShellスクリプトを書く機会に出会ったので,Linux上でvscodeを使用してPowerShellの開発環境を整備した.Microsoft謹製のPowerShell Extensionを使うことで,容易に十分な開発環境を整えることが出来るのだが,PowerShellとのインテグレーションや静的コード解析ツールであるPSScriptAnalyzerの使用に関して,いくつかドキュメントを読むだけではわからない問題にぶつかった.実際にはどれも大した問題ではないのだが,調査には少々時間を使ったので記録を残すことにした.

Tags: powershell vscode microsoft
Takafumi Asano · 26 minute read

導入

残念なことに,Windows 環境で自動化しなければいけないタスクがでてきた.

現在 2022 年,あえて Win32 Console Command を使った bat ファイルを書くという選択肢は無いので,PowerShell を使うことにした.

特定の言語.環境での開発を始める際,処理系の導入は言うまでもないとして,Linter と Formatter の導入は欠かせないだろう.作法をよく知らないなんなら覚える気もない言語の場合,特に.だ.

PowerShell スクリプトを書く機会など,個人的にはあまりあってほしくないものなのだが,こういうものは忘れた頃にまた書く羽目になり,そのタイミングでまた環境構築について調べたりするものである.

そのような時間の浪費はしたくないので,未来の自分のためにこの記事を残すことにした.

前提

この記事は Linux 上で,Visual Studio Code(以下,vscode)を使用して PowerShell スクリプトを書く環境を構築する方法について述べる.

まずは処理系の導入からだが,どの環境についても公式に十分な説明があるので,詳細については割愛する.

私は NixOS を使っているが,Nixpkgs に普通に登録があるので,home-manager で導入を行った.

当然ながら,vscode の導入についてもここでは扱わない.

開発環境の構築

とりあえず欲しいのは以下の機能群である.

  1. Syntax Highright
  2. Auto Complete
  3. Linter
  4. Formatter
  5. Debugger
  6. Functional Test Runner

PowerShell も vscode も Microsoft 謹製なので,当然のようにMicrosoft 謹製の Extensionがある.これを導入することで,全ての機能が自動的に手に入る.すばらしい.

しかし,この Extension はいくつか直感に反する動きをするので,それについての補足をこの記事の主題とする.

PowerShell Extension の導入

好きな方法でこれをインストールする.

最初の問題: Unable to find PowerShell

意気揚々と vscode を再起動し,PowerShell Script(.ps1 ファイル)を開くと,早速素敵なエラーが出た.

[ERROR] - Unable to find PowerShell. Do you have PowerShell installed? You can also configure custom PowerShell installations with the 'powershell.powerShellAdditionalExePaths' setting.

PowerShell が見つからないようだ.PowerShell をカスタムインストールしている場合,powershell.powerShellAdditionalExePathsを設定できる.と言われている.

おかしい.pwsh への PATH は通っており,PATH が通っていることが確認出来ているターミナルからcodeコマンドで vscode を起動しているはずだ.そう思って,コードを見てみることにした.

PowerShell Extension のリポジトリはここにある.

session.tsを見ると,デフォルトの設定ではPowerShellExeFinderクラスのgetFirstAvailablePowerShellInstallationメソッド呼び出しによって解決されているようだ.クラスの実態があるplatform.tsを見ていくと,どうやらこの Extension は以下のような動作をするように見える.

  • プラットフォームを識別し,各プラットフォーム毎の標準実行パスからファイルの実在を確認する
    • この各プラットフォームで期待される PowerShell の標準実行パスは 定数としてハードコードされている
  • この標準実行パスでファイルの実在が確認できなった場合,powershell.powerShellAdditionalExePaths に設定されたパスを順次走査する
    • このパラメータは複数設定できるが,最初にファイルの実在が確認されたパスが有効となる

つまり,この Extension は環境変数 PATH の解決を行わず,ハードコードされているパス以外に PowerShell のバイナリがある場合,必ず powershell.powerShellAdditionalExePaths を設定しなければ,PowerShell の起動に失敗する.

私の場合,home-manager で導入したため,当然ながらハードコードされているパスに pwsh のバイナリは存在しない.

原因が分かってしまえば,気持ちよくエラーメッセージの通り powershell.powerShellAdditionalExePaths に設定を追加することが出来る.

設定パラメータはちゃんと公式ドキュメントにも案内がある.ドキュメントのセッション メニューへの独自の PowerShell のパスの追加を参照して,exePathversionNameを設定すれば良い.

exePath は絶対パスを入力する必要がある.前述の通り,この Extension は PowerShell の実行前にファイルの実在を確認するのだが,それに使用されている関数がfs.lstatSync()であるためだ.この関数は引数として渡された文字列をそのまま解釈するため,環境変数や vscode の変数は展開されない.

複数環境で Setting Sync を使っている場合など若干もにょるが,標準実行パス -> powershell.powerShellAdditionalExePaths の定義順(powerShellAdditionalExePaths は Object 配列なので順序は保持される)で,最初に発見されたバイナリを使うという実装がわかっていれば十分管理可能だろう.

powershell.powerShellAdditionalExePaths を設定することで,無事 Language Server と PowerShell の連携に成功した.

第二の問題: PSScriptAnalyzer

PowerShell 用の静的コード解析ツールを探したのだが,ほぼ Microsoft 謹製のPSScriptAnalyzer一択という状況のようだ.

vscode の PowerShell Extension にはこの PSScriptAnalyzer が同梱されているため,追加の手順無しで使うことができる.

以下は,PowerShell Extension が導入されている vscode の Integrated Terminal として開かれた pwsh で,Get-Module -ListAvailableを実行した結果である.

PS /path/to/workspace> Get-Module -ListAvailable

    Directory:
/nix/store/<hash>-powershell-7.2.3/share/powershell/Modules

ModuleType Version    PreRelease Name                                PSEdition ExportedCommands
---------- -------    ---------- ----                                --------- ----------------
Manifest   1.2.5                 Microsoft.PowerShell.Archive        Desk      {Compress-Archive,Manifest   7.0.0.0               Microsoft.PowerShell.Host           Core      {Start-Transcript,Manifest   7.0.0.0               Microsoft.PowerShell.Management     Core      {Add-Content, Clear…
Manifest   7.0.0.0               Microsoft.PowerShell.Security       Core      {Get-Credential, Ge…
Manifest   7.0.0.0               Microsoft.PowerShell.Utility        Core      {Export-Alias, Get-…
Script     1.4.7                 PackageManagement                   Desk      {Find-Package, Get-…
Script     2.2.5                 PowerShellGet                       Desk      {Find-Command, Find…
Script     2.1.0                 PSReadLine                          Desk      {Get-PSReadLineKeyHBinary     2.0.3                 ThreadJob                           Desk      Start-ThreadJob

    Directory: /home/<user>/.vscode/extensions/ms-vscode.powershell-2022.7.2/modules

ModuleType Version    PreRelease Name                                PSEdition ExportedCommands
---------- -------    ---------- ----                                --------- ----------------
Script     1.1.3                 Plaster                             Desk      {Invoke-Plaster, Ne…
Binary     3.4.7                 PowerShellEditorServices            Core,Desk Start-EditorServices
Binary     0.2.0                 PowerShellEditorServices.VSCode     Desk      {New-VSCodeHtmlContScript     2.2.6                 PSReadLine                          Desk      {Get-PSReadLineKeyHScript     1.20.0                PSScriptAnalyzer                    Desk      {Get-ScriptAnalyzer

Plaster,PowerShellEditorServices,PowerShellEditorServices.VSCode,PSReadLine,PSScriptAnalyzer という 5 つのモジュールが,PowerShell Extension から読み込まれていることがわかる.

PSScriptAnalyzer の検査ルールセットについてはここをに一覧がある.ルール名,重大度,デフォルトで有効か.といった情報が表になっており,ルール名をクリックして開くと説明の他,誤ったコードと正しいコードのサンプルがある.

PSScriptAnalyzer が期待通り動いていることを確認するために,デフォルトでオンであるとするルールをいくつか試していたところ,vscode 上で検出されないものがあった.バンドルされている PSScriptAnalyzer の問題を疑い,Integrated Terminal でInvoke-ScriptAnalyzerコマンドを実行すると,ルールセット一覧上でデフォルトでオンとさrているものは全て正常に検出できた

この差はなんだろうか.

Invoke-ScriptAnalyzerは適用するルールについて記載された Profile を引数として渡すことができる.PowerShell Extension のデフォルトでは Workspace にあるPSScriptAnalyzerSettings.psd1というファイルがこの Profile として渡されるようだ.

このファイルが存在しない場合,PSScriptAnalyzer のデフォルト設定にフォールバックすることを期待したのだが,前述の通り動作としてはそうなっていない.

となると,Extension のどこかにフォールバックのロジックがあるのではないかと考えた.

結論から言うと,Extensions ではなく,Language Server の方でデフォルトの設定を持っていた.具体的には,このファイルの Internal ClassAnalysisServiceに以下のような定義がある.

/// <summary>
/// Defines the list of Script Analyzer rules to include by default if
/// no settings file is specified.
/// </summary>
private static readonly string[] s_defaultRules = {
    "PSAvoidAssignmentToAutomaticVariable",
    "PSUseToExportFieldsInManifest",
    "PSMisleadingBacktick",
    "PSAvoidUsingCmdletAliases",
    "PSUseApprovedVerbs",
    "PSAvoidUsingPlainTextForPassword",
    "PSReservedCmdletChar",
    "PSReservedParams",
    "PSShouldProcess",
    "PSMissingModuleManifestField",
    "PSAvoidDefaultValueSwitchParameter",
    "PSUseDeclaredVarsMoreThanAssignments",
    "PSPossibleIncorrectComparisonWithNull",
    "PSAvoidDefaultValueForMandatoryParameter",
    "PSPossibleIncorrectUsageOfRedirectionOperator"
};

ここまでの調査から,以下のことがわかる.

  • PSScriptAnalyzer の Rule を設定したければ Profile ファイルを作成し読み込ませる必要がある
    • PowerShell Extension が使用する PSScriptAnalyzer 用のプロファイルはpowershell.scriptAnalysis.settingsPathの設定値が使われる
      • この値のデフォルトは./PSScriptAnalyzerSettings.psd1である
  • powershell.scriptAnalysis.settingsPath に Profile が存在しない場合,Language Server にハードコードされたデフォルト値が使われる

Linter の設定は通常プロジェクト毎に設置するものなので,この作り自体に強い違和感があるわけではないのだが,この挙動が記載されたドキュメントは発見できず,コードを読むしかなかった.

なお,検索エンジンで雑にこの辺の挙動を調べていたとき,コマンドパレットからPowerShell: Select PSScriptAnalyzer Rulesを呼び出すことで,適用されるルールを vscode 上から選択できるといった記事を見たのだが,このコマンドは2019 年にはすでに壊れていたようで,2020 年には廃止されている

おとなしく Workspace に PSScriptAnalyzerSettings.psd1 を置こう.

この,PSScriptAnalyzerSettings.psd1 だが,PowerShell Extension に同梱されている Plaster を使ってファイルを生成することが出来る.

Plaster

Plaster は PowerShell で書かれたテンプレートベースのスキャファルディングツールで,標準で 2 つのテンプレートが同梱されている.

  • AddPSScriptAnalyzerSettings
  • New PowerShell Manifest Module

AddPSScriptAnalyzerSettingsはその名の通り,PSScriptAnalyzer のプロファイルを生成する.ここで生成されるファイルの中身はコメントアウトされているため,このファイルを読み込ませると実質 PSScriptAnalyzer のデフォルトの挙動をする.

Plaster はInvoke-Plasterコマンドか,vscode のコマンドパレット(Ctrl+Shift+P)からPowerShell: Create New Project from Plaster Templateを呼び出すことで実行できる.

コマンドパレットから該当のコマンドを呼び出すとテンプレートの選択を求められるので,AddPSScriptAnalyzerSettings を選択する.すると,プロジェクトを作成するフォルダの絶対パスを要求される.ここで相対パスを指定してもファイル自体は相対パスで作成されるのだが,Plasterによるファイルの展開後,入力された文字列を絶対パスしてvscodeで開くという動きをするので,おとなしく絶対パスを入力する.

実際に実行すると,Integrated Terminalに以下のような表示がでる.

PS /path/to/workspace>
  ____  _           _
 |  _ \| | __ _ ___| |_ ___ _ __
 | |_) | |/ _` / __| __/ _ \ '__|
 |  __/| | (_| \__ \ ||  __/ |
 |_|   |_|\__,_|___/\__\___|_|
                                            v1.1.3
==================================================
Enter the name of the Script Analyzer settings file (PSScriptAnalyzerSettings.psd1):
Select an editor for editor integration (or None):
[N] None  [C] Visual Studio Code  [?] Help (default is "N"): C
Destination path: /path/to/workspace
   Create PSScriptAnalyzerSettings.psd1

PSScriptAnalyzerSettings.psd1 has been added to the folder '/path/to/workspace'.
Edit this file to configure your PSScriptAnalyzer settings.

Enter the name of the Script Analyzer settings fileはデフォルトで PSScriptAnalyzerSettings.psd1 となっているので,何も入力しない.

Select an editor for editor integrationNでもC良いが,C を選択すると,PSScriptAnalyzerSettings.psd1 の生成だけではなく,.vscode/settings.jsonに以下の設定が追加される.

"powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1",

これを書いている 2022 年 7 月現在,PSScriptAnalyzer の Profile の変更を Language Server であるPowerShellEditorServicesが検知する仕組みは実装されておらず,また Reload コマンドも実装されていないようなので,Profile の設置や変更をした場合,Window ごとリロードする必要がある.

Pester

Test Runner は PowerShell Extension に含まれているが,テストの実行には Pester というテストとモックのためのフレームワークを使用する.

Pester は Microsoft 謹製でないためか,PowerShell Extension には含まれていないため,自分で追加する必要がある.

このモジュールはPowerShell Galleryに登録されているので,Install-Moduleコマンドで簡単に導入できる.

PS /path/to/workspace> Install-Module -Name Pester

Untrusted repository
You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from 'PSGallery'?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): Y

インストールされたことをを確認してみよう.

PS /path/to/workspace> Get-Module -ListAvailable

    Directory: /home/<user>/.local/share/powershell/Modules

ModuleType Version    PreRelease Name                                PSEdition ExportedCommands
---------- -------    ---------- ----                                --------- ----------------
Script     5.3.3                 Pester                              Desk      {Invoke-Pester, Describe, Context, It…}

    Directory: /nix/store/<hash>-powershell-7.2.3/share/powershell/Modules

ModuleType Version    PreRelease Name                                PSEdition ExportedCommands
---------- -------    ---------- ----                                --------- ----------------
Manifest   1.2.5                 Microsoft.PowerShell.Archive        Desk      {Compress-Archive, Expand-Archive}
Manifest   7.0.0.0               Microsoft.PowerShell.Host           Core      {Start-Transcript, Stop-Transcript}
Manifest   7.0.0.0               Microsoft.PowerShell.Management     Core      {Add-Content, Clear-Content, Clear-ItemProperty, Join-Path}
Manifest   7.0.0.0               Microsoft.PowerShell.Security       Core      {Get-Credential, Get-ExecutionPolicy, Set-ExecutionPolicy, ConvertFrom-SecureString}
Manifest   7.0.0.0               Microsoft.PowerShell.Utility        Core      {Export-Alias, Get-Alias, Import-Alias, New-Alias}
Script     1.4.7                 PackageManagement                   Desk      {Find-Package, Get-Package, Get-PackageProvider, Get-PackageSource}
Script     2.2.5                 PowerShellGet                       Desk      {Find-Command, Find-DSCResource, Find-Module, Find-RoleCapability}
Script     2.1.0                 PSReadLine                          Desk      {Get-PSReadLineKeyHandler, Set-PSReadLineKeyHandler, Remove-PSReadLineKeyHandler, Get-PSReadLineOption}
Binary     2.0.3                 ThreadJob                           Desk      Start-ThreadJob

    Directory: /home/<user>/.vscode/extensions/ms-vscode.powershell-2022.7.2/modules

ModuleType Version    PreRelease Name                                PSEdition ExportedCommands
---------- -------    ---------- ----                                --------- ----------------
Script     1.1.3                 Plaster                             Desk      {Invoke-Plaster, New-PlasterManifest, Get-PlasterTemplate, Test-PlasterManifest}
Binary     3.4.7                 PowerShellEditorServices            Core,Desk Start-EditorServices
Binary     0.2.0                 PowerShellEditorServices.VSCode     Desk      {New-VSCodeHtmlContentView, Show-VSCodeHtmlContentView, Close-VSCodeHtmlContentView, Set-VSCodeHtmlContentView}
Script     2.2.6                 PSReadLine                          Desk      {Get-PSReadLineKeyHandler, Set-PSReadLineKeyHandler, Remove-PSReadLineKeyHandler, Get-PSReadLineOption}
Script     1.20.0                PSScriptAnalyzer                    Desk      {Get-ScriptAnalyzerRule, Invoke-ScriptAnalyzer, Invoke-Formatter}

無事導入できたようだ.

インストールができれば,あとはREADMEの通りに動いたので,特に困ることはなかった.

Debugger

公式ドキュメントVisual Studio Code を使用したデバッグセクションの通りである.こちらも特に困ることはなかった.

最後に

PowerShellを本気で書く機会はなど個人的に少ないほうがいいのだが,やはり困ったときにソースを直接参照できるオープンソースソフトウェアは最高であるし,昨今のMicrosoftのオープンソースに関する取り組みには称賛を送りたい.