-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathprocessHandling.js
More file actions
688 lines (603 loc) · 26.3 KB
/
processHandling.js
File metadata and controls
688 lines (603 loc) · 26.3 KB
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
/*
* Windows Process Handling.
* A wrapper to integrate processHandlingCore into GPII.
*
* Copyright 2016 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The research leading to these results has received funding from the European Union's
* Seventh Framework Programme (FP7/2007-2013)
* under grant agreement no. 289016.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("gpii-universal");
var ref = require("ref-napi"),
child_process = require("child_process"),
path = require("path");
var gpii = fluid.registerNamespace("gpii");
var windows = fluid.registerNamespace("gpii.windows");
require("../WindowsUtilities/WindowsUtilities.js");
require("../processReporter/processReporter.js");
require("../windowMessages/index.js");
require("../registrySettingsHandler");
var c = windows.API_constants;
var processesBridge = gpii.processes.windows();
/**
* Kills any windows processes with a given application filename.
* http://stackoverflow.com/questions/7956519/how-to-kill-processes-by-name-win32-api
*
* @param {String} filename The filename of the application. For example, the Windows on
* screen keyboard is "osk.exe". Other examples include "Magnify.exe" and
* "firefox.exe".
*/
gpii.windows.killProcessByName = function (filename) {
var procs = processesBridge.findProcessesByCommand(filename);
for (var n = 0, len = procs.length; n < len; n++) {
var hProcess = windows.kernel32.OpenProcess(c.PROCESS_TERMINATE, 0, procs[n].pid);
if (hProcess) {
windows.kernel32.TerminateProcess(hProcess, 9);
windows.kernel32.CloseHandle(hProcess);
}
}
};
/**
* Gets the full path of a running process.
*
* If the process is running as another user (or the system), then getProcessExe is called, where only the filename of
* the executable is available.
*
* Originally, the process path was taken from the process reporter. However, it was too slow to be called from
* the metrics (when it records the active window) because it was blocking the UI. Work can be done to improve the
* process reporter's getProcessPath (or even hide it in another thread), but it still wouldn't be as quick as this
* method due to it capturing several pieces of information about every process. GPII-3471.
*
* @param {Number} pid The process id.
* @return {String} The executable file, including the full path if available.
*/
gpii.windows.getProcessPath = function (pid) {
var processPath = null;
var hProcess = windows.kernel32.OpenProcess(c.PROCESS_QUERY_INFORMATION, 0, pid);
if (hProcess) {
try {
var maxLength = 0x400;
var pathBuffer = Buffer.alloc(maxLength);
var length = ref.alloc(windows.types.DWORD, maxLength);
var ret = windows.kernel32.QueryFullProcessImageNameW(hProcess, 0, pathBuffer, length);
if (ret) {
processPath = gpii.windows.stringFromWideChar(pathBuffer);
} else {
fluid.log(windows.win32errorText("QueryFullProcessImageName", ret));
}
} finally {
windows.kernel32.CloseHandle(hProcess);
}
}
if (!processPath) {
processPath = gpii.windows.getProcessExe(pid);
}
return processPath;
};
/**
* Gets the executable file of a running process.
* getProcessPath calls this when it is unable to get the full path of a process. The method used in this function
* can be used against processes by other users, but it does not return the full path.
*
* @param {Number} pid The process id.
* @return {String} The filename of the executable.
*/
gpii.windows.getProcessExe = function (pid) {
var processExe = null;
// Take the process snapshot.
var snapShot = windows.kernel32.CreateToolhelp32Snapshot(windows.API_constants.TH32CS_SNAPPROCESS, null);
if (snapShot === windows.API_constants.INVALID_HANDLE_VALUE) {
fluid.log(windows.win32errorText("CreateToolhelp32Snapshot", snapShot));
} else {
try {
var processEntry = new windows.PROCESSENTRY32();
processEntry.dwSize = windows.PROCESSENTRY32.size;
// Enumerate the processes
var gotProcess = windows.kernel32.Process32First(snapShot, processEntry.ref());
while (gotProcess) {
if (processEntry.th32ProcessID === pid) {
// The matching process.
processExe = ref.readCString(processEntry.szExeFile.buffer);
break;
}
// Get the next one
gotProcess = windows.kernel32.Process32Next(snapShot, processEntry.ref());
}
} finally {
windows.kernel32.CloseHandle(snapShot);
}
}
return processExe;
};
/**
* Determines if a given process is running, returning true if it is.
*
* @param {String|Number} proc The name of the executable, or the process ID.
* @return {Boolean} true if the process is running.
*/
gpii.windows.isProcessRunning = function (proc) {
var togo = false;
if (isNaN(proc)) {
togo = gpii.processReporter.find(proc);
} else {
try {
process.kill(proc, 0);
togo = true;
} catch (e) {
togo = false;
}
}
return togo;
};
/**
* Waits for a process to either start or terminate, returning a promise which will resolve when the existence of a
* process is in the desired state.
*
* The promise will reject if the process has yet to become in the desired state after the timeout.
*
* @param {String|Number} proc The name of the executable, or the process ID.
* @param {Object} options The options.
* @param {Boolean} options.start The desired state: true to wait for the process to start, false to wait for the termination.
* @param {Number} options.timeout Approximate milliseconds to wait, or null for infinite.
* @param {Number} options.pollDelay How long to wait, in milliseconds, between checking for the process.
* @param {Boolean} options.dontReject If true, then resolve the promise with "timeout" instead of rejecting.
* @return {Promise} The promise will resolve when the process is in the desired state, or will reject if after the
* timeout the process is still in the same state.
*/
gpii.windows.waitForProcessState = function (proc, options) {
var defaultOptions = {
pollDelay: 500,
timeout: null,
start: false
};
options = fluid.extend(true, defaultOptions, options);
var waitOptions = {
argument: proc,
conditionValue: options.start,
pollDelay: options.pollDelay,
timeout: options.timeout,
dontReject: options.dontReject,
error: {
isError: true,
message: "Timed out waiting for process " + proc +
" to " + (options.start ? "start" : "terminate") + " after " + options.timeout + "ms"
}
};
return gpii.windows.waitForCondition(gpii.windows.isProcessRunning, waitOptions);
};
/**
* Waits for a process to terminate, returning a promise which will resolve when there are no matching processes
* running. If there are no processes running when the function is called, the promise will already be resolved.
* The promise will reject if the process is still running after the timeout.
*
* @param {String|Number} proc The name of the executable, or the process ID.
* @param {Object} userOptions The options.
* @param {Number} userOptions.timeout Approximate milliseconds to wait, or null for infinite.
* @param {Number} userOptions.pollDelay How long to wait, in milliseconds, between checking for the process.
* @param {Boolean} userOptions.dontReject If true, then resolve the promise with "timeout" instead of rejecting.
* @return {Promise} The promise will resolve when there are no matching processes running, or will reject if a matching
* process is still running after the timeout.
*/
gpii.windows.waitForProcessTermination = function (proc, userOptions) {
var options = fluid.extend(true, {start: false}, userOptions);
return gpii.windows.waitForProcessState(proc, options);
};
/**
* Waits for a process to start, returning a promise which will resolve when there there is a matching process running.
* If there are already processes running when the function is called, the promise will already be resolved.
* The promise will reject if a matching process is still not running after the timeout.
*
* @param {String|Number} proc The name of the executable, or the process ID.
* @param {Object} userOptions The options.
* @param {Number} userOptions.timeout Approximate milliseconds to wait, or null for infinite.
* @param {Number} userOptions.pollDelay How long to wait, in milliseconds, between checking for the process.
* @param {Boolean} userOptions.dontReject If true, then resolve the promise with "timeout" instead of rejecting.
* @return {Promise} The promise will resolve when there is a matching processes running, or will reject if a matching
* process is still not running after the timeout.
*/
gpii.windows.waitForProcessStart = function (proc, userOptions) {
var options = fluid.extend(true, {start: true}, userOptions);
return gpii.windows.waitForProcessState(proc, options);
};
/**
* Terminates a process in a kind manner by sending WM_QUIT to the windows it owns. This should allow the process to
* perform any clean up tasks.
*
* If options.gracefulOnly is false (default), and if the process has not shutdown after the timeout or the process
* does not have any Windows, then the process will be terminated.
*
* The returned promise resolves when the process has ended, with a boolean indicating whether a clean shutdown was
* possible.
*
* @param {String} filename The filename of the process.
* @param {Object} options The options.
* @param {Number} options.timeout How long to wait for the process to die, in milliseconds.
* @param {Boolean} options.cleanOnly true to reject if the process can't be closed cleanly; false to force the termination.
* @param {Number} options.exitCode The exit code the application should return.
* @return {Promise} Resolves when the process has ended, with a boolean indicating whether a clean shutdown was possible.
*/
gpii.windows.closeProcessByName = function (filename, options) {
var defaultOptions = {
timeout: 15000,
cleanOnly: false,
exitCode: 0
};
options = fluid.extend(defaultOptions, options);
var promiseTogo = fluid.promise();
var pids = fluid.getMembers(processesBridge.findProcessesByCommand(filename), "pid");
if (pids.length > 0) {
var processWindows = gpii.windows.getProcessWindows(pids);
if (processWindows.length > 0) {
fluid.each(processWindows, function (hwnd) {
if (options.closeWindow) {
// Send WM_CLOSE, which tells the application to exit nicely.
gpii.windows.user32.PostMessageW(hwnd, windows.API_constants.WM_CLOSE, options.exitCode, 0);
} else {
// Send WM_QUIT, which tells the thread to terminate.
gpii.windows.user32.PostMessageW(hwnd, windows.API_constants.WM_QUIT, options.exitCode, 0);
}
});
// Wait for the process to die.
gpii.windows.waitForProcessTermination(filename, {timeout: options.timeout}).then(function () {
promiseTogo.resolve(true);
}, function (err) {
// Taking too long to die.
if (options.cleanOnly) {
promiseTogo.reject({
isError: true,
message: "Process " + filename + " will not close cleanly: " + err.message,
error: err
});
} else {
// Force it to terminate
gpii.windows.killProcessByName(filename);
// This promise was rejected but the return promise should resolve as the process is terminated.
promiseTogo.resolve(false);
}
});
} else {
// The process does not have any windows so it can't be shutdown cleanly.
if (options.cleanOnly) {
promiseTogo.reject({
isError: true,
message: "Process " + filename + " will not close cleanly: No windows belong to the process"
});
} else {
// Force it to terminate.
gpii.windows.killProcessByName(filename);
promiseTogo.resolve(false);
}
}
} else {
// The process was not found
promiseTogo.resolve(false);
}
return promiseTogo;
};
/**
* Gets the window handles owned by a process, or a number of processes.
* The order appears to be the most recently activated first.
*
* @param {Array<Number>|Number} pid The PID(s).
* @return {Array<Number>} An array of handles to the top level windows owned by the process(es).
*/
gpii.windows.getProcessWindows = function (pid) {
var pids = fluid.makeArray(pid);
var windowsTogo = [];
if (pids.length) {
// Enumerate all the top-level windows on the desktop, to see which ones are owned by the process.
gpii.windows.enumerateWindows(function (hwnd) {
// Get the process ID that owns the Window.
var windowPid = gpii.windows.getWindowProcessId(hwnd);
if (pids.includes(windowPid)) {
windowsTogo.push(hwnd);
}
});
}
return windowsTogo;
};
/**
* Determines the current state of a service, which can be one of paused, running, or stopped:
*
*
* @param {String} serviceName The service name (The "service name", not the "display name").
* @return {String} paused, running, stopped, or unknown.
*/
gpii.windows.getServiceState = function (serviceName) {
var managerHandle = gpii.windows.advapi32.OpenSCManagerW(0, 0, gpii.windows.API_constants.SC_MANAGER_CONNECT);
var serviceHandle;
if (managerHandle) {
var serviceBuffer = gpii.windows.stringToWideChar(serviceName);
serviceHandle = gpii.windows.advapi32.OpenServiceW(managerHandle, serviceBuffer,
gpii.windows.API_constants.SERVICE_QUERY_STATUS);
}
var stateNumber;
if (serviceHandle) {
var serviceStatus = new gpii.windows.SERVICE_STATUS();
var success = gpii.windows.advapi32.QueryServiceStatus(serviceHandle, serviceStatus.ref());
if (success) {
stateNumber = serviceStatus.dwCurrentState;
}
gpii.windows.advapi32.CloseServiceHandle(serviceHandle);
}
if (managerHandle) {
gpii.windows.advapi32.CloseServiceHandle(managerHandle);
}
var state;
switch (stateNumber) {
case gpii.windows.API_constants.SERVICE_PAUSE_PENDING:
case gpii.windows.API_constants.SERVICE_PAUSED:
state = "paused";
break;
case gpii.windows.API_constants.SERVICE_CONTINUE_PENDING:
case gpii.windows.API_constants.SERVICE_START_PENDING:
case gpii.windows.API_constants.SERVICE_RUNNING:
state = "running";
break;
case gpii.windows.API_constants.SERVICE_STOP_PENDING:
case gpii.windows.API_constants.SERVICE_STOPPED:
state = "stopped";
break;
default:
state = "unknown";
}
return state;
};
/**
* Gets the handle of the tasktray window.
* @return {Number} The tasktray window handle, 0 if not found.
*/
gpii.windows.getTasktrayWindow = function () {
var classname = windows.stringToWideChar("Shell_TrayWnd");
return windows.user32.FindWindowW(classname, ref.NULL);
};
/**
* Gets the PID of the explorer process that owns the tasktray.
* @return {Number} The process ID of explorer.
*/
gpii.windows.getExplorerProcess = function () {
var trayWindow = windows.getTasktrayWindow();
return trayWindow && windows.getWindowProcessId(trayWindow);
};
/**
* Stops the Windows Explorer shell process, including the folder windows.
*
* Use restartExplorer() to restart it.
*
* @param {Object} options [optional] Options object.
* @param {Number} options.trayWindow Override the task tray's window handle.
* @param {Boolean} options.ignoreFolders True to keep the folder windows open [default: false].
* @param {Number} options.timeout Timeout, in ms, to wait before forcefully terminating the process [default: 8000].
* @param {String} options.explorerExe The explorer executable name to kill [default: explorer.exe]
* @return {Promise} Resolves when explorer has stopped.
*/
gpii.windows.stopExplorer = function (options) {
options = Object.assign({
trayWindow: null,
ignoreFolders: false,
timeout: 8000,
explorerExe: "explorer.exe"
}, options);
var promise = fluid.promise();
fluid.log("Stopping explorer");
if (gpii.windows.isProcessRunning(options.explorerExe)) {
// The tasktray handles a window message of 0x5b4 which causes it to save state and shutdown.
// See https://stackoverflow.com/questions/5689904/gracefully-exit-explorer-programmatically
var closeExplorerMessage = gpii.windows.API_constants.WM_USER + 0x1b4;
var trayWindow = options.trayWindow || windows.getTasktrayWindow();
// The folder windows also need to be closed, otherwise future ones will have a mixture of languages.
var explorerWindows = options.ignoreFolders ? [] : windows.findWindows("CabinetWClass");
var closeTrayPromise;
if (trayWindow) {
windows.user32.PostMessageW(trayWindow, closeExplorerMessage, 0, 0);
// Unable to completely make explorer terminate without killing it, so WM_ENDSESSION is being sent so explorer
// should at least expect to be killed.
windows.user32.PostMessageW(trayWindow, windows.API_constants.WM_QUERYENDSESSION, 0, 1);
closeTrayPromise = gpii.windows.messages.sendMessageAsync(trayWindow, windows.API_constants.WM_ENDSESSION,
1, 1, options.timeout);
} else {
closeTrayPromise = fluid.promise().resolve();
}
// Also instruct the folder windows to shutdown. Sending WM_QUERYENDSESSION causes the windows to re-appear when
// explorer is restarted.
fluid.each(explorerWindows, function (window) {
windows.user32.PostMessageW(window, windows.API_constants.WM_QUERYENDSESSION, 0, 1);
windows.user32.PostMessageW(window, windows.API_constants.WM_ENDSESSION, 1, 1);
});
// Wait for the tray to process the shutdown message.
closeTrayPromise.then(function (value) {
if (value === "timeout") {
fluid.log("Explorer did not respond to shutdown request");
} else {
fluid.log("Explorer processed shutdown");
// Explorer now expects to be killed
windows.killProcessByName(options.explorerExe);
}
// The process has been known to linger, preventing a new instance. Give it until the timeout, then be more forceful
windows.waitForProcessTermination(options.explorerExe, {timeout: options.timeout}).then(promise.resolve, function () {
fluid.log("Explorer did not close - terminating");
windows.killProcessByName(options.explorerExe);
promise.resolve();
});
});
} else {
// Explorer was not running.
promise.resolve();
}
return promise;
};
/**
* Restarts the Windows Explorer shell process.
*
* stopExplorer will be called if it hasn't been called already.
*
* @param {Object} options Options.
* @param {String} options.explorer Path to the explorer executable [default: %SystemRoot%\explorer.exe]
* @param {String} options.explorerExe Executable name of explorer [default: taken from options.explorer]
* @param {Number} options.trayWindow Override the task tray's window handle.
* @param {Number} options.timeout Timeout, in ms, to wait before forcefully terminating the process.
* @param {Array<String>} options.args Arguments to pass to explorer
* @return {Promise} Resolves when explorer has restarted.
*/
gpii.windows.restartExplorer = function (options) {
options = Object.assign({}, options);
if (!options.explorer) {
options.explorer = process.env.SystemRoot + "\\explorer.exe";
options.args = ["/LOADSAVEDWINDOWS"];
}
if (!options.explorerExe) {
options.explorerExe = path.basename(options.explorer);
}
// Hide the taskbar, in case someone tries to interact with it just before it restarts.
var trayWindow = options.trayWindow || windows.getTasktrayWindow();
if (trayWindow) {
gpii.windows.user32.ShowWindow(trayWindow, 0);
}
// Get the icon spacing settings, and re-apply them after. Explorer sometimes sets these to an incorrect value when
// it's restarted after a DPI change. [GPII-4428]
var iconSpacing = gpii.windows.getIconSpacing();
var work = [
function () {
// Don't restart explorer while high-contrast is currently being applied.
var p = fluid.promise();
gpii.windows.waitForProcessStart("sethc.exe", {timeout: 1000}).then(function () {
fluid.promise.follow(gpii.windows.waitForProcessTermination("sethc.exe", {timeout: 5000}), p);
}, p.resolve);
return p;
},
gpii.windows.stopExplorer,
function () {
// Wait for explorer to completely close.
return windows.waitForProcessTermination(options.explorerExe, {timeout: 1000, dontReject: true});
},
function () {
fluid.log("Starting explorer");
var child = child_process.spawn(options.explorer, options.args, {
detached: true,
stdio: "ignore"
});
child.on("error", function (err) {
fluid.log("Error starting explorer:", err);
});
return windows.waitForCondition(windows.getTasktrayWindow, {timeout: options.timeout, dontReject: true});
},
function () {
// Restore the icon spacing
gpii.windows.setIconSpacing(iconSpacing, true);
}
];
return fluid.promise.sequence(work, options);
};
/**
* Activates a window owned by the given process.
* @param {String|Number} process The PID or filename of the process.
* @return {Boolean} true if a window was activated.
*/
gpii.windows.activateProcess = function (process) {
var pids;
if (isNaN(process)) {
pids = fluid.getMembers(processesBridge.findProcessesByCommand(process), "pid");
} else {
pids = process;
}
var processWindows = gpii.windows.getProcessWindows(pids);
var activated = false;
// Activate the first visible window
if (processWindows) {
for (var n = 0; n < processWindows.length; n++) {
var hwnd = processWindows[n];
if (gpii.windows.user32.IsWindowVisible(hwnd)) {
gpii.windows.user32.SetForegroundWindow(hwnd);
activated = true;
break;
}
}
}
return activated;
};
/**
* Starts a process.
*
* Use this in favour of built-in child_process functions only if controlling the initial window is desired, or to
* activate an existing instance.
*
* Not all processes will honour the initial window state value.
*
* @param {String} executable The executable path.
* @param {String|Array<String>} args The arguments (passed directly to the process, with no shell expansion).
* @param {Object} options [optional] Options
* @param {String} options.windowState The state of the process's first window. "hide", "normal", "minimized",
* "maximized", or "noactivate".
* @param {Boolean} options.activateRunning true to activate the application's window if there's already an instance.
*
* @return {Number} The pid of the new process.
*/
gpii.windows.startProcess = function (executable, args, options) {
var instanceActivated = false;
if (options.activateRunning) {
instanceActivated = gpii.windows.activateProcess(path.basename(executable));
}
var pidTogo = 0;
if (!instanceActivated) {
var argArray = fluid.makeArray(args);
// Quote the executable name, in case it contains spaces
argArray.unshift("\"" + executable + "\"");
var commandLine = windows.stringToWideChar(argArray.join(" "));
var showWindow = ({
hide: gpii.windows.API_constants.SW_HIDE,
normal: gpii.windows.API_constants.SW_SHOWNORMAL,
minimized: gpii.windows.API_constants.SW_SHOWMINIMIZED,
maximized: gpii.windows.API_constants.SW_SHOWMAXIMIZED,
noactivate: gpii.windows.API_constants.SW_SHOWNOACTIVATE
})[options && options.windowState];
var startupInfo = new windows.STARTUPINFO();
startupInfo.ref().fill(0);
startupInfo.cb = windows.STARTUPINFO.size;
if (showWindow !== undefined) {
startupInfo.dwFlags |= windows.API_constants.STARTF_USESHOWWINDOW;
startupInfo.wShowWindow = showWindow;
}
var processInfoBuf = new windows.PROCESS_INFORMATION();
processInfoBuf.ref().fill(0);
var ret = windows.kernel32.CreateProcessW(ref.NULL, commandLine, ref.NULL, ref.NULL, 0, 0,
ref.NULL, ref.NULL, startupInfo.ref(), processInfoBuf.ref());
if (ret) {
pidTogo = processInfoBuf.dwProcessId;
} else {
var errorText = windows.win32errorText("CreateProcessW", ret);
fluid.log("Trouble starting '" + argArray.join(" ") + "': ", errorText);
}
windows.kernel32.CloseHandle(processInfoBuf.hThread);
windows.kernel32.CloseHandle(processInfoBuf.hProcess);
}
return pidTogo;
};
fluid.defaults("gpii.windows.stopExplorer", {
gradeNames: "fluid.function",
argumentMap: {}
});
fluid.defaults("gpii.windows.restartExplorer", {
gradeNames: "fluid.function",
argumentMap: {}
});
fluid.defaults("gpii.windows.killProcessByName", {
gradeNames: "fluid.function",
argumentMap: {
filename: 0
}
});
fluid.defaults("gpii.windows.closeProcessByName", {
gradeNames: "fluid.function",
argumentMap: {
filename: 0,
options: 1
}
});