• 作成:
  • 更新:

PowerShellのスクリプトを書く時の諸注意

先日Windows向けの作業のちょっとした自動化のためにPowerShellのスクリプトを書いたので、少し躓いたポイントを書きます。

大人しくLinux版のPowerShellもインストールします

WSL2のEmacsから快適に編集するためにシンタックスなどのエラーレポートなどがやはり欲しいですね。

Powershell - LSP Mode - LSP support for Emacs を使えば普通にLSPが利用可能ですが、インストールするためにpwshが実行ファイルとしてPATHに認識されている必要があります。

しかしWSL2上で作業しているのですから、 Windows上のpwsh.exeを使えば多重にインストールする必要がなくなるのではと考えました。

そこでlsp-pwsh-exepwsh.exeを追加してみたのですが、 Windows上のPowerShellをWSL2上で動かすと、 tempディレクトリを作る時にパスのルートの位置を誤認するようで動きませんでした。

インストール手順などをもう少し工夫すればいけそうな気もしますが、他にもハマりポイントがありそうなので、大人しくLinux版をインストールすることにしました。

今はWSL2にはUbuntuを使っているので、 Microsoftのリポジトリを以下からインストールすることにしました。

Ubuntu への PowerShell のインストール - PowerShell | Microsoft Learn

bash, zshで言うset -e$ErrorActionPreference = "Stop"

外部コマンドなどが失敗した時には当然スクリプト全体を止めたくて、続行して欲しい時は殆ど無いと思いますが、その場合のzshで言うset -eには$ErrorActionPreference = "Stop"が該当します。

実行ログを見たい場合はRead-HostPause

Linux向けスクリプトではどうせスクリプトはターミナルから実行してれると思うのでいちいち配慮しないのですが、素人がエクスプローラなどから動かすかもしれない、 Windows向けスクリプトでは最後にEnterを押さないと終了しないようにした方が良いでしょう。

エクスプローラを経由した.batファイルからの実行も考慮するなら尚更です。

Pauseコマンドか、 Read-Host "Enterを押したら終了します"のようなものを最後に書いておくとウィンドウが自動で閉じません。

ただ一つ問題がありまして、前述の$ErrorActionPreference = "Stop"を使うと機能しません。

ちゃんとtry-catchで制御すると問題ないのかもしれませんが、そのような複雑な処理をPowerShellで書くべきかと言うと微妙なラインだと思います。

Windows向けPowerShellでは文字コードはUTF-8(BOMあり)で改行コードはCRLFにします

改行コードはCRLFにします

気が付きにくい罠なのですが、改行コードはLFではなくCRLFにしましょう。

例えば以下のようなPowerShellスクリプトを書いた場合、

# `stable`ブランチに切り替え、存在しない場合は`origin/stable`から作成します。
if ($null -eq (git branch --list stable)) {
    git switch -c stable origin/stable
}
else {
    git switch stable
}

PowerShell 5で実行すると、

PS C:\Users\ncaq\Downloads> .\play.ps1
発生場所 C:\Users\ncaq\Downloads\play.ps1:3 文字:1
+ }
+ ~
式またはステートメントのトークン '}' を使用できません。
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : UnexpectedToken

のようにエラーが出ます。

何故かと言うと、コメントが正常に改行で終了してない扱いになるみたいですね。なので2行目などがなかったことになります。

コメントを含まないスクリプトの場合動いたり動かなかったりします。少し謎です。

パースエラーのエラー表示ではご覧のように改行されて出力されるので気がつくのが遅れました。

ちなみにPowerShell 7だと正常に実行出来ます。

文字コードはUTF-8にする場合BOMをつけます

Windows向けだろうと流石にUTF-8でソースコードは記述したいものです。

PowerShell 7では自動で認識するようです。

しかし、 Windows 10/11で標準で実行されるPowerShell 5では、 UTF-8にBOMが無いと日本語Windowsでは認識されるのか、 Write-Hostなどで文字化けが発生します。

BOMをつけるとちゃんとUTF-8で書かれていると認識して出力してくれます。

chcpとかでガチャガチャするより大多数のシステムの類推で動くように任せるほうがまだ安心できますね。

Emacsならばset-buffer-file-coding-systemutf-8-with-signature-dosを選択しておけば良いでしょう。

頻繁に忘れるのでEmacsのinit.elで設定してしまっても良いかもしれませんね。

まとまった記事がありました

後から調べたら詳細は以下の記事にまとまっていました。

改行コードについての記載はありませんが概ね下記のような理解で問題ないかと思います。

  • OSはWindowsを利用してWindows PowerShellを利用する場合 UTF8+BOM + CRLF

  • OSはWindowsLinuxのクロスプラットフォームでWindowsPowerShellPowerShellが混在する場合 UTF8+BOM + CRLF

  • OSはLinuxだけでPowerShellのみ利用 UTF-8+BOMless + LF

PowerShellでps1ファイルに使う文字コードと改行コードについて - Qiita

とりあえず今の所まだLinux向けにPowerShellを使う気が私には無いので、 UTF-8にBOMをつけておけば良いでしょう。

Linuxなどでも動かしてshebangに対応することを考えると、 PowerShell 7を前提にすること以外に解決策が無いのがなんだか虚しいですが。

.editorconfigの設定

次から間違えることを防止するために.editorconfigを設定しておきましょう。

プロジェクト全体ではcharset = utf-8, end_of_line = lfを設定しているので尚更です。

[*.bat]
end_of_line = crlf
charset = latin1

[*.ps1]
end_of_line = crlf
charset = utf-8-bom

署名のないPowerShellスクリプトがWindowsの設定次第で実行しづらいことに対処するために、同名のバッチファイルを作ってPowerShell -NoProfile -ExecutionPolicy Unrestrictedで実行するテクニックを使っているので、バッチファイル向けの設定もしています。

コマンドプロンプトのバッチファイルのUTF-8へのスマートな対応には全く期待してないのでユニコード文字は不許可にしています。

どうせPowerShellを起動する単純なスクリプトしか書かないのでcharset = latin1としています。これはASCIIと入力したかったのですが、 EditorConfig Properties · editorconfig/editorconfig Wiki を見てもus-asciiとかが存在しなかったので妥協しました。サポートしている実装はそれなりにあるのかもしれませんが。

とりあえず日本語圏では日本語を入力させないというだけでそれなりの働きはしてくれると思います。

何故PowerShell 7はWindowsのデフォルトの設定では無いのでしょうか

しかし何故MicrosoftはWindows 11のリリースタイミングで、 PowerShellの標準バージョンをレガシーな5からマルチプラットフォームの.NETベースで開発されたPowerShell 7にしなかったのでしょう? PowerShell 5を起動すると、

新機能と改善のために最新の PowerShell をインストールしてください!https://aka.ms/PSWindows

と表示されるあたり、別に本流になったPowerShell 7に興味がないというわけでもないでしょう。

PowerShell 6がリリースされたのは2016年で、本流として.NET CoreからCoreのサフィックスが外れたPowerShell 7.1のリリースから数えても2020年なので、それなりの期間が過ぎています。

もう安定しているかどうか分からないから本流にするか悩む段階を過ぎていると思います。

デフォルトをPowerShell 7系列にして、互換性のために一応PowerShell 5を残しておく程度で良いと思います。

はてなブックマークのコメントによる補足: PowerShell 7の互換性はまだまだらしい

PowerShellのスクリプトを書く時の諸注意

PowerShell7以降(pwsh)は、Windows PowerShell(いわゆる5.1)のモジュールが全然入ってない別物。故に7系はPowerShellの中心的なニーズであるWindowsの管理系作業では使い物にならない。7系がデフォルトになんて今の段階じゃあり得ん。

2023/07/29 20:08

なるほど、

Windows 管理モジュールでは、Windows のさまざまな機能やサービスの管理とサポートが提供されます。 これらのモジュールのほとんどは、PowerShell 7 でネイティブに動作するように更新されているか、PowerShell 7 との互換性がテストされています。

PowerShell 7 モジュールの互換性 - PowerShell | Microsoft Learn

とか書かれていたので殆ど互換性があるものかと考えてしまいましたが、まだまだモジュールの互換性が取れてないんですね。

具体的にどういうものが互換性が取れてなくてクリティカルに不便なのかは、 PowerShell 7 module compatibility | Microsoft Learn を見てもそんなにWindowsに詳しくない私にはすぐにはピンと来ませんでしたが。

コア部分での互換性は取れているはずなので、互換レイヤーでのモジュール対応も含めて、コーナーケースをひたすらに潰していったらいつか置き換わる日がくるかもしれませんね。 Microsoftもいつまでも2つの.NET、 2つのPowerShellをメンテナンスするのは嫌だと思うので。