Setup.SpawnClient.pas 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. unit Setup.SpawnClient;
  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. Spawn client
  8. NOTE: These functions are NOT thread-safe. Do not call them from multiple
  9. threads simultaneously.
  10. }
  11. interface
  12. uses
  13. Windows, SysUtils, Messages, Setup.InstFunc, Shared.CommonFunc;
  14. procedure InitializeSpawnClient(const AServerWnd: HWND);
  15. function InstExecEx(const RunAsOriginalUser: Boolean;
  16. const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
  17. const Wait: TExecWait; const ShowCmd: Integer;
  18. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  19. var ResultCode: DWORD): Boolean;
  20. function InstShellExecEx(const RunAsOriginalUser: Boolean;
  21. const Verb, Filename, Params, WorkingDir: String;
  22. const Wait: TExecWait; const ShowCmd: Integer;
  23. const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
  24. function IsSpawnServerPresent: Boolean;
  25. function StopSpawnServerProcess(const AExitCode: DWORD): Boolean;
  26. implementation
  27. uses
  28. Classes, Setup.SpawnCommon;
  29. var
  30. SpawnServerPresent: Boolean;
  31. SpawnServerWnd: HWND;
  32. procedure WriteLongintToStream(const M: TMemoryStream; const Value: Longint);
  33. begin
  34. M.WriteBuffer(Value, SizeOf(Value));
  35. end;
  36. procedure WriteStringToStream(const M: TMemoryStream; const Value: String);
  37. var
  38. Len: Integer;
  39. begin
  40. Len := Length(Value);
  41. if Len > $FFFF then
  42. InternalError('WriteStringToStream: Length limit exceeded');
  43. WriteLongintToStream(M, Len);
  44. M.WriteBuffer(Value[1], Len * SizeOf(Value[1]));
  45. end;
  46. procedure AllowSpawnServerToSetForegroundWindow;
  47. { This is called to allow processes started by the spawn server process to
  48. come to the foreground, above the current process's windows. The effect
  49. normally lasts until new input is generated (a keystroke or click, not
  50. simply mouse movement).
  51. Note: If the spawn server process has no visible windows, it seems this
  52. isn't needed; the process can set the foreground window as it pleases.
  53. If it does have a visible window, though, it definitely is needed (e.g. in
  54. the /DebugSpawnServer case). Let's not rely on any undocumented behavior and
  55. call AllowSetForegroundWindow unconditionally. }
  56. var
  57. PID: DWORD;
  58. AllowSetForegroundWindowFunc: function(dwProcessId: DWORD): BOOL; stdcall;
  59. begin
  60. if GetWindowThreadProcessId(SpawnServerWnd, @PID) <> 0 then begin
  61. AllowSetForegroundWindowFunc := GetProcAddress(GetModuleHandle(user32),
  62. 'AllowSetForegroundWindow');
  63. if Assigned(AllowSetForegroundWindowFunc) then
  64. AllowSetForegroundWindowFunc(PID);
  65. end;
  66. end;
  67. function QuerySpawnServer(const SequenceNumber: Word;
  68. const Operation: Integer): Word;
  69. var
  70. MsgResult: LRESULT;
  71. begin
  72. MsgResult := SendMessage(SpawnServerWnd, WM_SpawnServer_Query, Operation,
  73. SequenceNumber);
  74. if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
  75. InternalErrorFmt('QuerySpawnServer: Unexpected response: $%x', [MsgResult]);
  76. Result := Word(MsgResult);
  77. end;
  78. function CallSpawnServer(const CopyDataMsg: DWORD; var M: TMemoryStream;
  79. const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
  80. var
  81. CopyDataStruct: TCopyDataStruct;
  82. MsgResult: LRESULT;
  83. SequenceNumber: Word;
  84. Status: Word;
  85. LastQueryTime, NowTime: DWORD;
  86. begin
  87. CopyDataStruct.dwData := CopyDataMsg;
  88. if M.Size > High(DWORD) then
  89. InternalError('CallSpawnServer: Size limit exceeded');
  90. CopyDataStruct.cbData := DWORD(M.Size);
  91. CopyDataStruct.lpData := M.Memory;
  92. AllowSpawnServerToSetForegroundWindow;
  93. MsgResult := SendMessage(SpawnServerWnd, WM_COPYDATA, 0, LPARAM(@CopyDataStruct));
  94. FreeAndNil(M); { it isn't needed anymore, might as well free now }
  95. if MsgResult = SPAWN_MSGRESULT_OUT_OF_MEMORY then
  96. OutOfMemoryError;
  97. if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
  98. InternalErrorFmt('CallSpawnServer: Unexpected response: $%x', [MsgResult]);
  99. SequenceNumber := Word(MsgResult);
  100. LastQueryTime := GetTickCount;
  101. repeat
  102. ProcessMessagesProc;
  103. { Now that the queue is empty (we mustn't break without first processing
  104. messages found by a previous MsgWaitForMultipleObjects call), see if
  105. the status changed, but only if at least 10 ms has elapsed since the
  106. last query }
  107. NowTime := GetTickCount;
  108. if Cardinal(NowTime - LastQueryTime) >= Cardinal(10) then begin
  109. LastQueryTime := NowTime;
  110. Status := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_STATUS);
  111. case Status of
  112. SPAWN_STATUS_RUNNING: ;
  113. SPAWN_STATUS_RETURNED_TRUE, SPAWN_STATUS_RETURNED_FALSE: Break;
  114. else
  115. InternalErrorFmt('CallSpawnServer: Unexpected status: %d', [Status]);
  116. end;
  117. end;
  118. { Delay for 10 ms, or until a message arrives }
  119. MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
  120. until False;
  121. ResultCode := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_LO) or
  122. (QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_HI) shl 16);
  123. Result := (Status = SPAWN_STATUS_RETURNED_TRUE);
  124. end;
  125. function InstExecEx(const RunAsOriginalUser: Boolean;
  126. const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
  127. const Wait: TExecWait; const ShowCmd: Integer;
  128. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  129. var ResultCode: DWORD): Boolean;
  130. var
  131. M: TMemoryStream;
  132. begin
  133. if not RunAsOriginalUser or not SpawnServerPresent then begin
  134. Result := InstExec(DisableFsRedir, Filename, Params, WorkingDir,
  135. Wait, ShowCmd, ProcessMessagesProc, OutputReader, ResultCode);
  136. Exit;
  137. end;
  138. M := TMemoryStream.Create;
  139. try
  140. WriteLongintToStream(M, Ord(DisableFsRedir));
  141. WriteStringToStream(M, Filename);
  142. WriteStringToStream(M, Params);
  143. WriteStringToStream(M, WorkingDir);
  144. WriteLongintToStream(M, Ord(Wait));
  145. WriteLongintToStream(M, ShowCmd);
  146. WriteStringToStream(M, GetCurrentDir);
  147. Result := CallSpawnServer(CD_SpawnServer_Exec, M, ProcessMessagesProc,
  148. ResultCode);
  149. finally
  150. M.Free;
  151. end;
  152. end;
  153. function InstShellExecEx(const RunAsOriginalUser: Boolean;
  154. const Verb, Filename, Params, WorkingDir: String;
  155. const Wait: TExecWait; const ShowCmd: Integer;
  156. const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
  157. var
  158. M: TMemoryStream;
  159. begin
  160. if not RunAsOriginalUser or not SpawnServerPresent then begin
  161. Result := InstShellExec(Verb, Filename, Params, WorkingDir,
  162. Wait, ShowCmd, ProcessMessagesProc, ResultCode);
  163. Exit;
  164. end;
  165. M := TMemoryStream.Create;
  166. try
  167. WriteStringToStream(M, Verb);
  168. WriteStringToStream(M, Filename);
  169. WriteStringToStream(M, Params);
  170. WriteStringToStream(M, WorkingDir);
  171. WriteLongintToStream(M, Ord(Wait));
  172. WriteLongintToStream(M, ShowCmd);
  173. WriteStringToStream(M, GetCurrentDir);
  174. Result := CallSpawnServer(CD_SpawnServer_ShellExec, M, ProcessMessagesProc,
  175. ResultCode);
  176. finally
  177. M.Free;
  178. end;
  179. end;
  180. procedure InitializeSpawnClient(const AServerWnd: HWND);
  181. begin
  182. SpawnServerWnd := AServerWnd;
  183. SpawnServerPresent := True;
  184. end;
  185. function IsSpawnServerPresent: Boolean;
  186. begin
  187. Result := SpawnServerPresent;
  188. end;
  189. function StopSpawnServerProcess(const AExitCode: DWORD): Boolean;
  190. begin
  191. Result := PostMessage(SpawnServerWnd, WM_SpawnServer_ExitNow, AExitCode,
  192. SPAWN_EXITNOW_LPARAM_MAGIC);
  193. end;
  194. end.