こんにちは
ショートカットファイルのリンク先を求める為に次のような処理を使っています。
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本体のファイルの場所を求めることができませんでした。
いくつかのソフトでは
こういうインストールの方法がとられるようなのですが
このリンクの仕組みについてと、
本当のリンク先を見つける方法について
ご存じの方おられましたら教えてください。
よろしくお願いします。
こんにちわです。
ショートカットファイルはフォーマットが公開されてますので、
それを解析すれば取得可能です。
以前、未公開アプリ作成中に途中まで解析したものがあるのでお試しください。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.
お返事、ありがとうございます。
動作確認してみました。
ほとんどOKでした。
一部誤動作するショートカットファイルがありました。
それは、
一度もそのショートカットファイルから
起動していないファイルの場合には、
リンク先として
xlicons.exe を間違って指し示したり
「ドライブが見つからない」というエラーになりました。
一度、ショートカットファイルでExcelやInfoPathを起動すると
次からは正常にリンク先を見つけることができました。
一度も押されていないショートカットファイルを見つける事が
なかなか大変だとは思いますが、そのような動作をする。
ということを、ご報告します。
※Infopathとか、起動したことないよ...
非常におもしろいソースコードをありがとうございます。
これで勉強する足がかりになります。
> Fusaさんて、以前フサギコと名乗ってた方ですか?
えっと、そうでしたっけ?
まあ、多分、そうですね。
今も二chとかによく生息したりしてますが、、、
ミ゜∀゜;彡
GetPathの代わりにGetIDListしてSHGetPathFromIDListでどうでしょうか?
編集 削除と思ったのですが全然駄目でしたね・・・
MsiGetShortcutTarget/MsiGetComponentPathというAPIを使う必要があるようです。
かみずさん、ヒントありがとうございます。
APIの存在はわかったのですが
検索しても使い方がちょっとわからなかったです。
Delphi-Library.de - Die ausführbare Datei eines "advertised shortcut" ermitteln
http://www.delphi-library.de/topic_51393.html&sid=2b3c4a2a855c0d6d668dc5afb6a29bf2
ここのソースを改造せずに動かしてみたのですが
実行時にメモリアクセスエラーっぽい誤動作するので、
なんとも....
BDS2006で試してみましたが特に問題なく動作しました。
与えるリンクファイルによって駄目なのでしょうか?
getExecData(FileName, execName, execDir, execArgs);
ShowMessage(execName);
試していただいて、ありがとうございます。
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、
理由がわかりました。
Formのないアプリケーションで動かそうとしていたのです。
CUIアプリケーションでも
if InitProc <> nil then TProcedure(InitProc);
getExecData(FileName, execName, execDir, execArgs);
ShowMessage(execName);
このように記述することで正しく動作することを確認できました。
MSOFFICE系のリンクファイルでも
通常のリンクファイルでも
正しく動作しています。
大変、ありがとうございました。