Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2009年04月25日] [更新 2016年08月03日] Delphi サンプルプログラム集
462_環境変数とそのパス名の取得
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
462_EnvironmentList.zip [2,741 KB] 2016年08月03日版 (EXE 同梱)







Windows の環境変数


Windows の環境変数の値を取得して表示するサンプルです.
Windows の環境変数の値は,環境変数の設定ダイアログで確認することができます.この設定ダイアログは以下のコマンドで表示されたダイアログで,[環境変数] をクリックすると表示できます.

systempropertiesadvanced.exe


図1
環境変数の編集ダイアログ

環境変数は,以下のように,コマンドプロセッサの set コマンドを使用して取得できます.また,設定には setx コマンドが使用できます.設定には管理者権限が必要です.


//環境変数とその値をリスト
set

//環境変数PATHEXTの一覧(省略可能な拡張子の一覧)
set pathext

//環境変数PATHの一覧(パス名の一覧)
set path

//環境変数PATHにパスを追加
setx /M path "%path%;D:\000"


[備考]
特殊フォルダを定義した環境変数を,エクスプローラのアドレスバーに入力すると,その特殊フォルダを表示できます.例えば %APPDATA% を入力すると,C:\Users\<ユーザー名>\AppData\Roaming のフォルダを表示します.
環境変数は [ファイル名を指定して実行] のダイアログのコマンドとしても実行できます.[ファイル名を指定して実行] のダイアログで表示すると,別のエクスプローラで表示されます.




01_環境変数とパス名の取得  -  GetEnvironmentVariable 関数


上で説明した Windows の環境変数のリストと,その値を表示するテストプログラムです.現在のプロセスに対応する環境変数のリストを取得して表示します.

このサンプルでは ShellExecuteEx 関数でバッチファイルを実行していますが,バッチファイルの処理終了を待ってから次の処理を行うようになっています.これは,ShellExecuteEx 関数で起動したアプリケーション,プログラムの終了まで待つ,ということです.
この動作にするためには,TShellExecuteInfo の fMask メンバーに SEE_MASK_NOCLOSEPROCESS を含めて,起動したプロセスのハンドルの値を取得するようにします.このハンドルを使用して,オブジェクトの状態を検出します.


[備考 1]
SET コマンドではなく,Windows API の GetEnvironmentStrings 関数を使用したサンプルコードが以下の記事にあります.

[備考 2]
本ページのサンプルは,環境変数の取得ですが,環境変数の設定には SetEnvironmentVariable 関数を使用します.setx コマンドによる設定とは違い,SetEnvironmentVariable 関数で設定した環境変数は,自プロセスだけで有効な値となります.
例えば,PATH の値に新しいパスを追加すると,自アプリで,ディレクトリ名を省略しての操作が可能になります.


図2
設計時画面
図3
環境変数のリスト
  • IDE 上の [F9] で実行した場合
  • BDS, DELPHI, EMBARCA 等が含まれている

上の図は IDE 上で実行した場合ですが,デバッグ時に有効な Delphi 専用の環境変数である BDS 等も含まれています.
下図は EXE を直接実行した場合です.BDS 等は除かれています.


図4
環境変数のリスト
  • EXE を直接実行した場合
  • BDS, DELPHI, EMBARCA 等は含まれていない

リスト1
環境変数のリストとそのフルパス等を表示
GetEnvironmentVariable 関数を使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ListBox1: TListBox;
    procedure FormShow(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses  
  ShellAPI, 
  StrUtils;

{$R *.dfm}

//=============================================================================
//  現在のユーザの環境変数のリストをリストボックスにリストする
//  SETコマンドを実行してリストを一度ファイルに保存している
//  このSETコマンドでは,環境変数とそのフルパスが取得できるが,ここでは,テスト
//  のため,環境変数だけを取出し,プログラムコード内で使用する形式(左右の端に%
//  を付加したもの)を表示する
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var                    
  LShellInfo   : TShellExecuteInfo;
  LBatFullPath : String;
  LOutFullPath : String;
  LStrList     : TStringList;
  LEnvString   : String;
  LiPos        : Integer;
  i            : Integer;
begin
  LStrList := TStringList.Create;

  LBatFullPath := ExtractFilePath(Application.ExeName) + 'EnvDirList.BAT';
  LOutFullPath := ExtractFilePath(Application.ExeName) + 'EnvDirList.TXT';
  DeleteFile(LOutFullPath);

  //バッチファイルを作成してこのアプリのフォルダに保存
  try
    LStrList.Add('SET>' + LOutFullPath);
    LStrList.Add('DEL %0');
    LStrList.Add('EXIT');
    LStrList.SaveToFile(LBatFullPath);
  finally
    FreeAndNil(LStrList);
  end;

  //バッチファイルを実行
  //SEE_MASK_NOCLOSEPROCESSがないと終了待ちはできない
  with LShellInfo do begin
    cbSize       := Sizeof(LShellInfo);
    fMask        := SEE_MASK_NOCLOSEPROCESS;
    Wnd          := Application.Handle;
    lpVerb       := 'open';
    lpFile       := PChar(LBatFullPath);
    lpParameters := nil;
    lpDirectory  := nil;
    nShow        := SW_HIDE;
  end;
  ShellExecuteEx(@LShellInfo);
  //バッチファイルの実行終了を(ファイルが出来るまで)待つ
  while WaitForSingleObject(LShellInfo.hProcess, 0) = WAIT_TIMEOUT do begin
    Application.ProcessMessages;
    Sleep(100);
  end;

  //リストボックスに環境変数のみを取出して両端に%を付加
  ListBox1.Items.Clear;
  LStrList := TStringList.Create;
  try
    LStrList.LoadFromFile(LOutFullPath);
    for i := 0 to LStrList.Count - 1 do begin
      LEnvString := LStrList[i];
      LiPos      := Pos('=', LEnvString);
      LEnvString := StrUtils.AnsiLeftStr(Trim(LEnvString), LiPos - 1);
      ListBox1.Items.Add(SysUtils.AnsiQuotedStr(LEnvString, '%'));
    end;
  finally
    LStrList.Free;
  end;
end;

//=============================================================================
//  リストボックスで選択した環境変数のフルパスをEdit1に表示
//  GetEnvironmentVariable関数を使用
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LListItem : string;
  LPathStr  : string;
begin
  if ListBox1.ItemIndex >= 0 then begin
    LListItem  := ListBox1.Items[ListBox1.ItemIndex];

    //文字列中の%を取除く
    LPathStr   := StringReplace(LListItem, '%', '', [rfReplaceAll]);
    Edit1.Text := GetEnvironmentVariable(LPathStr);
  end else begin
    Edit1.Text := '';
  end;
end;

end.




02_環境変数とパス名の取得  -  ExpandEnvironmentStrings 関数


上サンプルでは GetEnvironmentVariable 関数を使用しました.
環境変数の値をフルパスに変換するには,ExpandEnvironmentStrings 関数も使用できます.この関数はサンプルプログラム集の他のサンプルでも使用していますが,それらは文字列の長さを MAX_PATH に固定しています.それは,単一のファイル名を取得するだけだからです.

本ページの場合,環境変数によっては,複数のパス名が含まれていて,MAX_PATH の長さを超える場合があります.そこで,まず,パス名を受け取るのに必要なバッファのサイズを取得して,文字列の長さを設定しています.それから再度 ExpandEnvironmentStrings 関数を実行して,実際のパス文字列を取得するようにしています.



リスト2
環境変数のリストとそのフルパス等を表示
ExpandEnvironmentStrings 関数を使用する例
//=============================================================================
//  リストボックスで選択した環境変数のフルパスをEdit1に表示
//  ExpandEnvironmentStrings関数を使用
//  対象の環境変数は%で囲まれた文字列である必要がある
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LEnvText : String;
  LEnvPath : String;
  LLen     : DWORD;
begin
  if ListBox1.ItemIndex >= 0 then begin
    LEnvText := ListBox1.Items[ListBox1.ItemIndex];

    LLen := ExpandEnvironmentStrings(PChar(LEnvText), nil, 0);
    if LLen > 0 then begin
      SetLength(LEnvPath, LLen);
    ExpandEnvironmentStrings(PChar(LEnvText), PChar(LEnvPath), LLen);
      // Trim しないと Null 終端文字が含まれたままとなる
      Edit1.Text := Trim(LEnvPath);
    end;
  end else begin
    Edit1.Text := '';
  end;
end;




03_環境変数 %PATH% のパスのリスト


環境変数の %PATH% の実際の値は,複数あります.それらは ; (セミコロン) で区切られています.
これらを個別に取得するサンプルです.
これらのパス (ディレクトリ) 内にあるファイルは,ディレクトリ名を指定しなくても実行したり,開いたり利用できます.つまり「システム的にパスが通っている」場所にあるファイルということになります.


[備考]
Windows の Update で Delphi 関係のパスが削除されていまったという報告があります.筆者も経験しました.マイクロソフトは Delphi に恨みがあるのかも知れません.


図5
環境変数 %PATH% のリスト

リスト3
環境変数 %PATH% のパスのリスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  StrUtils;

{$R *.DFM}

//=============================================================================
//  環境変数%PATH%のパスのリスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LEnvStr   : string;
  LPathList : TStringList;
begin
  LEnvStr   := 'PATH';
  LPathList := TStringList.Create;
  try
    LPathList.Sorted := True;

    //デリミタの設定
    LPathList.Delimiter       := ';';
    LPathList.StrictDelimiter := True;
    //環境変数のパスをセット
    LPathList.DelimitedText := GetEnvironmentVariable(LEnvStr);

    Memo1.Lines := LPathList;
    Label1.Caption := IntToStr(LPathList.Count);
  finally
    FreeAndNil(LPathList);
  end;
end;

end.




04_環境変数 %PATH% のパスの長さ制限と回避方法


環境変数の PATH の文字列の長さには制限があります.
下記のリンクの記事は,新規に環境変数を追加して,既にあるパス名の一部を新しく追加した変数の文字列で置き換えることにより,PATH 全体の文字列の長さを短くする方法です.






05_省略可能な拡張子のリスト - 環境変数 %PATHEXT%


%PATH% に似た環境変数 %PATHEXT% があります.EXT,つまり拡張子関係の環境変数です.
筆者の Windows 7 の環境では以下のようになっています.コマンドを実行する際,拡張子を省略すると,コマンドにこの拡張子の付加した実行ファイル名を順番に検索して,見つかれば実行する仕組みになっています.なければ実行エラーとなります.

拡張子はそのファイルの種類を表しています.基本的には省略する必要はないでしょう.ネット上に掲載されているコード類でも,ほとんど省略していないようです.



図6
環境変数 %PATHEXT% のリスト (省略可能な拡張子)
  • 優先順位は図のリストの順
  • AAA を実行すると,AAA.COM と AAA.EXE の両方が存在する場合,AAA.COM が実行される

リスト4
環境変数 %PATHEXT% のリスト
//=============================================================================
//  環境変数%PATHEXT%のリスト
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LEnvStr   : string;
  LPathList : TStringList;
begin
  LEnvStr   := 'PATHEXT';
  LPathList := TStringList.Create;
  try
    //デリミタの設定
    LPathList.Delimiter       := ';';
    LPathList.StrictDelimiter := True;
    //環境変数のパスをセット
    LPathList.DelimitedText   := GetEnvironmentVariable(LEnvStr);

    Memo1.Lines := LPathList;
    Label1.Caption := IntToStr(LPathList.Count);
  finally
    FreeAndNil(LPathList);
  end;
end;




06_ディレクトリの指定なしで実行可能なプログラム


環境変数の %PATH% で設定されているディレクトリ内のファイルは,そのディレクトリ名を省略しても実行可能です.それらは例えば,System32 や SysWOW64 フォルダ内のファイルです.
それ以外にもディレクトリ名を省略して実行できるプログラムがあります.それらは,レジストリの以下の階層下に登録されているファイル名のものです.本サンプルはこれらを列挙します.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths


選択中のファイルをダブルクリックすると起動するようにしてありますが,必ずしも正常に起動できるとは限りません.プログラムやファイルの中には,他のプログラムから使用するような仕様になっていたり,パラメータが必要な場合があります.

また,64 ビットの Windows で 32 ビットのプログラムから実行する場合,リダイレクトの機能が働くため 64 ビットのアプリは起動できません.リダイレクトの処理については以下の最初のリンクの記事を参考にしてください.



図7
ディレクトリ名を省略しても実行可能なプログラムのリスト

リスト5
ディレクトリ名を省略しても実行可能なプログラムのリスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    Label1: TLabel;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Registry,
  ShellAPI;

{$R *.DFM}

//=============================================================================
//  レジストリからパスが通っていなくても実行可能なファイルの一覧を取得
//  usesにRegistryが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRegistry : TRegistry;
  LRegKey   : string;
  LItemList : TStringList;
  i         : Integer;
begin
  Edit1.Text := '';

  LItemList         := TStringList.Create;
  LRegistry         := TRegistry.Create;
  LRegistry.Access  := KEY_READ;
  LRegistry.RootKey := HKEY_LOCAL_MACHINE;
  try
    LRegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths';

    //FolderDescriptions階層下のキー名(GUID)を列挙
    if LRegistry.OpenKey(LRegKey, False) then begin
      LRegistry.GetKeyNames(LItemList);
    end;
    LRegistry.CloseKey;

    ListBox1.Sorted := True;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;
    for i := 0 to LItemList.Count - 1 do begin
      if LowerCase(ExtractFileExt(LItemList[i])) = '.exe' then begin
        if LRegistry.OpenKey(LRegKey + PathDelim + LItemList[i], False) then begin
          if LRegistry.ReadString('') <> '' then begin
            ListBox1.Items.Add(LItemList[i]);
          end;
          LRegistry.CloseKey;
        end;
      end;
    end;
    ListBox1.Items.EndUpdate;
    Label1.Caption := IntToStr(ListBox1.Items.Count);
  finally
    FreeAndNil(LRegistry);
    FreeAndNil(LItemList);
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  TListBoxの選択中のEXEを実行
//
//  usesにShellAPIが必要
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LShellCmd : string;
  LRetCode  : Cardinal;
begin
  if ListBox1.ItemIndex < 0 then exit;

  if Edit1.Text <> '' then begin
    LShellCmd := ListBox1.Items[ListBox1.ItemIndex];
    LRetCode := ShellExecute(Handle, '', PChar(LShellCmd), nil, nil, SW_SHOW);

    if LRetCode <=32 then begin
      MessageBox(Handle, PChar(SysErrorMessage(LRetCode)), '情報', MB_OK);
    end;
  end;
end;


//-----------------------------------------------------------------------------
//  短いパス名を長いパス名に変換
//-----------------------------------------------------------------------------
function ChangeShortToLongPath(ShortName: String): String;
var
  Buff : array[0..MAX_PATH-1] of Char;
begin
  if (GetLongPathName(PChar(ShortName), Buff, Length(Buff))) > 0 then begin
    Result := Buff;
  end else begin
    Result := ShortName;
  end;
end;

//=============================================================================
//  ListBox1のOnClickイベント処理
//  選択中のApps PathsキーのEXEファイルのフルパスを取得してEdit1に表示
//
//  usesにRegistryが必要
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LRegistry : TRegistry;
  LRegKey   : string;
  LRegItem  : string;
begin
  Edit1.Text := '';
  if ListBox1.ItemIndex < 0 then exit;

  LRegistry         := TRegistry.Create;
  LRegistry.Access  := KEY_READ;
  LRegistry.RootKey := HKEY_LOCAL_MACHINE;
  try
    LRegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths'
             + PathDelim + ListBox1.Items[ListBox1.ItemIndex];

    if LRegistry.OpenKey(LRegKey, False) then begin
      LRegItem := LRegistry.ReadString('');
      if LRegItem <> '' then begin
        Edit1.Text := ChangeShortToLongPath(LRegItem);
      end;
    end;
  finally
    LRegistry.CloseKey;
    FreeAndNil(LRegistry);
  end;
end;

//=============================================================================
//  ListBox1のOnKeyDownイベント処理
//=============================================================================
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if ListBox1.ItemIndex < 0 then exit;

  if Key = VK_RETURN then begin
    ListBox1DblClick(nil);
    Key := 0;
  end;
end;

end.




07_環境変数 $(DELPHI) と $(BDS) の取得 - デバッグ時のみ


上のサンプルの実行結果のリストに %BDS% があります.これはデバッグ時に実行した場合にのみ取得できる値です.上のサンプルの EXE を直接実行した場合はリストに表示されません.
古い Delphi のバージョンでは %DELPHI% となります.
これらの値の取得コードについては,サンプルプログラム [827_デバッグ中の実行かEXE実行か] に掲載しています.参考にしてください.