Import WixBA from old v4 repo
Sean Hall committed
Feb 15, 2019 at 18:49 UTC
8d92f6d48aa428b1e92a26de53fbf511e780793c
20 files changed
+2636
src/WixToolset.WixBA/BrowserProperties.cs
new
+40
@@ -0,0 +1,40 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System.Windows;
6
+ using System.Windows.Controls;
7
+
8
+ /// <summary>
9
+ /// Dependency Properties to support using a WebBrowser object.
10
+ /// </summary>
11
+ class BrowserProperties
12
+ {
13
+ /// <summary>
14
+ /// Dependency Propery used to pass an HTML string to the webBrowser object.
15
+ /// </summary>
16
+ public static readonly DependencyProperty HtmlDocProperty =
17
+ DependencyProperty.RegisterAttached("HtmlDoc", typeof(string), typeof(BrowserProperties), new PropertyMetadata(OnHtmlDocChanged));
18
+
19
+ public static string GetHtmlDoc(DependencyObject dependencyObject)
20
+ {
21
+ return (string)dependencyObject.GetValue(HtmlDocProperty);
22
+ }
23
+
24
+ public static void SetHtmlDoc(DependencyObject dependencyObject, string htmldoc)
25
+ {
26
+ dependencyObject.SetValue(HtmlDocProperty, htmldoc);
27
+ }
28
+
29
+ /// <summary>
30
+ /// Event handler that passes the HtmlDoc Dependency Property to MavigateToString method.
31
+ /// </summary>
32
+ /// <param name="d"></param>
33
+ /// <param name="e"></param>
34
+ private static void OnHtmlDocChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
35
+ {
36
+ var webBrowser = (WebBrowser)d;
37
+ webBrowser.NavigateToString((string)e.NewValue);
38
+ }
39
+ }
40
+}
src/WixToolset.WixBA/Hresult.cs
new
+22
@@ -0,0 +1,22 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+
7
+ /// <summary>
8
+ /// Utility class to work with HRESULTs
9
+ /// </summary>
10
+ internal class Hresult
11
+ {
12
+ /// <summary>
13
+ /// Determines if an HRESULT was a success code or not.
14
+ /// </summary>
15
+ /// <param name="status">HRESULT to verify.</param>
16
+ /// <returns>True if the status is a success code.</returns>
17
+ public static bool Succeeded(int status)
18
+ {
19
+ return status >= 0;
20
+ }
21
+ }
22
+}
src/WixToolset.WixBA/InstallationViewModel.cs
new
+670
@@ -0,0 +1,670 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.ComponentModel;
8
+ using System.Linq;
9
+ using System.Reflection;
10
+ using System.Windows;
11
+ using System.Windows.Input;
12
+ using IO = System.IO;
13
+ using WixToolset.Bootstrapper;
14
+
15
+ /// <summary>
16
+ /// The states of detection.
17
+ /// </summary>
18
+ public enum DetectionState
19
+ {
20
+ Absent,
21
+ Present,
22
+ Newer,
23
+ }
24
+
25
+ /// <summary>
26
+ /// The states of installation.
27
+ /// </summary>
28
+ public enum InstallationState
29
+ {
30
+ Initializing,
31
+ Detecting,
32
+ Waiting,
33
+ Planning,
34
+ Applying,
35
+ Applied,
36
+ Failed,
37
+ }
38
+
39
+ /// <summary>
40
+ /// The model of the installation view in WixBA.
41
+ /// </summary>
42
+ public class InstallationViewModel : PropertyNotifyBase
43
+ {
44
+ private RootViewModel root;
45
+
46
+ private Dictionary<string, int> downloadRetries;
47
+ private bool downgrade;
48
+ private string downgradeMessage;
49
+
50
+ private ICommand licenseCommand;
51
+ private ICommand launchHomePageCommand;
52
+ private ICommand launchNewsCommand;
53
+ private ICommand launchVSExtensionPageCommand;
54
+ private ICommand installCommand;
55
+ private ICommand repairCommand;
56
+ private ICommand uninstallCommand;
57
+ private ICommand openLogCommand;
58
+ private ICommand openLogFolderCommand;
59
+ private ICommand tryAgainCommand;
60
+
61
+ private string message;
62
+ private DateTime cachePackageStart;
63
+ private DateTime executePackageStart;
64
+
65
+ /// <summary>
66
+ /// Creates a new model of the installation view.
67
+ /// </summary>
68
+ public InstallationViewModel(RootViewModel root)
69
+ {
70
+ this.root = root;
71
+ this.downloadRetries = new Dictionary<string, int>();
72
+
73
+ this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged);
74
+
75
+ WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin;
76
+ WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle;
77
+ WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete;
78
+ WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin;
79
+ WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete;
80
+ WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin;
81
+ WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin;
82
+ WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete;
83
+ WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin;
84
+ WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete;
85
+ WixBA.Model.Bootstrapper.Error += this.ExecuteError;
86
+ WixBA.Model.Bootstrapper.ResolveSource += this.ResolveSource;
87
+ WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete;
88
+ }
89
+
90
+ void RootPropertyChanged(object sender, PropertyChangedEventArgs e)
91
+ {
92
+ if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName))
93
+ {
94
+ base.OnPropertyChanged("RepairEnabled");
95
+ base.OnPropertyChanged("InstallEnabled");
96
+ base.OnPropertyChanged("IsComplete");
97
+ base.OnPropertyChanged("IsSuccessfulCompletion");
98
+ base.OnPropertyChanged("IsFailedCompletion");
99
+ base.OnPropertyChanged("StatusText");
100
+ base.OnPropertyChanged("UninstallEnabled");
101
+ }
102
+ }
103
+
104
+ /// <summary>
105
+ /// Gets the version for the application.
106
+ /// </summary>
107
+ public string Version
108
+ {
109
+ get { return String.Concat("v", WixBA.Model.Version.ToString()); }
110
+ }
111
+
112
+ /// <summary>
113
+ /// The Publisher of this bundle.
114
+ /// </summary>
115
+ public string Publisher
116
+ {
117
+ get
118
+ {
119
+ string company = "[AssemblyCompany]";
120
+ return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly);
121
+ }
122
+ }
123
+
124
+ /// <summary>
125
+ /// The Publisher of this bundle.
126
+ /// </summary>
127
+ public string SupportUrl
128
+ {
129
+ get
130
+ {
131
+ return WixDistribution.SupportUrl;
132
+ }
133
+ }
134
+ public string VSExtensionUrl
135
+ {
136
+ get
137
+ {
138
+ return WixDistribution.VSExtensionsLandingUrl;
139
+ }
140
+ }
141
+
142
+ public string Message
143
+ {
144
+ get
145
+ {
146
+ return this.message;
147
+ }
148
+
149
+ set
150
+ {
151
+ if (this.message != value)
152
+ {
153
+ this.message = value;
154
+ base.OnPropertyChanged("Message");
155
+ }
156
+ }
157
+ }
158
+
159
+ /// <summary>
160
+ /// Gets and sets whether the view model considers this install to be a downgrade.
161
+ /// </summary>
162
+ public bool Downgrade
163
+ {
164
+ get
165
+ {
166
+ return this.downgrade;
167
+ }
168
+
169
+ set
170
+ {
171
+ if (this.downgrade != value)
172
+ {
173
+ this.downgrade = value;
174
+ base.OnPropertyChanged("Downgrade");
175
+ }
176
+ }
177
+ }
178
+
179
+ public string DowngradeMessage
180
+ {
181
+ get
182
+ {
183
+ return this.downgradeMessage;
184
+ }
185
+ set
186
+ {
187
+ if (this.downgradeMessage != value)
188
+ {
189
+ this.downgradeMessage = value;
190
+ base.OnPropertyChanged("DowngradeMessage");
191
+ }
192
+ }
193
+ }
194
+
195
+ public ICommand LaunchHomePageCommand
196
+ {
197
+ get
198
+ {
199
+ if (this.launchHomePageCommand == null)
200
+ {
201
+ this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true);
202
+ }
203
+
204
+ return this.launchHomePageCommand;
205
+ }
206
+ }
207
+
208
+ public ICommand LaunchNewsCommand
209
+ {
210
+ get
211
+ {
212
+ if (this.launchNewsCommand == null)
213
+ {
214
+ this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true);
215
+ }
216
+
217
+ return this.launchNewsCommand;
218
+ }
219
+ }
220
+
221
+ public ICommand LaunchVSExtensionPageCommand
222
+ {
223
+ get
224
+ {
225
+ if (this.launchVSExtensionPageCommand == null)
226
+ {
227
+ this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true);
228
+ }
229
+
230
+ return this.launchVSExtensionPageCommand;
231
+ }
232
+ }
233
+
234
+ public ICommand LicenseCommand
235
+ {
236
+ get
237
+ {
238
+ if (this.licenseCommand == null)
239
+ {
240
+ this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true);
241
+ }
242
+
243
+ return this.licenseCommand;
244
+ }
245
+ }
246
+
247
+ public bool LicenseEnabled
248
+ {
249
+ get { return this.LicenseCommand.CanExecute(this); }
250
+ }
251
+
252
+ public ICommand CloseCommand
253
+ {
254
+ get { return this.root.CloseCommand; }
255
+ }
256
+
257
+ public bool IsComplete
258
+ {
259
+ get { return IsSuccessfulCompletion || IsFailedCompletion; }
260
+ }
261
+
262
+ public bool IsSuccessfulCompletion
263
+ {
264
+ get { return InstallationState.Applied == this.root.InstallState; }
265
+ }
266
+
267
+ public bool IsFailedCompletion
268
+ {
269
+ get { return InstallationState.Failed == this.root.InstallState; }
270
+ }
271
+
272
+ public ICommand InstallCommand
273
+ {
274
+ get
275
+ {
276
+ if (this.installCommand == null)
277
+ {
278
+ this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting);
279
+ }
280
+
281
+ return this.installCommand;
282
+ }
283
+ }
284
+
285
+ public bool InstallEnabled
286
+ {
287
+ get { return this.InstallCommand.CanExecute(this); }
288
+ }
289
+
290
+ public ICommand RepairCommand
291
+ {
292
+ get
293
+ {
294
+ if (this.repairCommand == null)
295
+ {
296
+ this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting);
297
+ }
298
+
299
+ return this.repairCommand;
300
+ }
301
+ }
302
+
303
+ public bool RepairEnabled
304
+ {
305
+ get { return this.RepairCommand.CanExecute(this); }
306
+ }
307
+
308
+ public ICommand UninstallCommand
309
+ {
310
+ get
311
+ {
312
+ if (this.uninstallCommand == null)
313
+ {
314
+ this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting);
315
+ }
316
+
317
+ return this.uninstallCommand;
318
+ }
319
+ }
320
+
321
+ public bool UninstallEnabled
322
+ {
323
+ get { return this.UninstallCommand.CanExecute(this); }
324
+ }
325
+
326
+ public ICommand OpenLogCommand
327
+ {
328
+ get
329
+ {
330
+ if (this.openLogCommand == null)
331
+ {
332
+ this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.StringVariables["WixBundleLog"])));
333
+ }
334
+ return this.openLogCommand;
335
+ }
336
+ }
337
+
338
+ public ICommand OpenLogFolderCommand
339
+ {
340
+ get
341
+ {
342
+ if (this.openLogFolderCommand == null)
343
+ {
344
+ string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.StringVariables["WixBundleLog"]);
345
+ this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder));
346
+ }
347
+ return this.openLogFolderCommand;
348
+ }
349
+ }
350
+
351
+ public ICommand TryAgainCommand
352
+ {
353
+ get
354
+ {
355
+ if (this.tryAgainCommand == null)
356
+ {
357
+ this.tryAgainCommand = new RelayCommand(param =>
358
+ {
359
+ this.root.Canceled = false;
360
+ WixBA.Plan(WixBA.Model.PlannedAction);
361
+ }, param => IsFailedCompletion);
362
+ }
363
+
364
+ return this.tryAgainCommand;
365
+ }
366
+ }
367
+
368
+ public string StatusText
369
+ {
370
+ get
371
+ {
372
+ switch(this.root.InstallState)
373
+ {
374
+ case InstallationState.Applied:
375
+ return "Complete";
376
+ case InstallationState.Failed:
377
+ return this.root.Canceled ? "Cancelled" : "Failed";
378
+ default:
379
+ return "Unknown"; // this shouldn't be shown in the UI.
380
+ }
381
+ }
382
+ }
383
+
384
+ /// <summary>
385
+ /// Launches the license in the default viewer.
386
+ /// </summary>
387
+ private void LaunchLicense()
388
+ {
389
+ string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
390
+ WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt"));
391
+ }
392
+
393
+ private void DetectBegin(object sender, DetectBeginEventArgs e)
394
+ {
395
+ this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent;
396
+ WixBA.Model.PlannedAction = LaunchAction.Unknown;
397
+ }
398
+
399
+ private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e)
400
+ {
401
+ if (e.Operation == RelatedOperation.Downgrade)
402
+ {
403
+ this.Downgrade = true;
404
+ }
405
+
406
+ if (!WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode))
407
+ {
408
+ WixBA.Model.Bootstrapper.BAManifest.Bundle.AddRelatedBundleAsPackage(e);
409
+ }
410
+ }
411
+
412
+ private void DetectComplete(object sender, DetectCompleteEventArgs e)
413
+ {
414
+ // Parse the command line string before any planning.
415
+ this.ParseCommandLine();
416
+ this.root.InstallState = InstallationState.Waiting;
417
+
418
+ if (LaunchAction.Uninstall == WixBA.Model.Command.Action &&
419
+ ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too.
420
+ {
421
+ WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall");
422
+ WixBA.Plan(LaunchAction.Uninstall);
423
+ }
424
+ else if (Hresult.Succeeded(e.Status))
425
+ {
426
+ if (this.Downgrade)
427
+ {
428
+ this.root.DetectState = DetectionState.Newer;
429
+ IEnumerable<PackageInfo> relatedPackages = WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle);
430
+ Version installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null;
431
+ if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10)
432
+ {
433
+ this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this.";
434
+ }
435
+ else
436
+ {
437
+ this.DowngradeMessage = "There is already a newer version of WiX installed on this machine.";
438
+ }
439
+ }
440
+
441
+ if (LaunchAction.Layout == WixBA.Model.Command.Action)
442
+ {
443
+ WixBA.PlanLayout();
444
+ }
445
+ else if (WixBA.Model.Command.Display != Display.Full)
446
+ {
447
+ // If we're not waiting for the user to click install, dispatch plan with the default action.
448
+ WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode.");
449
+ WixBA.Plan(WixBA.Model.Command.Action);
450
+ }
451
+ }
452
+ else
453
+ {
454
+ this.root.InstallState = InstallationState.Failed;
455
+ }
456
+
457
+ // Force all commands to reevaluate CanExecute.
458
+ // InvalidateRequerySuggested must be run on the UI thread.
459
+ root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested));
460
+ }
461
+
462
+ private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e)
463
+ {
464
+ // If we're able to run our BA, we don't want to install the .NET Framework since the framework on the machine is already good enough.
465
+ if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase))
466
+ {
467
+ e.State = RequestState.None;
468
+ }
469
+ }
470
+
471
+ private void PlanComplete(object sender, PlanCompleteEventArgs e)
472
+ {
473
+ if (Hresult.Succeeded(e.Status))
474
+ {
475
+ this.root.PreApplyState = this.root.InstallState;
476
+ this.root.InstallState = InstallationState.Applying;
477
+ WixBA.Model.Engine.Apply(this.root.ViewWindowHandle);
478
+ }
479
+ else
480
+ {
481
+ this.root.InstallState = InstallationState.Failed;
482
+ }
483
+ }
484
+
485
+ private void ApplyBegin(object sender, ApplyBeginEventArgs e)
486
+ {
487
+ this.downloadRetries.Clear();
488
+ }
489
+
490
+ private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e)
491
+ {
492
+ this.cachePackageStart = DateTime.Now;
493
+ }
494
+
495
+ private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e)
496
+ {
497
+ this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status);
498
+ }
499
+
500
+ private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e)
501
+ {
502
+ lock (this)
503
+ {
504
+ this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue;
505
+ }
506
+ }
507
+
508
+ private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e)
509
+ {
510
+ lock (this)
511
+ {
512
+ if (DateTime.MinValue < this.executePackageStart)
513
+ {
514
+ this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status);
515
+ this.executePackageStart = DateTime.MinValue;
516
+ }
517
+ }
518
+ }
519
+
520
+ private void ExecuteError(object sender, ErrorEventArgs e)
521
+ {
522
+ lock (this)
523
+ {
524
+ if (!this.root.Canceled)
525
+ {
526
+ // If the error is a cancel coming from the engine during apply we want to go back to the preapply state.
527
+ if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode)
528
+ {
529
+ this.root.InstallState = this.root.PreApplyState;
530
+ }
531
+ else
532
+ {
533
+ this.Message = e.ErrorMessage;
534
+
535
+ if (Display.Full == WixBA.Model.Command.Display)
536
+ {
537
+ // On HTTP authentication errors, have the engine try to do authentication for us.
538
+ if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType)
539
+ {
540
+ e.Result = Result.TryAgain;
541
+ }
542
+ else // show an error dialog.
543
+ {
544
+ MessageBoxButton msgbox = MessageBoxButton.OK;
545
+ switch (e.UIHint & 0xF)
546
+ {
547
+ case 0:
548
+ msgbox = MessageBoxButton.OK;
549
+ break;
550
+ case 1:
551
+ msgbox = MessageBoxButton.OKCancel;
552
+ break;
553
+ // There is no 2! That would have been MB_ABORTRETRYIGNORE.
554
+ case 3:
555
+ msgbox = MessageBoxButton.YesNoCancel;
556
+ break;
557
+ case 4:
558
+ msgbox = MessageBoxButton.YesNo;
559
+ break;
560
+ // default: stay with MBOK since an exact match is not available.
561
+ }
562
+
563
+ MessageBoxResult result = MessageBoxResult.None;
564
+ WixBA.View.Dispatcher.Invoke((Action)delegate()
565
+ {
566
+ result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error);
567
+ }
568
+ );
569
+
570
+ // If there was a match from the UI hint to the msgbox value, use the result from the
571
+ // message box. Otherwise, we'll ignore it and return the default to Burn.
572
+ if ((e.UIHint & 0xF) == (int)msgbox)
573
+ {
574
+ e.Result = (Result)result;
575
+ }
576
+ }
577
+ }
578
+ }
579
+ }
580
+ else // canceled, so always return cancel.
581
+ {
582
+ e.Result = Result.Cancel;
583
+ }
584
+ }
585
+ }
586
+
587
+ private void ResolveSource(object sender, ResolveSourceEventArgs e)
588
+ {
589
+ int retries = 0;
590
+
591
+ this.downloadRetries.TryGetValue(e.PackageOrContainerId, out retries);
592
+ this.downloadRetries[e.PackageOrContainerId] = retries + 1;
593
+
594
+ e.Action = retries < 3 && !String.IsNullOrEmpty(e.DownloadSource) ? BOOTSTRAPPER_RESOLVESOURCE_ACTION.Download : BOOTSTRAPPER_RESOLVESOURCE_ACTION.None;
595
+ }
596
+
597
+ private void ApplyComplete(object sender, ApplyCompleteEventArgs e)
598
+ {
599
+ WixBA.Model.Result = e.Status; // remember the final result of the apply.
600
+
601
+ // Set the state to applied or failed unless the state has already been set back to the preapply state
602
+ // which means we need to show the UI as it was before the apply started.
603
+ if (this.root.InstallState != this.root.PreApplyState)
604
+ {
605
+ this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed;
606
+ }
607
+
608
+ // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive.
609
+ if (Bootstrapper.Display.Full != WixBA.Model.Command.Display)
610
+ {
611
+ // If its passive, send a message to the window to close.
612
+ if (Bootstrapper.Display.Passive == WixBA.Model.Command.Display)
613
+ {
614
+ WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install");
615
+ WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
616
+ }
617
+ else
618
+ {
619
+ WixBA.Dispatcher.InvokeShutdown();
620
+ }
621
+ return;
622
+ }
623
+ else if (Hresult.Succeeded(e.Status) && LaunchAction.UpdateReplace == WixBA.Model.PlannedAction) // if we successfully applied an update close the window since the new Bundle should be running now.
624
+ {
625
+ WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful.");
626
+ WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
627
+ return;
628
+ }
629
+ else if (root.AutoClose)
630
+ {
631
+ // Automatically closing since the user clicked the X button.
632
+ WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
633
+ return;
634
+ }
635
+
636
+ // Force all commands to reevaluate CanExecute.
637
+ // InvalidateRequerySuggested must be run on the UI thread.
638
+ root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested));
639
+ }
640
+
641
+ private void ParseCommandLine()
642
+ {
643
+ // Get array of arguments based on the system parsing algorithm.
644
+ string[] args = WixBA.Model.Command.GetCommandLineArgs();
645
+ for (int i = 0; i < args.Length; ++i)
646
+ {
647
+ if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase))
648
+ {
649
+ // Allow relative directory paths. Also validates.
650
+ string[] param = args[i].Split(new char[] {'='}, 2);
651
+ this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]);
652
+ }
653
+ }
654
+ }
655
+
656
+ private void AddPackageTelemetry(string prefix, string id, double time, int result)
657
+ {
658
+ lock (this)
659
+ {
660
+ string key = String.Format("{0}Time_{1}", prefix, id);
661
+ string value = time.ToString();
662
+ WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value));
663
+
664
+ key = String.Format("{0}Result_{1}", prefix, id);
665
+ value = String.Concat("0x", result.ToString("x"));
666
+ WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value));
667
+ }
668
+ }
669
+ }
670
+}
src/WixToolset.WixBA/Model.cs
new
+129
@@ -0,0 +1,129 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Net;
8
+ using WixToolset.Bootstrapper;
9
+
10
+ /// <summary>
11
+ /// The model.
12
+ /// </summary>
13
+ public class Model
14
+ {
15
+ private const string BurnBundleInstallDirectoryVariable = "InstallFolder";
16
+ private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory";
17
+ private const string BurnBundleVersionVariable = "WixBundleVersion";
18
+
19
+ /// <summary>
20
+ /// Creates a new model for the UX.
21
+ /// </summary>
22
+ /// <param name="bootstrapper">Bootstrapper hosting the UX.</param>
23
+ public Model(BootstrapperApplication bootstrapper)
24
+ {
25
+ this.Bootstrapper = bootstrapper;
26
+ this.Telemetry = new List<KeyValuePair<string, string>>();
27
+ this.Version = this.Engine.VersionVariables[BurnBundleVersionVariable];
28
+ }
29
+
30
+ /// <summary>
31
+ /// Gets the bootstrapper.
32
+ /// </summary>
33
+ public BootstrapperApplication Bootstrapper { get; private set; }
34
+
35
+ /// <summary>
36
+ /// Gets the bootstrapper command-line.
37
+ /// </summary>
38
+ public Command Command { get { return this.Bootstrapper.Command; } }
39
+
40
+ /// <summary>
41
+ /// Gets the bootstrapper engine.
42
+ /// </summary>
43
+ public Engine Engine { get { return this.Bootstrapper.Engine; } }
44
+
45
+ /// <summary>
46
+ /// Gets the key/value pairs used in telemetry.
47
+ /// </summary>
48
+ public List<KeyValuePair<string, string>> Telemetry { get; private set; }
49
+
50
+ /// <summary>
51
+ /// Get or set the final result of the installation.
52
+ /// </summary>
53
+ public int Result { get; set; }
54
+
55
+ /// <summary>
56
+ /// Get the version of the install.
57
+ /// </summary>
58
+ public Version Version { get; private set; }
59
+
60
+ /// <summary>
61
+ /// Get or set the path where the bundle is installed.
62
+ /// </summary>
63
+ public string InstallDirectory
64
+ {
65
+ get
66
+ {
67
+ if (!this.Engine.StringVariables.Contains(BurnBundleInstallDirectoryVariable))
68
+ {
69
+ return null;
70
+ }
71
+
72
+ return this.Engine.StringVariables[BurnBundleInstallDirectoryVariable];
73
+ }
74
+
75
+ set
76
+ {
77
+ this.Engine.StringVariables[BurnBundleInstallDirectoryVariable] = value;
78
+ }
79
+ }
80
+
81
+ /// <summary>
82
+ /// Get or set the path for the layout to be created.
83
+ /// </summary>
84
+ public string LayoutDirectory
85
+ {
86
+ get
87
+ {
88
+ if (!this.Engine.StringVariables.Contains(BurnBundleLayoutDirectoryVariable))
89
+ {
90
+ return null;
91
+ }
92
+
93
+ return this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable];
94
+ }
95
+
96
+ set
97
+ {
98
+ this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable] = value;
99
+ }
100
+ }
101
+
102
+ public LaunchAction PlannedAction { get; set; }
103
+
104
+ /// <summary>
105
+ /// Creates a correctly configured HTTP web request.
106
+ /// </summary>
107
+ /// <param name="uri">URI to connect to.</param>
108
+ /// <returns>Correctly configured HTTP web request.</returns>
109
+ public HttpWebRequest CreateWebRequest(string uri)
110
+ {
111
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
112
+ request.UserAgent = String.Concat("WixInstall", this.Version.ToString());
113
+
114
+ return request;
115
+ }
116
+
117
+ /// <summary>
118
+ /// Gets the display name for a package if possible.
119
+ /// </summary>
120
+ /// <param name="packageId">Identity of the package to find the display name.</param>
121
+ /// <returns>Display name of the package if found or the package id if not.</returns>
122
+ public string GetPackageName(string packageId)
123
+ {
124
+ PackageInfo package;
125
+
126
+ return this.Bootstrapper.BAManifest.Bundle.Packages.TryGetValue(packageId, out package) ? package.DisplayName : packageId;
127
+ }
128
+ }
129
+}
src/WixToolset.WixBA/NewsItem.cs
new
+18
@@ -0,0 +1,18 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+
7
+ /// <summary>
8
+ /// The model for an individual news item.
9
+ /// </summary>
10
+ public class NewsItem
11
+ {
12
+ public string Author { get; set; }
13
+ public string Title { get; set; }
14
+ public string Url { get; set; }
15
+ public string Snippet { get; set; }
16
+ public DateTime Updated { get; set; }
17
+ }
18
+}
src/WixToolset.WixBA/ProgressViewModel.cs
new
+194
@@ -0,0 +1,194 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+using System;
4
+using System.Collections.Generic;
5
+using System.ComponentModel;
6
+using System.Diagnostics;
7
+using System.Text.RegularExpressions;
8
+using WixToolset.Bootstrapper;
9
+
10
+namespace WixToolset.UX
11
+{
12
+ public class ProgressViewModel : PropertyNotifyBase
13
+ {
14
+ private static readonly Regex TrimActionTimeFromMessage = new Regex(@"^\w+\s+\d+:\d+:\d+:\s+", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline);
15
+
16
+ private RootViewModel root;
17
+ private Dictionary<string, int> executingPackageOrderIndex;
18
+
19
+ private int progressPhases;
20
+ private int progress;
21
+ private int cacheProgress;
22
+ private int executeProgress;
23
+ private string package;
24
+ private string message;
25
+
26
+ public ProgressViewModel(RootViewModel root)
27
+ {
28
+ this.root = root;
29
+ this.executingPackageOrderIndex = new Dictionary<string, int>();
30
+
31
+ this.root.PropertyChanged += this.RootPropertyChanged;
32
+
33
+ WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin;
34
+ WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete;
35
+ WixBA.Model.Bootstrapper.ExecuteProgress += this.ApplyExecuteProgress;
36
+ WixBA.Model.Bootstrapper.PlanBegin += this.PlanBegin;
37
+ WixBA.Model.Bootstrapper.PlanPackageComplete += this.PlanPackageComplete;
38
+ WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin;
39
+ WixBA.Model.Bootstrapper.Progress += this.ApplyProgress;
40
+ WixBA.Model.Bootstrapper.CacheAcquireProgress += this.CacheAcquireProgress;
41
+ WixBA.Model.Bootstrapper.CacheComplete += this.CacheComplete;
42
+ }
43
+
44
+ public bool ProgressEnabled
45
+ {
46
+ get { return this.root.InstallState == InstallationState.Applying; }
47
+ }
48
+
49
+ public int Progress
50
+ {
51
+ get
52
+ {
53
+ return this.progress;
54
+ }
55
+
56
+ set
57
+ {
58
+ if (this.progress != value)
59
+ {
60
+ this.progress = value;
61
+ base.OnPropertyChanged("Progress");
62
+ }
63
+ }
64
+ }
65
+
66
+ public string Package
67
+ {
68
+ get
69
+ {
70
+ return this.package;
71
+ }
72
+
73
+ set
74
+ {
75
+ if (this.package != value)
76
+ {
77
+ this.package = value;
78
+ base.OnPropertyChanged("Package");
79
+ }
80
+ }
81
+ }
82
+
83
+ public string Message
84
+ {
85
+ get
86
+ {
87
+ return this.message;
88
+ }
89
+
90
+ set
91
+ {
92
+ if (this.message != value)
93
+ {
94
+ this.message = value;
95
+ base.OnPropertyChanged("Message");
96
+ }
97
+ }
98
+ }
99
+
100
+ void RootPropertyChanged(object sender, PropertyChangedEventArgs e)
101
+ {
102
+ if ("InstallState" == e.PropertyName)
103
+ {
104
+ base.OnPropertyChanged("ProgressEnabled");
105
+ }
106
+ }
107
+
108
+ private void PlanBegin(object sender, PlanBeginEventArgs e)
109
+ {
110
+ lock (this)
111
+ {
112
+ this.executingPackageOrderIndex.Clear();
113
+ }
114
+ }
115
+
116
+ private void PlanPackageComplete(object sender, PlanPackageCompleteEventArgs e)
117
+ {
118
+ if (ActionState.None != e.Execute)
119
+ {
120
+ lock (this)
121
+ {
122
+ Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId));
123
+ this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count);
124
+ }
125
+ }
126
+ }
127
+
128
+ private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e)
129
+ {
130
+ lock (this)
131
+ {
132
+ this.Package = WixBA.Model.GetPackageName(e.PackageId);
133
+ this.Message = String.Format("Processing: {0}", this.Package);
134
+ e.Cancel = this.root.Canceled;
135
+ }
136
+ }
137
+
138
+ private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e)
139
+ {
140
+ lock (this)
141
+ { // avoid a stale display
142
+ this.Message = String.Empty;
143
+ }
144
+ }
145
+
146
+ private void ApplyBegin(object sender, ApplyBeginEventArgs e)
147
+ {
148
+ this.progressPhases = e.PhaseCount;
149
+ }
150
+
151
+ private void ApplyProgress(object sender, ProgressEventArgs e)
152
+ {
153
+ lock (this)
154
+ {
155
+ e.Cancel = this.root.Canceled;
156
+ }
157
+ }
158
+
159
+ private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e)
160
+ {
161
+ lock (this)
162
+ {
163
+ this.cacheProgress = e.OverallPercentage;
164
+ this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases;
165
+ e.Cancel = this.root.Canceled;
166
+ }
167
+ }
168
+
169
+ private void CacheComplete(object sender, CacheCompleteEventArgs e)
170
+ {
171
+ lock (this)
172
+ {
173
+ this.cacheProgress = 100;
174
+ this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases;
175
+ }
176
+ }
177
+
178
+ private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e)
179
+ {
180
+ lock (this)
181
+ {
182
+ this.executeProgress = e.OverallPercentage;
183
+ this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases;
184
+
185
+ if (WixBA.Model.Command.Display == Display.Embedded)
186
+ {
187
+ WixBA.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress);
188
+ }
189
+
190
+ e.Cancel = this.root.Canceled;
191
+ }
192
+ }
193
+ }
194
+}
src/WixToolset.WixBA/Properties/AssemblyInfo.cs
new
+22
@@ -0,0 +1,22 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+using System;
4
+using System.Reflection;
5
+using System.Runtime.InteropServices;
6
+using WixToolset.Bootstrapper;
7
+using WixToolset.UX;
8
+
9
+
10
+[assembly: AssemblyTitle("WixBA")]
11
+[assembly: AssemblyDescription("WiX User Experience")]
12
+
13
+// Setting ComVisible to false makes the types in this assembly not visible
14
+// to COM components. If you need to access a type in this assembly from
15
+// COM, set the ComVisible attribute to true on that type.
16
+[assembly: ComVisible(false)]
17
+[assembly: Guid("0ffc4944-9295-40b7-adac-3a6864b5219b")]
18
+[assembly: CLSCompliantAttribute(true)]
19
+
20
+// Identifies the class that derives from UserExperience and is the UX class that gets
21
+// instantiated by the interop layer
22
+[assembly: BootstrapperApplication(typeof(WixBA))]
src/WixToolset.WixBA/PropertyNotifyBase.cs
new
+59
@@ -0,0 +1,59 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.ComponentModel;
7
+ using System.Diagnostics;
8
+
9
+ /// <summary>
10
+ /// It provides support for property change notifications.
11
+ /// </summary>
12
+ public abstract class PropertyNotifyBase : INotifyPropertyChanged
13
+ {
14
+ /// <summary>
15
+ /// Initializes a new instance of the <see cref="PropertyNotifyBase"/> class.
16
+ /// </summary>
17
+ protected PropertyNotifyBase()
18
+ {
19
+ }
20
+
21
+ /// <summary>
22
+ /// Raised when a property on this object has a new value.
23
+ /// </summary>
24
+ public event PropertyChangedEventHandler PropertyChanged;
25
+
26
+ /// <summary>
27
+ /// Warns the developer if this object does not have a public property with the
28
+ /// specified name. This method does not exist in a Release build.
29
+ /// </summary>
30
+ /// <param name="propertyName">Property name to verify.</param>
31
+ [Conditional("DEBUG")]
32
+ [DebuggerStepThrough]
33
+ public void VerifyPropertyName(string propertyName)
34
+ {
35
+ // Verify that the property name matches a real, public, instance property
36
+ // on this object.
37
+ if (null == TypeDescriptor.GetProperties(this)[propertyName])
38
+ {
39
+ Debug.Fail(String.Concat("Invalid property name: ", propertyName));
40
+ }
41
+ }
42
+
43
+ /// <summary>
44
+ /// Raises this object's PropertyChanged event.
45
+ /// </summary>
46
+ /// <param name="propertyName">The property that has a new value.</param>
47
+ protected virtual void OnPropertyChanged(string propertyName)
48
+ {
49
+ this.VerifyPropertyName(propertyName);
50
+
51
+ PropertyChangedEventHandler handler = this.PropertyChanged;
52
+ if (null != handler)
53
+ {
54
+ PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
55
+ handler(this, e);
56
+ }
57
+ }
58
+ }
59
+}
src/WixToolset.WixBA/RelayCommand.cs
new
+45
@@ -0,0 +1,45 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Diagnostics;
7
+ using System.Windows.Input;
8
+
9
+ /// <summary>
10
+ /// Base class that implements ICommand interface via delegates.
11
+ /// </summary>
12
+ public class RelayCommand : ICommand
13
+ {
14
+ private readonly Action<object> execute;
15
+ private readonly Predicate<object> canExecute;
16
+
17
+ public RelayCommand(Action<object> execute)
18
+ : this(execute, null)
19
+ {
20
+ }
21
+
22
+ public RelayCommand(Action<object> execute, Predicate<object> canExecute)
23
+ {
24
+ this.execute = execute;
25
+ this.canExecute = canExecute;
26
+ }
27
+
28
+ public event EventHandler CanExecuteChanged
29
+ {
30
+ add { CommandManager.RequerySuggested += value; }
31
+ remove { CommandManager.RequerySuggested -= value; }
32
+ }
33
+
34
+ [DebuggerStepThrough]
35
+ public bool CanExecute(object parameter)
36
+ {
37
+ return this.canExecute == null ? true : this.canExecute(parameter);
38
+ }
39
+
40
+ public void Execute(object parameter)
41
+ {
42
+ this.execute(parameter);
43
+ }
44
+ }
45
+}
src/WixToolset.WixBA/Resources/logo-black-hollow.png
Binary files /dev/null and b/src/WixToolset.WixBA/Resources/logo-black-hollow.png differ
src/WixToolset.WixBA/Resources/logo-white-hollow.png
Binary files /dev/null and b/src/WixToolset.WixBA/Resources/logo-white-hollow.png differ
src/WixToolset.WixBA/RootView.xaml
new
+420
@@ -0,0 +1,420 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<Window x:Class="WixToolset.UX.RootView"
5
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
6
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
7
+ xmlns:ux="clr-namespace:WixToolset.UX"
8
+ MinWidth="550"
9
+ MinHeight="400"
10
+ Width="750"
11
+ Height="400"
12
+ AllowsTransparency="False"
13
+ Background="{x:Null}"
14
+ Closing="Window_Closing"
15
+ ResizeMode="CanResizeWithGrip"
16
+ WindowStartupLocation="CenterScreen"
17
+ WindowStyle="ThreeDBorderWindow">
18
+
19
+ <Window.Resources>
20
+ <ResourceDictionary>
21
+ <ResourceDictionary.MergedDictionaries>
22
+ <ResourceDictionary Source="pack://application:,,,/WixBA;component/Styles.xaml" />
23
+ </ResourceDictionary.MergedDictionaries>
24
+ </ResourceDictionary>
25
+ </Window.Resources>
26
+
27
+ <Border Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
28
+ BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
29
+ BorderThickness="0">
30
+
31
+ <DockPanel x:Name="AppArtBoardArea"
32
+ Margin="20, 10">
33
+
34
+ <DockPanel x:Name="ActionArea"
35
+ DockPanel.Dock="Bottom">
36
+
37
+ <Grid x:Name="ActionGrid">
38
+ <Grid.RowDefinitions>
39
+ <RowDefinition Height="Auto" />
40
+ </Grid.RowDefinitions>
41
+ <Grid.ColumnDefinitions>
42
+ <ColumnDefinition Width="*" MinWidth="150" />
43
+ <ColumnDefinition Width="Auto" />
44
+ </Grid.ColumnDefinitions>
45
+
46
+ <StackPanel x:Name="LeftMarqueeDcIsland"
47
+ Grid.Column="0"
48
+ VerticalAlignment="Center"
49
+ DataContext="{Binding UpdateViewModel}">
50
+ <ProgressBar x:Name="CheckingUpdatesProgress"
51
+ MinHeight="10"
52
+ IsIndeterminate="{Binding CheckingEnabled}"
53
+ Style="{DynamicResource UpdateMarqueeStyle}"
54
+ Visibility="{Binding CheckingEnabled,
55
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
56
+ </StackPanel>
57
+
58
+ <StackPanel x:Name="InitiateActionArea"
59
+ Grid.Column="1"
60
+ Margin="30, 0, 0, 0"
61
+ HorizontalAlignment="Right"
62
+ Orientation="Horizontal">
63
+ <!-- Displayed from left to right, when ordered from top to bottom. Most preferred is at the top. -->
64
+ <StackPanel x:Name="UpdateBtnDcIsland"
65
+ DataContext="{Binding UpdateViewModel}"
66
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
67
+ <Button AutomationProperties.HelpText="An update is available. Click to get the update"
68
+ AutomationProperties.Name="Update"
69
+ Command="{Binding UpdateCommand}"
70
+ Content="Update"
71
+ Style="{DynamicResource ActionButtonStyle}"
72
+ Visibility="{Binding CanUpdate,
73
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
74
+ </StackPanel>
75
+ <StackPanel x:Name="InstallBtnDcIsland"
76
+ DataContext="{Binding InstallationViewModel}"
77
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
78
+ <Button AutomationProperties.HelpText="Click to accept license and install"
79
+ AutomationProperties.Name="Install"
80
+ Command="{Binding InstallCommand}"
81
+ Content="_Install"
82
+ IsDefault="True"
83
+ Style="{DynamicResource ActionButtonStyle}"
84
+ Visibility="{Binding InstallEnabled,
85
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
86
+ </StackPanel>
87
+ <StackPanel x:Name="RepairBtnDcIsland"
88
+ DataContext="{Binding InstallationViewModel}"
89
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
90
+ <Button x:Name="RepairButton"
91
+ AutomationProperties.HelpText="Click to Repair"
92
+ AutomationProperties.Name="Repair"
93
+ Command="{Binding RepairCommand}"
94
+ Content="Repair"
95
+ Style="{DynamicResource ActionButtonStyle}"
96
+ Visibility="{Binding RepairEnabled,
97
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
98
+ </StackPanel>
99
+ <StackPanel x:Name="UninstallBtnDcIsland"
100
+ DataContext="{Binding InstallationViewModel}"
101
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
102
+ <Button AutomationProperties.HelpText="Click to Uninstall"
103
+ AutomationProperties.Name="Uninstall"
104
+ Command="{Binding UninstallCommand}"
105
+ Content="Uninstall"
106
+ Style="{DynamicResource ActionButtonStyle}"
107
+ Visibility="{Binding UninstallEnabled,
108
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
109
+ </StackPanel>
110
+ <StackPanel x:Name="TryAgainBtnDcIsland"
111
+ DataContext="{Binding InstallationViewModel}"
112
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
113
+ <Button HorizontalAlignment="Center"
114
+ VerticalAlignment="Center"
115
+ AutomationProperties.HelpText="Click to try again"
116
+ AutomationProperties.Name="Try Again?"
117
+ Command="{Binding TryAgainCommand}"
118
+ Content="Try Again?"
119
+ Style="{DynamicResource ActionButtonStyle}"
120
+ Visibility="{Binding IsFailedCompletion,
121
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
122
+ </StackPanel>
123
+
124
+ <!-- Final Actions - only one of these is expected to be displayed, if any -->
125
+ <StackPanel x:Name="CloseBtnDcIsland"
126
+ HorizontalAlignment="Right"
127
+ DataContext="{Binding InstallationViewModel}"
128
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
129
+ <Button x:Name="CloseButton"
130
+ AutomationProperties.HelpText="Click to Close"
131
+ AutomationProperties.Name="Close"
132
+ Command="{Binding CloseCommand}"
133
+ Content="Close"
134
+ Style="{DynamicResource FinalActionButtonStyle}"
135
+ Visibility="{Binding IsSuccessfulCompletion,
136
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
137
+ </StackPanel>
138
+ <StackPanel x:Name="CancelBtnDcIsland"
139
+ HorizontalAlignment="Right"
140
+ DataContext="{Binding}"
141
+ Style="{DynamicResource ActionBtnStkPnlStyle}">
142
+ <Button AutomationProperties.HelpText="Press to Cancel"
143
+ AutomationProperties.Name="Cancel"
144
+ Command="{Binding CancelCommand}"
145
+ Content="Cancel"
146
+ Style="{DynamicResource FinalActionButtonStyle}"
147
+ Visibility="{Binding CancelAvailable,
148
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
149
+ </StackPanel>
150
+ </StackPanel>
151
+ </Grid>
152
+ </DockPanel>
153
+
154
+ <StackPanel x:Name="FinalStatusArea"
155
+ DataContext="{Binding InstallationViewModel}"
156
+ HorizontalAlignment="Right"
157
+ Margin="0,10,0,20"
158
+ Orientation="Horizontal"
159
+ Visibility="{Binding IsComplete,
160
+ Converter={StaticResource BooleanToVisibilityConverter}}"
161
+ DockPanel.Dock="Bottom">
162
+ <TextBlock x:Name="StatusTextBlk"
163
+ Style="{DynamicResource LabelTextBlkStyle}"
164
+ Text="{Binding StatusText, StringFormat={}{0}:}"
165
+ TextBlock.FontWeight="Bold" />
166
+ <Button x:Name="ViewLogLink"
167
+ Margin="10,2,10,0"
168
+ Command="{Binding OpenLogCommand}"
169
+ Style="{StaticResource HyperlinkedButtonStyle}">
170
+ <TextBlock>
171
+ <Hyperlink Command="{Binding OpenLogCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log" /></Hyperlink>
172
+ </TextBlock>
173
+ </Button>
174
+ <Button x:Name="ViewLogFolderLink"
175
+ Command="{Binding OpenLogFolderCommand}"
176
+ Style="{StaticResource HyperlinkedButtonStyle}">
177
+ <TextBlock>
178
+ <Hyperlink Command="{Binding OpenLogFolderCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log Folder" /></Hyperlink>
179
+ </TextBlock>
180
+ </Button>
181
+ </StackPanel>
182
+
183
+ <StackPanel x:Name="StatusStkPnlDcIsland"
184
+ Margin="0,10"
185
+ DataContext="{Binding ProgressViewModel}"
186
+ DockPanel.Dock="Bottom">
187
+
188
+ <StackPanel x:Name="ActionPackageNameTextStkPnl"
189
+ VerticalAlignment="Top">
190
+ <TextBlock Style="{StaticResource StatusTextStyle}"
191
+ Text="{Binding Message}"
192
+ TextWrapping="WrapWithOverflow"
193
+ Visibility="{Binding ProgressEnabled,
194
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
195
+ </StackPanel>
196
+
197
+ <StackPanel x:Name="ActionProgressStkPnl"
198
+ Margin="0,5,0,0">
199
+ <ProgressBar x:Name="ActionProgress"
200
+ Height="20"
201
+ VerticalAlignment="Center"
202
+ Style="{DynamicResource ActionProgressStyle}"
203
+ Visibility="{Binding ProgressEnabled,
204
+ Converter={StaticResource BooleanToVisibilityConverter}}"
205
+ Value="{Binding Progress}" />
206
+ </StackPanel>
207
+ </StackPanel>
208
+
209
+ <StackPanel x:Name="TitleAndLogoStkPnl"
210
+ DockPanel.Dock="Top">
211
+ <Grid x:Name="TitleGrid">
212
+ <Grid.RowDefinitions>
213
+ <RowDefinition Height="Auto" />
214
+ </Grid.RowDefinitions>
215
+ <Grid.ColumnDefinitions>
216
+ <ColumnDefinition Width="Auto" />
217
+ <ColumnDefinition Width="*" />
218
+ <ColumnDefinition Width="Auto" />
219
+ </Grid.ColumnDefinitions>
220
+
221
+ <TextBlock x:Name="TitleTextBlk"
222
+ Grid.Column="0"
223
+ Style="{DynamicResource TitleTextBlkStyle}"
224
+ Text="{Binding Title,
225
+ Mode=OneTime}" />
226
+ <Image x:Name="Logo"
227
+ Grid.Column="2"
228
+ Style="{DynamicResource LogoStyle}">
229
+ </Image>
230
+ </Grid>
231
+ </StackPanel>
232
+
233
+ <Grid x:Name="MainStkPnl">
234
+ <Grid.RowDefinitions>
235
+ <RowDefinition Height="*" />
236
+ <RowDefinition Height="Auto" />
237
+ </Grid.RowDefinitions>
238
+ <Grid.ColumnDefinitions>
239
+ <ColumnDefinition Width="Auto" />
240
+ <ColumnDefinition Width="1*" />
241
+ <ColumnDefinition Width="4*" MinWidth="200" />
242
+ </Grid.ColumnDefinitions>
243
+
244
+ <Grid x:Name="SkuInfo"
245
+ Grid.Column="0"
246
+ Grid.Row="0"
247
+ DataContext="{Binding InstallationViewModel}">
248
+ <Grid.RowDefinitions>
249
+ <RowDefinition Height="Auto" />
250
+ <RowDefinition Height="Auto" />
251
+ <RowDefinition Height="Auto" />
252
+ <RowDefinition Height="Auto" />
253
+ <RowDefinition Height="Auto" />
254
+ <RowDefinition Height="Auto" />
255
+ <RowDefinition Height="Auto" />
256
+ </Grid.RowDefinitions>
257
+ <Grid.ColumnDefinitions>
258
+ <ColumnDefinition Width="Auto" />
259
+ <ColumnDefinition Width="*" />
260
+ </Grid.ColumnDefinitions>
261
+
262
+ <TextBlock x:Name="SkuPublisherLabel"
263
+ Grid.Row="0"
264
+ Grid.Column="0"
265
+ Style="{DynamicResource LabelTextBlkStyle}"
266
+ Text="Publisher:" />
267
+ <TextBlock x:Name="SkuPublisherData"
268
+ Grid.Row="0"
269
+ Grid.Column="1"
270
+ Style="{DynamicResource DataTextBlkStyle}"
271
+ Text="{Binding Publisher,
272
+ Mode=OneTime}" />
273
+
274
+ <TextBlock x:Name="SkuVersionLabel"
275
+ Grid.Row="1"
276
+ Grid.Column="0"
277
+ Style="{DynamicResource LabelTextBlkStyle}"
278
+ Text="Version:" />
279
+ <TextBlock Grid.Row="1"
280
+ Grid.Column="1"
281
+ Style="{DynamicResource DataTextBlkStyle}"
282
+ Text="{Binding Version}" />
283
+
284
+ <TextBlock x:Name="SkuLicenseLabel"
285
+ Grid.Row="2"
286
+ Grid.Column="0"
287
+ Style="{DynamicResource LabelTextBlkStyle}"
288
+ Text="License:" />
289
+ <Button x:Name="SkuLicenseBtn"
290
+ Grid.Row="2"
291
+ Grid.Column="1"
292
+ HorizontalAlignment="Left"
293
+ AutomationProperties.HelpText="View the license"
294
+ AutomationProperties.Name="License"
295
+ Command="{Binding LicenseCommand}"
296
+ KeyboardNavigation.IsTabStop="False"
297
+ Style="{StaticResource HyperlinkedButtonStyle}">
298
+ <TextBlock HorizontalAlignment="Left">
299
+ <Hyperlink Command="{Binding LicenseCommand}"
300
+ IsEnabled="True"
301
+ KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="View License" /></Hyperlink>
302
+ </TextBlock>
303
+ </Button>
304
+
305
+ <TextBlock x:Name="SkuNewsLabel"
306
+ Grid.Row="3"
307
+ Grid.Column="0"
308
+ Style="{DynamicResource LabelTextBlkStyle}"
309
+ Text="News:" />
310
+ <Button x:Name="SkuNewsBtn"
311
+ Grid.Row="3"
312
+ Grid.Column="1"
313
+ HorizontalAlignment="Left"
314
+ AutomationProperties.HelpText="Latest News"
315
+ AutomationProperties.Name="News"
316
+ Command="{Binding LaunchNewsCommand}"
317
+ KeyboardNavigation.IsTabStop="False"
318
+ Style="{StaticResource HyperlinkedButtonStyle}">
319
+ <TextBlock HorizontalAlignment="Left">
320
+ <Hyperlink Command="{Binding LaunchNewsCommand}"
321
+ IsEnabled="True"
322
+ KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="Latest News" /></Hyperlink>
323
+ </TextBlock>
324
+ </Button>
325
+
326
+ <TextBlock x:Name="SkuSupportLabel"
327
+ Grid.Row="4"
328
+ Grid.Column="0"
329
+ Style="{DynamicResource LabelTextBlkStyle}"
330
+ Text="Support:" />
331
+ <Button x:Name="SkuSupportBtn"
332
+ Grid.Row="4"
333
+ Grid.Column="1"
334
+ HorizontalAlignment="Left"
335
+ AutomationProperties.HelpText="View Home Page for Support"
336
+ AutomationProperties.Name="Home Page"
337
+ Command="{Binding LaunchHomePageCommand}"
338
+ KeyboardNavigation.IsTabStop="False"
339
+ Style="{StaticResource HyperlinkedButtonStyle}">
340
+ <TextBlock HorizontalAlignment="Left">
341
+ <Hyperlink Command="{Binding LaunchHomePageCommand}"
342
+ IsEnabled="True"
343
+ KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding SupportUrl, Mode=OneTime}" /></Hyperlink>
344
+ </TextBlock>
345
+ </Button>
346
+
347
+ <TextBlock x:Name="SkuVSExtensionLabel1"
348
+ Grid.Row="5"
349
+ Grid.Column="0"
350
+ Style="{DynamicResource LabelTextBlkStyle}"
351
+ Text="Visual Studio" />
352
+ <TextBlock x:Name="SkuVSExtensionLabel2"
353
+ Grid.Row="6"
354
+ Grid.Column="0"
355
+ Style="{DynamicResource LabelTextBlkStyle}"
356
+ Text="Extension:" />
357
+ <Button x:Name="SkuVSExtensionBtn"
358
+ Grid.Row="6"
359
+ Grid.Column="1"
360
+ HorizontalAlignment="Left"
361
+ AutomationProperties.HelpText="View Releases Page for VS Extension"
362
+ AutomationProperties.Name="Releases Page"
363
+ Command="{Binding LaunchVSExtensionPageCommand}"
364
+ KeyboardNavigation.IsTabStop="False"
365
+ Style="{StaticResource HyperlinkedButtonStyle}">
366
+ <TextBlock HorizontalAlignment="Left">
367
+ <Hyperlink Command="{Binding LaunchVSExtensionPageCommand}"
368
+ IsEnabled="True"
369
+ KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding VSExtensionUrl, Mode=OneTime}" /></Hyperlink>
370
+ </TextBlock>
371
+ </Button>
372
+ </Grid>
373
+
374
+ <DockPanel x:Name="UpdateChangesStkPnlDcIsland"
375
+ Grid.Row="0"
376
+ Grid.Column="2"
377
+ DataContext="{Binding UpdateViewModel}"
378
+ Visibility="{Binding IsUpdateAvailable,
379
+ Converter={StaticResource BooleanToVisibilityConverter}}">
380
+
381
+ <Grid x:Name="UpdateInfoGrid"
382
+ DockPanel.Dock="Top">
383
+ <Grid.RowDefinitions>
384
+ <RowDefinition Height="Auto" />
385
+ </Grid.RowDefinitions>
386
+ <Grid.ColumnDefinitions>
387
+ <ColumnDefinition Width="Auto" />
388
+ <ColumnDefinition Width="*" />
389
+ </Grid.ColumnDefinitions>
390
+
391
+ <TextBlock x:Name="UpdateTitleLabel"
392
+ Grid.Row="0"
393
+ Grid.Column="0"
394
+ Style="{DynamicResource LabelTextBlkStyle}"
395
+ Text="Available Update:" />
396
+
397
+ <TextBlock x:Name="UpdateVersionLabel"
398
+ Grid.Row="0"
399
+ Grid.Column="1"
400
+ Style="{DynamicResource DataTextBlkStyle}"
401
+ Text="{Binding UpdateVersion}" />
402
+ </Grid>
403
+
404
+ <WebBrowser DockPanel.Dock="Bottom"
405
+ ux:BrowserProperties.HtmlDoc="{Binding UpdateChanges}" />
406
+ </DockPanel>
407
+
408
+ <TextBlock x:Name="DowngradeMessageTextBlk"
409
+ Grid.Row="1"
410
+ Grid.Column="0"
411
+ Grid.ColumnSpan="3"
412
+ DataContext="{Binding InstallationViewModel}"
413
+ Style="{DynamicResource LabelTextBlkStyle}"
414
+ Text="{Binding DowngradeMessage}"
415
+ Visibility="{Binding Downgrade,
416
+ Converter={StaticResource BooleanToVisibilityConverter}}" />
417
+ </Grid>
418
+ </DockPanel>
419
+ </Border>
420
+</Window>
src/WixToolset.WixBA/RootView.xaml.cs
new
+51
@@ -0,0 +1,51 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System.ComponentModel;
6
+ using System.Windows;
7
+ using System.Windows.Interop;
8
+
9
+ /// <summary>
10
+ /// Interaction logic for View.xaml
11
+ /// </summary>
12
+ public partial class RootView : Window
13
+ {
14
+ /// <summary>
15
+ /// Creates the view populated with it's model.
16
+ /// </summary>
17
+ /// <param name="viewModel">Model for the view.</param>
18
+ public RootView(RootViewModel viewModel)
19
+ {
20
+ this.DataContext = viewModel;
21
+
22
+ this.Loaded += (sender, e) => WixBA.Model.Engine.CloseSplashScreen();
23
+ this.Closed += (sender, e) => this.Dispatcher.InvokeShutdown(); // shutdown dispatcher when the window is closed.
24
+
25
+ this.InitializeComponent();
26
+
27
+ viewModel.Dispatcher = this.Dispatcher;
28
+ viewModel.ViewWindowHandle = new WindowInteropHelper(this).EnsureHandle();
29
+ }
30
+
31
+ /// <summary>
32
+ /// Event is fired when the window is closing.
33
+ /// </summary>
34
+ /// <param name="sender"></param>
35
+ /// <param name="e"></param>
36
+ private void Window_Closing(object sender, CancelEventArgs e)
37
+ {
38
+ RootViewModel rvm = this.DataContext as RootViewModel;
39
+ if ((null != rvm) && (InstallationState.Applying == rvm.InstallState))
40
+ {
41
+ rvm.CancelButton_Click();
42
+ if (rvm.Canceled)
43
+ {
44
+ // Defer closing until the engine has canceled processing.
45
+ e.Cancel = true;
46
+ rvm.AutoClose = true;
47
+ }
48
+ }
49
+ }
50
+ }
51
+}
src/WixToolset.WixBA/RootViewModel.cs
new
+204
@@ -0,0 +1,204 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Diagnostics;
7
+ using System.Reflection;
8
+ using System.Windows;
9
+ using System.Windows.Input;
10
+ using System.Windows.Threading;
11
+ using WixToolset.Bootstrapper;
12
+
13
+ /// <summary>
14
+ /// The errors returned from the engine
15
+ /// </summary>
16
+ public enum Error
17
+ {
18
+ UserCancelled = 1223,
19
+ }
20
+
21
+ /// <summary>
22
+ /// The model of the root view in WixBA.
23
+ /// </summary>
24
+ public class RootViewModel : PropertyNotifyBase
25
+ {
26
+ private ICommand cancelCommand;
27
+ private ICommand closeCommand;
28
+
29
+ private bool canceled;
30
+ private InstallationState installState;
31
+ private DetectionState detectState;
32
+
33
+ /// <summary>
34
+ /// Creates a new model of the root view.
35
+ /// </summary>
36
+ public RootViewModel()
37
+ {
38
+ this.InstallationViewModel = new InstallationViewModel(this);
39
+ this.ProgressViewModel = new ProgressViewModel(this);
40
+ this.UpdateViewModel = new UpdateViewModel(this);
41
+ }
42
+
43
+ public InstallationViewModel InstallationViewModel { get; private set; }
44
+ public ProgressViewModel ProgressViewModel { get; private set; }
45
+ public UpdateViewModel UpdateViewModel { get; private set; }
46
+ public Dispatcher Dispatcher { get; set; }
47
+ public IntPtr ViewWindowHandle { get; set; }
48
+ public bool AutoClose { get; set; }
49
+
50
+ public ICommand CloseCommand
51
+ {
52
+ get
53
+ {
54
+ if (this.closeCommand == null)
55
+ {
56
+ this.closeCommand = new RelayCommand(param => WixBA.View.Close());
57
+ }
58
+
59
+ return this.closeCommand;
60
+ }
61
+ }
62
+
63
+ public ICommand CancelCommand
64
+ {
65
+ get
66
+ {
67
+ if (this.cancelCommand == null)
68
+ {
69
+ this.cancelCommand = new RelayCommand(param =>
70
+ {
71
+ this.CancelButton_Click();
72
+ },
73
+ param => !this.Canceled);
74
+ }
75
+
76
+ return this.cancelCommand;
77
+ }
78
+ }
79
+
80
+ public bool CancelAvailable
81
+ {
82
+ get { return InstallationState.Applying == this.InstallState; }
83
+ }
84
+
85
+ public bool Canceled
86
+ {
87
+ get
88
+ {
89
+ return this.canceled;
90
+ }
91
+
92
+ set
93
+ {
94
+ if (this.canceled != value)
95
+ {
96
+ this.canceled = value;
97
+ base.OnPropertyChanged("Canceled");
98
+ }
99
+ }
100
+ }
101
+
102
+ /// <summary>
103
+ /// Gets and sets the detect state of the view's model.
104
+ /// </summary>
105
+ public DetectionState DetectState
106
+ {
107
+ get
108
+ {
109
+ return this.detectState;
110
+ }
111
+
112
+ set
113
+ {
114
+ if (this.detectState != value)
115
+ {
116
+ this.detectState = value;
117
+
118
+ // Notify all the properties derived from the state that the state changed.
119
+ base.OnPropertyChanged("DetectState");
120
+ }
121
+ }
122
+ }
123
+
124
+ /// <summary>
125
+ /// Gets and sets the installation state of the view's model.
126
+ /// </summary>
127
+ public InstallationState InstallState
128
+ {
129
+ get
130
+ {
131
+ return this.installState;
132
+ }
133
+
134
+ set
135
+ {
136
+ if (this.installState != value)
137
+ {
138
+ this.installState = value;
139
+
140
+ // Notify all the properties derived from the state that the state changed.
141
+ base.OnPropertyChanged("InstallState");
142
+ base.OnPropertyChanged("CancelAvailable");
143
+ }
144
+ }
145
+ }
146
+
147
+ /// <summary>
148
+ /// Gets and sets the state of the view's model before apply begins in order to return to that state if cancel or rollback occurs.
149
+ /// </summary>
150
+ public InstallationState PreApplyState { get; set; }
151
+
152
+ /// <summary>
153
+ /// Gets and sets the path where the bundle is currently installed or will be installed.
154
+ /// </summary>
155
+ public string InstallDirectory
156
+ {
157
+ get
158
+ {
159
+ return WixBA.Model.InstallDirectory;
160
+ }
161
+
162
+ set
163
+ {
164
+ if (WixBA.Model.InstallDirectory != value)
165
+ {
166
+ WixBA.Model.InstallDirectory = value;
167
+ base.OnPropertyChanged("InstallDirectory");
168
+ }
169
+ }
170
+ }
171
+
172
+ /// <summary>
173
+ /// The Title of this bundle.
174
+ /// </summary>
175
+ public string Title
176
+ {
177
+ get
178
+ {
179
+ return WixDistribution.ShortProduct;
180
+ }
181
+ }
182
+
183
+ /// <summary>
184
+ /// Prompts the user to make sure they want to cancel.
185
+ /// This needs to run on the UI thread, use Dispatcher.Invoke to call this from a background thread.
186
+ /// </summary>
187
+ public void CancelButton_Click()
188
+ {
189
+ if (this.Canceled)
190
+ {
191
+ return;
192
+ }
193
+
194
+ if (Display.Full == WixBA.Model.Command.Display)
195
+ {
196
+ this.Canceled = (MessageBoxResult.Yes == MessageBox.Show(WixBA.View, "Are you sure you want to cancel?", "WiX Toolset", MessageBoxButton.YesNo, MessageBoxImage.Error));
197
+ }
198
+ else
199
+ {
200
+ this.Canceled = true;
201
+ }
202
+ }
203
+ }
204
+}
src/WixToolset.WixBA/Styles.xaml
new
+194
@@ -0,0 +1,194 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
5
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6
+ xmlns:System="clr-namespace:System;assembly=mscorlib"
7
+ xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
8
+ xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
9
+ xmlns:ux="clr-namespace:WixToolset.UX">
10
+
11
+ <!-- Converters -->
12
+ <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
13
+
14
+ <!-- Fonts -->
15
+ <FontFamily x:Key="FontFamily">Segoe UI, Arial</FontFamily>
16
+ <System:Double x:Key="FontSizeBranding">45</System:Double>
17
+ <System:Double x:Key="FontSizeMedium">12</System:Double>
18
+ <System:Double x:Key="FontSizeButton">14</System:Double>
19
+
20
+ <!-- Images -->
21
+ <BitmapImage x:Key="LogoOverLightBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-white-hollow.png" />
22
+ <BitmapImage x:Key="LogoOverDarkBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-black-hollow.png" />
23
+
24
+ <!-- Colors -->
25
+ <Color x:Key="ProgressIndicatorColor">#FF1EF1E8</Color>
26
+
27
+ <!-- Brushs -->
28
+ <SolidColorBrush x:Key="ProgressIndicatorBrush" Color="{DynamicResource ProgressIndicatorColor}" />
29
+
30
+ <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0">
31
+ <LinearGradientBrush.GradientStops>
32
+ <GradientStopCollection>
33
+ <GradientStop Offset="0" Color="#000000FF" />
34
+ <GradientStop Offset="0.5" Color="#600000FF" />
35
+ <GradientStop Offset="0.54" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" />
36
+ <GradientStop Offset="0.56" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" />
37
+ <GradientStop Offset="0.6" Color="#600000FF" />
38
+ <GradientStop Offset="1" Color="#000000FF" />
39
+ </GradientStopCollection>
40
+ </LinearGradientBrush.GradientStops>
41
+ </LinearGradientBrush>
42
+
43
+ <!-- Control Templates -->
44
+ <ControlTemplate x:Key="HyperlinkedButtonTemplateKey" TargetType="{x:Type Button}">
45
+ <ContentPresenter Margin="{TemplateBinding Control.Padding}"
46
+ HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"
47
+ VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
48
+ Content="{TemplateBinding ContentControl.Content}"
49
+ ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
50
+ SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
51
+ </ControlTemplate>
52
+
53
+ <ControlTemplate x:Key="ProgressBarTemplateKey" TargetType="{x:Type ProgressBar}">
54
+ <Border Name="TemplateRoot"
55
+ Margin="0,5"
56
+ BorderBrush="{TemplateBinding BorderBrush}"
57
+ BorderThickness="{TemplateBinding BorderThickness}"
58
+ CornerRadius="3">
59
+ <Grid ClipToBounds="True" SnapsToDevicePixels="true">
60
+ <Rectangle Fill="{TemplateBinding Background}" />
61
+ <Rectangle Name="PART_Track" ClipToBounds="True" />
62
+ <Decorator x:Name="PART_Indicator" HorizontalAlignment="Left">
63
+ <Grid Name="Foreground">
64
+ <Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" />
65
+ <Grid x:Name="Animation">
66
+ <Rectangle x:Name="PART_GlowRect"
67
+ Width="80"
68
+ Margin="-100,0,0,0"
69
+ HorizontalAlignment="Left"
70
+ Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" />
71
+ </Grid>
72
+ </Grid>
73
+ </Decorator>
74
+ </Grid>
75
+ </Border>
76
+ </ControlTemplate>
77
+
78
+ <!-- Styles -->
79
+ <Style x:Key="ActionBtnStkPnlStyle" TargetType="StackPanel">
80
+ <Setter Property="Margin" Value="0,2,0,0" />
81
+ <Setter Property="HorizontalAlignment" Value="Center" />
82
+ </Style>
83
+
84
+ <Style x:Key="FinalActionsStkPnlStyle" TargetType="StackPanel">
85
+ <Setter Property="Margin" Value="80,2,0,0" />
86
+ </Style>
87
+
88
+ <Style x:Key="BrandStkPnlStyle" TargetType="StackPanel">
89
+ <Setter Property="Margin" Value="0,0,20,0" />
90
+ <Setter Property="VerticalAlignment" Value="Top" />
91
+ <Setter Property="HorizontalAlignment" Value="Right" />
92
+ <Setter Property="Width" Value="100" />
93
+ <Setter Property="Width" Value="100" />
94
+ </Style>
95
+
96
+ <Style x:Key="CommonTextBlkStyle" TargetType="TextBlock">
97
+ <Setter Property="VerticalAlignment" Value="Center" />
98
+ <Setter Property="HorizontalAlignment" Value="Center" />
99
+ <Setter Property="FontWeight" Value="Bold" />
100
+ <Setter Property="TextAlignment" Value="Center" />
101
+ <Setter Property="TextWrapping" Value="WrapWithOverflow" />
102
+ <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
103
+ </Style>
104
+
105
+ <Style x:Key="TitleTextBlkStyle"
106
+ BasedOn="{StaticResource CommonTextBlkStyle}"
107
+ TargetType="TextBlock">
108
+ <Setter Property="VerticalAlignment" Value="Top" />
109
+ <Setter Property="HorizontalAlignment" Value="Left" />
110
+ <Setter Property="FontSize" Value="{DynamicResource ResourceKey=FontSizeBranding}" />
111
+ <Setter Property="FontWeight" Value="ExtraBold" />
112
+ <Setter Property="Margin" Value="0,5,0,0" />
113
+ </Style>
114
+
115
+
116
+ <Style x:Key="LabelTextBlkStyle"
117
+ BasedOn="{StaticResource CommonTextBlkStyle}"
118
+ TargetType="TextBlock">
119
+ <Setter Property="HorizontalAlignment" Value="Left" />
120
+ <Setter Property="FontSize" Value="{DynamicResource FontSizeButton}" />
121
+ <Setter Property="FontWeight" Value="Bold" />
122
+ <Setter Property="TextAlignment" Value="Left" />
123
+ <Setter Property="Margin" Value="0,2,3,0" />
124
+ </Style>
125
+
126
+ <Style x:Key="DataTextBlkStyle"
127
+ BasedOn="{StaticResource CommonTextBlkStyle}"
128
+ TargetType="TextBlock">
129
+ <Setter Property="HorizontalAlignment" Value="Left" />
130
+ <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" />
131
+ <Setter Property="TextAlignment" Value="Left" />
132
+ <Setter Property="Margin" Value="2,2,3,0" />
133
+ </Style>
134
+
135
+ <Style x:Key="StatusTextStyle"
136
+ BasedOn="{StaticResource CommonTextBlkStyle}"
137
+ TargetType="TextBlock">
138
+ <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.MessageFontFamilyKey}}" />
139
+ <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" />
140
+ <Setter Property="Margin" Value="8" />
141
+ <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
142
+ <Setter Property="VerticalAlignment" Value="Top" />
143
+ <Setter Property="HorizontalAlignment" Value="Left" />
144
+ </Style>
145
+
146
+ <Style x:Key="ActionButtonStyle" TargetType="Button">
147
+ <Setter Property="MinHeight" Value="30" />
148
+ <Setter Property="MinWidth" Value="100" />
149
+ <Setter Property="Margin" Value="5,0,0,0" />
150
+ <Setter Property="VerticalAlignment" Value="Center" />
151
+ <Setter Property="VerticalContentAlignment" Value="Center" />
152
+ <Setter Property="HorizontalAlignment" Value="Center" />
153
+ <Setter Property="HorizontalContentAlignment" Value="Center" />
154
+ <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" />
155
+ </Style>
156
+
157
+ <Style x:Key="FinalActionButtonStyle"
158
+ BasedOn="{StaticResource ActionButtonStyle}"
159
+ TargetType="Button">
160
+ <Setter Property="Margin" Value="40,0,0,0" />
161
+ </Style>
162
+
163
+ <Style x:Key="HyperlinkedButtonStyle" TargetType="Button">
164
+ <Setter Property="Margin" Value="0,2,0,0" />
165
+ <Setter Property="Template" Value="{StaticResource HyperlinkedButtonTemplateKey}" />
166
+ <Setter Property="IsHitTestVisible" Value="True" />
167
+ </Style>
168
+
169
+ <Style x:Key="LogoStyle" TargetType="Image">
170
+ <Setter Property="Height" Value="65" />
171
+ <Setter Property="Width" Value="102" />
172
+ <Setter Property="VerticalAlignment" Value="Top" />
173
+ <Setter Property="IsHitTestVisible" Value="False" />
174
+ <Setter Property="Source" Value="{DynamicResource LogoOverLightBackground}" />
175
+ <Style.Triggers>
176
+ <DataTrigger Binding="{Binding Path=IsLightBackground, Source={x:Static ux:WindowProperties.Instance}}" Value="false">
177
+ <Setter Property="Source" Value="{DynamicResource LogoOverDarkBackground}" />
178
+ </DataTrigger>
179
+ </Style.Triggers>
180
+ </Style>
181
+
182
+ <Style x:Key="UpdateMarqueeStyle" TargetType="ProgressBar">
183
+ <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" />
184
+ <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
185
+ <Setter Property="BorderThickness" Value="1" />
186
+ </Style>
187
+
188
+ <Style x:Key="ActionProgressStyle" TargetType="ProgressBar">
189
+ <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" />
190
+ <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
191
+ <Setter Property="BorderThickness" Value="1" />
192
+ <Setter Property="Template" Value="{StaticResource ProgressBarTemplateKey}" />
193
+ </Style>
194
+</ResourceDictionary>
src/WixToolset.WixBA/UpdateViewModel.cs
new
+207
@@ -0,0 +1,207 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.ComponentModel;
7
+ using System.Windows.Input;
8
+ using WixToolset.Bootstrapper;
9
+
10
+ /// <summary>
11
+ /// The states of the update view model.
12
+ /// </summary>
13
+ public enum UpdateState
14
+ {
15
+ Unknown,
16
+ Initializing,
17
+ Checking,
18
+ Current,
19
+ Available,
20
+ Failed,
21
+ }
22
+
23
+ /// <summary>
24
+ /// The model of the update view.
25
+ /// </summary>
26
+ public class UpdateViewModel : PropertyNotifyBase
27
+ {
28
+ private RootViewModel root;
29
+ private UpdateState state;
30
+ private ICommand updateCommand;
31
+ private string updateVersion;
32
+ private string updateChanges;
33
+
34
+
35
+ public UpdateViewModel(RootViewModel root)
36
+ {
37
+ this.root = root;
38
+ WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin;
39
+ WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate;
40
+ WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete;
41
+
42
+ this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged);
43
+
44
+ this.State = UpdateState.Initializing;
45
+ }
46
+
47
+ void RootPropertyChanged(object sender, PropertyChangedEventArgs e)
48
+ {
49
+ if ("InstallState" == e.PropertyName)
50
+ {
51
+ base.OnPropertyChanged("CanUpdate");
52
+ }
53
+ }
54
+
55
+ public bool CheckingEnabled
56
+ {
57
+ get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; }
58
+ }
59
+
60
+ public bool CanUpdate
61
+ {
62
+ get
63
+ {
64
+ switch(this.root.InstallState)
65
+ {
66
+ case InstallationState.Waiting:
67
+ case InstallationState.Applied:
68
+ case InstallationState.Failed:
69
+ return this.IsUpdateAvailable;
70
+ default:
71
+ return false;
72
+ }
73
+ }
74
+ }
75
+
76
+ public ICommand UpdateCommand
77
+ {
78
+ get
79
+ {
80
+ if (this.updateCommand == null)
81
+ {
82
+ this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate);
83
+ }
84
+
85
+ return this.updateCommand;
86
+ }
87
+ }
88
+
89
+ public bool IsUpdateAvailable
90
+ {
91
+ get { return this.State == UpdateState.Available; }
92
+ }
93
+
94
+ /// <summary>
95
+ /// Gets and sets the state of the update view model.
96
+ /// </summary>
97
+ public UpdateState State
98
+ {
99
+ get
100
+ {
101
+ return this.state;
102
+ }
103
+
104
+ set
105
+ {
106
+ if (this.state != value)
107
+ {
108
+ this.state = value;
109
+ base.OnPropertyChanged("State");
110
+ base.OnPropertyChanged("CanUpdate");
111
+ base.OnPropertyChanged("CheckingEnabled");
112
+ base.OnPropertyChanged("IsUpdateAvailable");
113
+ }
114
+ }
115
+ }
116
+ /// <summary>
117
+ /// The version of an available update.
118
+ /// </summary>
119
+ public string UpdateVersion
120
+ {
121
+ get
122
+ {
123
+ return updateVersion;
124
+ }
125
+ set
126
+ {
127
+ if (this.updateVersion != value)
128
+ {
129
+ this.updateVersion = value;
130
+ base.OnPropertyChanged("UpdateVersion");
131
+ }
132
+ }
133
+ }
134
+
135
+ /// <summary>
136
+ /// The changes in the available update.
137
+ /// </summary>
138
+ public string UpdateChanges
139
+ {
140
+ get
141
+ {
142
+ return updateChanges;
143
+ }
144
+ set
145
+ {
146
+ if (this.updateChanges != value)
147
+ {
148
+ this.updateChanges = value;
149
+ base.OnPropertyChanged("UpdateChanges");
150
+ }
151
+ }
152
+ }
153
+
154
+ private void DetectUpdateBegin(object sender, Bootstrapper.DetectUpdateBeginEventArgs e)
155
+ {
156
+ // Don't check for updates if:
157
+ // the first check failed (no retry)
158
+ // if we are being run as an uninstall
159
+ // if we are not under a full UI.
160
+ if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display))
161
+ {
162
+ this.State = UpdateState.Checking;
163
+ e.Skip = false;
164
+ }
165
+ }
166
+
167
+ private void DetectUpdate(object sender, Bootstrapper.DetectUpdateEventArgs e)
168
+ {
169
+ // The list of updates is sorted in descending version, so the first callback should be the largest update available.
170
+ // This update should be either larger than ours (so we are out of date), the same as ours (so we are current)
171
+ // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and
172
+ // enumerate all of the updates.
173
+ WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version));
174
+ if (e.Version > WixBA.Model.Version)
175
+ {
176
+ WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null);
177
+ this.UpdateVersion = String.Concat("v", e.Version.ToString());
178
+ string changesFormat = @"<body style='overflow: auto;'>{0}</body>";
179
+ this.UpdateChanges = String.Format(changesFormat, e.Content);
180
+ this.State = UpdateState.Available;
181
+ }
182
+ else
183
+ {
184
+ this.State = UpdateState.Current;
185
+ }
186
+ e.StopProcessingUpdates = true;
187
+ }
188
+
189
+ private void DetectUpdateComplete(object sender, Bootstrapper.DetectUpdateCompleteEventArgs e)
190
+ {
191
+ // Failed to process an update, allow the existing bundle to still install.
192
+ if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status))
193
+ {
194
+ this.State = UpdateState.Failed;
195
+ WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status));
196
+ e.IgnoreError = true;
197
+ }
198
+ // If we are uninstalling, we don't want to check or show an update
199
+ // If we are checking, then the feed didn't find any valid enclosures
200
+ // If we are initializing, we're either uninstalling or not a full UI
201
+ else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State))
202
+ {
203
+ this.State = UpdateState.Unknown;
204
+ }
205
+ }
206
+ }
207
+}
src/WixToolset.WixBA/WindowProperties.cs
new
+65
@@ -0,0 +1,65 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Windows;
7
+ using System.Windows.Media;
8
+
9
+ /// <summary>
10
+ /// Dependency Properties associated with the main Window object.
11
+ /// </summary>
12
+ public class WindowProperties : DependencyObject
13
+ {
14
+ /// <summary>
15
+ /// Dependency Property to hold the result of detecting the relative luminosity (or brightness) of a Windows background.
16
+ /// </summary>
17
+ public static readonly DependencyProperty IsLightBackgroundProperty = DependencyProperty.Register(
18
+ "IsLightBackground", typeof(bool), typeof(WindowProperties), new PropertyMetadata( false ));
19
+
20
+ private static Lazy<WindowProperties> _instance = new Lazy<WindowProperties>(() =>
21
+ {
22
+ WindowProperties wp = new WindowProperties();
23
+ wp.CheckBackgroundBrightness();
24
+ return wp;
25
+ });
26
+
27
+ public static WindowProperties Instance
28
+ {
29
+ get
30
+ {
31
+ return _instance.Value;
32
+ }
33
+ }
34
+
35
+
36
+ public bool IsLightBackground
37
+ {
38
+ get { return (bool)GetValue(IsLightBackgroundProperty); }
39
+ private set { SetValue(IsLightBackgroundProperty, value); }
40
+ }
41
+
42
+ /// <summary>
43
+ /// Use the Luminosity parameter of the background color to detect light vs dark theme settings.
44
+ /// </summary>
45
+ /// <remarks>
46
+ /// This approach detects both the common High Contrast themes (White vs Black) and custom themes which may have relatively lighter backgrounds.
47
+ /// </remarks>
48
+ public void CheckBackgroundBrightness()
49
+ {
50
+ SolidColorBrush windowbrush = System.Windows.SystemColors.WindowBrush;
51
+ System.Drawing.Color dcolor = System.Drawing.Color.FromArgb(windowbrush.Color.A, windowbrush.Color.R, windowbrush.Color.G, windowbrush.Color.B);
52
+
53
+ var brightness = dcolor.GetBrightness();
54
+ // Test for 'Lightness' at an arbitrary point, approaching 1.0 (White).
55
+ if (0.7 < brightness)
56
+ {
57
+ this.IsLightBackground = true;
58
+ }
59
+ else
60
+ {
61
+ this.IsLightBackground = false;
62
+ }
63
+ }
64
+ }
65
+}
src/WixToolset.WixBA/WixBA.BootstrapperCore.config
new
+16
@@ -0,0 +1,16 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<configuration>
5
+ <configSections>
6
+ <sectionGroup name="wix.bootstrapper" type="WixToolset.Bootstrapper.BootstrapperSectionGroup, BootstrapperCore">
7
+ <section name="host" type="WixToolset.Bootstrapper.HostSection, BootstrapperCore" />
8
+ </sectionGroup>
9
+ </configSections>
10
+ <startup useLegacyV2RuntimeActivationPolicy="true">
11
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
12
+ </startup>
13
+ <wix.bootstrapper>
14
+ <host assemblyName="WixBA" />
15
+ </wix.bootstrapper>
16
+</configuration>
src/WixToolset.WixBA/WixBA.cs
new
+216
@@ -0,0 +1,216 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.UX
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Diagnostics;
8
+ using System.IO;
9
+ using System.Net;
10
+ using System.Text;
11
+ using System.Windows.Input;
12
+ using Threading = System.Windows.Threading;
13
+ using WinForms = System.Windows.Forms;
14
+
15
+ using WixToolset.Bootstrapper;
16
+
17
+ /// <summary>
18
+ /// The WiX toolset user experience.
19
+ /// </summary>
20
+ public class WixBA : BootstrapperApplication
21
+ {
22
+ /// <summary>
23
+ /// Gets the global model.
24
+ /// </summary>
25
+ static public Model Model { get; private set; }
26
+
27
+ /// <summary>
28
+ /// Gets the global view.
29
+ /// </summary>
30
+ static public RootView View { get; private set; }
31
+ // TODO: We should refactor things so we dont have a global View.
32
+
33
+ /// <summary>
34
+ /// Gets the global dispatcher.
35
+ /// </summary>
36
+ static public Threading.Dispatcher Dispatcher { get; private set; }
37
+
38
+ /// <summary>
39
+ /// Launches the default web browser to the provided URI.
40
+ /// </summary>
41
+ /// <param name="uri">URI to open the web browser.</param>
42
+ public static void LaunchUrl(string uri)
43
+ {
44
+ WixBA.UseShellExecute(uri);
45
+ }
46
+
47
+ /// <summary>
48
+ /// Open a log file.
49
+ /// </summary>
50
+ /// <param name="uri">URI to a log file.</param>
51
+ internal static void OpenLog(Uri uri)
52
+ {
53
+ WixBA.UseShellExecute(uri.ToString());
54
+ }
55
+
56
+ /// <summary>
57
+ /// Open a log folder.
58
+ /// </summary>
59
+ /// <param name="string">path to a log folder.</param>
60
+ internal static void OpenLogFolder(string logFolder)
61
+ {
62
+ WixBA.UseShellExecute(logFolder);
63
+ }
64
+
65
+ /// <summary>
66
+ /// Open a log folder.
67
+ /// </summary>
68
+ /// <param name="uri">path to a log folder.</param>
69
+ private static void UseShellExecute(string path)
70
+ {
71
+ // Switch the wait cursor since shellexec can take a second or so.
72
+ System.Windows.Input.Cursor cursor = WixBA.View.Cursor;
73
+ WixBA.View.Cursor = System.Windows.Input.Cursors.Wait;
74
+ Process process = null;
75
+ try
76
+ {
77
+ process = new Process();
78
+ process.StartInfo.FileName = path;
79
+ process.StartInfo.UseShellExecute = true;
80
+ process.StartInfo.Verb = "open";
81
+
82
+ process.Start();
83
+ }
84
+ finally
85
+ {
86
+ if (null != process)
87
+ {
88
+ process.Dispose();
89
+ }
90
+ // back to the original cursor.
91
+ WixBA.View.Cursor = cursor;
92
+ }
93
+ }
94
+
95
+ /// <summary>
96
+ /// Starts planning the appropriate action.
97
+ /// </summary>
98
+ /// <param name="action">Action to plan.</param>
99
+ public static void Plan(LaunchAction action)
100
+ {
101
+ WixBA.Model.PlannedAction = action;
102
+ WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction);
103
+ }
104
+
105
+ public static void PlanLayout()
106
+ {
107
+ // Either default or set the layout directory
108
+ if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory))
109
+ {
110
+ WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory();
111
+
112
+ // Ask the user for layout folder if one wasn't provided and we're in full UI mode
113
+ if (WixBA.Model.Command.Display == Display.Full)
114
+ {
115
+ WixBA.Dispatcher.Invoke((Action)delegate()
116
+ {
117
+ WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog();
118
+ browserDialog.RootFolder = Environment.SpecialFolder.MyComputer;
119
+
120
+ // Default to the current directory.
121
+ browserDialog.SelectedPath = WixBA.Model.LayoutDirectory;
122
+ WinForms.DialogResult result = browserDialog.ShowDialog();
123
+
124
+ if (WinForms.DialogResult.OK == result)
125
+ {
126
+ WixBA.Model.LayoutDirectory = browserDialog.SelectedPath;
127
+ WixBA.Plan(WixBA.Model.Command.Action);
128
+ }
129
+ else
130
+ {
131
+ WixBA.View.Close();
132
+ }
133
+ }
134
+ );
135
+ }
136
+ }
137
+ else
138
+ {
139
+ WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory;
140
+ WixBA.Plan(WixBA.Model.Command.Action);
141
+ }
142
+ }
143
+
144
+ /// <summary>
145
+ /// Thread entry point for WiX Toolset UX.
146
+ /// </summary>
147
+ protected override void Run()
148
+ {
149
+ this.Engine.Log(LogLevel.Verbose, "Running the WiX BA.");
150
+ WixBA.Model = new Model(this);
151
+ WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher;
152
+ RootViewModel viewModel = new RootViewModel();
153
+
154
+ // Kick off detect which will populate the view models.
155
+ this.Engine.Detect();
156
+
157
+ // Create a Window to show UI.
158
+ if (WixBA.Model.Command.Display == Display.Passive ||
159
+ WixBA.Model.Command.Display == Display.Full)
160
+ {
161
+ this.Engine.Log(LogLevel.Verbose, "Creating a UI.");
162
+ WixBA.View = new RootView(viewModel);
163
+ WixBA.View.Show();
164
+ }
165
+
166
+ Threading.Dispatcher.Run();
167
+
168
+ this.PostTelemetry();
169
+ this.Engine.Quit(WixBA.Model.Result);
170
+ }
171
+
172
+ private void PostTelemetry()
173
+ {
174
+ string result = String.Concat("0x", WixBA.Model.Result.ToString("x"));
175
+
176
+ StringBuilder telemetryData = new StringBuilder();
177
+ foreach (KeyValuePair<string, string> kvp in WixBA.Model.Telemetry)
178
+ {
179
+ telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value);
180
+ }
181
+ telemetryData.AppendFormat("Result={0}", result);
182
+
183
+ byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString());
184
+
185
+ try
186
+ {
187
+ HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result));
188
+ post.Method = "POST";
189
+ post.ContentType = "application/x-www-form-urlencoded";
190
+ post.ContentLength = data.Length;
191
+
192
+ using (Stream postStream = post.GetRequestStream())
193
+ {
194
+ postStream.Write(data, 0, data.Length);
195
+ }
196
+
197
+ HttpWebResponse response = (HttpWebResponse)post.GetResponse();
198
+ }
199
+ catch (ArgumentException)
200
+ {
201
+ }
202
+ catch (FormatException)
203
+ {
204
+ }
205
+ catch (OverflowException)
206
+ {
207
+ }
208
+ catch (ProtocolViolationException)
209
+ {
210
+ }
211
+ catch (WebException)
212
+ {
213
+ }
214
+ }
215
+ }
216
+}
src/WixToolset.WixBA/WixBA.csproj
new
+64
@@ -0,0 +1,64 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
4
+ <PropertyGroup>
5
+ <ProjectGuid>{7C27518B-84AD-4679-8EF4-29DF552CF1AC}</ProjectGuid>
6
+ <AssemblyName>WixBA</AssemblyName>
7
+ <OutputType>Library</OutputType>
8
+ <RootNamespace>WixToolset.UX</RootNamespace>
9
+ </PropertyGroup>
10
+ <ItemGroup>
11
+ <Compile Include="Hresult.cs" />
12
+ <Compile Include="Model.cs" />
13
+ <Compile Include="BrowserProperties.cs" />
14
+ <Compile Include="NewsItem.cs" />
15
+ <Compile Include="ProgressViewModel.cs" />
16
+ <Compile Include="RelayCommand.cs" />
17
+ <Compile Include="InstallationViewModel.cs" />
18
+ <Page Include="Styles.xaml">
19
+ <Generator>MSBuild:Compile</Generator>
20
+ <SubType>Designer</SubType>
21
+ </Page>
22
+ <Page Include="RootView.xaml">
23
+ <Generator>MSBuild:Compile</Generator>
24
+ <SubType>Designer</SubType>
25
+ </Page>
26
+ <Compile Include="PropertyNotifyBase.cs" />
27
+ <Compile Include="RootView.xaml.cs">
28
+ <DependentUpon>RootView.xaml</DependentUpon>
29
+ </Compile>
30
+ <Compile Include="RootViewModel.cs" />
31
+ <Compile Include="UpdateViewModel.cs" />
32
+ <Compile Include="WindowProperties.cs" />
33
+ <Compile Include="WixBA.cs" />
34
+ <Compile Include="Properties\AssemblyInfo.cs" />
35
+ <None Include="WixBA.BootstrapperCore.config">
36
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
37
+ <SubType>Designer</SubType>
38
+ </None>
39
+ </ItemGroup>
40
+ <ItemGroup>
41
+ <Reference Include="PresentationCore" />
42
+ <Reference Include="PresentationFramework" />
43
+ <Reference Include="System" />
44
+ <Reference Include="System.ComponentModel.DataAnnotations" />
45
+ <Reference Include="System.Core" />
46
+ <Reference Include="System.Drawing">
47
+ <Private>False</Private>
48
+ </Reference>
49
+ <Reference Include="System.ServiceModel" />
50
+ <Reference Include="System.Windows.Forms" />
51
+ <Reference Include="System.Xml.Linq" />
52
+ <Reference Include="System.Xml" />
53
+ <Reference Include="System.Xaml" />
54
+ <Reference Include="WindowsBase" />
55
+ <ProjectReference Include="..\..\ext\BalExtension\mba\core\core.csproj" />
56
+ </ItemGroup>
57
+ <ItemGroup>
58
+ <Resource Include="Resources\logo-white-hollow.png" />
59
+ </ItemGroup>
60
+ <ItemGroup>
61
+ <Resource Include="Resources\logo-black-hollow.png" />
62
+ </ItemGroup>
63
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
64
+</Project>
\ No newline at end of file