| 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.WixBA |
| 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 WixToolset.BootstrapperApplicationApi; |
| 12 | |
| 13 | using Threading = System.Windows.Threading; |
| 14 | using WinForms = System.Windows.Forms; |
| 15 | |
| 16 | /// <summary> |
| 17 | /// The WiX toolset bootstrapper application. |
| 18 | /// </summary> |
| 19 | public class WixBA : BootstrapperApplication |
| 20 | { |
| 21 | internal IBootstrapperApplicationData BAManifest { get; private set; } |
| 22 | |
| 23 | internal IBootstrapperCommand Command { get; private set; } |
| 24 | |
| 25 | internal IEngine Engine => this.engine; |
| 26 | |
| 27 | /// <summary> |
| 28 | /// Gets the global model. |
| 29 | /// </summary> |
| 30 | static public Model Model { get; private set; } |
| 31 | |
| 32 | /// <summary> |
| 33 | /// Gets the global view. |
| 34 | /// </summary> |
| 35 | static public RootView View { get; private set; } |
| 36 | // TODO: We should refactor things so we dont have a global View. |
| 37 | |
| 38 | /// <summary> |
| 39 | /// Gets the global dispatcher. |
| 40 | /// </summary> |
| 41 | static public Threading.Dispatcher Dispatcher { get; private set; } |
| 42 | |
| 43 | /// <summary> |
| 44 | /// Launches the default web browser to the provided URI. |
| 45 | /// </summary> |
| 46 | /// <param name="uri">URI to open the web browser.</param> |
| 47 | public static void LaunchUrl(string uri) |
| 48 | { |
| 49 | WixBA.UseShellExecute(uri); |
| 50 | } |
| 51 | |
| 52 | /// <summary> |
| 53 | /// Open a log file. |
| 54 | /// </summary> |
| 55 | /// <param name="uri">URI to a log file.</param> |
| 56 | internal static void OpenLog(Uri uri) |
| 57 | { |
| 58 | WixBA.UseShellExecute(uri.ToString()); |
| 59 | } |
| 60 | |
| 61 | /// <summary> |
| 62 | /// Open a log folder. |
| 63 | /// </summary> |
| 64 | /// <param name="string">path to a log folder.</param> |
| 65 | internal static void OpenLogFolder(string logFolder) |
| 66 | { |
| 67 | WixBA.UseShellExecute(logFolder); |
| 68 | } |
| 69 | |
| 70 | /// <summary> |
| 71 | /// Open a log folder. |
| 72 | /// </summary> |
| 73 | /// <param name="uri">path to a log folder.</param> |
| 74 | private static void UseShellExecute(string path) |
| 75 | { |
| 76 | // Switch the wait cursor since shellexec can take a second or so. |
| 77 | System.Windows.Input.Cursor cursor = WixBA.View.Cursor; |
| 78 | WixBA.View.Cursor = System.Windows.Input.Cursors.Wait; |
| 79 | Process process = null; |
| 80 | try |
| 81 | { |
| 82 | process = new Process(); |
| 83 | process.StartInfo.FileName = path; |
| 84 | process.StartInfo.UseShellExecute = true; |
| 85 | process.StartInfo.Verb = "open"; |
| 86 | |
| 87 | process.Start(); |
| 88 | } |
| 89 | finally |
| 90 | { |
| 91 | if (null != process) |
| 92 | { |
| 93 | process.Dispose(); |
| 94 | } |
| 95 | // back to the original cursor. |
| 96 | WixBA.View.Cursor = cursor; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | /// <summary> |
| 101 | /// Starts planning the appropriate action. |
| 102 | /// </summary> |
| 103 | /// <param name="action">Action to plan.</param> |
| 104 | public static void Plan(LaunchAction action) |
| 105 | { |
| 106 | WixBA.Model.PlannedAction = action; |
| 107 | WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction); |
| 108 | } |
| 109 | |
| 110 | public static void PlanLayout() |
| 111 | { |
| 112 | // Either default or set the layout directory |
| 113 | if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory)) |
| 114 | { |
| 115 | WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory(); |
| 116 | |
| 117 | // Ask the user for layout folder if one wasn't provided and we're in full UI mode |
| 118 | if (WixBA.Model.Command.Display == Display.Full) |
| 119 | { |
| 120 | WixBA.Dispatcher.Invoke((Action)delegate() |
| 121 | { |
| 122 | WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog(); |
| 123 | browserDialog.RootFolder = Environment.SpecialFolder.MyComputer; |
| 124 | |
| 125 | // Default to the current directory. |
| 126 | browserDialog.SelectedPath = WixBA.Model.LayoutDirectory; |
| 127 | WinForms.DialogResult result = browserDialog.ShowDialog(); |
| 128 | |
| 129 | if (WinForms.DialogResult.OK == result) |
| 130 | { |
| 131 | WixBA.Model.LayoutDirectory = browserDialog.SelectedPath; |
| 132 | WixBA.Plan(WixBA.Model.Command.Action); |
| 133 | } |
| 134 | else |
| 135 | { |
| 136 | WixBA.View.Close(); |
| 137 | } |
| 138 | } |
| 139 | ); |
| 140 | } |
| 141 | } |
| 142 | else |
| 143 | { |
| 144 | WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory; |
| 145 | WixBA.Plan(WixBA.Model.Command.Action); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /// <summary> |
| 150 | /// Thread entry point for WiX Toolset Bootstrapper Application. |
| 151 | /// </summary> |
| 152 | protected override void Run() |
| 153 | { |
| 154 | this.Engine.Log(LogLevel.Verbose, "Running the WiX BA."); |
| 155 | WixBA.Model = new Model(this); |
| 156 | WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher; |
| 157 | RootViewModel viewModel = new RootViewModel(); |
| 158 | WixBA.View = new RootView(viewModel); |
| 159 | |
| 160 | // Create a Window to show UI. |
| 161 | if (WixBA.Model.Command.Display == Display.Passive || |
| 162 | WixBA.Model.Command.Display == Display.Full) |
| 163 | { |
| 164 | this.Engine.Log(LogLevel.Verbose, "Creating a UI."); |
| 165 | WixBA.View.Show(); |
| 166 | } |
| 167 | |
| 168 | // Kick off detect which will populate the view models. |
| 169 | this.Engine.Detect(); |
| 170 | |
| 171 | Threading.Dispatcher.Run(); |
| 172 | |
| 173 | this.PostTelemetry(); |
| 174 | |
| 175 | var exitCode = WixBA.Model.Result; |
| 176 | if ((exitCode & 0xFFFF0000) == unchecked(0x80070000)) |
| 177 | { |
| 178 | exitCode &= 0xFFFF; // return plain old Win32 error, not HRESULT. |
| 179 | } |
| 180 | |
| 181 | this.Engine.Quit(exitCode); |
| 182 | } |
| 183 | |
| 184 | protected override void OnCreate(CreateEventArgs args) |
| 185 | { |
| 186 | base.OnCreate(args); |
| 187 | this.Command = args.Command; |
| 188 | this.BAManifest = new BootstrapperApplicationData(); |
| 189 | } |
| 190 | |
| 191 | private void PostTelemetry() |
| 192 | { |
| 193 | string result = String.Concat("0x", WixBA.Model.Result.ToString("x")); |
| 194 | |
| 195 | StringBuilder telemetryData = new StringBuilder(); |
| 196 | foreach (KeyValuePair<string, string> kvp in WixBA.Model.Telemetry) |
| 197 | { |
| 198 | telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value); |
| 199 | } |
| 200 | telemetryData.AppendFormat("Result={0}", result); |
| 201 | |
| 202 | byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString()); |
| 203 | |
| 204 | try |
| 205 | { |
| 206 | HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result)); |
| 207 | post.Method = "POST"; |
| 208 | post.ContentType = "application/x-www-form-urlencoded"; |
| 209 | post.ContentLength = data.Length; |
| 210 | |
| 211 | using (Stream postStream = post.GetRequestStream()) |
| 212 | { |
| 213 | postStream.Write(data, 0, data.Length); |
| 214 | } |
| 215 | |
| 216 | HttpWebResponse response = (HttpWebResponse)post.GetResponse(); |
| 217 | } |
| 218 | catch (ArgumentException) |
| 219 | { |
| 220 | } |
| 221 | catch (FormatException) |
| 222 | { |
| 223 | } |
| 224 | catch (OverflowException) |
| 225 | { |
| 226 | } |
| 227 | catch (ProtocolViolationException) |
| 228 | { |
| 229 | } |
| 230 | catch (WebException) |
| 231 | { |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | } |