DotNet.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. unit DotNet;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2020 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://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/fusion
  15. https://docs.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, CmnFunc2;
  25. type
  26. IAssemblyCache = class(Ole2.IUnknown)
  27. function UninstallAssembly(dwFlags: Integer; pszAssemblyName: PWideChar; pvReserved: Integer; var pulDisposition: Integer): Integer; virtual; stdcall; abstract;
  28. function QueryAssemblyInfo(dwFlags: Integer; pszAssemblyName: PWideChar; pAsmInfo: Integer): Integer; virtual; stdcall; abstract;
  29. function CreateAssemblyCacheItem(dwFlags: Integer; pvReserved: Integer; var ppAsmItem: Integer; pszAssemblyName: PWideChar): Integer; virtual; stdcall; abstract;
  30. function CreateAssemblyScavenger(var ppAsmScavenger: Pointer): Integer; virtual; stdcall; abstract;
  31. function InstallAssembly(dwFlags: Integer; pszManifestFilePath: PWideChar; pvReserved: Integer): Integer; 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. TDotNetVersion = (net11, net20, net30, net35, net4Client, net4Full, net45, net451, net452, net46, net461, net462, net47, net471, net472, net48, net481);
  46. function GetDotNetInstallRoot(const RegView: TRegView): String;
  47. function GetDotNetVersionInstallRoot(const RegView: TRegView; const Version: TDotNetBaseVersion): String;
  48. function IsDotNetInstalled(const RegView: TRegView; const MinVersion: TDotNetVersion; const MinServicePack: DWORD): Boolean;
  49. implementation
  50. uses
  51. InstFunc, PathFunc;
  52. var
  53. DotNetRoot: array [TRegView] of String;
  54. DotNetVersionRoot: array [TRegView, TDotNetBaseVersion] of String;
  55. { GetDotNet(Version)InstallRoot }
  56. function GetDotNetInstallRoot(const RegView: TRegView): String;
  57. var
  58. K: HKEY;
  59. begin
  60. if DotNetRoot[RegView] = '' then begin
  61. if RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\.NETFramework', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  62. RegQueryStringValue(K, 'InstallRoot', DotNetRoot[RegView]);
  63. RegCloseKey(K);
  64. end;
  65. if DotNetRoot[RegView] = '' then
  66. InternalError('.NET Framework not found');
  67. end;
  68. Result := DotNetRoot[RegView];
  69. end;
  70. function GetDotNetVersionInstallRoot(const RegView: TRegView; const Version: TDotNetBaseVersion): String;
  71. const
  72. VersionStrings: array [TDotNetBaseVersion] of String = ('1.1', '2.0', '4.0', '');
  73. var
  74. K: HKEY;
  75. begin
  76. if DotNetVersionRoot[RegView, Version] = '' then begin
  77. GetDotNetInstallRoot(RegView);
  78. 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
  79. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v4.0.30319';
  80. RegCloseKey(K);
  81. 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
  82. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v2.0.50727';
  83. RegCloseKey(K);
  84. 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
  85. DotNetVersionRoot[RegView, Version] := AddBackslash(DotNetRoot[RegView]) + 'v1.1.4322';
  86. RegCloseKey(K);
  87. end;
  88. if DotNetVersionRoot[RegView, Version] = '' then begin
  89. if Version <> netbaseHighestKnown then
  90. InternalError(Format('.NET Framework version %s not found', [VersionStrings[Version]]))
  91. else
  92. InternalError('.NET Framework not found');
  93. end;
  94. end;
  95. Result := DotNetVersionRoot[RegView, Version];
  96. end;
  97. { TAssemblyCacheInfo }
  98. constructor TAssemblyCacheInfo.Create(const RegView: TRegView);
  99. type
  100. TCreateAssemblyCache = function (var ppAsmCache: IAssemblyCache; dwReserved: Integer): Integer; stdcall;
  101. var
  102. FileName: string;
  103. Proc: TCreateAssemblyCache;
  104. begin
  105. inherited Create;
  106. FileName := AddBackslash(GetDotNetVersionInstallRoot(RegView, netbaseHighestKnown)) + 'Fusion.dll';
  107. fDll := SafeLoadLibrary(PChar(FileName), SEM_NOOPENFILEERRORBOX);
  108. if fDll = 0 then
  109. InternalError(Format('Failed to load .NET Framework DLL "%s"', [FileName]));
  110. Proc := GetProcAddress(fDll, 'CreateAssemblyCache');
  111. if not Assigned(Proc) then
  112. InternalError('Failed to get address of .NET Framework CreateAssemblyCache function');
  113. Proc(fCache, 0);
  114. if fCache = nil then
  115. InternalError('.NET Framework CreateAssemblyCache function failed');
  116. end;
  117. destructor TAssemblyCacheInfo.Destroy;
  118. begin
  119. if fCache <> nil then
  120. fCache.Release;
  121. fCache := nil;
  122. FreeLibrary(fDll);
  123. inherited Destroy;
  124. end;
  125. procedure TAssemblyCacheInfo.InstallAssembly(const FileName: string);
  126. const
  127. IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH = 2;
  128. var
  129. lOleString: PWideChar;
  130. OleResult: HRESULT;
  131. begin
  132. lOleString := StringToOleStr(FileName);
  133. try
  134. OleResult := fCache.InstallAssembly(IASSEMBLYCACHE_INSTALL_FLAG_FORCE_REFRESH, lOleString, 0);
  135. if Failed(OleResult) then
  136. RaiseOleError('InstallAssembly', OleResult);
  137. finally
  138. SysFreeString(lOleString);
  139. end;
  140. end;
  141. procedure TAssemblyCacheInfo.UninstallAssembly(
  142. const StrongAssemblyName: string);
  143. var
  144. lOleString: PWideChar;
  145. OleResult: HRESULT;
  146. begin
  147. lOleString := StringToOleStr(StrongAssemblyName);
  148. try
  149. OleResult := fCache.UninstallAssembly(0, lOleString, 0, Integer(nil^));
  150. if Failed(OleResult) then
  151. RaiseOleError('UninstallAssembly', OleResult);
  152. finally
  153. SysFreeString(lOleString);
  154. end;
  155. end;
  156. { IsDotNetDetected }
  157. function IsDotNetInstalled(const RegView: TRegView; const MinVersion: TDotNetVersion; const MinServicePack: DWORD): Boolean;
  158. function GetVersionString(const Version: TDotNetVersion): String;
  159. begin
  160. case Version of
  161. net11: Result := 'v1.1';
  162. net20: Result := 'v2.0';
  163. net30: Result := 'v3.0';
  164. net35: Result := 'v3.5';
  165. net4Client: Result := 'v4\Client';
  166. net4Full: Result := 'v4\Full';
  167. net45: Result := 'v4.5';
  168. net451: Result := 'v4.5.1';
  169. net452: Result := 'v4.5.2';
  170. net46: Result := 'v4.6';
  171. net461: Result := 'v4.6.1';
  172. net462: Result := 'v4.6.2';
  173. net47: Result := 'v4.7';
  174. net471: Result := 'v4.7.1';
  175. net472: Result := 'v4.7.2';
  176. net48: Result := 'v4.8';
  177. net481: Result := 'v4.8.1';
  178. else
  179. InternalError('IsDotNetDetected: Invalid Version');
  180. end;
  181. end;
  182. function QueryDWord(const SubKey, ValueName: String; var Value: DWORD): Boolean;
  183. var
  184. K: HKEY;
  185. Typ, Size: DWORD;
  186. begin
  187. if RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, PChar(SubKey), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  188. Size := SizeOf(Value);
  189. Result := (RegQueryValueEx(K, PChar(ValueName), nil, @Typ, @Value, @Size) = ERROR_SUCCESS) and (Typ = REG_DWORD);
  190. RegCloseKey(K);
  191. end else
  192. Result := False;
  193. end;
  194. var
  195. VersionString, VersionKey, SubKey: String;
  196. Install, InstalledRelease, InstalledServicePack, RequiredRelease: DWORD;
  197. Success: Boolean;
  198. begin
  199. VersionString := GetVersionString(MinVersion);
  200. RequiredRelease := 0;
  201. // .NET 1.1 and 2.0 embed release number in version key
  202. if VersionString = 'v1.1' then
  203. VersionKey := 'v1.1.4322'
  204. else if VersionString = 'v2.0' then
  205. VersionKey := 'v2.0.50727'
  206. else begin
  207. // .NET 4.5 and newer install as update to .NET 4.0 Full
  208. if Pos('v4.', VersionString) = 1 then begin
  209. VersionKey := 'v4\Full';
  210. if VersionString = 'v4.5' then
  211. RequiredRelease := 378389
  212. else if VersionString = 'v4.5.1' then
  213. RequiredRelease := 378675 // 378758 on Windows 8 and older
  214. else if VersionString = 'v4.5.2' then
  215. RequiredRelease := 379893
  216. else if VersionString = 'v4.6' then
  217. RequiredRelease := 393295 // 393297 on Windows 8.1 and older
  218. else if VersionString = 'v4.6.1' then
  219. RequiredRelease := 394254 // 394271 before Win10 November Update
  220. else if VersionString = 'v4.6.2' then
  221. RequiredRelease := 394802 // 394806 before Win10 Anniversary Update
  222. else if VersionString = 'v4.7' then
  223. RequiredRelease := 460798 // 460805 before Win10 Creators Update
  224. else if VersionString = 'v4.7.1' then
  225. RequiredRelease := 461308 // 461310 before Win10 Fall Creators Update
  226. else if VersionString = 'v4.7.2' then
  227. RequiredRelease := 461808 // 461814 before Win10 April 2018 Update
  228. else if VersionString = 'v4.8' then
  229. RequiredRelease := 528040 // 528049 before Win10 May 2019 Update
  230. else if VersionString = 'v4.8.1' then
  231. RequiredRelease := 533320 // 533325 before Win11 2022 Update
  232. else
  233. InternalError('IsDotNetDetected: Invalid VersionString');
  234. end else
  235. VersionKey := VersionString;
  236. end;
  237. SubKey := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + VersionKey;
  238. if Pos('v3.0', VersionString) = 1 then
  239. Success := QueryDWord(SubKey + '\Setup', 'InstallSuccess', Install)
  240. else
  241. Success := QueryDWord(SubKey, 'Install', Install);
  242. if Success and (Install = 1) then begin
  243. if Pos('v4', VersionString) = 1 then
  244. Success := QueryDWord(SubKey, 'Servicing', InstalledServicePack)
  245. else
  246. Success := QueryDWord(SubKey, 'SP', InstalledServicePack);
  247. if Success and (InstalledServicePack >= MinServicePack) then begin
  248. if RequiredRelease > 0 then
  249. Success := QueryDWord(SubKey, 'Release', InstalledRelease) and (InstalledRelease >= RequiredRelease);
  250. Result := Success;
  251. end else
  252. Result := False;
  253. end else
  254. Result := False;
  255. end;
  256. end.