summaryrefslogtreecommitdiffhomepage
path: root/installer/non-ui-blocking-exec.iss
blob: 6eec259d1ef126e34606acf47d25e9e4d619c6f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
[Code]
{
	Exec Helper for executing commands without blocking the InnoSetup GUI

    ----

	The main procedure is NonUiBlockingExec().
	Your GUI will remain responsive during the operation.

    Initially written for 7zip by Rik and Jens A. Koch (@jakoch) on StackOverflow:
    http://stackoverflow.com/questions/32256432/how-to-execute-7zip-without-blocking-the-innosetup-ui

    ----

    Usage:

	1. Include this ISS with

	   // #include "..\some\where\non-ui-blocking-exec.iss"

	2. extract your files using NonUiBlockingExec(command, params); in the [Code] section.
}

#IFDEF UNICODE
  #DEFINE AW "W"
#ELSE
  #DEFINE AW "A"
#ENDIF

// --- Start "ShellExecuteEx" Helper

const
  WAIT_TIMEOUT = $00000102;
  SEE_MASK_NOCLOSEPROCESS = $00000040;
  INFINITE = $FFFFFFFF;     { Infinite timeout }

type
  TShellExecuteInfo = record
    cbSize: DWORD;
    fMask: Cardinal;
    Wnd: HWND;
    lpVerb: string;
    lpFile: string;
    lpParameters: string;
    lpDirectory: string;
    nShow: Integer;
    hInstApp: THandle;
    lpIDList: DWORD;
    lpClass: string;
    hkeyClass: THandle;
    dwHotKey: DWORD;
    hMonitor: THandle;
    hProcess: THandle;
  end;

function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL;
  external 'ShellExecuteEx{#AW}@shell32.dll stdcall';
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD;
  external 'WaitForSingleObject@kernel32.dll stdcall';
function CloseHandle(hObject: THandle): BOOL; external 'CloseHandle@kernel32.dll stdcall';

// --- End "ShellExecuteEx" Helper

// --- Start "Application.ProcessMessage" Helper
{
   InnoSetup does not provide Application.ProcessMessage().
   This is "generic" code to recreate a "Application.ProcessMessages"-ish procedure,
   using the WinAPI function PeekMessage(), TranslateMessage() and DispatchMessage().
}
type
  TMsg = record
    hwnd: HWND;
    message: UINT;
    wParam: Longint;
    lParam: Longint;
    time: DWORD;
    pt: TPoint;
  end;

const
  PM_REMOVE = 1;

function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): BOOL; external 'PeekMessageA@user32.dll stdcall';
function TranslateMessage(const lpMsg: TMsg): BOOL; external 'TranslateMessage@user32.dll stdcall';
function DispatchMessage(const lpMsg: TMsg): Longint; external 'DispatchMessageA@user32.dll stdcall';

procedure AppProcessMessage;
var
  Msg: TMsg;
begin
  while PeekMessage(Msg, WizardForm.Handle, 0, 0, PM_REMOVE) do begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
end;

// --- End "Application.ProcessMessage" Helper

procedure NonUiBlockingExec(command: String; params: String);
var
  ExecInfo: TShellExecuteInfo;     // info object for ShellExecuteEx()
begin
    // source and targetdir might contain {tmp} or {app} constant, so expand/resolve it to path names
    command := ExpandConstant(command);
    params := ExpandConstant(params);

    // prepare information about the application being executed by ShellExecuteEx()
    ExecInfo.cbSize := SizeOf(ExecInfo);
    ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS;
    ExecInfo.Wnd := 0;
    ExecInfo.lpFile := command;
    ExecInfo.lpParameters := params;
    ExecInfo.nShow := SW_HIDE;

	{
	 The executable is executed via ShellExecuteEx()
	 Then the installer uses a while loop with the condition
	 WaitForSingleObject and a very minimal timeout
	 to execute AppProcessMessage.

	 AppProcessMessage is itself a helper function, because
	 Innosetup does not provide Application.ProcessMessages().
	 Its job is to be the message pump to the InnoSetup GUI.

	 This trick makes the window responsive/dragable again,
	 while the extraction is done in the background.
	}

	if ShellExecuteEx(ExecInfo) then
	begin
		while WaitForSingleObject(ExecInfo.hProcess, 100) = WAIT_TIMEOUT
		do begin
			AppProcessMessage;
			WizardForm.Refresh();
		end;
		CloseHandle(ExecInfo.hProcess);
	end;
end;