123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- unit Setup.SpawnClient;
- {
- Inno Setup
- Copyright (C) 1997-2024 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Spawn client
- NOTE: These functions are NOT thread-safe. Do not call them from multiple
- threads simultaneously.
- }
- interface
- uses
- Windows, SysUtils, Messages, Setup.InstFunc, Shared.CommonFunc;
- procedure InitializeSpawnClient(const AServerWnd: HWND);
- function InstExecEx(const RunAsOriginalUser: Boolean;
- const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
- var ResultCode: Integer): Boolean;
- function InstShellExecEx(const RunAsOriginalUser: Boolean;
- const Verb, Filename, Params, WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
- implementation
- uses
- Classes, Setup.SpawnCommon;
- var
- SpawnServerPresent: Boolean;
- SpawnServerWnd: HWND;
- procedure WriteLongintToStream(const M: TMemoryStream; const Value: Longint);
- begin
- M.WriteBuffer(Value, SizeOf(Value));
- end;
- procedure WriteStringToStream(const M: TMemoryStream; const Value: String);
- var
- Len: Integer;
- begin
- Len := Length(Value);
- if Len > $FFFF then
- InternalError('WriteStringToStream: Length limit exceeded');
- WriteLongintToStream(M, Len);
- M.WriteBuffer(Value[1], Len * SizeOf(Value[1]));
- end;
- procedure AllowSpawnServerToSetForegroundWindow;
- { This is called to allow processes started by the spawn server process to
- come to the foreground, above the current process's windows. The effect
- normally lasts until new input is generated (a keystroke or click, not
- simply mouse movement).
- Note: If the spawn server process has no visible windows, it seems this
- isn't needed; the process can set the foreground window as it pleases.
- If it does have a visible window, though, it definitely is needed (e.g. in
- the /DebugSpawnServer case). Let's not rely on any undocumented behavior and
- call AllowSetForegroundWindow unconditionally. }
- var
- PID: DWORD;
- AllowSetForegroundWindowFunc: function(dwProcessId: DWORD): BOOL; stdcall;
- begin
- if GetWindowThreadProcessId(SpawnServerWnd, @PID) <> 0 then begin
- AllowSetForegroundWindowFunc := GetProcAddress(GetModuleHandle(user32),
- 'AllowSetForegroundWindow');
- if Assigned(AllowSetForegroundWindowFunc) then
- AllowSetForegroundWindowFunc(PID);
- end;
- end;
- function QuerySpawnServer(const SequenceNumber: Word;
- const Operation: Integer): Word;
- var
- MsgResult: LRESULT;
- begin
- MsgResult := SendMessage(SpawnServerWnd, WM_SpawnServer_Query, Operation,
- SequenceNumber);
- if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
- InternalErrorFmt('QuerySpawnServer: Unexpected response: $%x', [MsgResult]);
- Result := Word(MsgResult);
- end;
- function CallSpawnServer(const CopyDataMsg: DWORD; var M: TMemoryStream;
- const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
- var
- CopyDataStruct: TCopyDataStruct;
- MsgResult: LRESULT;
- SequenceNumber: Word;
- Status: Word;
- LastQueryTime, NowTime: DWORD;
- begin
- CopyDataStruct.dwData := CopyDataMsg;
- CopyDataStruct.cbData := M.Size;
- CopyDataStruct.lpData := M.Memory;
- AllowSpawnServerToSetForegroundWindow;
- MsgResult := SendMessage(SpawnServerWnd, WM_COPYDATA, 0, LPARAM(@CopyDataStruct));
- FreeAndNil(M); { it isn't needed anymore, might as well free now }
- if MsgResult = SPAWN_MSGRESULT_OUT_OF_MEMORY then
- OutOfMemoryError;
- if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
- InternalErrorFmt('CallSpawnServer: Unexpected response: $%x', [MsgResult]);
- SequenceNumber := Word(MsgResult);
- LastQueryTime := GetTickCount;
- repeat
- ProcessMessagesProc;
- { Now that the queue is empty (we mustn't break without first processing
- messages found by a previous MsgWaitForMultipleObjects call), see if
- the status changed, but only if at least 10 ms has elapsed since the
- last query }
- NowTime := GetTickCount;
- if Cardinal(NowTime - LastQueryTime) >= Cardinal(10) then begin
- LastQueryTime := NowTime;
- Status := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_STATUS);
- case Status of
- SPAWN_STATUS_RUNNING: ;
- SPAWN_STATUS_RETURNED_TRUE, SPAWN_STATUS_RETURNED_FALSE: Break;
- else
- InternalErrorFmt('CallSpawnServer: Unexpected status: %d', [Status]);
- end;
- end;
- { Delay for 10 ms, or until a message arrives }
- MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
- until False;
- ResultCode := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_LO) or
- (QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_HI) shl 16);
- Result := (Status = SPAWN_STATUS_RETURNED_TRUE);
- end;
- function InstExecEx(const RunAsOriginalUser: Boolean;
- const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
- var ResultCode: Integer): Boolean;
- var
- M: TMemoryStream;
- begin
- if not RunAsOriginalUser or not SpawnServerPresent then begin
- Result := InstExec(DisableFsRedir, Filename, Params, WorkingDir,
- Wait, ShowCmd, ProcessMessagesProc, OutputReader, ResultCode);
- Exit;
- end;
- M := TMemoryStream.Create;
- try
- WriteLongintToStream(M, Ord(DisableFsRedir));
- WriteStringToStream(M, Filename);
- WriteStringToStream(M, Params);
- WriteStringToStream(M, WorkingDir);
- WriteLongintToStream(M, Ord(Wait));
- WriteLongintToStream(M, ShowCmd);
- WriteStringToStream(M, GetCurrentDir);
- Result := CallSpawnServer(CD_SpawnServer_Exec, M, ProcessMessagesProc,
- ResultCode);
- finally
- M.Free;
- end;
- end;
- function InstShellExecEx(const RunAsOriginalUser: Boolean;
- const Verb, Filename, Params, WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
- var
- M: TMemoryStream;
- begin
- if not RunAsOriginalUser or not SpawnServerPresent then begin
- Result := InstShellExec(Verb, Filename, Params, WorkingDir,
- Wait, ShowCmd, ProcessMessagesProc, ResultCode);
- Exit;
- end;
- M := TMemoryStream.Create;
- try
- WriteStringToStream(M, Verb);
- WriteStringToStream(M, Filename);
- WriteStringToStream(M, Params);
- WriteStringToStream(M, WorkingDir);
- WriteLongintToStream(M, Ord(Wait));
- WriteLongintToStream(M, ShowCmd);
- WriteStringToStream(M, GetCurrentDir);
- Result := CallSpawnServer(CD_SpawnServer_ShellExec, M, ProcessMessagesProc,
- ResultCode);
- finally
- M.Free;
- end;
- end;
- procedure InitializeSpawnClient(const AServerWnd: HWND);
- begin
- SpawnServerWnd := AServerWnd;
- SpawnServerPresent := True;
- end;
- end.
|