Setup.DotNetFunc.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. unit Setup.DotNetFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. .NET functions
  8. Fusion code based on LibFusion.pas by RemObject Software
  9. License:
  10. // LibFusion.pas
  11. // copyright (c) 2009 by RemObjects Software
  12. //
  13. // Uses InnoSetup License
  14. Also see https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/fusion
  15. https://learn.microsoft.com/en-us/windows/win32/sbscs/side-by-side-assembly-api
  16. IsDotNetInstalled code based on http://www.kynosarges.de/DotNetVersion.html by Cristoph Nahr
  17. License:
  18. // I’m placing this small bit of code in the public domain, so you may embed it in your own
  19. // projects, modify and redistribute it as you see fit.
  20. Also see https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
  21. }
  22. interface
  23. uses
  24. Ole2, SysUtils, Windows, Shared.CommonFunc, Shared.DotNetVersion;
  25. type
  26. IAssemblyCache = class(IUnknown)
  27. function UninstallAssembly(dwFlags: DWORD; pszAssemblyName: PChar; pRefData: Pointer; var pulDisposition: ULONG): HRESULT; virtual; stdcall; abstract;
  28. function QueryAssemblyInfo(dwFlags: DWORD; pszAssemblyName: PChar; pAsmInfo: Pointer): HRESULT; virtual; stdcall; abstract;
  29. function CreateAssemblyCacheItem(dwFlags: DWORD; pvReserved: Pointer; var ppAsmItem: Pointer; pszAssemblyName: PChar): HRESULT; virtual; stdcall; abstract;
  30. function CreateAssemblyScavenger(var ppUnkReserved: Pointer): HRESULT; virtual; stdcall; abstract;
  31. function InstallAssembly(dwFlags: DWORD; pszManifestFilePath: PChar; pRefData: Pointer): HRESULT; virtual; stdcall; abstract;
  32. end;
  33. TAssemblyCacheInfo = class
  34. private
  35. fDll: THandle;
  36. fCache: IAssemblyCache;
  37. public
  38. constructor Create(const RegView: TRegView);
  39. destructor Destroy; override;
  40. property Cache: IAssemblyCache read FCache;
  41. procedure InstallAssembly(const FileName: string);
  42. procedure UninstallAssembly(const StrongAssemblyName: string); // Full name! in 'AssemblyName, version=1.0.0.0, culture=neutral, publickeytoken=abcdef123456' format
  43. end;
  44. TDotNetBaseVersion = (netbase11, netbase20, netbase40, netbaseHighestKnown);
  45. function GetDotNetInstallRoot(const RegView: TRegView): String;
  46. function GetDotNetVersionInstallRoot(const RegView: TRegView; const Version: TDotNetBaseVersion): String;
  47. function IsDotNetInstalled(const RegView: TRegView; const MinVersion: TDotNetVersion; const MinServicePack: DWORD): Boolean;
  48. implementation
  49. uses
  50. Setup.InstFunc, PathFunc;
  51. var
  52. DotNetRoot: array [TRegView] of String;
  53. DotNetVersionRoot: array [TRegView, TDotNetBaseVersion] of String;
  54. { GetDotNet(Version)InstallRoot }
  55. function GetDotNetInstallRoot(const RegView: TRegView): String;
  56. var
  57. K: HKEY;
  58. begin
  59. if DotNetRoot[RegView] = '' then begin
  60. if RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\.NETFramework', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  61. RegQueryStringValue(K, 'InstallRoot', DotNetRoot[RegView]);
  62. RegCloseKey(K);
  63. end;
  64. if DotNetRoot[RegView] = '' then
  65. InternalError('.NET Framework not found');
  66. end;
  67. Result := DotNetRoot[RegView];
  68. end;
  69. function GetDotNetVersionInstallRoot(const RegView: TRegView; const Version: TDotNetBaseVersion): String;
  70. const
  71. VersionStrings: array [TDotNetBaseVersion] of String = ('1.1', '2.0', '4.0', '');
  72. var
  73. K: HKEY;
  74. begin
  75. if DotNetVersionRoot[RegView, Version] = '' then begin
  76. GetDotNetInstallRoot(RegView);
  77. if (Version in [netbase40, netbaseHighestKnown]) and (RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\.NETFramework\Policy\v4.0', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  78. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v4.0.30319';
  79. RegCloseKey(K);
  80. end else if (Version in [netbase20, netbaseHighestKnown]) and (RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\.NETFramework\Policy\v2.0', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  81. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v2.0.50727';
  82. RegCloseKey(K);
  83. end else if (Version in [netbase11, netbaseHighestKnown]) and (RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\.NETFramework\Policy\v1.1', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  84. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v1.1.4322';
  85. RegCloseKey(K);
  86. end;
  87. if DotNetVersionRoot[RegView, Version] = '' then begin
  88. if Version <> netbaseHighestKnown then
  89. InternalError(Format('.NET Framework version %s not found', [VersionStrings[Version]]))
  90. else
  91. InternalError('.NET Framework not found');
  92. end;
  93. end;
  94. Result := DotNetVersionRoot[RegView, Version];
  95. end;
  96. { TAssemblyCacheInfo }
  97. constructor TAssemblyCacheInfo.Create(const RegView: TRegView);
  98. type
  99. TCreateAssemblyCache = function (var ppAsmCache: IAssemblyCache; dwReserved: Integer): Integer; stdcall;
  100. var
  101. FileName: string;
  102. Proc: TCreateAssemblyCache;
  103. begin
  104. inherited Create;
  105. FileName := AddBackslash(GetDotNetVersionInstallRoot(RegView, netbaseHighestKnown)) + 'Fusion.dll';
  106. fDll := SafeLoadLibrary(PChar(FileName), SEM_NOOPENFILEERRORBOX);
  107. if fDll = 0 then
  108. InternalError(Format('Failed to load .NET Framework DLL "%s"', [FileName]));
  109. Proc := GetProcAddress(fDll, 'CreateAssemblyCache');
  110. if not Assigned(Proc) then
  111. InternalError('Failed to get address of .NET Framework CreateAssemblyCache function');
  112. Proc(fCache, 0);
  113. if fCache = nil then
  114. InternalError('.NET Framework CreateAssemblyCache function failed');
  115. end;
  116. destructor TAssemblyCacheInfo.Destroy;
  117. begin
  118. if fCache <> nil then
  119. fCache.Release;
  120. fCache := nil;
  121. FreeLibrary(fDll);
  122. inherited Destroy;
  123. end;
  124. procedure TAssemblyCacheInfo.InstallAssembly(const FileName: string);
  125. const
  126. IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH = 2;
  127. begin
  128. const lOleString = StringToOleStr(FileName);
  129. try
  130. const OleResult = fCache.InstallAssembly(IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH, lOleString, nil);
  131. if Failed(OleResult) then
  132. RaiseOleError('InstallAssembly', OleResult);
  133. finally
  134. SysFreeString(lOleString);
  135. end;
  136. end;
  137. procedure TAssemblyCacheInfo.UninstallAssembly(
  138. const StrongAssemblyName: string);
  139. begin
  140. const lOleString = StringToOleStr(StrongAssemblyName);
  141. try
  142. const OleResult = fCache.UninstallAssembly(0, lOleString, nil, ULONG(nil^));
  143. if Failed(OleResult) then
  144. RaiseOleError('UninstallAssembly', OleResult);
  145. finally
  146. SysFreeString(lOleString);
  147. end;
  148. end;
  149. { IsDotNetDetected }
  150. function IsDotNetInstalled(const RegView: TRegView; const MinVersion: TDotNetVersion; const MinServicePack: DWORD): Boolean;
  151. function GetVersionString(const Version: TDotNetVersion): String;
  152. begin
  153. case Version of
  154. net11: Result := 'v1.1';
  155. net20: Result := 'v2.0';
  156. net30: Result := 'v3.0';
  157. net35: Result := 'v3.5';
  158. net4Client: Result := 'v4\Client';
  159. net4Full: Result := 'v4\Full';
  160. net45: Result := 'v4.5';
  161. net451: Result := 'v4.5.1';
  162. net452: Result := 'v4.5.2';
  163. net46: Result := 'v4.6';
  164. net461: Result := 'v4.6.1';
  165. net462: Result := 'v4.6.2';
  166. net47: Result := 'v4.7';
  167. net471: Result := 'v4.7.1';
  168. net472: Result := 'v4.7.2';
  169. net48: Result := 'v4.8';
  170. net481: Result := 'v4.8.1';
  171. else
  172. InternalError('IsDotNetDetected: Invalid Version');
  173. end;
  174. end;
  175. function QueryDWord(const SubKey, ValueName: String; var Value: DWORD): Boolean;
  176. var
  177. K: HKEY;
  178. Typ, Size: DWORD;
  179. begin
  180. if RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, PChar(SubKey), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  181. Size := SizeOf(Value);
  182. Result := (RegQueryValueEx(K, PChar(ValueName), nil, @Typ, PByte(@Value), @Size) = ERROR_SUCCESS) and (Typ = REG_DWORD);
  183. RegCloseKey(K);
  184. end else
  185. Result := False;
  186. end;
  187. var
  188. VersionString, VersionKey, SubKey: String;
  189. Install, InstalledRelease, InstalledServicePack, RequiredRelease: DWORD;
  190. Success: Boolean;
  191. begin
  192. VersionString := GetVersionString(MinVersion);
  193. RequiredRelease := 0;
  194. // .NET 1.1 and 2.0 embed release number in version key
  195. if VersionString = 'v1.1' then
  196. VersionKey := 'v1.1.4322'
  197. else if VersionString = 'v2.0' then
  198. VersionKey := 'v2.0.50727'
  199. else begin
  200. // .NET 4.5 and newer install as update to .NET 4.0 Full
  201. if Pos('v4.', VersionString) = 1 then begin
  202. VersionKey := 'v4\Full';
  203. if VersionString = 'v4.5' then
  204. RequiredRelease := 378389
  205. else if VersionString = 'v4.5.1' then
  206. RequiredRelease := 378675 // 378758 on Windows 8 and older
  207. else if VersionString = 'v4.5.2' then
  208. RequiredRelease := 379893
  209. else if VersionString = 'v4.6' then
  210. RequiredRelease := 393295 // 393297 on Windows 8.1 and older
  211. else if VersionString = 'v4.6.1' then
  212. RequiredRelease := 394254 // 394271 before Win10 November Update
  213. else if VersionString = 'v4.6.2' then
  214. RequiredRelease := 394802 // 394806 before Win10 Anniversary Update
  215. else if VersionString = 'v4.7' then
  216. RequiredRelease := 460798 // 460805 before Win10 Creators Update
  217. else if VersionString = 'v4.7.1' then
  218. RequiredRelease := 461308 // 461310 before Win10 Fall Creators Update
  219. else if VersionString = 'v4.7.2' then
  220. RequiredRelease := 461808 // 461814 before Win10 April 2018 Update
  221. else if VersionString = 'v4.8' then
  222. RequiredRelease := 528040 // 528049 before Win10 May 2019 Update
  223. else if VersionString = 'v4.8.1' then
  224. RequiredRelease := 533320 // 533325 before Win11 2022 Update
  225. else
  226. InternalError('IsDotNetDetected: Invalid VersionString');
  227. end else
  228. VersionKey := VersionString;
  229. end;
  230. SubKey := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + VersionKey;
  231. if Pos('v3.0', VersionString) = 1 then
  232. Success := QueryDWord(SubKey + '\Setup', 'InstallSuccess', Install)
  233. else
  234. Success := QueryDWord(SubKey, 'Install', Install);
  235. if Success and (Install = 1) then begin
  236. if Pos('v4', VersionString) = 1 then
  237. Success := QueryDWord(SubKey, 'Servicing', InstalledServicePack)
  238. else
  239. Success := QueryDWord(SubKey, 'SP', InstalledServicePack);
  240. if Success and (InstalledServicePack >= MinServicePack) then begin
  241. if RequiredRelease > 0 then
  242. Success := QueryDWord(SubKey, 'Release', InstalledRelease) and (InstalledRelease >= RequiredRelease);
  243. Result := Success;
  244. end else
  245. Result := False;
  246. end else
  247. Result := False;
  248. end;
  249. end.