🔥

dotnet, windowsの環境変数の動き

2022/09/25に公開約5,200字

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

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

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上書きのため、
環境変数の展開が不能になる

まとめ

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

ログインするとコメントできます