1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | const
|
15 | appc = require('node-appc'),
|
16 | async = require('async'),
|
17 | fs = require('fs'),
|
18 | magik = require('./utilities').magik,
|
19 | checkOutdated = require('./utilities').checkOutdated,
|
20 | path = require('path'),
|
21 | visualstudio = require('./visualstudio'),
|
22 | __ = appc.i18n(__dirname).__,
|
23 | wstool = path.resolve(__dirname, '..', 'bin', 'wstool.exe');
|
24 |
|
25 | var architectures = [ 'arm', 'x86', 'x64' ];
|
26 |
|
27 | var detectCache,
|
28 | deviceCache = {};
|
29 |
|
30 | exports.install = install;
|
31 | exports.launch = launch;
|
32 | exports.uninstall = uninstall;
|
33 | exports.detect = detect;
|
34 | exports.getAppxPackages = getAppxPackages;
|
35 | exports.loopbackExempt = loopbackExempt;
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | function install(projectDir, options, callback) {
|
50 | return magik(options, callback, function (emitter, options, callback) {
|
51 | var scripts = [],
|
52 | packageScript = 'Add-AppDevPackage.ps1';
|
53 |
|
54 |
|
55 | (function walk(dir) {
|
56 | fs.readdirSync(dir).forEach(function (name) {
|
57 | var file = path.join(dir, name);
|
58 | if (fs.statSync(file).isDirectory()) {
|
59 | walk(file);
|
60 | } else if (name === packageScript && (!options.buildConfiguration || path.basename(dir).indexOf('_' + options.buildConfiguration) !== -1)) {
|
61 | scripts.push(file);
|
62 | }
|
63 | });
|
64 | }(projectDir));
|
65 |
|
66 | if (!scripts.length) {
|
67 | var err = new Error(__('Unable to find built application. Please rebuild the project.'));
|
68 | emitter.emit('error', err);
|
69 | return callback(err);
|
70 | }
|
71 |
|
72 |
|
73 | appc.subprocess.getRealName(scripts[0], function (err, psScript) {
|
74 | if (err) {
|
75 | emitter.emit('error', err);
|
76 | return callback(err);
|
77 | }
|
78 |
|
79 | appc.subprocess.run(options.powershell || 'powershell', ['-ExecutionPolicy', 'Bypass', '-NoLogo', '-NoProfile', '-File', psScript, '-Force'], function (code, out, err) {
|
80 | if (!code) {
|
81 | emitter.emit('installed');
|
82 | return callback();
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | if ((code && (code == 9 || code == 14)) ||
|
90 | out.indexOf('script without the -Force parameter') !== -1) {
|
91 | require('child_process').exec((options.powershell || 'powershell') + ' -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Start-Process powershell -Wait -argument ' + psScript + '"', function(code, out, err) {
|
92 | if (err) {
|
93 | emitter.emit('error', err);
|
94 | callback(err);
|
95 | } else {
|
96 | emitter.emit('installed');
|
97 | callback();
|
98 | }
|
99 | });
|
100 | return;
|
101 | }
|
102 |
|
103 |
|
104 | var ex = new Error(__('Failed to install app: %s', out));
|
105 | emitter.emit('error', ex);
|
106 | callback(ex);
|
107 | });
|
108 | });
|
109 | });
|
110 | }
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | function getAppxPackages(options, callback) {
|
120 | appc.subprocess.run(options.powershell || 'powershell', ['-NoLogo', '-NoProfile', '-NonInteractive', '-command', 'Get-AppxPackage'], function (code, out, err) {
|
121 | if (code) {
|
122 | var ex = new Error(__('Could not query the list of installed Windows Store apps: %s', err || code));
|
123 | return callback(ex);
|
124 | }
|
125 |
|
126 | var keyValueRegexp = /([a-z]+)\s+:\s(.*)/i,
|
127 | packageName,
|
128 | packages = {},
|
129 | key = '';
|
130 |
|
131 | out.split(/\r\n|\n/).forEach(function (line) {
|
132 | var trimmed = line.trim(),
|
133 | m = trimmed.match(keyValueRegexp),
|
134 | value;
|
135 | if (m) {
|
136 | key = m[1];
|
137 | value = m[2];
|
138 | if (key == 'Name') {
|
139 | packageName = value;
|
140 | packages[packageName] = {};
|
141 | }
|
142 |
|
143 | if (value == 'False') {
|
144 | value = false;
|
145 | } else if (value == 'True') {
|
146 | value = true;
|
147 | }
|
148 | packages[packageName][key] = value;
|
149 | } else {
|
150 | if (trimmed.length > 0) {
|
151 |
|
152 | packages[packageName][key] += trimmed;
|
153 | }
|
154 | }
|
155 | });
|
156 |
|
157 | callback(null, packages);
|
158 | });
|
159 | }
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | function loopbackExempt(appId, options, callback) {
|
171 | getAppxPackages(options, function (err, packages) {
|
172 | if (err) {
|
173 | return callback(err);
|
174 | }
|
175 |
|
176 | if (!packages[appId]) {
|
177 | var ex = new Error(__('Unable to find an installed app with id: %s', appId));
|
178 | return callback(ex);
|
179 | }
|
180 |
|
181 | appc.subprocess.run('CheckNetIsolation.exe', ['LoopbackExempt', '-a', '-n=' + packages[appId].PackageFamilyName], function (code, out, err) {
|
182 | if (!code) {
|
183 | return callback();
|
184 | }
|
185 | return callback(err);
|
186 | });
|
187 | });
|
188 | }
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | function getPackageFullName(appId, options, callback) {
|
199 | getAppxPackages(options, function (err, packages) {
|
200 | if (err) {
|
201 | return callback(err);
|
202 | }
|
203 |
|
204 | return callback(null, packages[appId] && packages[appId].PackageFullName);
|
205 | });
|
206 | }
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 | function uninstall(appId, options, callback) {
|
222 | return magik(options, callback, function (emitter, options, callback) {
|
223 | getPackageFullName(appId, options, function(err, packageName) {
|
224 | if (err) {
|
225 | emitter.emit('error', err);
|
226 | return callback(err);
|
227 | }
|
228 |
|
229 | if (packageName) {
|
230 | appc.subprocess.run(options.powershell || 'powershell', ['-NoLogo', '-NoProfile', '-NonInteractive', '-command', 'Remove-AppxPackage ' + packageName], function (code, out, err) {
|
231 | if (err) {
|
232 | emitter.emit('error', err);
|
233 | callback(err);
|
234 | } else {
|
235 | emitter.emit('uninstalled');
|
236 | callback();
|
237 | }
|
238 | });
|
239 | } else {
|
240 | emitter.emit('uninstalled');
|
241 | callback();
|
242 | }
|
243 | });
|
244 | });
|
245 | }
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | function launch(appId, options, callback) {
|
263 | return magik(options, callback, function (emitter, options, callback) {
|
264 | function runTool() {
|
265 | var args = ['launch', '--appid', appId];
|
266 |
|
267 | if (options.version) {
|
268 | args.push('--version');
|
269 | args.push(options.version);
|
270 | }
|
271 |
|
272 | if (options.windowsAppId) {
|
273 | args.push('--windowsAppId');
|
274 | args.push(options.windowsAppId);
|
275 | }
|
276 |
|
277 | appc.subprocess.run(wstool, args, function (code, out, err) {
|
278 | if (code) {
|
279 | var ex = new Error(__('Erroring running wstool (code %s)', code) + '\n' + out);
|
280 | emitter.emit('error', ex);
|
281 | callback(ex);
|
282 | } else {
|
283 | emitter.emit('installed', out.trim());
|
284 | callback(null, out.trim());
|
285 | }
|
286 | });
|
287 | }
|
288 |
|
289 | var wsToolCs = path.resolve(__dirname, '..', 'wstool', 'wstool.cs');
|
290 | checkOutdated(wsToolCs, wstool, function(err, outdated) {
|
291 | if (err) {
|
292 | emitter.emit('error', err);
|
293 | return callback(err);
|
294 | }
|
295 |
|
296 | if (outdated) {
|
297 | return buildWsTool(options, function (err, path) {
|
298 | if (err) {
|
299 | emitter.emit('error', err);
|
300 | return callback(err);
|
301 | }
|
302 | runTool();
|
303 | });
|
304 | }
|
305 |
|
306 | return runTool();
|
307 | });
|
308 | });
|
309 | }
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 | function buildWsTool(options, callback) {
|
319 | visualstudio.build(appc.util.mix({
|
320 | buildConfiguration: 'Release',
|
321 | project: path.resolve(__dirname, '..', 'wstool', 'wstool.csproj')
|
322 | }, options), function (err, result) {
|
323 | if (err) {
|
324 | return callback(err);
|
325 | }
|
326 |
|
327 | var src = path.resolve(__dirname, '..', 'wstool', 'bin', 'Release', 'wstool.exe');
|
328 | if (!fs.existsSync(src)) {
|
329 | var ex = new Error(__('Failed to build the wstool executable.') + (result ? '\n' + result.out : ''));
|
330 | return callback(ex);
|
331 | }
|
332 |
|
333 |
|
334 | if (!fs.existsSync(wstool)) {
|
335 | fs.writeFileSync(wstool, fs.readFileSync(src));
|
336 | }
|
337 |
|
338 | callback(null, wstool);
|
339 | });
|
340 | }
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 | function detect(options, callback) {
|
357 | return magik(options, callback, function (emitter, options, callback) {
|
358 | if (detectCache && !options.bypassCache) {
|
359 | emitter.emit('detected', detectCache);
|
360 | return callback(null, detectCache);
|
361 | }
|
362 |
|
363 | var results = {
|
364 | windows: {},
|
365 | issues: []
|
366 | },
|
367 | searchPaths = [
|
368 | 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Microsoft SDKs\\Windows',
|
369 | 'HKEY_LOCAL_MACHINE\\Software\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows'
|
370 | ];
|
371 |
|
372 | function finalize() {
|
373 | detectCache = results;
|
374 | emitter.emit('detected', results);
|
375 | callback(null, results);
|
376 | }
|
377 |
|
378 | async.each(searchPaths, function (keyPath, next) {
|
379 | appc.subprocess.run('reg', ['query', keyPath], function (code, out, err) {
|
380 | var keyRegExp = /.+\\(v\d+\.\d)$/;
|
381 | if (!code) {
|
382 | out.trim().split(/\r\n|\n/).forEach(function (key) {
|
383 | key = key.trim();
|
384 | var m = key.match(keyRegExp);
|
385 | if (!m) {
|
386 | return;
|
387 | }
|
388 | var version = m[1].replace(/^v/, '');
|
389 | if (m) {
|
390 | results.windows || (results.windows = {});
|
391 | results.windows[version] = {
|
392 | version: version,
|
393 | registryKey: keyPath + '\\' + m[1],
|
394 | supported: !options.supportedWindowsSDKVersions || appc.version.satisfies(version, options.supportedWindowsSDKVersions, false),
|
395 | path: null,
|
396 | signTool: null,
|
397 | makeCert: null,
|
398 | pvk2pfx: null,
|
399 | selected: false,
|
400 | sdks: []
|
401 | };
|
402 | }
|
403 | });
|
404 | }
|
405 | next();
|
406 | });
|
407 | }, function () {
|
408 |
|
409 | if (!Object.keys(results.windows).length) {
|
410 | results.issues.push({
|
411 | id: 'WINDOWS_STORE_SDK_NOT_INSTALLED',
|
412 | type: 'error',
|
413 | message: __('Microsoft Windows Store SDK not found.') + '\n' +
|
414 | __('You will be unable to build Windows Store apps.')
|
415 | });
|
416 | return finalize();
|
417 | }
|
418 |
|
419 |
|
420 | async.each(Object.keys(results.windows), function (ver, next) {
|
421 | appc.subprocess.run('reg', ['query', results.windows[ver].registryKey, '/v', '*'], function (code, out, err) {
|
422 | if (code) {
|
423 |
|
424 | delete results.windows[ver];
|
425 | } else {
|
426 |
|
427 | out.trim().split(/\r\n|\n/).forEach(function (line) {
|
428 | var parts = line.trim().split(' ').map(function (p) { return p.trim(); });
|
429 | if (parts.length == 3) {
|
430 | if (parts[0] == 'InstallationFolder') {
|
431 | results.windows[ver].path = parts[2];
|
432 |
|
433 | function addIfExists(key, exe) {
|
434 | for (var i = 0; i < architectures.length; i++) {
|
435 | var arch = architectures[i],
|
436 | tool = path.join(parts[2], 'bin', arch, exe);
|
437 | if (fs.existsSync(tool)) {
|
438 | !results.windows[ver][key] && (results.windows[ver][key] = {});
|
439 | results.windows[ver][key][arch] = tool;
|
440 | }
|
441 | }
|
442 | }
|
443 |
|
444 | addIfExists('signTool', 'SignTool.exe');
|
445 | addIfExists('makeCert', 'MakeCert.exe');
|
446 | addIfExists('pvk2pfx', 'pvk2pfx.exe');
|
447 | }
|
448 | }
|
449 | });
|
450 | }
|
451 | next();
|
452 | });
|
453 | }, function () {
|
454 |
|
455 | if (Object.keys(results.windows).every(function (v) { return !results.windows[v].path; })) {
|
456 | results.issues.push({
|
457 | id: 'WINDOWS_STORE_SDK_NOT_INSTALLED',
|
458 | type: 'error',
|
459 | message: __('Microsoft Windows Store SDK not found.') + '\n' +
|
460 | __('You will be unable to build Windows Store apps.')
|
461 | });
|
462 | return finalize();
|
463 | }
|
464 |
|
465 |
|
466 | var win10 = '10.0'
|
467 | if (results.windows[win10] && results.windows[win10].path) {
|
468 | var sdks_path = path.join(results.windows[win10].path, 'Extension SDKs', 'WindowsDesktop');
|
469 | if (fs.existsSync(sdks_path)) {
|
470 | results.windows[win10].sdks = fs.readdirSync(sdks_path);
|
471 | }
|
472 | }
|
473 |
|
474 | var preferred = options.preferredWindowsSDK;
|
475 | if (!results.windows[preferred] || !results.windows[preferred].supported) {
|
476 | preferred = Object.keys(results.windows).filter(function (v) { return results.windows[v].supported; }).sort().pop();
|
477 | }
|
478 | if (preferred) {
|
479 | results.windows[preferred].selected = true;
|
480 | }
|
481 |
|
482 | finalize();
|
483 | });
|
484 | });
|
485 | });
|
486 | }
|