PowerShell スクリプトの実行要件を指定する #Requires

# 概要

`#Require` ステートメントにバージョンやモジュールなどを指定することで、スクリプトの実行要件を満たさない環境での実行を制限することができます。

# テスト用スクリプト作成

適当に PowerShell スクリプトを作成します。

```posh:TestScript.ps1
Write-Output -InputObject "Hello World!"
```

```posh:実行
> .\TestScript.ps1
Hello World!
```

# Requires Version

スクリプトの実行に必要なミニマムのバージョンを指定します。

```posh:TestScript.ps1
#Requires -Version 5.1
Write-Output -InputObject "Hello World!"
```

PowerShell 5.1 で実行してみます。問題なく実行できますね。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
Hello World!
```

PowerShell 7.1 でも実行してみます。ミニマムバージョンを指定しているので PowerShell 7.1 でも問題ありません。

```posh:PowerShell7.1で実行
> .\TestScript.ps1
Hello World!
```

必要なバージョンを 7.1 にあげてみましょう。

```posh:TestScript.ps1
#Requires -Version 7.1
Write-Output -InputObject "Hello World!"
```

Powershell 5.1 で実行するとバージョンが合ってないので怒られます。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
.\TestScript.ps1 : スクリプト 'TestScript.ps1' を実行できません。このスクリプトには Windows PowerShell 7.1 用の "#requires" ステートメントがありますが、このスクリプトで必要な Windows PowerShell のバージョンが、現在実行されている Windows PowerShell のバージョン 5.1.19041.1023 と一致しません。
発生場所 行:1 文字:1
+ .\TestScript.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (TestScript.ps1:String) [], ScriptRequiresException
    + FullyQualifiedErrorId : ScriptRequiresUnmatchedPSVersion
```

PowerShell 7.1 だと問題なく実行できます。

```posh:PowerShell7.1で実行
> .\TestScript.ps1
Hello World!
```

# Requires PsEdition

Windows PowerShell（PowerShell 5.1 以前）か PowerShell Core（PowerShell 6.0 以降）を指定します。設定値は Windows PowerShell の場合は `Desktop`、PowerShell Core の場合は `Core` です。

`Desktop` を指定してみます。

```posh:TestScript.ps1
#Requires -PsEdition Desktop
Write-Output -InputObject "Hello World!"
```

PowerShell 5.1 では問題なく実行できます。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
Hello World!
```

PowerShell 7.1 で実行するとエディションが違うと言って怒られます。

```posh:PowerShell7.1で実行
> .\TestScript.ps1
.\TestScript.ps1: The script 'TestScript.ps1' cannot be run because it contained a "#requires" statement for PowerShell editions 'Desktop'. The edition of PowerShell that is required by the script does not match the currently running PowerShell Core edition.
```

`Core` を指定してみます。

```posh:TestScript.ps1
#Requires -PsEdition Core
Write-Output -InputObject "Hello World!"
```

PowerShell 5.1 で実行するとエディションが違うと言って怒られます。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
.\TestScript.ps1 : スクリプト 'TestScript.ps1' を実行できません。このスクリプトには PowerShell エディション 'Core' 用の "#requires" ステートメントがありますが、このスクリプトで必要な PowerShell のエディションが、現在実行されている PowerShell の Desktop エディションと一致しません。
発生場所 行:1 文字:1
+ .\TestScript.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (TestScript.ps1:String) [], RuntimeException
    + FullyQualifiedErrorId : ScriptRequiresUnmatchedPSEdition
```

PowerShell 7.1 だと問題なく実行できます。

```posh:PowerShell7.1で実行
> .\TestScript.ps1
Hello World!
```

# Require RunAsAdministrator

管理者権限でないと実行できないようにします。

```posh:TestScript.ps1
#Requires -RunAsAdministrator
Write-Output -InputObject "Hello World!"
```

一般ユーザー権限で実行すると PowerShell 5.1/7.1 ともに失敗します。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
.\TestScript.ps1 : スクリプト 'TestScript.ps1' を実行できません。このスクリプトには管理者として実行する "#requires" ステートメントが含まれています。現在の Windows PowerShell セッションは管理者として実行されていません。[管理者として実行]オプションを使用して Windows PowerShell を起動し、スクリプトを再度実行してください。
発生場所 行:1 文字:1
+ .\TestScript.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (TestScript.ps1:String) [], ScriptRequiresException
    + FullyQualifiedErrorId : ScriptRequiresElevation
```

```posh:PowerShell7.1で実行
> .\TestScript.ps1
.\TestScript.ps1: The script 'TestScript.ps1' cannot be run because it contains a "#requires" statement for running as Administrator. The current PowerShell session is not running as Administrator. Start PowerShell by using the Run as Administrator option, and then try running the script again.
```

管理者権限だとどちらも問題なく実行できます。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
Hello World!
```

```posh:PowerShell7.1で実行
> .\TestScript.ps1
Hello World!
```

# Requires Modules

モジュールを指定します。必要なモジュールがない場合、スクリプトの実行に失敗します。PowerShell 5.1/7.1 ともに `Az` モジュールインストール済みの環境で実行してみます。

```posh:TestScript.ps1
#Requires -Modules Az.Accounts
Write-Output -InputObject "Hello World!"
```

PowerShell 5.1/7.1 ともに実行できました。実行時に Requires に指定されたモジュールが自動的にインポートされます。

```posh:PowerShell5.1で実行
> Get-Module -Name Az.Accounts

> .\TestScript.ps1
Hello World!

> Get-Module -Name Az.Accounts

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Script     2.5.1      Az.Accounts                         {Add-AzEnvironment, Clear-AzContext, Clear-AzDefault, Conn...
```

```posh:PowerShell7.1で実行
> Get-Module -Name Az.Accounts

> .\TestScript.ps1
Hello World!

> Get-Module -Name Az.Accounts

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Script     2.5.1                 Az.Accounts                         {Add-AzEnvironment, Clear-AzContext, Clear-AzDefa…
```

インストールされていないモジュールを指定してみましょう。

```posh:TestScript.ps1
#Requires -Modules AzureAd
Write-Output -InputObject "Hello World!"
```

そんなモジュールないよ！って怒られます。

```posh:PowerShell5.1で実行
> .\TestScript.ps1
.\TestScript.ps1 : スクリプト 'TestScript.ps1' を実行できません。このスクリプトの #requires ステートメントで指定された次のモジュールがありません: AzureAd。
発生場所 行:1 文字:1
+ .\TestScript.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (TestScript.ps1:String) [], ScriptRequiresException
    + FullyQualifiedErrorId : ScriptRequiresMissingModules
```

```posh:PowerShell7.1で実行
> .\TestScript.ps1
.\TestScript.ps1: The script 'TestScript.ps1' cannot be run because the following modules that are specified by the "#requires" statements of the script are missing: AzureAd.
```

モジュールのバージョンを指定することもできます。

最小バージョンを指定する場合。

```posh:TestScript.ps1
#Requires -Modules @{ ModuleName="Az.Accounts"; ModuleVersion="2.5.1" }
Write-Output -InputObject "Hello World!"
```

特定のバージョンを指定する場合。

```posh:TestScript.ps1
#Requires -Modules @{ ModuleName="Az.Accounts"; RequiredVersion="2.5.1" }
Write-Output -InputObject "Hello World!"
```

最大バージョンを指定する場合。

```posh:TestScript.ps1
#Requires -Modules @{ ModuleName="Az.Accounts"; MaximumVersion="2.5.1" }
Write-Output -InputObject "Hello World!"
```

## 補足：Az を指定した場合の PowerShell 5.1 と PowerShell 7.1 の挙動の違い

Requires に Az を指定すると…

```posh:TestScript.ps1
#Requires -Modules Az
Write-Output -InputObject "Hello World!"
```

PowerShell 5.1 では Az モジュールがインストールされていても実行に失敗します。あらかじめ Az モジュールをインポートしておく必要があります。

```posh:PowerShell5.1で実行
> Get-InstalledModule -Name Az

Version              Name                                Repository           Description
-------              ----                                ----------           -----------
6.2.1                Az                                  PSGallery            Microsoft Azure PowerShell - Cmdlets t...

> .\TestScript.ps1
.\TestScript.ps1 : スクリプト 'TestScript.ps1' を実行できません。このスクリプトの #requires ステートメントで指定された次のモジュールがありません: Az。
発生場所 行:1 文字:1
+ .\TestScript.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (TestScript.ps1:String) [], ScriptRequiresException
    + FullyQualifiedErrorId : ScriptRequiresMissingModules

> Import-Module Az

> .\TestScript.ps1
Hello World!
```

一方 PowerShell 7.1 では問題なく実行できます（Az の関連モジュールが自動的にインポートされます）。

```posh:PowerShell7.1で実行
> .\TestScript.ps1
Hello World!
```

そもそもルート Az モジュールをインポートすることは推奨されません（関連モジュールが大量にインポートされてパフォーマンスの問題が発生する可能性があるため）。互換性の問題もありそうなので Az に関わらず必要なモジュールだけを指定するようにしましょう。

# まとめ

他にもいくつか指定できる条件はありますが、主に使いそうなのはこんなところかと。現状 PowerShell Core（7 系）への移行の過渡期で、旧環境でしか動かないモジュールやスクリプトもまだまだあるので、できるだけ実行環境を明示しておきたいですね。