検索
連載

Windows PowerShellで動くスクリプトがPowerShellでも動くとは限らない、なぜなのか?山市良のうぃんどうず日記(279)

Microsoftは2024年2月初め、「回復パーティション」のサイズを拡張するサンプルスクリプトを公開しました。もし、このサンプルスクリプトを実行したい場合は、クロスプラットフォーム対応の「PowerShell 7.x」ではなく、Windowsのコンポーネントである「Windows PowerShell 5.1」の使用をお勧めします。なぜなら、サンプルスクリプト内で使用されている「Split」メソッドの挙動に重大な違いがあるからです。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「山市良のうぃんどうず日記」のインデックス

山市良のうぃんどうず日記

Windowsコンポーネントの「Windows PowerShell」とオープンソースの「PowerShell」

 Windowsのコンポーネントである「Windows PowerShell」は「5.1」が現行バージョンであり、その後のロードマップは示されていません。

 その代わり、Microsoftはオープンソースプロジェクトである「PowerShell」(旧称:PowerShell Core)を提供し、機能強化やパフォーマンス改善を実施してきました。現在の最新バージョンは「7.4.1」です。「PowerShell Core 6」のリリース以降、Windows PowerShellを起動すると、「新しいクロスプラットフォームのPowerShell」を試すように案内されるようになりました(画面1)。ちなみに、「powershell.exe -nologo」を実行すれば、この案内なしでWindows PowerShellを起動できます。

画面1
画面1 Windows PowerShellを起動するたびに、クロスプラットフォームのPowerShellが案内される

 「.NET Framework」上に構築されたWindows PowerShellに対し、PowerShellは「.NET」(旧称、.NET Core)上に構築され、Windows、macOS、Linuxのクロスプラットフォーム対応です。Windows上ではWindows PowerShellとの共存が可能で、既存のWindows PowerShellモジュールとの互換性を提供します。Windows PowerShellに含まれるモジュールやコマンドの一部は、さまざまな理由からPowerShellから削除されていますが、削除されていないモジュールやコマンドを使用する限り、Windows PowerShellで動作するスクリプトはそのままPowerShellでも動くはずです。

Windows PowerShellでは問題ないのにPowerShellではエラー! なぜなのか?

 冒頭で触れたMicrosoft提供のサンプルスクリプトは、2024年1月の「Windows回復環境(Windows RE、WinRE)」更新プログラムのインストールエラー回避策の一つとして示されたものです。

 以下の画面2は、このサンプルスクリプトのコピーを管理者として開いたWindows PowerShell(powershell.exe)とPowerShell(pwsh.exe)でそれぞれ実行した結果です。

画面2
画面2 Microsoft提供のサンプルスクリプトは、Windows PowerShellでは問題なく進むが(奥の青地の画面)、PowerShellではエラーで失敗する(手前の黒地の画面)

 Windows PowerShellでは問題なくスクリプトが進みますが(実際の拡張にはユーザーの指示が必要です)、PowerShellでは「Get-Partiton」コマンドレットがエラーとなり、スクリプトが途中でストップしてしまいます。

PowerShellのエラー原因は「Split」メソッドの挙動の違い

 何がエラーとなっているのか、調査したところ、サンプルスクリプト内にある「$WinRELocationItems = $WinRELocation.Split('\\')」の部分が返す結果の違いが原因となっていました(同じコードはサンプルスクリプト内に2カ所あります。

……
# Get WinRE partition
$WinRELocationItems = $WinRELocation.Split('\\')
foreach ($item in $WinRELocationItems)
{
    if ($item -like "harddisk*")
	{
		$OSDiskIndex = ExtractNumbers($item)
	}
	if ($item -like "partition*")
	{
		$WinREPartitionIndex = ExtractNumbers($item)
	}
}
……

 「$WinRELocation」には、このコードに入る前までに「reagentc /info」のWinREの場所(WinREが有効な場合)を含むパス(例:\\?\GLOBALROOT\device\harddisk0\partition4\Recovery\WindowsRE)が入っています。

 このパスを「Split()」メソッドで「\」を区切り文字として分割し、「harddiskX」と「partitonY」を取得して、最終的にWinREを含む回復パーティション番号(XとY)を取得しようとしています。

 問題は、「Split()」メソッドの返す結果が、Windows PowerShellとPowerShellで異なる点にあります。

 以下の画面3を見てください。Windows PowerShellでは「Split("\\")」は「\」で文字列を分割できてますが、PowerShellでは分割されていません。「Split("\")」と書き換えると、どちらも分割されます。

画面3
画面3 Windows PowerShellでは「Split()」メソッドで「\」記号をエスケープしてもしなくても同じ結果が返ってくるが、PowerShellではエスケープすると期待通りの結果が返ってこない

 文字列分割には「-Split」オペレーター(演算子)を使用することも可能です。以下の画面4を見てください。

画面4
画面4 PowerShellのSplit演算子としては、Windows PowerShellとPowerShellで共通であり、「\」記号はエスケープすべき文字

 「-Split」オペレーターでは「\」区切り文字は「\」記号(円、バックスラッシュ)でエスケープすべき文字であることが分かります。「Split()」メソッドは、「-Split」オペレーターのように「\」記号をエスケープする必要がないようにも見えます。言い方を変えると、後出のPowerShellの「Split()」メソッドは仕様に厳格で、Windows PowerShellの「Split()」メソッドはある程度の曖昧さを許容しているように見えます。

 これがWindows PowerShellとPowerShellの仕様の違いなのか、どちらか一方がバグなのかどうか分かりませんが、このサンプルコードを書いた人は「-Split」オペレーターと「Split()」メソッドが同じものと勘違いしているのかもしれません。少なくとも、PowerShellで実行するユーザーがいることを想定し、テストされているとは思えませんでした。

 実は筆者自身、「-Split」と「Split()」は書き方の違いで、別物とは想像もしていませんでした。そのため、区切り文字の指定方法にも違いはないと思っていました。

 以下のドキュメントが別になっていることからも分かるように、「Split()」メソッドは.NET Frameworkや.NETのメソッドの一つであり、「-Split」オペレーターはWindows PowerShellやPowerShellの演算子の一つです。

 そして、これらの使用方法には異なる部分が多くあります。例えば、「-Split」オペレーターでは正規表現を使用できますが(例:-split ",+"はカンマ区切りで空行を除く)、「Split()」メソッドではできません。

 これと似たようなケースに、ドット(.)のエスケープがあります。ドット(.)は改行文字を除く任意の文字と一致するように解釈されるため、改行を除く全ての文字列に対して空白行を返します。

 そのため、ドット(.)で分割したい場合は、「『「\』記号を使用してドット(.)区切り記号をエスケープするように」と上記の「about_Split」オペレーターのドキュメントに説明されています。このドット(.)の扱いも、PowerShellの「Split()」メソッドでは「\」記号と同様にエスケープする必要はありませんでした。

 また、Windows PowerShellではエスケープする/しないに関係なく、同じ結果が返ってきました(画面5)。このことからも、PowerShellの「Spilit()」メソッドの方が本来の実装であると思えます。

画面5
画面5 ドット(.)の扱いも、「Split()」メソッドでは注意が必要

 もし、あなたが何か管理用のスクリプトを書いたのなら、特にシステムに変更を加える操作をしようとしているのなら、想定される環境で問題がないかどうかをテストしてください。例えば、Windows用のPowerShellスクリプトなら、少なくとも、最新のWindows PowerShell 5.1とPowerShell 7.4.1でテストしましょう。

筆者紹介

山市 良(やまいち りょう)

岩手県花巻市在住。Microsoft MVP 2008 to 2024(Cloud and Datacenter Management)。SIer、IT出版社、中堅企業のシステム管理者を経て、フリーのテクニカルライターに。Microsoft製品、テクノロジーを中心に、IT雑誌、Webサイトへの記事の寄稿、ドキュメント作成、事例取材などを手掛ける。個人ブログは『山市良のえぬなんとかわーるど』。近著は『Windows版Docker&Windowsコンテナーテクノロジ入門』(日経BP社)、『ITプロフェッショナル向けWindowsトラブル解決 コマンド&テクニック集』(日経BP社)。


Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る