Setup.InstFunc.Ole.pas 11 KB

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