Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2016年07月25日] [更新 2016年08月20日] Delphi サンプルプログラム集
461_特殊フォルダ等をエクスプローラで表示するシェルコマンド
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro, Delphi XE5(UP2) Pro
461_Shell_command.zip [5,248 KB] 2016年08月21日版 (EXE 同梱)







01_特殊フォルダ等をエクスプローラで表示するシェルコマンド


主に,エクスプローラで仮想フォルダ等の特殊フォルダを表示するために使用する Shell コマンドのサンプルプログラムです.
このコマンドは,Shell:XXX という形式となっています.これらはレジストリの以下の場所に情報があります.本サンプルは,この値をレジストリから取得してリスト表示します.


HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions


[備考 1]
64 ビット版の Windows には 64 ビット専用のコマンドがあります.32 ビットの EXE からは,それらのコマンドが実行できない場合があります.

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



図1
設計時画面
  • Edit1 は選択中のコマンド表示用
  • このコマンドは [ファイル名を指定して実行] のダイアログで実行できる
図2
シェルコマンドリストの取得結果
  • [Buttn1] クリックで列挙
  • 選択項目のダブルクリックまたは [Enter] で表示
  • 筆者の環境では 104 項目を取得
  • 64 ビットの EXE を実行すると 106 項目
  • 定義はあるが,自システムには設定されていない機能もある

Shell:XXX 形式のコマンドには,コントロールパネル関係を表示するコマンドがあります.
下図はその例です.


図3
shell:ControlPanelFolder
  • [すべてのコントロールパネル項目] ダイアログ
図4
shell:SyncCenterFolder
  • [同期センター] ダイアログ
  • 64 ビット Windows では,32 ビットの EXE からは表示不可

リスト1
シェルコマンドで特殊フォルダ等をエクスプローラで表示
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}

//=============================================================================
//  レジストリからShellコマンドのリストを取得
//  usesにRegistryが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LRegistry : TRegistry;
  LRegKey   : string;
  LRegItem  : string;
  LItemList : TStringList;
  i         : Integer;
begin
  Edit1.Text := '';

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

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

    //列挙したキーのName項目の値を取得してTListBoxに追加
    ListBox1.Sorted := True;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;
    for i := 0 to LItemList.Count - 1 do begin
      try
        if LRegistry.OpenKey(LRegKey + PathDelim + LItemList[i], False) then begin
          if not LRegistry.ValueExists('Name') then Continue;
          LRegItem := LRegistry.ReadString('Name');
          if LRegItem <> '' then begin
            ListBox1.Items.Add('shell:' + LRegItem);
          end;
        end;
      finally
        LRegistry.CloseKey;
      end;
    end;
    ListBox1.Items.EndUpdate;
    Label1.Caption := IntToStr(ListBox1.Items.Count);
  finally
    FreeAndNil(LRegistry);
    FreeAndNil(LItemList);
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  TListBoxの選択中のShellコマンドを実行
//
//  64ビットWindowsでの実行
//  32ビットのEXEでは,コントロールパネル関係の一部のダイアログは表示できない
//  64ビットのEXEを作成して実行すれば表示可能 
//
//  usesにShellAPIが必要
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LShellCmd : string;
  LRetCode  : Cardinal;
begin
  if ListBox1.ItemIndex < 0 then exit;

  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;

//=============================================================================
//  ListBox1のOnClickイベント処理
//  Edit1にコマンド文字列を表示する(コピー用)
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
begin
  if ListBox1.ItemIndex < 0 then exit;

  Edit1.Text := ListBox1.Items[ListBox1.ItemIndex];
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.




02_Shell コマンドが表示するフォルダのフルパスの値


Shell:XXX に該当するフォルダのフルパスは,Shell:XXX の項目識別子を取得すれば,その項目識別子をパス名に変換することで取得できます.そのサンプルです.
コントロールパネル関係と仮想フォルダは GUID 等を含む文字列となります.システムに実装されていない機能は空文字となります.
取得したパス名または GUID 文字列をコマンドとして実行して,エクスプローラで表示したり,コントロールパネル関係のダイアログを表示します.


図5
Shell コマンドのフルパス

リスト2
シェルコマンドが表示するフォルダのフルパスを取得
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

uses
  Registry,
  ShellAPI,
  ShlObj,
  ActiveX;

{$R *.DFM}

//=============================================================================
//  フォーム破棄時の処理
//  ListBox1のオブジェクトを破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  i : Integer;
begin
  for i := 0 to ListBox1.Items.Count-1 do begin
    Dispose(PString(ListBox1.Items.Objects[i]));
  end;
end;

//=============================================================================
//  レジストリからShellコマンドのリストを取得
//  usesにRegistryが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  ListBox1に文字列と実行用のParsingName名をセット
  //---------------------------------------------------------------------------
  procedure AddItem(ACaption, APath: String);
  var
    pAddStr : PString;
  begin
    New(pAddStr);
    pAddStr^ := APath;
    ListBox1.AddItem(ACaption, TObject(pAddStr));
  end;
//-----------------------------------------------------------------------------

var
  LRegistry : TRegistry;
  LRegKey   : string;
  LRegItemName  : string;
  LRegItemPars  : string;
  LItemList : TStringList;
  i         : Integer;
begin
  Edit1.Text := '';

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

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

    //列挙したキーのName項目の値を取得してTListBoxに追加
    ListBox1.Sorted := True;
    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;
    for i := 0 to LItemList.Count - 1 do begin
      try
        if LRegistry.OpenKey(LRegKey + PathDelim + LItemList[i], False) then begin
          if not LRegistry.ValueExists('Name') then Continue;
          LRegItemName := LRegistry.ReadString('Name');
          if not LRegistry.ValueExists('ParsingName') then begin
            LRegItemPars := '';
          end else begin
            LRegItemPars := LRegistry.Readstring('ParsingName');
          end;

          if LRegItemName <> '' then begin
            AddItem('shell:' + LRegItemName, LRegItemPars);
          end;
        end;
      finally
        LRegistry.CloseKey;
      end;
    end;
    ListBox1.Items.EndUpdate;
    Label1.Caption := IntToStr(ListBox1.Items.Count);
  finally
    FreeAndNil(LRegistry);
    FreeAndNil(LItemList);
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//
//  TListBoxの選択中のShellコマンドを実行
//
//  64ビットWindowsでの実行
//  32ビットのEXEでは,コントロールパネル関係の一部のダイアログは表示できない
//  64ビットの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 := Edit1.Text;
  end else begin
    LShellCmd := ListBox1.Items[ListBox1.ItemIndex];
  end;
  if LShellCmd <> '' then begin
    LRetCode  := ShellExecute(Handle, '', PChar(LShellCmd), nil, nil, SW_SHOW);

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

//-----------------------------------------------------------------------------
//  相対項目識別子からフォルダまたはファイル名を求める
//  仮想フォルダはGUID文字列として求まる
//
//  第1引数  対象の項目識別子を取得する際に使用したシェルフォルダオブジェクト
//  第2引数  対象の項目識別子.ShFolder階層下の相対値
//  第3引数  フルパスを取得する場合は,SHGDN_FORPARSING
//           ファイル名だけ取得する場合は,SHGDN_NORMALまたはSHGDN_INFOLDER
//-----------------------------------------------------------------------------
function GetDispNameFromPItemID(ShFolder: IShellFolder; ItemID:
  PItemIDList;Flag: DWORD=SHGDN_NORMAL): String;
var
  StrRet : TStrRet;
begin
  Result := '';
  if ShFolder = nil then exit;
  if ItemID = nil then exit;

  FillChar(StrRet, SizeOf(StrRet), 0);
  if NOERROR = ShFolder.GetDisplayNameOf(ItemID, Flag, StrRet) then begin
    Result := StrRet.pOleStr;
  end;
end;

//=============================================================================
//  ListBox1のOnClickイベント処理
//  Edit1にコマンド文字列の実パス名ディレクトリ名)を表示
//  仮想フォルダとコントロールパネル関係はGUID文字列となる
//
//  項目識別子から実パスを取得するSHGetPathFromIDList関数がある
//  SHGetPathFromIDListは仮想フォルダ等が取得できないので自前の関数を使用
//
//  usesにShlObj,Activeが必要
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LPathBuff : array [0..MAX_PATH - 1] of Char;
  LShellCmd : string;
  LPItemID  : PItemIDList;
  LpDesktop : IShellFolder;
begin
  SpeedButton1.Visible := False;
  Edit1.Text := '';
  if ListBox1.ItemIndex < 0 then exit;

  //項目識別子を取得
  LShellCmd := ListBox1.Items[ListBox1.ItemIndex];
  LPItemID  := ILCreateFromPath(PChar(LShellCmd));

  //項目識別子からパス名を取得
  if LPItemID <> nil then begin
    FillChar(LPathBuff, SizeOF(LPathBuff), #0);
    SHGetPathFromIDList(LPItemID, LPathBuff);
    Edit1.Text := LPathBuff;

    if Edit1.Text = '' then begin
      //[デスクトップ]のシェルフォルダオブジェクトを取得
      SHGetDesktopFolder(LpDesktop);
      //デスクトップフォルダオブジェクトの項目識別子としてパス名を取得
      Edit1.Text := GetDispNameFromPItemID(LpDesktop, LPItemID, SHGDN_FORPARSING);
    end;

    CoTaskMemFree(LPItemID);
  end;

  if Edit1.Text = '' then begin
    Edit1.Text :=  PString(ListBox1.Items.Objects[ListBox1.ItemIndex])^;
  end;
  SpeedButton1.Visible := Edit1.Text <> '';
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.


上のコードで取得したパス名は英語表記になっています.取得した Edit1.Text の値に対して次のコードを実行すると,エクスプローラでの表示名を取得できます.

最初の図は,C:\Users\Public\Desktop のバスを分解して表示しています.この順番にエクスプローラを展開していけば shell:Common Desktop が表示する場所を表示できます.

2 番目の図は shell:AppUpdatesFolder の場合です.これはコントロールパネル関係です.コントロールパネルから順番に操作していくと,このシェルコマンドが表示するダイアログが表示できます.
[すべてのコントロールパネル項目] というのは,コントロールパネルの [表示方法] を [カテゴリ] 以外にすることを意味します.



図6
フォルダの場合
図7
コントロールパネル関係の場合

shell:XXX コマンドと同じく,本サンプルで取得したコマンドも [ファイル名を指定して実行] のダイアログでも実行できます.
また,エクスプローラやコマンドプロンプトでも実行できますが,以下のように,コマンドの先頭に shell: あるいは explorer.exe が必要です.
エクスプローラから実行する場合,explorer.exe があると,新規にエクスプローラを起動して表示します.explorer.exe がない場合は,現在のエクスプローラで表示します.コマンドプロンプトから実行するには explorer.exe が必要です.

shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\0
explorer.exe shell:::{26EE0668-A00A-44D7-9371-BEB064C98683}\0
explorer.exe ::{26EE0668-A00A-44D7-9371-BEB064C98683}\0


リスト3
取得したパスを分解して表示名を取得する
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  LpDesktop   : IShellFolder;
  LStringList : TStringList;
  LPItemID    : PItemIDList;
  LText       : string;
  LPathText   : string;
  i           : Integer;
begin
  if Edit1.Text = '' then exit;

  SHGetDesktopFolder(LpDesktop);

  LStringList := TStringList.Create;
  try
    LStringList.Delimiter       := PathDelim;
    LStringList.StrictDelimiter := True;
    LStringList.DelimitedText   := Edit1.Text;

    LText     := '';
    LPathText := '';
    for i := 0 to LStringList.Count - 1 do begin

      LText    := LText + LStringList[i];
      LPItemID := ILCreateFromPath(PChar(LText));
      if i = 0 then begin
        LPathText := GetDispNameFromPItemID(LpDesktop, LPItemID, SHGDN_NORMAL);
      end else begin
        LPathText := LPathText + sLineBreak
                   + GetDispNameFromPItemID(LpDesktop, LPItemID, SHGDN_NORMAL);
      end;

      if i < LStringList.Count - 1 then begin
        LText := LText + PathDelim;
      end;
    end;
    CoTaskMemFree(LPItemID);

    MessageBox(Handle, PChar(LPathText), 'パスリスト', MB_ICONINFORMATION);
  finally
    FreeAndNil(LStringList);
  end;
end;




03_コントロールパネルの [ 表示方法 ] とカテゴリ別表示


上のサンプルでは shell:ControlPanelFolder のパス名が次のように表示されます.これはコントロールパネルですが,[大きいアイコン] または [小さいアイコン] で表示されます.どちらで表示されるかは,最後に [スタート] メニューから表示した時の設定によります.
このコマンドでコントロールパネルを表示しても,次回,[スタート] メニューからコントロールパネルを表示する時の [表示方法] は変わりません.

::{26EE0668-A00A-44D7-9371-BEB064C98683}\0


図8
すべてのコントロールパネル項目
  • [大きいアイコン] または [小さいアイコン] の表示と同じ
図9
[カテゴリ] 表示
  • コマンド末尾の \0 がない場合

上のコマンドの末尾の値を変更すると,下の表のように,表示カテゴリが変わります.
末尾が 2, 4 の時は同じ [ハードウェアとサウンド] カテゴリのパネルを表示します.


[備考]
control.exe でコントロールパネルを表示した場合は,最後に [スタート] メニューからコントロールパネルを表示した時の [表示方法] と同じになります.


表1
コントロールパネルの各カテゴリのダイアログを表示するコマンド

コマンド コントロールパネルのカテゴリ
::{26EE0668-A00A-44D7-9371-BEB064C98683} [表示方法] [カテゴリ] と同じ表示
::{26EE0668-A00A-44D7-9371-BEB064C98683}\0 すべてのコントロールパネル項目
::{26EE0668-A00A-44D7-9371-BEB064C98683}\1 デスクトップのカスタマイズ
::{26EE0668-A00A-44D7-9371-BEB064C98683}\2 ハードウェアとサウンド
::{26EE0668-A00A-44D7-9371-BEB064C98683}\3 ネットワークとインターネット
::{26EE0668-A00A-44D7-9371-BEB064C98683}\4 ハードウェアとサウンド
::{26EE0668-A00A-44D7-9371-BEB064C98683}\5 システムとセキュリティ
::{26EE0668-A00A-44D7-9371-BEB064C98683}\6 時計,言語,および地域
::{26EE0668-A00A-44D7-9371-BEB064C98683}\7 コンピューターの簡単操作
::{26EE0668-A00A-44D7-9371-BEB064C98683}\8 プログラム
::{26EE0668-A00A-44D7-9371-BEB064C98683}\9 ユーザーアカウントと家族のための安全設定


次のコマンドで [ローカルセキュリティポリシー] の設定画面を表示します.
その設定画面の [ユーザーの構成] [管理用テンプレート] [コントロールパネル] の [コントロール パネルを開くときは常にすべてのコントロール パネル項目を開く] を有効にすると,[スタート] メニューからコントロールパネルを表示する時に [すべてのコントロールパネル項目] となります.
上の表のコマンドは,この設定の影響は受けません.

%SystemRoot%\System32\gpedit.msc


[表示方法] の [大きいアイコン] と [小さいアイコン] の設定はレジストリに記録されています.
次のコードはレジストリを操作して,この設定を強制的に変更する関数です.[コントロールパネル] を表示する前に,このコードを実行すれば [大きいアイコン] または [小さいアイコン] で表示できまます.ただし,[スタート] メニューから表示する時もこの設定になります.

添付のサンプルでは,この設定も確認できるようにしています.



図10
添付のサンプルの実行時画面

リスト4
コントロールパネルの [表示方法] を [大きいアイコン] または [小さいアイコン] にする
このコードの実行には uses に Registry が必要
//-----------------------------------------------------------------------------
//  コントロールパネルの[表示方法]
//  レジストの[表示方法]の[大きいアイコン]または[小さいアイコン]の設定
//
//  AValue = -1 の時は現在の設定値の読み出し
//  AValue = 0 の時は大きいアイコン
//  AValue = 1 の時は小さいアイコン
//
//  戻り値は現在の設定値
//  対象の項目がない場合は-1を返す
//  書き込みに失敗した場合も-1を返す
//-----------------------------------------------------------------------------
function TForm1.IconReadWriteValue_Registry(AValue: Integer): Integer;
var
  LRegistry     : TRegistry;
  LRegKey       : String;
  LRegistryItem : string;
begin
  Result := -1;
  LRegistryItem  := 'AllItemsIconView';


  LRegistry := TRegistry.Create;
  try
    LRegistry.Access  := KEY_WRITE or KEY_READ;
    LRegistry.RootKey := HKEY_CURRENT_USER;
    LRegKey           := 'Software\Microsoft\Windows\CurrentVersion\Explorer\'
                       + 'ControlPanel';

    if LRegistry.OpenKey(LRegKey, False) then begin
      try
        try
          Result := LRegistry.ReadInteger(LRegistryItem);
          if (Result >= 0) and (AValue >= 0) then begin
            LRegistry.WriteInteger(LRegistryItem, AValue);
          end;
        except
          Result := -1;
        end;
      finally
        LRegistry.CloseKey;
      end;
    end;
  finally
    FreeAndNil(LRegistry);
  end;
end;




04_コントロールパネルやシステムダイアログ関係の CLSID 値一覧


上のサンプルで CLSID (GUID) の文字列でコントロールパネルを表示する説明をしました.

Windows で扱うオブジェクトには CLSID (GUID) が割り当てられています.それらは CLSID 値がキー名になってレジストリに記録されています.本サンプルはそれらのキーの内,InfoTip という項目名を持つキーだけを取得して列挙します.
TListBox に一覧表示しているのはキー名である CLSID の値の先頭に Shell: を追加して,コマンドとして実行可能とした文字列です.これらのキーの InfoTip 項目の値はダイアログの説明文を格納している DLL のパス名です.その DLL から文字列リソースを取り出して表示しています.

下図の表示例は,以下のコマンドを実行した結果です.


shell:::{ED7BA470-8E54-465E-825C-99712043E01C}


図11
システム関係ダイアログを表示するための CLSID 値一覧
  • 筆者の Windows 7 には 6813 の CLSID 値が登録されている
図12
表示例 - すべてのタスク
  • 筆者の環境では 278 項目

リスト5
コントロールパネルやシステムダイアログ関係の CLSID (GUID) 値一覧
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;
    Edit2: TEdit;
    Edit3: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure ListBox1DblClick(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ListBox1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

  function SHLoadIndirectString(pszSource, psuOutBuf: PWideChar; const ccOutBuf:
     UINT; var ppvReserved: Pointer): HRESULT; stdcall; external 'ShLwApi.dll';

var
  Form1: TForm1;

implementation

uses
  Registry,
  ShellAPI;

{$R *.DFM}

//=============================================================================
//  フォーム破棄時の処理
//  ListBox1のオブジェクトを破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
  i : Integer;
begin
  for i := 0 to ListBox1.Items.Count-1 do begin
    Dispose(PString(ListBox1.Items.Objects[i]));
  end;
end;

//-----------------------------------------------------------------------------
//  レジストリの項目に以下のようなリソースのデータの記述がある
//  @%SystemRoot%\system32\shell32.dll,-31361
//  この値を元にしてDLLから文字列リソースを取り出す
//  取得できなかった場合は空文字を返す
//-----------------------------------------------------------------------------
function ReadStrResourceFromRegItemValue(ARegItem: string): string;
var
  LBuffer  : array [0..4095] of WideChar;
  LPointer : Pointer;
begin
  Result := '';
  FillChar(LBuffer[0], SizeOf(LBuffer), #0);
  if SHLoadIndirectString(PWideChar(ARegItem),
                          LBuffer,
                          Length(Lbuffer),
                          LPointer) = S_OK then begin
    Result := Lbuffer;
  end;
end;

//=============================================================================
//  コントロールパネル関係とシステム関係ダイアログ関係のCLSID(GUID)値を取得
//  ダイアログの説明文はDLLのリソースから取得
//
//  usesにRegistry,HTTPAppが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  ListBox1の項目とObjectsプロパティに値をセット
  //---------------------------------------------------------------------------
  procedure AddItem(ACaption, APath: String);
  var
    pAddStr : PString;
  begin
    New(pAddStr);
    pAddStr^ := APath;
    ListBox1.AddItem(ACaption, TObject(pAddStr));
  end;
  //---------------------------------------------------------------------------

var
  LRegistry   : TRegistry;
  LRegKey     : string;
  LRegItem    : string;
  LItemList   : TStringList;
  i           : Integer;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  Edit3.Text := '';
  Edit1.Update;
  Edit2.Update;
  Edit3.Update;

  LItemList         := TStringList.Create;
  LRegistry         := TRegistry.Create;
  LRegistry.Access  := KEY_READ;
  LRegistry.RootKey := HKEY_LOCAL_MACHINE;
  LRegKey           := 'SOFTWARE\Classes\CLSID';

  try
    //CLSIDの値を列挙
    if LRegistry.OpenKey(LRegKey, False) then begin
      LRegistry.GetKeyNames(LItemList);
    end;
    LRegistry.CloseKey;

    ListBox1.Items.Clear;
    ListBox1.Items.BeginUpdate;
    for i := 0 to LItemList.Count - 1 do begin
      try
        if LRegistry.OpenKey(LRegKey + PathDelim + LItemList[i], False) then begin
          if not LRegistry.ValueExists('InfoTip') then Continue;

          LRegItem := LRegistry.ReadString('InfoTip');
          //レジストリのデータから文字列リソースを取得
          LRegItem := ReadStrResourceFromRegItemValue(LRegItem);
          if LRegItem <> '' then begin
            AddItem('shell:::' + LItemList[i], LRegItem);
          end;
        end;
      finally
        LRegistry.CloseKey;
      end;
    end;
    ListBox1.Items.EndUpdate;
    Label1.Caption := IntToStr(ListBox1.Items.Count);
  finally
    FreeAndNil(LRegistry);
    FreeAndNil(LItemList);
  end;
end;

//=============================================================================
//  ListBox1のOnDblClickイベント処理
//  TListBoxの選択中のShellコマンドを実行
//
//  64ビットWindowsでの実行
//  32ビットのEXEでは,コントロールパネル関係の一部のダイアログは表示できない
//  64ビットのEXEを作成して実行すれば表示可能 
//
//  usesにShellAPIが必要
//=============================================================================
procedure TForm1.ListBox1DblClick(Sender: TObject);
var
  LShellCmd : string;
  LRetCode  : Cardinal;
begin
  if ListBox1.ItemIndex < 0 then exit;

  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;

//=============================================================================
//  ListBox1のOnClickイベント処理
//  Edit1にコマンド文字列を表示する
//=============================================================================
procedure TForm1.ListBox1Click(Sender: TObject);
var
  LItemText : string;
  LRegistry : TRegistry;
  LRegKey   : string;
  LRegItem  : string;
begin
  if ListBox1.ItemIndex < 0 then exit;

  LItemText  := ListBox1.Items[ListBox1.ItemIndex];
  Edit1.Text := LItemText;
  Edit3.Text := PString(ListBox1.Items.Objects[ListBox1.ItemIndex])^;

  //参考としてLocalizedStringの値を取得して表示
  LRegistry         := TRegistry.Create;
  LRegistry.Access  := KEY_READ;
  LRegistry.RootKey := HKEY_LOCAL_MACHINE;
  LItemText := StringReplace(LItemText,  'shell:::', '', []);
  LRegKey   := 'SOFTWARE\Classes\CLSID' + PathDelim + LItemText;
  try
    if LRegistry.OpenKey(LRegKey, False) then begin
      if LRegistry.ValueExists('LocalizedString') then begin
        LRegItem := LRegistry.ReadString('LocalizedString');
        //レジストリのデータから文字列リソースを取得
        LRegItem := ReadStrResourceFromRegItemValue(LRegItem);
        if LRegItem <> '' then begin
          Edit2.Text := LRegItem;
        end;
      end;
      LRegistry.CloseKey;
    end;
  finally
    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.