@joebigelow / wix / commits / 8d92f6d4

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