InstFnc2.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. unit InstFnc2;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. OLE-related installation functions
  8. }
  9. interface
  10. {$I VERSION.INC}
  11. function CreateShellLink(const Filename, Description, ShortcutTo, Parameters,
  12. WorkingDir: String; IconFilename: String; const IconIndex, ShowCmd: Integer;
  13. const HotKey: Word; const AppUserModelID: String;
  14. const AppUserModelToastActivatorCLSID: PGUID;
  15. const ExcludeFromShowInNewInstall, PreventPinning: Boolean): String;
  16. procedure RegisterTypeLibrary(const Filename: String);
  17. procedure UnregisterTypeLibrary(const Filename: String);
  18. function UnpinShellLink(const Filename: String): Boolean;
  19. implementation
  20. uses
  21. Windows, SysUtils, PathFunc, CmnFunc2, InstFunc, Main, Msgs, MsgIDs,
  22. ActiveX, ComObj, PropSys, ShellAPI, ShlObj;
  23. function IsWindows8: Boolean;
  24. { Returns True if running Windows 8 or later }
  25. begin
  26. Result := (WindowsVersion >= Cardinal($06020000));
  27. end;
  28. function IsWindows10: Boolean;
  29. { Returns True if running Windows 10 or later }
  30. begin
  31. Result := (WindowsVersion >= Cardinal($0A000000));
  32. end;
  33. procedure AssignWorkingDir(const SL: IShellLink; const WorkingDir: String);
  34. { Assigns the specified working directory to SL. If WorkingDir is empty then
  35. we select one ourself as best we can. (Leaving the working directory field
  36. empty is a security risk.) Note: SL.SetPath must be called first. }
  37. var
  38. Dir: String;
  39. Buf: array[0..1023] of Char;
  40. begin
  41. { Try any caller-supplied WorkingDir first }
  42. if WorkingDir <> '' then
  43. { SetWorkingDirectory *shouldn't* fail, but we might as well check }
  44. if SL.SetWorkingDirectory(PChar(WorkingDir)) = S_OK then
  45. Exit;
  46. { Otherwise, try to extract a directory name from the shortcut's target
  47. filename. We use GetPath to retrieve the filename as it will expand any
  48. environment strings. }
  49. if SL.GetPath(Buf, SizeOf(Buf) div SizeOf(Buf[0]), TWin32FindData(nil^), 0) = S_OK then begin
  50. Dir := PathExtractDir(PathExpand(Buf));
  51. if SL.SetWorkingDirectory(PChar(Dir)) = S_OK then
  52. Exit;
  53. end;
  54. { As a last resort, use the system directory }
  55. Dir := GetSystemDir;
  56. SL.SetWorkingDirectory(PChar(Dir));
  57. end;
  58. function GetResultingFilename(const PF: IPersistFile;
  59. const OriginalFilename: String): String;
  60. { Determines the actual resulting filename. IPersistFile::Save doesn't always
  61. save to the specified filename; it may rename the extension to .pif if the
  62. shortcut points to an MS-DOS application. }
  63. var
  64. CurFilename: PWideChar;
  65. OleResult: HRESULT;
  66. begin
  67. Result := '';
  68. CurFilename := nil;
  69. OleResult := PF.GetCurFile(CurFilename);
  70. if SUCCEEDED(OleResult) and Assigned(CurFilename) then begin
  71. if OleResult = S_OK then
  72. Result := WideCharToString(CurFilename);
  73. CoTaskMemFree(CurFilename);
  74. end;
  75. { If GetCurFile didn't work, we have no choice but to try to guess the filename }
  76. if Result = '' then begin
  77. if NewFileExists(OriginalFilename) then
  78. Result := OriginalFilename
  79. else if NewFileExists(PathChangeExt(OriginalFilename, '.pif')) then
  80. Result := PathChangeExt(OriginalFilename, '.pif')
  81. else begin
  82. { Neither exist? Shouldn't happen, but return something anyway }
  83. Result := OriginalFilename;
  84. end;
  85. end;
  86. end;
  87. function CreateShellLink(const Filename, Description, ShortcutTo, Parameters,
  88. WorkingDir: String; IconFilename: String; const IconIndex, ShowCmd: Integer;
  89. const HotKey: Word; const AppUserModelID: String;
  90. const AppUserModelToastActivatorCLSID: PGUID;
  91. const ExcludeFromShowInNewInstall, PreventPinning: Boolean): String;
  92. { Creates a lnk file named Filename, with a description of Description, with a
  93. HotKey hotkey, which points to ShortcutTo. Filename should be a full path.
  94. NOTE! If you want to copy this procedure for use in your own application
  95. be sure to call CoInitialize at application startup and CoUninitialize at
  96. application shutdown. See the bottom of this unit for an example. But this
  97. is not necessary if you are using Delphi 3 and your project already 'uses'
  98. the ComObj RTL unit. }
  99. const
  100. PKEY_AppUserModel_ID: TPropertyKey = (
  101. fmtid: (D1:$9F4C2855; D2:$9F79; D3:$4B39; D4:($A8,$D0,$E1,$D4,$2D,$E1,$D5,$F3));
  102. pid: 5);
  103. PKEY_AppUserModel_ExcludeFromShowInNewInstall: TPropertyKey = (
  104. fmtid: (D1:$9F4C2855; D2:$9F79; D3:$4B39; D4:($A8,$D0,$E1,$D4,$2D,$E1,$D5,$F3));
  105. pid: 8);
  106. PKEY_AppUserModel_PreventPinning: TPropertyKey = (
  107. fmtid: (D1:$9F4C2855; D2:$9F79; D3:$4B39; D4:($A8,$D0,$E1,$D4,$2D,$E1,$D5,$F3));
  108. pid: 9);
  109. PKEY_AppUserModel_StartPinOption: TPropertyKey = (
  110. fmtid: (D1:$9F4C2855; D2:$9F79; D3:$4B39; D4:($A8,$D0,$E1,$D4,$2D,$E1,$D5,$F3));
  111. pid: 12);
  112. PKEY_AppUserModel_ToastActivatorCLSID: TPropertyKey = (
  113. fmtid: (D1:$9F4C2855; D2:$9F79; D3:$4B39; D4:($A8,$D0,$E1,$D4,$2D,$E1,$D5,$F3));
  114. pid: 26);
  115. APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL = 1;
  116. var
  117. OleResult: HRESULT;
  118. Obj: IUnknown;
  119. SL: IShellLink;
  120. PS: PropSys.IPropertyStore;
  121. PV: TPropVariant;
  122. PF: IPersistFile;
  123. WideAppUserModelID, WideFilename: WideString;
  124. begin
  125. Obj := CreateComObject(CLSID_ShellLink);
  126. SL := Obj as IShellLink;
  127. SL.SetPath(PChar(ShortcutTo));
  128. SL.SetArguments(PChar(Parameters));
  129. AssignWorkingDir(SL, WorkingDir);
  130. if IconFilename <> '' then begin
  131. { Work around a 64-bit Windows bug. It replaces pf32 with %ProgramFiles%
  132. which is wrong. This causes an error when the user tries to change the
  133. icon of the installed shortcut. Note that the icon does actually display
  134. fine because it *also* stores the original 'non replaced' path in the
  135. shortcut. }
  136. if IsWin64 then
  137. StringChangeEx(IconFileName, ExpandConst('{pf32}\'), '%ProgramFiles(x86)%\', True);
  138. SL.SetIconLocation(PChar(IconFilename), IconIndex);
  139. end;
  140. SL.SetShowCmd(ShowCmd);
  141. if Description <> '' then
  142. SL.SetDescription(PChar(Description));
  143. if HotKey <> 0 then
  144. SL.SetHotKey(HotKey);
  145. if (AppUserModelID <> '') or (AppUserModelToastActivatorCLSID <> nil) or ExcludeFromShowInNewInstall or PreventPinning then begin
  146. PS := Obj as PropSys.IPropertyStore;
  147. { According to MSDN the PreventPinning property should be set before the ID property. In practice
  148. this doesn't seem to matter - at least not for shortcuts - but do it first anyway. }
  149. if PreventPinning then begin
  150. PV.vt := VT_BOOL;
  151. PV.boolVal := True;
  152. OleResult := PS.SetValue(PKEY_AppUserModel_PreventPinning, PV);
  153. if OleResult <> S_OK then
  154. RaiseOleError('IPropertyStore::SetValue(PKEY_AppUserModel_PreventPinning)', OleResult);
  155. end;
  156. if AppUserModelID <> '' then begin
  157. WideAppUserModelID := AppUserModelID;
  158. PV.vt := VT_BSTR;
  159. PV.bstrVal := PWideChar(WideAppUserModelID);
  160. OleResult := PS.SetValue(PKEY_AppUserModel_ID, PV);
  161. if OleResult <> S_OK then
  162. RaiseOleError('IPropertyStore::SetValue(PKEY_AppUserModel_ID)', OleResult);
  163. end;
  164. if IsWindows10 and (AppUserModelToastActivatorCLSID <> nil) then begin
  165. PV.vt := VT_CLSID;
  166. PV.puuid := AppUserModelToastActivatorCLSID;
  167. OleResult := PS.SetValue(PKEY_AppUserModel_ToastActivatorCLSID, PV);
  168. if OleResult <> S_OK then
  169. RaiseOleError('IPropertyStore::SetValue(PKEY_AppUserModel_ToastActivatorCLSID)', OleResult);
  170. end;
  171. if ExcludeFromShowInNewInstall then begin
  172. PV.vt := VT_BOOL;
  173. PV.boolVal := True;
  174. OleResult := PS.SetValue(PKEY_AppUserModel_ExcludeFromShowInNewInstall, PV);
  175. if OleResult <> S_OK then
  176. RaiseOleError('IPropertyStore::SetValue(PKEY_AppUserModel_ExcludeFromShowInNewInstall)', OleResult);
  177. if IsWindows8 then begin
  178. PV.vt := VT_UI4;
  179. PV.ulVal := APPUSERMODEL_STARTPINOPTION_NOPINONINSTALL;
  180. OleResult := PS.SetValue(PKEY_AppUserModel_StartPinOption, PV);
  181. if OleResult <> S_OK then
  182. RaiseOleError('IPropertyStore::SetValue(PKEY_AppUserModel_StartPinOption)', OleResult);
  183. end;
  184. end;
  185. OleResult := PS.Commit;
  186. if OleResult <> S_OK then
  187. RaiseOleError('IPropertyStore::Commit', OleResult);
  188. end;
  189. PF := SL as IPersistFile;
  190. WideFilename := Filename;
  191. OleResult := PF.Save(PWideChar(WideFilename), True);
  192. if OleResult <> S_OK then
  193. RaiseOleError('IPersistFile::Save', OleResult);
  194. Result := GetResultingFilename(PF, Filename);
  195. end;
  196. procedure RegisterTypeLibrary(const Filename: String);
  197. var
  198. WideFilename: WideString;
  199. OleResult: HRESULT;
  200. TypeLib: ITypeLib;
  201. begin
  202. WideFilename := PathExpand(Filename);
  203. OleResult := LoadTypeLib(PWideChar(WideFilename), TypeLib);
  204. if OleResult <> S_OK then
  205. RaiseOleError('LoadTypeLib', OleResult);
  206. OleResult := RegisterTypeLib(TypeLib, PWideChar(WideFilename), nil);
  207. if OleResult <> S_OK then
  208. RaiseOleError('RegisterTypeLib', OleResult);
  209. end;
  210. procedure UnregisterTypeLibrary(const Filename: String);
  211. type
  212. TUnRegTlbProc = function(const libID: TGUID; wVerMajor, wVerMinor: Word;
  213. lcid: TLCID; syskind: TSysKind): HResult; stdcall;
  214. var
  215. UnRegTlbProc: TUnRegTlbProc;
  216. WideFilename: WideString;
  217. OleResult: HRESULT;
  218. TypeLib: ITypeLib;
  219. LibAttr: PTLibAttr;
  220. begin
  221. { Dynamically import UnRegisterTypeLib since older OLEAUT32.DLL versions
  222. don't have this function }
  223. @UnRegTlbProc := GetProcAddress(GetModuleHandle('OLEAUT32.DLL'),
  224. 'UnRegisterTypeLib');
  225. if @UnRegTlbProc = nil then
  226. Win32ErrorMsg('GetProcAddress');
  227. WideFilename := PathExpand(Filename);
  228. OleResult := LoadTypeLib(PWideChar(WideFilename), TypeLib);
  229. if OleResult <> S_OK then
  230. RaiseOleError('LoadTypeLib', OleResult);
  231. OleResult := TypeLib.GetLibAttr(LibAttr);
  232. if OleResult <> S_OK then
  233. RaiseOleError('ITypeLib::GetLibAttr', OleResult);
  234. try
  235. with LibAttr^ do
  236. OleResult := UnRegTlbProc(Guid, wMajorVerNum, wMinorVerNum, LCID, SysKind);
  237. if OleResult <> S_OK then
  238. RaiseOleError('UnRegisterTypeLib', OleResult);
  239. finally
  240. TypeLib.ReleaseTLibAttr(LibAttr);
  241. end;
  242. end;
  243. const
  244. CLSID_StartMenuPin: TGUID = (
  245. D1:$a2a9545d; D2:$a0c2; D3:$42b4; D4:($97,$08,$a0,$b2,$ba,$dd,$77,$c8));
  246. IID_StartMenuPinnedList: TGUID = (
  247. D1:$4CD19ADA; D2:$25A5; D3:$4A32; D4:($B3,$B7,$34,$7B,$EE,$5B,$E3,$6B));
  248. IID_ShellItem: TGUID = (
  249. D1:$43826D1E; D2:$E718; D3:$42EE; D4:($BC,$55,$A1,$E2,$61,$C3,$7B,$FE));
  250. type
  251. IStartMenuPinnedList = interface(IUnknown)
  252. ['{4CD19ADA-25A5-4A32-B3B7-347BEE5BE36B}']
  253. function RemoveFromList(const pitem: IShellItem): HRESULT; stdcall;
  254. end;
  255. var
  256. SHCreateItemFromParsingNameFunc: function(pszPath: LPCWSTR; const pbc: IBindCtx;
  257. const riid: TIID; var ppv): HResult; stdcall;
  258. { Attempt to unpin a shortcut. Returns True if the shortcut was successfully
  259. removed from the list of pinned items and/or the taskbar, or if the shortcut
  260. was not pinned at all. http://msdn.microsoft.com/en-us/library/bb774817.aspx }
  261. function UnpinShellLink(const Filename: String): Boolean;
  262. var
  263. WideFileName: WideString;
  264. ShellItem: IShellItem;
  265. StartMenuPinnedList: IStartMenuPinnedList;
  266. begin
  267. WideFilename := PathExpand(Filename);
  268. if Assigned(SHCreateItemFromParsingNameFunc) and
  269. SUCCEEDED(SHCreateItemFromParsingNameFunc(PWideChar(WideFilename), nil, IID_ShellItem, ShellItem)) and
  270. SUCCEEDED(CoCreateInstance(CLSID_StartMenuPin, nil, CLSCTX_INPROC_SERVER, IID_StartMenuPinnedList, StartMenuPinnedList)) then
  271. Result := StartMenuPinnedList.RemoveFromList(ShellItem) = S_OK
  272. else
  273. Result := True;
  274. end;
  275. procedure InitOle;
  276. var
  277. OleResult: HRESULT;
  278. begin
  279. OleResult := CoInitialize(nil);
  280. if FAILED(OleResult) then
  281. raise Exception.CreateFmt('CoInitialize failed (0x%.8x)', [OleResult]);
  282. { ^ doesn't use a SetupMessage since messages probably aren't loaded
  283. during 'initialization' section below, which calls this procedure }
  284. end;
  285. initialization
  286. InitOle;
  287. SHCreateItemFromParsingNameFunc := GetProcAddress(SafeLoadLibrary(AddBackslash(GetSystemDir) + shell32,
  288. SEM_NOOPENFILEERRORBOX), 'SHCreateItemFromParsingName');
  289. finalization
  290. CoUninitialize;
  291. end.