PowerShellスクリプトを書く機会に出会ったので,Linux上でvscodeを使用してPowerShellの開発環境を整備した.Microsoft謹製のPowerShell Extensionを使うことで,容易に十分な開発環境を整えることが出来るのだが,PowerShellとのインテグレーションや静的コード解析ツールであるPSScriptAnalyzerの使用に関して,いくつかドキュメントを読むだけではわからない問題にぶつかった.実際にはどれも大した問題ではないのだが,調査には少々時間を使ったので記録を残すことにした.
残念なことに,Windows 環境で自動化しなければいけないタスクがでてきた.
現在 2022 年,あえて Win32 Console Command を使った bat ファイルを書くという選択肢は無いので,PowerShell を使うことにした.
特定の言語.環境での開発を始める際,処理系の導入は言うまでもないとして,Linter と Formatter の導入は欠かせないだろう.作法をよく知らないなんなら覚える気もない言語の場合,特に.だ.
PowerShell スクリプトを書く機会など,個人的にはあまりあってほしくないものなのだが,こういうものは忘れた頃にまた書く羽目になり,そのタイミングでまた環境構築について調べたりするものである.
そのような時間の浪費はしたくないので,未来の自分のためにこの記事を残すことにした.
この記事は Linux 上で,Visual Studio Code(以下,vscode)を使用して PowerShell スクリプトを書く環境を構築する方法について述べる.
まずは処理系の導入からだが,どの環境についても公式に十分な説明があるので,詳細については割愛する.
私は NixOS を使っているが,Nixpkgs に普通に登録があるので,home-manager で導入を行った.
当然ながら,vscode の導入についてもここでは扱わない.
とりあえず欲しいのは以下の機能群である.
PowerShell も vscode も Microsoft 謹製なので,当然のようにMicrosoft 謹製の Extensionがある.これを導入することで,全ての機能が自動的に手に入る.すばらしい.
しかし,この Extension はいくつか直感に反する動きをするので,それについての補足をこの記事の主題とする.
好きな方法でこれをインストールする.
意気揚々と 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 は以下のような動作をするように見える.
つまり,この Extension は環境変数 PATH の解決を行わず,ハードコードされているパス以外に PowerShell のバイナリがある場合,必ず powershell.powerShellAdditionalExePaths を設定しなければ,PowerShell の起動に失敗する.
私の場合,home-manager で導入したため,当然ながらハードコードされているパスに pwsh のバイナリは存在しない.
原因が分かってしまえば,気持ちよくエラーメッセージの通り powershell.powerShellAdditionalExePaths に設定を追加することが出来る.
設定パラメータはちゃんと公式ドキュメントにも案内がある.ドキュメントのセッション メニューへの独自の PowerShell のパスの追加を参照して,exePath
とversionName
を設定すれば良い.
exePath は絶対パスを入力する必要がある.前述の通り,この Extension は PowerShell の実行前にファイルの実在を確認するのだが,それに使用されている関数がfs.lstatSync()
であるためだ.この関数は引数として渡された文字列をそのまま解釈するため,環境変数や vscode の変数は展開されない.
複数環境で Setting Sync を使っている場合など若干もにょるが,標準実行パス -> powershell.powerShellAdditionalExePaths の定義順(powerShellAdditionalExePaths は Object 配列なので順序は保持される)で,最初に発見されたバイナリを使うという実装がわかっていれば十分管理可能だろう.
powershell.powerShellAdditionalExePaths を設定することで,無事 Language Server と PowerShell の連携に成功した.
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-PSReadLineKeyH…
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, Ne…
Binary 3.4.7 PowerShellEditorServices Core,Desk Start-EditorServices
Binary 0.2.0 PowerShellEditorServices.VSCode Desk {New-VSCodeHtmlCont…
Script 2.2.6 PSReadLine Desk {Get-PSReadLineKeyH…
Script 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"
};
ここまでの調査から,以下のことがわかる.
powershell.scriptAnalysis.settingsPath
の設定値が使われる
./PSScriptAnalyzerSettings.psd1
であるLinter の設定は通常プロジェクト毎に設置するものなので,この作り自体に強い違和感があるわけではないのだが,この挙動が記載されたドキュメントは発見できず,コードを読むしかなかった.
なお,検索エンジンで雑にこの辺の挙動を調べていたとき,コマンドパレットからPowerShell: Select PSScriptAnalyzer Rules
を呼び出すことで,適用されるルールを vscode 上から選択できるといった記事を見たのだが,このコマンドは2019 年にはすでに壊れていたようで,2020 年には廃止されている.
おとなしく Workspace に PSScriptAnalyzerSettings.psd1 を置こう.
この,PSScriptAnalyzerSettings.psd1 だが,PowerShell Extension に同梱されている Plaster を使ってファイルを生成することが出来る.
Plaster は PowerShell で書かれたテンプレートベースのスキャファルディングツールで,標準で 2 つのテンプレートが同梱されている.
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 integrationはNでもC良いが,C を選択すると,PSScriptAnalyzerSettings.psd1 の生成だけではなく,.vscode/settings.jsonに以下の設定が追加される.
"powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1",
これを書いている 2022 年 7 月現在,PSScriptAnalyzer の Profile の変更を Language Server であるPowerShellEditorServicesが検知する仕組みは実装されておらず,また Reload コマンドも実装されていないようなので,Profile の設置や変更をした場合,Window ごとリロードする必要がある.
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の通りに動いたので,特に困ることはなかった.
公式ドキュメントのVisual Studio Code を使用したデバッグセクションの通りである.こちらも特に困ることはなかった.
PowerShellを本気で書く機会はなど個人的に少ないほうがいいのだが,やはり困ったときにソースを直接参照できるオープンソースソフトウェアは最高であるし,昨今のMicrosoftのオープンソースに関する取り組みには称賛を送りたい.