SpawnClient.pas 6.8 KB

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