MSOfficeのショートカットファイルのリンク先を知るには

解決


Fusa  2006-10-05 09:52:58  No: 23467  IP: 192.*.*.*

こんにちは
ショートカットファイルのリンク先を求める為に次のような処理を使っています。

  ShellLink := CreateComObject(CLSID_ShellLink) As IShellLink;
  省略
  OleCheck(ShellLink.GetPath(FilePath,MAX_PATH,pfd,SLGP_SHORTPATH));

http://delfusa.main.jp/delfusalibrary/20060527212010/ShortcutFile/ShortcutFile.pas
ここの TShortcutFile.Create の部分です。

質問内容は、この方法でリンク先を求めることのできない
特殊なリンク方法のショートカットファイルについても
リンク先を求める方法を知りたい。ということです。


上記方法だと
MS-Office2003のリンク先を求めようとして
スタートボタン内のショートカットファイルに対して
リンク先を求めると

C:\WINDOWS\Installer\{謎な英数字の羅列} フォルダの
    wordicon.exe
    xlicons.exe

というプログラムが見つかりましたが
Word本体やExcel本体のファイルの場所を求めることができませんでした。

いくつかのソフトでは
こういうインストールの方法がとられるようなのですが
このリンクの仕組みについてと、
本当のリンク先を見つける方法について
ご存じの方おられましたら教えてください。

よろしくお願いします。

編集 削除
もにゃ  2006-10-05 18:41:32  No: 23468  IP: 192.*.*.*

こんにちわです。
ショートカットファイルはフォーマットが公開されてますので、
それを解析すれば取得可能です。
以前、未公開アプリ作成中に途中まで解析したものがあるのでお試しください。WindowsXp上では問題なく取得できています。
で、私から質問ですが、Fusaさんて、以前フサギコと名乗ってた方ですか?

uses 
  hShortCut

procedure TForm1.Button1Click(Sender: TObject);
var
  ShortCut:TShortCut;
begin
  ShortCut:=TShortCut.Create;
  ShortCut.LoadFromFile(Edit1.Text); //ショートカットファイルのパス
  Edit2.Text:=ShortCut.LinkPath; //リンク先の出力
  ShortCut.Free;
end;

-- hShortCut.pas --------------
unit hShortCut;

interface

uses
  Windows, SysUtils, Classes, ShlObj, ShellAPI;

type
  TShHeader = record
    hCode : array [0..3] of byte; // 4 bytes Always 4C 00 00 00 This is how windows knows it is a shortcut file
    GUID  : TGUID;                // 16 bytes GUID for shortcut files The current GUID for shortcuts. It may change in the future. 01 14 02 00 00 00 00 00 C0 00 00 00 00 00 46
    ShortcutFlags:DWORD;          // 1 dword Shortcut flags Shortcut flags are explained below
    TargetFileFlags:DWORD;        // 1 dword Target file flags Flags are explained below
    CreationTime:TFileTime;       // 1 qword Creation time
    LastAccessTime:TFileTime;     // 1 qword Last access time
    ModificationTime:TFileTime;   // 1 qword Modification time
    FileLength:DWORD;             // 1 dword File length The length of the target file. 0 if the target is not a file. This value is used to find the target when the link is broken.
    IconNo:DWORD;                 // 1 dword Icon number If the file has a custom icon (set by the flags bit 6), then this long integer indicates the index of the icon to use. Otherwise it is zero.
    ShwoWindowValue:DWORD;        // 1 dword Show Window the ShowWnd value to pass to the target application when starting it. 1:Normal Window 2:Minimized 3:Maximized
    Hotkey:DWORD;                 // 1 dword Hot Key The hot key assigned for this shortcut
    Reserved0:DWORD;              // 1 dword Reserved Always 0
    Reserved1:DWORD;              // 1 dword Reserved Always 0
  end;

  TShellIDItem = record
    Length:WORD;
    DataType:WORD;
    data:array of byte;
    GUID:TGUID;
  end;

  TShellIDList = record
    Length: WORD;
    data  : array of byte;
    path  : String;
    //ShellIDItems : array of TShellIDItem;
  end;

  TFileLocationInfo = record
    Length:DWORD;                 // 1 dword This length value includes all the assorted pathnames and other data structures. All offsets are relative to the start of this section.
    OffsetBasicFileInfo:DWORD;    // 1 dword The offset at which the basic file info structure ends. Should be 1C.
    FileAvailable:DWORD;          // 1 dword File available on local volume (0) or network share(1)
    OffsetLocalVolume:DWORD;      // 1 dword Offset to the local volume table.
    OffsetBasePath:DWORD;         // 1 dword Offset to the base path on the local volume.
    OffsetNetwork:DWORD;          // 1 dword Offset to the network volume table.
    OffsetFinalPart:DWORD;        // 1 dword Offset to the final part of the pathname.
  end;

  TLocalVolumeTable = record
    Length:DWORD;                 //1 dword Length of this structure including the volume label string.
    TypeOfVolume:DWORD;           //1 dword Type of volume (code below)
    VolumeSerialNumber:DWORD;     //1 dword Volume serial number
    OffsetVolumeName:DWORD;       //1 dword Offset of the volume name (Always 0x10)
    Path:array of char ;          //ASCIZ Volume label
  end;

TShortCut = class(TPersistent)
  private
    ShellItemIdIistPresent:boolean;
    TargetIsAFileOrDirectory:boolean;
    HasADescription:boolean;
    HasARelativePath:boolean;
    HasAWorkingDirectory:boolean;
    HasCommandLineArguments:boolean;
    HasACustomIcon:boolean;
    fItemIDList: PItemIDList;
    fLinkPath: WideString;
  public
    Handle      : HWND;
    Header      : TShHeader;
    ShellIDList : TShellIDList;
    FileLocationInfo:TFileLocationInfo;
    LocalVolumeTable:TLocalVolumeTable;
    procedure LoadFromFile(path:WideString);
    procedure ShellExe(AItemIDList: PItemIDList);
    property ItemIDList:PItemIDList read fItemIDList;
    property LinkPath:WideString read fLinkPath;
  end;

implementation

function SD(s:String):String;
var p:Integer;
begin
  p:=Pos(#0,s);
  p:=p-1;
  if p>=0 then
    Result:=copy(s,1,p)
    else
    Result:=s;
end;


function ByteFlugIn(x:DWORD; Index:byte) : boolean;
const
  cIntBitWidth = sizeOf(Byte)*8;
var
  i : Byte;
begin
  Result:= False;
  for i:=cIntBitWidth downto 1 do begin
    if i=cIntBitWidth - Index then
    begin
      Result:= ((x And 1) = 1);
      exit;
    end;
    x := x shr 1;
  end;
end;

function GetPath(ItemIDList:PItemIDList):String;
var path:String;
begin
  SetLength(Path,MAX_PATH);
  SHGetPathFromIDList(ItemIDList,PChar(Path));
  Result:=SD(path);
end;

{ TShortCut }

procedure TShortCut.LoadFromFile(path: WideString);
var
  mStream:TMemoryStream;
begin
  mStream:=TMemoryStream.Create;

  mStream.LoadFromFile(path);
  mStream.Position:=0;
  //Header
  mStream.Read(Header.hCode,4);
  mStream.Read(Header.GUID,Sizeof(Header.GUID));
  mStream.Read(Header.ShortcutFlags,Sizeof(Header.ShortcutFlags));

    ShellItemIdIistPresent   :=ByteFlugIn(Header.ShortcutFlags,0);
    TargetIsAFileOrDirectory :=ByteFlugIn(Header.ShortcutFlags,1);
    HasADescription          :=ByteFlugIn(Header.ShortcutFlags,2);
    HasARelativePath         :=ByteFlugIn(Header.ShortcutFlags,3);
    HasAWorkingDirectory     :=ByteFlugIn(Header.ShortcutFlags,4);
    HasCommandLineArguments  :=ByteFlugIn(Header.ShortcutFlags,5);
    HasACustomIcon           :=ByteFlugIn(Header.ShortcutFlags,6);

  mStream.Read(Header.TargetFileFlags,Sizeof(Header.TargetFileFlags));
  mStream.Read(Header.CreationTime,Sizeof(Header.CreationTime));
  mStream.Read(Header.LastAccessTime,Sizeof(Header.LastAccessTime));
  mStream.Read(Header.ModificationTime,Sizeof(Header.ModificationTime));

  mStream.Read(Header.FileLength,Sizeof(Header.FileLength));
  mStream.Read(Header.IconNo,Sizeof(Header.IconNo));
  mStream.Read(Header.ShwoWindowValue,Sizeof(Header.ShwoWindowValue));
  mStream.Read(Header.Hotkey,Sizeof(Header.Hotkey));
  mStream.Read(Header.Reserved0,Sizeof(Header.Reserved0));
  mStream.Read(Header.Reserved1,Sizeof(Header.Reserved1));

  //ShellIDList
  if ShellItemIdIistPresent then
  begin
    mStream.Read(ShellIDList.Length ,Sizeof(ShellIDList.Length));
    SetLength(ShellIDList.data,ShellIDList.Length);
    mStream.Read(ShellIDList.data[0],ShellIDList.Length);
    fItemIDList:=@ShellIDList.data[0];
    fLinkPath:=GetPath(fItemIDList);
  end;
  mStream.Free;

end;

procedure TShortCut.ShellExe(AItemIDList: PItemIDList);
var
  ShellExecuteInfo: TShellExecuteInfo;
begin
    try
      with ShellExecuteInfo do
      begin
        cbSize       := sizeof(TShellExecuteInfo);
        fMask        := SEE_MASK_INVOKEIDLIST;
        wnd          := Handle;
        lpVerb       := '';
        lpFile       := nil;
        lpParameters := '';
        lpDirectory  := '';
        nShow        := SW_SHOWNORMAL;
        hInstApp     := 0;
        lpIDList     := AItemIDList;
        lpClass      := nil;
        hkeyClass    := 0;
        dwHotKey     := 0;
        hIcon        := 0;
      end;
      ShellExecuteEx(@ShellExecuteInfo);
    finally
    end;
end;

end.

編集 削除
Fusa  2006-10-05 21:21:45  No: 23469  IP: 192.*.*.*

お返事、ありがとうございます。

動作確認してみました。
ほとんどOKでした。
一部誤動作するショートカットファイルがありました。

それは、
一度もそのショートカットファイルから
起動していないファイルの場合には、
リンク先として
 xlicons.exe  を間違って指し示したり
「ドライブが見つからない」というエラーになりました。

一度、ショートカットファイルでExcelやInfoPathを起動すると
次からは正常にリンク先を見つけることができました。

一度も押されていないショートカットファイルを見つける事が
なかなか大変だとは思いますが、そのような動作をする。
ということを、ご報告します。

※Infopathとか、起動したことないよ...


非常におもしろいソースコードをありがとうございます。
これで勉強する足がかりになります。


> Fusaさんて、以前フサギコと名乗ってた方ですか?

えっと、そうでしたっけ?
まあ、多分、そうですね。
今も二chとかによく生息したりしてますが、、、

ミ゜∀゜;彡

編集 削除
かみづ  2006-10-05 21:47:53  No: 23470  IP: 192.*.*.*

GetPathの代わりにGetIDListしてSHGetPathFromIDListでどうでしょうか?

編集 削除
かみづ  2006-10-05 22:15:28  No: 23471  IP: 192.*.*.*

と思ったのですが全然駄目でしたね・・・
MsiGetShortcutTarget/MsiGetComponentPathというAPIを使う必要があるようです。

編集 削除
Fusa  2006-10-07 00:05:50  No: 23472  IP: 192.*.*.*

かみずさん、ヒントありがとうございます。
APIの存在はわかったのですが
検索しても使い方がちょっとわからなかったです。

Delphi-Library.de - Die ausführbare Datei eines "advertised shortcut" ermitteln
http://www.delphi-library.de/topic_51393.html&sid=2b3c4a2a855c0d6d668dc5afb6a29bf2

ここのソースを改造せずに動かしてみたのですが
実行時にメモリアクセスエラーっぽい誤動作するので、
なんとも....

編集 削除
かみづ  2006-10-07 16:51:50  No: 23473  IP: 192.*.*.*

BDS2006で試してみましたが特に問題なく動作しました。
与えるリンクファイルによって駄目なのでしょうか?

  getExecData(FileName, execName, execDir, execArgs);
  ShowMessage(execName);

編集 削除
Fusa  2006-10-08 22:03:29  No: 23474  IP: 192.*.*.*

試していただいて、ありがとうございます。

getAdvShortcut.pasの67行目

  AnObj       := CreateComObject(CLSID_ShellLink);  

ここで、

> ---------------------------
> デバッガ例外通知
> ---------------------------
> プロジェクト test.exe は例外クラス EOleSysError (メッセージ 'CoInitialize は呼び出されていません。')を送出しました。
> ---------------------------
> ブレーク(B)   継続(C)   ヘルプ   
> ---------------------------

このダイアログがでてしまいます。
続いて、以下のダイアログが出ます。

> ---------------------------
> デバッガ例外通知
> ---------------------------
> プロジェクト test.exe は例外クラス EOleSysError (メッセージ 'CoInitialize は呼び出されていません。, ClassID: {00021401-0000-0000-C000-000000000046}')を送出しました。
> ---------------------------
> ブレーク(B)   継続(C)   ヘルプ   
> ---------------------------

> ---------------------------
> アプリケーション エラー
> ---------------------------
> EOleSysError がモジュール test.exe の 0005D19B で発生しました。
> CoInitialize は呼び出されていません。, ClassID: {00021401-0000-0000-C000-000000000046}.
> ---------------------------
> OK   
> ---------------------------

私の環境では
どんな.LNKFileでもだめなようです。

環境。
BDS2006ProUP2hotfixもすべて当て、WinXP、

編集 削除
Fusa  2006-10-08 22:23:36  No: 23475  IP: 192.*.*.*

理由がわかりました。
Formのないアプリケーションで動かそうとしていたのです。

CUIアプリケーションでも
  if InitProc <> nil then TProcedure(InitProc);
  getExecData(FileName, execName, execDir, execArgs);
  ShowMessage(execName);

このように記述することで正しく動作することを確認できました。

MSOFFICE系のリンクファイルでも
通常のリンクファイルでも
正しく動作しています。

大変、ありがとうございました。

編集 削除