連載
» 2018年03月07日 05時00分 公開

.NET TIPS:環境変数の値を取得するには?[C#/VB]

環境変数の値や、展開前の環境変数の値をレジストリから取得する方法や、GetEnvironmentVariablesメソッドで得た値をLINQで処理する方法を説明する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 一般的に環境変数の値を取得するのは簡単なことだが、アプリの実行中に変更された値や、展開される前の生の値を得ようと思うと首をひねる場合がある。本稿では、環境変数の値を取得する方法をまとめて解説する。

POINT 環境変数の値を取得する方法

環境変数の値を取得する方法まとめ 環境変数の値を取得する方法まとめ
C#の文法で示しているが、VBでも同様である。


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。

using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using static System.Console;

Imports Microsoft.Win32
Imports System.Console

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

環境変数の値を取得するには?

 アプリに与えられた環境変数の値を取得するには、Environmentクラス(System名前空間)のGetEnvironmentVariable静的メソッドかGetEnvironmentVariables静的メソッドを使う。

 取得したい環境変数が1つだけの場合は、環境変数名を指定してGetEnvironmentVariable静的メソッドを使えばよい(次のコード)。

// 環境変数「TEMP」の値を取得する
var tempPath = Environment.GetEnvironmentVariable("temp");
WriteLine($"TEMP={tempPath}");
// 出力例:TEMP=C:\Users\biac\AppData\Local\Temp

' 環境変数「TEMP」の値を取得する
Dim tempPath = Environment.GetEnvironmentVariable("temp")
WriteLine($"TEMP={tempPath}")
' 出力例:TEMP=C:\Users\biac\AppData\Local\Temp

変数名を指定して環境変数の値を取得する例(上:C#、下:VB)
テンポラリーフォルダ(一時作業フォルダ)のパスを取得する例である。
Windowsでは環境変数の名前の大文字/小文字は区別されないので、引数に与える名前の大文字/小文字はどちらでも構わない。
なお、EnvironmentクラスのGetEnvironmentVariableメソッドなどは.NET Standard(1.3以降)に含まれていて多くのプラットフォームで動作するが、予期した結果が返ってくるとは限らない。例えば、UWPでは例外は発生しないものの、環境変数の値は全く取得できない。

 全ての環境変数を列挙したい場合は、GetEnvironmentVariables静的メソッドを使えばよい(次のコード)。

IDictionary envVars = Environment.GetEnvironmentVariables();
foreach (DictionaryEntry entry in envVars)
{
  string name = entry.Key as string; // 環境変数の名前
  string value = entry.Value as string; // 環境変数の値
  WriteLine($"{name}={value}");
}
// 出力例:
// PROCESSOR_ARCHITEW6432=AMD64
// COMPUTERNAME=ELICHIKA
// CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
// (以下略)

Dim envVars As IDictionary = Environment.GetEnvironmentVariables()
For Each entry As DictionaryEntry In envVars
  Dim name As String = TryCast(entry.Key, String) ' 環境変数の名前
  Dim value As String = TryCast(entry.Value, String) ' 環境変数の値
  WriteLine($"{name}={value}")
Next
' 出力例:
' PROCESSOR_ARCHITEW6432=AMD64
' COMPUTERNAME=ELICHIKA
' CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
' (以下略)

全ての環境変数を列挙する例(上:C#、下:VB)
GetEnvironmentVariables静的メソッドが返すコレクションはジェネリックではないため、ループ内で取り出したKeyプロパティ(環境変数の名前)とValueプロパティ(環境変数の値)は文字列型にキャストする必要がある。
なお、ジェネリックコレクションでなければLINQ拡張の便利な機能は使えない。使いたいときは後述の「LINQで扱えるようにするには?」をご覧いただきたい。

 このようにして取得できる環境変数は、アプリの起動時にプロセスへ割り当てられたものである。アプリの実行中に(ユーザー操作などによって)環境変数が追加/変更されても、この方法では追加/変更後の値を取得できない。この問題の対処方法は、次の「システム/ユーザー/プロセスの環境変数」をご覧いただきたい。

 また、このようにして取得した環境変数の値は、(もしあれば)他の環境変数の値を使って展開したものになっている。例えば、最初のサンプルコードで示した環境変数「TEMP」の本来の値は「%USERPROFILE%\AppData\Local\Temp」なのだが、「%USERPROFILE%」の部分が環境変数「USERPROFILE」の値を使って展開されて「C:\Users\biac\AppData\Local\Temp」になっているのだ。読み取るときはこのように展開された方が便利だ。しかし、環境変数の値を書き込むときには、展開前の生の値を元にして設定しないとシステムに不具合を引き起こす心配がある。展開前の値を取得したい場合は、後述の「生の環境変数」をご覧いただきたい。

システム/ユーザー/プロセスの環境変数

 環境変数は、3段階に分けて管理されている。

  1. システムの環境変数:Windows起動時に設定される環境変数
  2. ユーザーの環境変数:ユーザーがログオンしたときに設定される環境変数。同名の環境変数がある場合は、ユーザー環境変数でシステム環境変数を上書きする
  3. プロセスの環境変数:アプリ起動時に設定される環境変数。上記1.と2.の環境変数がコピーされる。アプリ起動後に1.と2.の環境変数が変わっても反映されない

 前述の方法で取得できるのはプロセスの環境変数なのだ。アプリ起動時にセットされるだけなので、その後にシステムやユーザーの環境変数に変化があっても、それは反映されない。そのため変更後の環境変数が取得できないということになる。また、アプリがプロセス環境変数に変更を加えても、それはシステムやユーザーの環境変数に反映されない。

 システムやユーザーの環境変数を取得するには、引数にEnvironmentVariableTarget列挙体(System名前空間)を指定する(次のコード)。

// 結果を検証できるように、プロセスの環境変数「TEMP」を変更しておく
string tempFolder = Environment.ExpandEnvironmentVariables(@"%SystemDrive%\TEST");
Environment.SetEnvironmentVariable("temp", tempFolder);

// システム環境変数「TEMP」を取得する
var tempPathS = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.Machine);
WriteLine($"システム TEMP={tempPathS}");
// 出力例:システム TEMP=C:\WINDOWS\TEMP

// ユーザー環境変数「TEMP」を取得する
var tempPathU = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.User);
WriteLine($"ユーザー TEMP={tempPathU}");
// 出力例:ユーザー TEMP=C:\Users\biac\AppData\Local\Temp

// プロセス環境変数「TEMP」を取得する(EnvironmentVariableTarget未指定と同じ)
var tempPathP = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.Process);
WriteLine($"プロセス TEMP={tempPathP}");
// 出力:プロセス TEMP=C:\TEST

// システム環境変数を列挙する例
IDictionary envVars 
  = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine);
foreach (DictionaryEntry entry in envVars)
{
  string name = entry.Key as string; // 環境変数の名前
  string value = entry.Value as string; // 環境変数の値
  ……省略……
}

' 結果を検証できるように、プロセスの環境変数「TEMP」を変更しておく
Dim tempFolder As String _
  = Environment.ExpandEnvironmentVariables("%SystemDrive%\TEST")
Environment.SetEnvironmentVariable("temp", tempFolder)

' システムの環境変数「TEMP」を取得する
Dim tempPathS = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.Machine)
WriteLine($"システム TEMP={tempPathS}")
' 出力例:システム TEMP=C:\WINDOWS\TEMP

' ユーザーの環境変数「TEMP」を取得する
Dim tempPathU = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.User)
WriteLine($"ユーザー TEMP={tempPathU}")
' 出力例:ユーザー TEMP=C:\Users\biac\AppData\Local\Temp

' プロセスの環境変数「TEMP」を取得する(EnvironmentVariableTarget未指定と同じ)
Dim tempPathP = Environment.GetEnvironmentVariable("temp",
                              EnvironmentVariableTarget.Process)
WriteLine($"プロセス TEMP={tempPathP}")
' 出力:プロセス TEMP=C:\TEST

' システムの環境変数を列挙する例
Dim envVars As IDictionary _
  = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine)
For Each entry As DictionaryEntry In envVars
  Dim name As String = TryCast(entry.Key, String) ' 環境変数の名前
  Dim value As String = TryCast(entry.Value, String) ' 環境変数の値
  ……省略……
Next

システム/ユーザー/プロセスの環境変数の値を取得する例(上:C#、下:VB)
環境変数「TEMP」の値を取得したのだが、出力結果を見るとシステム/ユーザー/プロセスの環境変数で異なる値になっている(通常、プロセス環境変数はシステムまたはユーザーの環境変数と同じだが、このコードの冒頭でプロセス環境変数を変更している)。
なお、EnvironmentVariableTarget.Machine/Userを指定するときは、アプリに適切な権限が必要になる。大体は一般ユーザー権限で問題ないだろう。システムの環境変数に書き込むときだけは、管理者権限が必要になる。管理者権限付きでアプリを起動する方法は「.NET TIPS:常に管理者としてアプリケーションを実行させるには?」を参照。また、システムまたはユーザーの環境変数に書き込むときは、他のアプリに対してWindowsからWM_SETTINGCHANGEメッセージが通知される(その通知の完了を待つのでシステムまたはユーザーの環境変数への書き込みは時間がかかる)。

生の環境変数

 Environmentクラスで取得する環境変数の値は、他の環境変数を使って展開したものになっている。例えば、最初のサンプルコードで出力例として示した環境変数「TEMP」の本来の値は「%USERPROFILE%\AppData\Local\Temp」なのだが、「%USERPROFILE%」の部分が展開されて「C:\Users\biac\AppData\Local\Temp」として取得されているのだ。なお、この展開処理は、アプリがプロセスの環境変数に書き込んだ値には適用されない(例えば、アプリからプロセスの環境変数に「%USERPROFILE%」と設定してから読み出しても、「%USERPROFILE%」のままになる)。

 システム環境変数とユーザー環境変数は、レジストリに保存されている。展開前の環境変数が欲しいときは、レジストリから直接取得すればよい(次のコード)。その際、オプションとしてRegistryValueOptions列挙体(Microsoft.Win32名前空間)のDoNotExpandEnvironmentNames値を指定すると、展開されていない環境変数の値が得られる。

// 展開前のシステムの環境変数「TEMP」を取得する
// システム環境変数が保管されているサブキーの名前
const string SystemEnvKeyName
  = @"System\CurrentControlSet\Control\Session Manager\Environment";
// レジストリHKEY_LOCAL_MACHINEのサブキーを開く
RegistryKey regKeyS 
  = Registry.LocalMachine.OpenSubKey(SystemEnvKeyName);
// サブキー内から「TEMP」という名前の値を取得する
string tempPathS = regKeyS.GetValue("temp", string.Empty,
  RegistryValueOptions.DoNotExpandEnvironmentNames) as string;
WriteLine($"システム TEMP={tempPathS}");
// 出力例:システム TEMP=%SystemRoot%\TEMP

// 展開前のユーザーの環境変数「TEMP」を取得する
// ユーザー環境変数が保管されているサブキーの名前
const string UserEnvKeyName = @"Environment";
// レジストリHKEY_CURRENT_USERのサブキーを開く
RegistryKey regKeyU
  = Registry.CurrentUser.OpenSubKey(UserEnvKeyName);
// サブキー内から「TEMP」という名前の値を取得する
string tempPathU = regKeyU.GetValue("temp", string.Empty,
  RegistryValueOptions.DoNotExpandEnvironmentNames) as string;
WriteLine($"ユーザー TEMP={tempPathU}");
// 出力例:ユーザー TEMP=%USERPROFILE%\AppData\Local\Temp

' 展開前のシステムの環境変数「TEMP」を取得する
' システム環境変数が保管されているサブキーの名前
Const SystemEnvKeyName As String _
  = "System\CurrentControlSet\Control\Session Manager\Environment"
' レジストリHKEY_LOCAL_MACHINEのサブキーを開く
Dim regKeyS As RegistryKey _
  = Registry.LocalMachine.OpenSubKey(SystemEnvKeyName)
' サブキー内から「TEMP」という名前の値を取得する
Dim tempPathS As String _
  = TryCast(regKeyS.GetValue("temp", String.Empty,
              RegistryValueOptions.DoNotExpandEnvironmentNames),
            String)
WriteLine($"システム TEMP={tempPathS}")
' 出力例:システム TEMP=%SystemRoot%\TEMP

' 展開前のユーザーの環境変数「TEMP」を取得する
' ユーザー環境変数が保管されているサブキーの名前
Const UserEnvKeyName As String = "Environment"
' レジストリHKEY_CURRENT_USERのサブキーを開く
Dim regKeyU As RegistryKey _
  = Registry.CurrentUser.OpenSubKey(UserEnvKeyName)
' サブキー内から「TEMP」という名前の値を取得する
Dim tempPathU As String _
  = TryCast(regKeyU.GetValue("temp", String.Empty,
              RegistryValueOptions.DoNotExpandEnvironmentNames),
            String)
WriteLine($"ユーザー TEMP={tempPathU}")
' 出力例:ユーザー TEMP=%USERPROFILE%\AppData\Local\Temp

レジストリから環境変数の値を取得する例(上:C#、下:VB)
RegistryValueOptions.DoNotExpandEnvironmentNamesを指定して取得したレジストリの値は、環境変数による展開が行われていないものになる。

LINQで扱えるようにするには?

 GetEnvironmentVariables静的メソッドで全ての環境変数を取得した後は、並べ替えたり特定の環境変数を見つけ出したりするためにLINQ拡張が使えると便利だ。しかし、GetEnvironmentVariables静的メソッドが返してくるコレクションはジェネリックではないので、そのままでは便利なLINQ拡張を使えないのである。そんなときは、LINQ拡張のOfTypeメソッド(またはCastメソッド)を使ってジェネリックコレクションに変換すればよい(次のコード)。

// 取得したままの順で列挙(比較用)
IDictionary envVarsU
  = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
foreach (DictionaryEntry entry in envVarsU)
{
  string name = entry.Key as string;
  string value = entry.Value as string;
  WriteLine($"{name}={value}");
}
// 出力例:
// TEMP=C:\Users\biac\AppData\Local\Temp
// Path=C:\Users\biac\AppData\Local\Microsoft\WindowsApps;C:\Users\……省略……;
// TMP=C:\Users\biac\AppData\Local\Temp
// OneDrive=C:\Users\biac\OneDrive

// ジェネリックコレクションに変換
IEnumerable<DictionaryEntry> entries = envVarsU.OfType<DictionaryEntry>();
// LINQ拡張でソートしてから列挙
foreach (DictionaryEntry entry in entries.OrderBy(e => e.Key))
{
  string name = entry.Key as string;
  string value = entry.Value as string;
  WriteLine($"{name}={value}");
}
// 出力例:
// OneDrive=C:\Users\biac\OneDrive
// Path=C:\Users\biac\AppData\Local\Microsoft\WindowsApps;C:\Users\……省略……;
// TEMP=C:\Users\biac\AppData\Local\Temp
// TMP=C:\Users\biac\AppData\Local\Temp

' 取得したままの順で列挙(比較用)
Dim envVarsU As IDictionary _
  = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User)
For Each entry As DictionaryEntry In envVarsU
  Dim name As String = TryCast(entry.Key, String)
  Dim value As String = TryCast(entry.Value, String)
  WriteLine($"{name}={value}")
Next
' 出力例:
' TEMP=C:\Users\biac\AppData\Local\Temp
' Path=C:\Users\biac\AppData\Local\Microsoft\WindowsApps;C:\Users\……省略……;
' TMP=C:\Users\biac\AppData\Local\Temp
' OneDrive=C:\Users\biac\OneDrive

' ジェネリックコレクションに変換
Dim entries As IEnumerable(Of DictionaryEntry) _
  = envVarsU.OfType(Of DictionaryEntry)()
' LINQ拡張でソートしてから列挙
For Each entry As DictionaryEntry _
    In entries.OrderBy(Function(e) e.Key)
  Dim name As String = TryCast(entry.Key, String)
  Dim value As String = TryCast(entry.Value, String)
  WriteLine($"{name}={value}")
Next
' 出力例:
' OneDrive=C:\Users\biac\OneDrive
' Path=C:\Users\biac\AppData\Local\Microsoft\WindowsApps;C:\Users\……省略……;
' TEMP=C:\Users\biac\AppData\Local\Temp
' TMP=C:\Users\biac\AppData\Local\Temp

環境変数をLINQを使ってソートする例(上:C#、下:VB)
ここではOfType拡張メソッドを使ってジェネリックコレクションに変換したが、Cast拡張メソッドを使っても同じ結果になる。
ちなみに、出力例に見えるPath環境変数は複数のパスが「;」で区切られて1つの文字列になっている。これを個別のパスに分解するにはStringクラス(System名前空間)のSplitメソッドを使う。Splitメソッドの使い方は「Stringクラスにより文字列を文字列で分割するには?[2.0のみ、C#、VB]」を参照。

まとめ

 アプリのプロセスに与えられた環境変数の値を取得するのは簡単にできる。システム環境変数やユーザー環境変数、あるいは、展開される前の環境変数の値を取得したいときには工夫が必要だ。

「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。