PowerShellの実行ポリシー変更

# 概要
クライアント版 Windows[^1] の PowerShell は、標準設定ではスクリプト（拡張子 .ps1 のファイル）の実行が制限されています。これは、マルウェアなどの危険なスクリプトの不用意な実行を防ぐというセキュリティ上の配慮によるものです。スクリプトを実行するには、事前に実行ポリシーを変更する必要があります。個人用のスクリプトを特別な権限なしで実行するための実行ポリシーの変更方法をまとめます。

[^1]: サーバ版でない方の Windows のことです。

# 実行ポリシーとスコープ
予備知識として、実行ポリシーの種類やスコープについて知っておりた方がよいです。
スコープは、優先度順に MachinePolicy（グループポリシーでそのコンピュータの全ユーザに強制）、UserPolicy（グループポリシーでそのコンピュータの現在のユーザに強制）、Process（現在のセッションに対して適用）、CurrentUser（現在のユーザに対して適用）、LocalMachine（そのコンピュータの全ユーザに対して適用）の5種類があります。

それぞれのスコープについて、AllSigned（署名付きスクリプトのみ実行可）、Bypass（検査迂回）、RemoteSigned（ローカルスクリプトと署名付きのリモートスクリプトのみ実行可）、Restricted（全て実行不可）、Undefined（未定義）、Unrestricted（全て実行可だがリモートスクリプトに対しては警告が出力される）の6種類のポリシーを設定することができます。ただし、特別な権限なしに変更できるのは Process、CurrentUser の2種類のみです。

各スコープの既定のポリシーは Undefined です[^101]。全てのスコープのポリシーが Undefined の場合は、クライアント版の Windows では Restricted のポリシーが適用されるため標準状態ではスクリプトを実行することが出来ません。日本語では、下記のサイトの説明が詳しいです。

https://www.atmarkit.co.jp/ait/articles/0805/16/news139.html

[^101]: PowerShell 7 では LocalMachine スコープが既定で RemoteSigned に設定されており、ローカルスクリプトの実行に制約がなくなりました。なお Windows 以外の OS の場合は Unrestricted 固定です（実質的に Bypass と同等）。

# 実行ポリシーの変更
実行ポリシーは、`Set-ExecutionPolicy` コマンドレット（または PowerShell コマンドの -ExecutionPolicy パラメータ）を用いて変更できますが、-Scope パラメータを指定しない場合の既定のスコープである LocalMachine の実行ポリシーを変更するには管理者権限が必要です[^2] [^3]。Process や CurrentUser のスコープであれば管理者権限なしに実行ポリシーを変更することができます。Process スコープの実行ポリシーの変更は現在のセッションのみ有効ですが、CurrentUser のスコープの実行ポリシーは一度変更するとその設定はセッション終了後も有効になります。以下に、「セキュリティ重視」、「利便性重視」、「バランス」の３通りの変更方法を紹介します[^4]。

[^2]: `-Scope` パラメータを指定しないで `Set-ExecutionPolicy` コマンドレットを実行すると、LocalMachineの実行ポリシーを変更することになり、管理者として実行していない場合はエラーになります。PowerShell の初心者は、ここで躓くことが多いかと思います。
[^3]: グループポリシーによって優先度の高い MachinePolicy や UserPolicy スコープの実行ポリシーが Restricted に設定されている場合は、残念ながらローカルユーザ（アドミニストレーターを含む）はスクリプトの実行を可能にすることができません。　備考：バッチファイルやVBSなどからスクリプトブロックとして実行することは可能（⇒ [こちら](https://qiita.com/earthdiver1/items/cab769aad623a03a0f2d)の記事を参照）。

[^4]: 私なりの分類です。

## a) セキュリティ重視
なるべく（セキュリティ重視の）標準設定から変更したくない方は、下記のコマンドで一時的に実行ポリシーを変更するとよいでしょう。現在のセッション（プロセスとその子プロセス）に限り、ローカルで作成された（インターネット経由で入手されていない）スクリプトを署名なしで実行できるようになります。また、インターネット経由で入手したスクリプトについても、ファイルのプロパティでブロックの解除を行なうことで実行できるようになります。

```powershell:
Set-ExecutionPolicy RemoteSigned -Scope Process -Force
```

## b) 利便性重視
逆に、セキュリティよりも利便性優先という方は、スコープを CurrentUser にすることで毎回実行ポリシーを変更する手間を省くことができます。

```powershell:
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
```

## c) バランス
一定のセキュリティを確保しつつ、よく使用するスクリプトについてはすぐに実行できるようにしたい方は、CurrentUser スコープの実行ポリシーを AllSigned にするとよいでしょう。

```powershell:
Set-ExecutionPolicy AllSigned -Scope CurrentUser -Force
```

署名付きのスクリプトはすぐに実行できます。このポリシーでは署名のないスクリプトは実行できないので、署名のない（ローカルの）スクリプトを実行する際には a) のコマンドで一時的にポリシーを変更します。署名付きの設定ファイル（\$Profile）を利用できるので、例えば下記のように a) のコマンドの関数を定義しておくと便利です。

```powershell:
function ps1ok {Set-ExecutionPolicy RemoteSigned -Scope Process -Force}
function ps1ng {Set-ExecutionPolicy Undefined    -Scope Process -Force}
```

スクリプトの実行を伴う作業の開始前と終了後に、それぞれ ps1ok、ps1ng コマンドを実行します。

# 実行ポリシーの確認
スコープ毎の実行ポリシーを `Get-ExecutionPolicy` コマンドレットで確認することができます。
下記は、CurrentUser スコープの実行ポリシーを AllSigned に変更した場合の出力結果です。

```
PS C:\> Get-ExecutionPolicy -List

        Scope ExecutionPolicy
        ----- ---------------
MachinePolicy       Undefined
   UserPolicy       Undefined
      Process       Undefined
  CurrentUser       AllSigned
 LocalMachine       Undefined

```


# スクリプトに署名する方法
実行ポリシーが AllSinged の場合は スクリプトが署名されている必要があります。*信頼のできる第三者認証機関*の証明書による署名というのが建前ですが、個人用途ならば自分で作成した自己署名証明書をローカルコンピューター上の信頼されたルート証明機関ストアに登録して署名することで代替できます。ここではその方法を紹介します。

## 1. 署名用の証明書を作成

Windows 10, PowerShell V5 以降は、`New-SelfSignedCertificate` コマンドレットを使用して署名用の証明書を作成することができます。

```powershell:
# 署名用の証明書を作成
$cert = New-SelfSignedCertificate `
        -Subject "CN=PowerShellスクリプト署名用証明書" `
        -KeyAlgorithm RSA `
        -KeyLength 2048 `
        -Type CodeSigningCert `
        -CertStoreLocation Cert:\CurrentUser\My\ `
        -NotAfter ([datetime]"2099/01/01")
# 信頼済みのルートとして使用
Move-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" Cert:\CurrentUser\Root
```

Windows 8 では certreq コマンドを使用して署名用の証明書を作成することができます。

```powershell:
# INFファイル作成
Write-Output @"
[NewRequest] 
Subject = "CN=PowerShellスクリプト署名用証明書" 
Exportable = TRUE
KeyAlgorithm = RSA
KeyLength = 2048
RequestType = Cert
NotAfter = "2099/01/01"
[Extensions] 
2.5.29.37 = "{text}1.3.6.1.5.5.7.3.3"
"@ > cert.inf
# 署名用の証明書を作成
certreq -new cert.inf dummy.cer
# 信頼済みのルートとして使用
$cert = @(Get-ChildItem cert:\CurrentUser\My -CodeSigningCert)[0]
Move-Item "Cert:\CurrentUser\My\$($cert.Thumbprint)" Cert:\CurrentUser\Root
```

（2024.7.18追記）**kita77777** さんより 2030年以降 4096bit 未満の KeyLength は使用できなくなる可能性が高い旨ご指摘いただきました。長く利用される可能性のある方は KeyLength に 4096 を指定することをお薦めします。

## 2. PowerShellスクリプトに署名

1.で作成した証明書を用いてスクリプトに署名します[^3]。

[^3]: 信頼されたルート証明機関ストアに複数のコード署名用の証明書が存在する場合は、Get-ChildItem コマンドレットが返す最初の証明書が使用されます。

```powershell:
# 証明書への参照を取得
$rootcert = @(Get-ChildItem cert:\CurrentUser\Root -CodeSigningCert)[0]
# スクリプトに署名
Set-AuthenticodeSignature <対象スクリプトのパス> $rootcert
```

## 3. $Profile の署名を更新するコマンドを定義
\$Profile ファイルは度々更新する可能性があるので、署名を更新するためのコマンド（関数）を定義しておくと便利です(`. signprofile` のようにドットソースで実行すると、更新した内容をすぐに現在のセッションに反映することができます)。

```powershell:
function SignProfile {
    $rootcert = @(Get-ChildItem cert:\CurrentUser\Root -CodeSigningCert)[0]
    Set-AuthenticodeSignature $Profile $rootcert
    . $Profile
}
```


　
クリエイティブ・コモンズ 表示 - 継承 4.0 国際