🔥

dotnet, windowsの環境変数の動き

2022/09/25に公開

環境変数いじるコマンドレット作っているので、
自分用メモ

windowsの環境変数のバグ仕様についてはconpass資料のスライド
にあるので、興味ある人は見てください。
connpass

値の取得

プロセス環境変数は、レジストリには展開されない。

# システム環境変数 HKLM -> HKEY_LOCAL_MACHINE 
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
# 値のみ取得
Get-ItemPropertyValue "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name Path
# キーの型を取得 ExpandString ([Microsoft.Win32.RegistryValueKind]) -> REG_EXPAND_SZ (レジストリ上)
(Get-Item "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment").GetValueKind('Path')

# ユーザー環境変数  HKCU -> HKEY_CURRENT_USER
Get-ItemProperty "HKCU:\Environment\"
# 値のみ取得
Get-ItemPropertyValue "HKCU:\Environment\" -Name Path
# REG_SGなら%%があるものでも勝手に展開しない。
Get-ItemPropertyValue "HKCU:\Environment\" -name "abcd"
# REG_EXPANd_SGなら%%があるものは展開する。(存在しない変数の場合は展開しない。)
Get-ItemPropertyValue "HKCU:\Environment\" -name "abcd"
# REG_MULTI_SZならちゃんと配列で取得できる。(エディタ上も複数扱い)
Get-ItemPropertyValue "HKCU:\Environment\" -Name abcdef
# $(powershellの変数のマーク)があってもそのまま表示される。
Get-ItemPropertyValue "HKCU:\Environment\" -Name abc

# キーの型を取得 ExpandString ([Microsoft.Win32.RegistryValueKind]) -> REG_EXPAND_SZ (レジストリ上)
(Get-Item "HKCU:\Environment").GetValueKind('Path')

# ユーザー環境変数(揮発性)
Get-ItemProperty "HKCU:\Volatile Environment\"

New-ItemProperty "HKCU:\Environment" -Name abc -Value "%rewrew%"

# レジストリ上でREG_SGの場合は%%があっても展開されない。(変数がある、無し問わず。)
[Environment]::GetEnvironmentVariable("abcd","User")
# レジストリ上でREG_SGで%%があっても勝手にREG_EXPAND_SGにならない。
[Environment]::GetEnvironmentVariable("abcd","User")
# REG_EXPAND_SGの場合でもその変数が存在しなければ展開されない。%変数名%で値が取得される
[Environment]::GetEnvironmentVariable("abcd","User")

# REG_MULTI_SGの場合は取得できない。
[Environment]::GetEnvironmentVariable("abcd","User")

# REG_MULTI_SGの文字列はstring[]と表示される。
# Bynaryも同様System.Byte[]
# unkownも同様System.Byte[]
# Dword, QWordの値はIntとして見れる。
[Environment]::GetEnvironmentVariables("User")
# ただ、DWORD, Qwordの値は入れれるが、値をしたで取得できない。
[Environment]::GetEnvironmentVariable("abcd","User")

キーの作成

New-Itempropertyは右の方で指定するのでNew-Itempropertyに合わせる。

REG_SG -> String
REG_EXPAND_SZ -> MultiString

New-ItemPropertyを使う場合。

# ユーザー環境変数  HKCU -> HKEY_CURRENT_USER
# propertyTypeを使わない場合はStringになる。
New-ItemProperty "HKCU:\Environment\" -Name abc -Value "rewrew"
# %を見つけても勝手にREG_EXPAND_SZに変更しない。(レジストリエディタと同じ動き。)
New-ItemProperty "HKCU:\Environment\" -Name abc -Value "%rewrew%"
# 配列の環境変数(あるのか?そんなの)
New-ItemProperty "HKCU:\Environment\" -Name abcdef -PropertyType MultiString  -Value "hello", "world", "sir"

# 値のみ取得
Get-ItemPropertyValue "HKCU:\Environment\" -Name Path
# キーの型を取得 ExpandString ([Microsoft.Win32.RegistryValueKind]) -> REG_EXPAND_SZ (レジストリ上)
(Get-Item "HKCU:\Environment").GetValueKind('Path')

上書き
新規作成は上と一緒。

# REG_MULTI_SZの型があらかじめ設定されている場合、REB_EXPAND_SGに変更される
# みたいな動きは無かった。
# Typeを設定しない場合は前のものがそのまま使われている。
# これはユーザーが期待する正常な動き。
# 他の型も同様だった。
Set-ItemProperty "HKCU:\Environment\" -Name abcdef -Value "%COMMONPROGRAMFILES%"
# %がいっこだけ含まれていてもREG_EXPAND_SGにはならない。
[Environment]::SetEnvironmentVariable("abcd", "%rewre", "User")
# もともと型がREG_EXPAND_SGになってても勝手にREG_SGに変更される。
[Environment]::SetEnvironmentVariable("abcd", "rewre", "User")
# %が一個だけの場合もREG_SGとして登録される。
[Environment]::SetEnvironmentVariable("abcd", "%rewre", "User")
# %%と存在が確定されている変数でもREG_SGとして登録されるし、もともとREG_EXPAND_SGとして登録されていても
# まさかの上書き。
[Environment]::SetEnvironmentVariable("abcd", "%CommonProgramFiles%", "User")

Edit the Environment Variableの動き。

%%がある環境変数を更新にせよ、保存にせよ実行した時点で
勝手にREG_EXPAND_SZになる。
Edit the Environment Variableは扱いが壊れている。

Edit the Environment Variableは%を一個だけの場合も
なぜかREG_EXPAND_SGに変更される(SetEnvironmentVariableと動きが違う)。

複数行文字列REG_MULTI_SZで登録した環境変数はここから見えない(レジストリエディタでは見える)。
複数行文字列を一行にしてもここから見えない。
COMMONPROGRAMFILES

また、このアプリの起動になぜか管理者権限が必要になるため、
管理者でないと自分の環境変数の操作をGUIからできない

おそらく、雑にユーザーの環境変数とシステム環境変数の操作を同じアプリ、画面から
できるように作ったので、「管理者権限が無いと危険だよね」という事で後付けでそういう作りになったのだと思う。

一応、setxコマンドと、コントロールパネルとregistry editorとSet-ItemPropertyから環境変数を操作することは
できるが、コントロールパネルは古のアプリなので今後廃止されるのと、setxには後述のバグがある。

SetEnvironmentVariableで%Program Filesなど%

明示的にpowershellx86, x64bitを呼び出す

C:\Program Files\PowerShell\7\pwsh.exe
Start-Process "C:\Program Files (x86)\PowerShell\7\pwsh.exe"
64bitのcmdが無い。

64bit上でpowershell x86, x64からcmdを起動させた時の
動きの違い。

変数 | 32bit | 64bit
%CommonProgramFiles% | C:\Program Files (x86)\Common Files | C:\Program Files\Common Files
%COMSPEC% | C:\Windows\system32\cmd.exe | C:\Windows\system32\cmd.exe
%PROCESSOR_ARCHITECTURE% | x86 | AMD64
%ProgramFiles% | C:\Program Files | C:\Program Files (x86)

REG_EXPAND_SGで登録して、32bit, 64bitに合わせて展開されることが期待されるが、
SetEnvironmentVariable()だとまさかのREG_SG上書きのため、
環境変数の展開が不能になる

setxの動き

環境変数の値の長さが1024を超えると、まさかの切り捨て。エラーも発生せずに警告のみで強引に切り捨てて保存します。

環境変数の値が1024文字なんて超えるわけないと思う方も多いと思いますが、windowsは自由にいろんな所に
プログラムをおける都合上、PATHが簡単に1024文字は超えます。
下のパスはすべてwinget経由でインストールしたアプリで、自分で変な位置にプログラムを
置いたわけではないですが、1024文字を超えていました。

setx PATH "C:\Ruby31-x64\bin;C:\Program Files\PowerShell\7;C:\Program Files\Microsoft\jdk-11.0.18.10-hotspot\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\Windows Kits\10\Microsoft Application Virtualization\Sequencer\;C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\;C:\Program Files\dotnet\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;C:\Program Files\swift\icu-69.1\usr\bin;C:\Program Files\swift\runtime-development\usr\bin;C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin;C:\HashiCorp\Vagrant\bin;C:\Program Files\Go\bin;C:\Program Files\PowerShell\7\;C:\Program Files\Docker\Docker\resources\bin;C:\Program Files\SqlCmd\Tools\;C:\Program Files\nodejs\;C:\Program Files\GitHub CLI\;C:\Program Files\Git\cmd;C:\Users\science\AppData\Local\Microsoft\WindowsApps;C:\Users\science\AppData\Local\Programs\Microsoft VS Code\bin"

実行すると下記のように表示され、環境変数の値が書き換わります

WARNING: The data being saved is truncated to 1024 characters.
SUCCESS: Specified value was saved.

はっきり言って、仕様と言い張ったら仕様になるけど実際の動きはバグそのものです。

いやいや、成功したらあかんやろ..
この場合はRegistry Editorか、Set-ItemPropertyから操作しましょう。
HKEY_CURRENT_USER\Environmentに環境変数の値があります。

まとめ

Edit the Environment Variableが
[Environment]::SetEnvironmentVariableを使っているから
バグがあるとかそういうのだと思ったけど、違う由来のバグっぽい。

ずっと放置されているバグなので本当にもう...

レジストリの動きに合わせて、
型がREG_EXPAND_SGで登録されていたら、変数の展開、変数無い場合はそのまま表示
という形がよさそう。
型がREG_SGなら問答無用でそのまま表示される。
Edit the Environment VariableとSetEnvironmentVariableは使わんほうが良い。
動き自体がバグ臭い。

GetEnvironmentVariable,
GetEnvironmentVariablesも動きがバグ臭いが
ギリ仕様で通る。ただレジストリと動きが違う。

環境変数として想定しているのは
String, ExpandStringかな。

ユーザー環境変数、 システム環境変数で無い場合(Process)はレジストリが絡まないので、
REGなんたら考えなくてよくて、壊れてはいない感じはする。

これは.net Framework時代からのバグで
最新の.net 7.0 rcでもこのバグは残っているぞ!
やっべえな...

参考

github

Discussion