【PowerShell】読み取り権限しかないレジストリキーのアクセス権限を変更する

# はじめに
Windowsのコアな機能を強引に無効化したいときに、直接レジストリを書き換えるしか手がないという、よくある（？）ケース。
それを自動化するときは、バッチファイルで`REG`コマンドを使ってやれば概ね実現できる。
ただ、稀に対象のレジストリキーのアクセス権が変更されていて、`Administrators`グループにフルコントロールが割り当てられていない時がある。
困ったことに、`REG`コマンドではアクセス権を編集できなくて、バッチファイルからどうにかするのは難しそう・・・（`regini`コマンドで出来そうな雰囲気はあったけど、外部ファイルを用意する必要があったり微妙すぎた）

さらに調べてみると、できれば触りたくない`PowerShell`の`Set-Acl`を使えば出来るらしい。
（普段は、いちいちPowerShell使うくらいならC#でサクッと書いちゃう派）

# よく見るPowerShellでレジストリキーのACLの変え方
`Get-Acl`で取得したレジストリキーのACLを編集して、`Set-Acl`で反映するやつ。
```powershell
$acl = Get-Acl "HKLM:\SYSTEM\TEST\AAA"
$ruleFullControl = New-Object System.Security.AccessControl.RegistryAccessRule ("BUILTIN\Administrators", [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)
$acl.SetAccessRule($ruleFullControl)
$acl | Set-Acl "HKLM:\SYSTEM\TEST\AAA"
```
これを読み取り専用になったレジストリキーで実行すると・・・
```
Set-Acl : 要求されたレジストリ アクセスは許可されていません。
発生場所 T:\TEST.ps1:103 文字:36
+                             $acl | Set-Acl -Path Registry::$key
+                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : PermissionDenied: (HKEY_LOCAL_MACHINE\SYSTEM\TEST\AAA:String) [Set-Acl], SecurityException
    + FullyQualifiedErrorId : System.Security.SecurityException,Microsoft.PowerShell.Commands.SetAclCommand
````
(´・ω・｀)
レジストリエディターでは普通に編集できるのだから、なにか手はあるはず・・・

# Microsoft.Win32.Registry クラスを使う
.NETのクラスを使って何とかする技を見つけた。
```powershell
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("SYSTEM\TEST\AAA", [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::ChangePermissions)
$acl = $key.GetAccessControl()
$ruleFullControl = New-Object System.Security.AccessControl.RegistryAccessRule ("BUILTIN\Administrators", [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)
$acl.SetAccessRule($ruleFullControl)
$key.SetAccessControl($acl)
$key.Close()
```
これなら`Administrators`グループのアクセス権限をフルコントロールに変更できた。

ちなみに同時に値も読み取りたいときは、`OpenSubKey`の第３引数を
`[System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::ReadKey`
にする。

値を書き込むときは、
`[System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::WriteKey`
にする。
（`FullControl`を設定してからじゃないと、`OpenSubKey`メソッドで失敗する）

# バッチファイルからps1を実行する
既にバッチファイルで色々と組んでいて、丸ごとPowerShellに移植するのは面倒くさい・・・
ってことで、バッチファイルからps1ファイルを実行することに。
```bat
powershell -ExecutionPolicy Bypass -File %~dp0\TEST.ps1
```
既定の設定だとPowerShellのスクリプトが実行できないので、`-ExecutionPolicy Bypass`で回避。

# 所有者がTrustedInstallerのキーでも変更する(2023/9/26追記)
以前書いた方法だけでは、`TrustedInstaller`が所有者になっているキーのアクセス権限を変更できないことが分かったので、さらに強引な手段を取ることに・・・
さらに値と書き換えたあとに、所有者やアクセス権の設定を元に戻せるように工夫もしてみた。

まず、別記事に書いた特権を有効にする方法を使って、２つ特権を有効にする。

https://qiita.com/mitsu_at3/items/889a96b37f69ffe089f0

```PowerShell
Set-Privilege SeTakeOwnershipPrivilege $true
Set-Privilege SeRestorePrivilege $true
```

それから所有者を`Administrators`グループに変更。
一連の処理が終わったあとに、所有者を元に戻せるように保持しておく。

```PowerShell
$path = "SYSTEM\CurrentControlSet\Control\Diagnostics\Performance"

# 所有者変更の権限を有効にして開く
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($path, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::TakeOwnership)

# 現在の所有者を記憶
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::Owner)
$oldOwner = $acl.GetOwner([System.Security.Principal.NTAccount])

# 所有者を[Administrators]に変更
$adminAccount = [System.Security.Principal.NTAccount]("BUILTIN\Administrators")
$acl.SetOwner($adminAccount)
$key.SetAccessControl($acl)

# １度閉じる
$key.Close()
```

次に、`Administrators`グループのアクセス権をを変更。
既にフルコントロールの権限がある場合は何もしなかったり、ここでも元に戻せるよう少し小細工。

```Powershell
# アクセス権限の操作を有効にして開く
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($path, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::ChangePermissions)

# 現在のアクセス権限を取得
$acl = $key.GetAccessControl()
foreach ($rule in $acl.GetAccessRules($true, $true, [System.Security.Principal.NTAccount])) {
    # [Administrators]グループのルールが対象
    if ($rule.IdentityReference.Value -eq "BUILTIN\Administrators") {
        # サブキーのみが対象のものは除外
        if ($rule.PropagationFlags -ne [System.Security.AccessControl.PropagationFlags]::InheritOnly) {
            # フルコントロールの権限がない
            if ($rule.RegistryRights -ne [System.Security.AccessControl.RegistryRights]::FullControl) {
                $ruleFullControl = New-Object System.Security.AccessControl.RegistryAccessRule ("BUILTIN\Administrators", [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)
                if ($rule.IsInherited) {
                    # 一時的なルールを記録
                    $tmpRule = $ruleFullControl
                    # フルコントロールを追加
                    $acl.AddAccessRule($ruleFullControl)
                } else {
                    # 元のルールを記録
                    $oldRule = $rule
                    # フルコントロールで上書き
                    $acl.SetAccessRule($ruleFullControl)
                }
                $key.SetAccessControl($acl)
                break;
            }
        }
    }
}

# １度閉じる
$key.Close()
```

ようやく値の編集。

```Powershell
# 書き込み権限+αの操作を有効にして開く
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($path, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::TakeOwnership -bor [System.Security.AccessControl.RegistryRights]::ChangePermissions -bor [System.Security.AccessControl.RegistryRights]::WriteKey)

# 値を編集
$key.SetValue("DisableDiagnosticTracing", 1, [Microsoft.Win32.RegistryValueKind]::DWord)
```

最後に所有者とアクセス権を元に戻して閉じる。

```Powershell
# ACLを元に戻す
$acl = $key.GetAccessControl()

if ($tmpRule -ne $null) {
    # 一時的なルールを削除
    $acl.RemoveAccessRule($tmpRule)
    $tmpRule = $null
} elseif ($oldRule -ne $null) {
    # アクセス権限を編集した場合は元に戻す
    $acl.SetAccessRule($oldRule)
    $oldRule = $null
}

# 所有者を元に戻す
$acl.SetOwner($oldOwner)
$key.SetAccessControl($acl)

$key.Close()
```

これで、`TrustedInstaller`が所有しているキーの値でも、強引に書き換えて、何事もなかったかのようにACLが元に戻るようにできた。

### ハマったポイント

所有者を「`TrustedInstaller`から`Administrators`に書き換える」というのは直ぐに出来たけど、「`TrustedInstaller`に戻す」というのが上手くいかなくて手こずった・・・

結果的には、有効にする特権が `SeTakeOwnershipPrivilege` だけではなく `SeRestorePrivilege` も必要だった。

調べてる最中に、`TrustedInstaller`が所有しているフォルダやファイルで、同じように一時的に所有者を変えて元に戻そうとすると上手くいかないって話をちらほら見かけたけど、それも同様の方法で元に戻せるはず。

# おわりに
なにかと面倒で、どうにも好きになれないPowerShellくん・・・