The Man Who Fell From The Wrong Side Of Sky:2019年5月6日分

2019/5/6(Mon)

[Windows] PowerShellで生活するために - Requires宣言編

だいたいのスクリプト言語においては前方互換性のない変更、例えばPerl 5.10で導入されたswicth文なんかの新文法(今更かよ)を使いたい場合には

use 5.10;
use feature ":5.10";

のように記述して、これより古い環境ならエラーにするとか新機能をアンロックするみたいな機能が備わってることが多いと思われる。

同様の機能はPowerShellにも存在して

#Requires -Version メジャー番号(.マイナー番号)

と指定することで特定のバージョン以下の環境での実行を禁止する機能がある。

#Requires -Version 5.1

と指定しておけば5.1に満たないバージョンのPowerShellで実行するとエラーになる。

ちなみにこれはコマンドレットではなくコメント内に書くステートメントいう扱いなので、スクリプト中にではなくプロンプトで手打ちした場合は文字通りコメントとして扱われるので無意味だ、うーんこの。

まあええわこっから本題、移植性のあるスクリプトを書こうと思ったらチェック条件としてはRequires -Versionだけじゃ全く足りないのよね。

どういうことかというと、Windows 7/Server 2008 R2に WMF5.1を導入するとWindows 10 1809と同じPowerShell 5.1環境になるのだけれど、実はサポートされるモジュールの数がまったく別物といっていいくらい異なるのだ。 後者にあって前者にないモジュールはおよそ50前後存在するし、それらはInstall-Moduleコマンドレットを使っても追加できないOSバージョン依存のモジュールだ。

例えば同じPowerShell 5.1にも関わらずWindows 7環境だとPKIモジュールがない、Windows 10なら以下のようにヒットするんだけどWindows 7では死んだオウムのごとし。

Get-Module -ListAvailable | Where-Object { $_.Name -eq 'PKI' }

    ディレクトリ: C:\Windows\system32\WindowsPowerShell\v1.0\Modules

ModuleType Version    Name                                ExportedCommands                                                                            
---------- -------    ----                                ----------------                                                                            
Manifest   1.0.0.0    PKI                                 {Add-CertificateEnrollmentPolicyServer, Export-Certificate, Export-PfxCertificate, Get-Ce...

じゃあモジュール追加すりゃいいっすねとInstall-Module叩いても

Install-Module -Name PKI

PackageManagement\Install-Package : 指定された検索条件とパッケージ名 'PKI' と一致するものが見つかりませんでした。登録さ
れている使用可能なすべてのパッケージ ソースを確認するには、Get-PSRepository を使用します。
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1772 char:21
+ ...          $null = PackageManagement\Install-Package @PSBoundParameters
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Ex
   ception
    + FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

ちゅー感じ、そもそもオーエスがWindows 8 Pro/Windows Server 2012以降でないとダメなのだ、ご愁傷さまでした。

ちなみにそれぞれのOSにおけるPowerShell 5.1 PSVersionの結果

なるほど、じゃあWindows 10のみで実行できるようRequiresにBuildも指定してやればいいと思うよね…

#Requires -Version 5.1.17763

でもこれだとWindows 7/10どちらでも実行できてしまう、というのはRequiresはMajorとMinorまでしか認識せずBuildやRevisionは無視するのだ、どんな判断だ。

そもそもこのBuildやRevisionが導入されたのはバージョン5.0からなんだけれども、TechNetでの Windows PowerShell build numbersの管理は早々に放棄されてるのでBuildとRevisionにはまともな意味づけはされていないと思っていい、公式が忘れてんだから俺達もスルー推奨やってられっかクソバージョン管理。

それではWindows 10にはあるけどWindows 7にはモジュールが存在しないからスクリプトの実行を禁止したい場合にはどうするか?それはやはりRequiresを使用して

#Requires -Modules モジュール名1,モジュール名2...

と指定したモジュールの有無を検査すればよい、モジュール自体のバージョンも指定できる(詳しくはマニュアル嫁)。

でもPerlみたいにモジュールをuseしたりrequireするわけじゃないからいちいちコマンドレットからモジュール名調べるのめんどくさいから…と思うのが人情。 Windows 10 Proでだけ実行可能とするバージョン指定がしたいなぁと考える人は多いのではないだろうか。

しかしこれが困ったことにPowerShellにOSバージョンを取得する方法はいくつかあれど、まともに機能してるもんが今のところ無いんですわ。

つーことでそれぞれ問題があってろくなもんじゃない。

ということでめんどくさかろうがOS名でのチェックではなくRequires -Modulesを使って機能ごとにチェックすべし、たぶんそれでも非互換性ある気はしないでもないが。