aboutsummaryrefslogtreecommitdiff
path: root/src/samples/Dtf/EmbeddedUI
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-05-11 07:36:37 -0700
committerRob Mensching <rob@firegiant.com>2021-05-11 07:36:37 -0700
commit3f583916719eeef598d10a5d4e14ef14f008243b (patch)
tree3d528e0ddb5c0550954217c97059d2f19cd6152a /src/samples/Dtf/EmbeddedUI
parent2e5ab696b8b4666d551b2a0532b95fb7fe6dbe03 (diff)
downloadwix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.gz
wix-3f583916719eeef598d10a5d4e14ef14f008243b.tar.bz2
wix-3f583916719eeef598d10a5d4e14ef14f008243b.zip
Merge Dtf
Diffstat (limited to 'src/samples/Dtf/EmbeddedUI')
-rw-r--r--src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs5
-rw-r--r--src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj56
-rw-r--r--src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs176
-rw-r--r--src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs132
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml17
-rw-r--r--src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs111
6 files changed, 497 insertions, 0 deletions
diff --git a/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs
new file mode 100644
index 00000000..7a2fa039
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/AssemblyInfo.cs
@@ -0,0 +1,5 @@
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
3using System.Reflection;
4
5[assembly: AssemblyDescription("Sample managed embedded external UI")]
diff --git a/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
new file mode 100644
index 00000000..e4c52a26
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/EmbeddedUI.csproj
@@ -0,0 +1,56 @@
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 ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4 <PropertyGroup>
5 <ProjectGuid>{864B8C50-7895-4485-AC89-900D86FD8C0D}</ProjectGuid>
6 <OutputType>Library</OutputType>
7 <RootNamespace>WixToolset.Dtf.Samples.EmbeddedUI</RootNamespace>
8 <AssemblyName>WixToolset.Dtf.Samples.EmbeddedUI</AssemblyName>
9 <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
10 <FileAlignment>512</FileAlignment>
11 </PropertyGroup>
12 <ItemGroup>
13 <Compile Include="AssemblyInfo.cs" />
14 <Compile Include="InstallProgressCounter.cs" />
15 <Compile Include="SampleEmbeddedUI.cs" />
16 <Compile Include="SetupWizard.xaml.cs">
17 <DependentUpon>SetupWizard.xaml</DependentUpon>
18 </Compile>
19 </ItemGroup>
20 <ItemGroup>
21 <Page Include="SetupWizard.xaml">
22 <Generator>MSBuild:Compile</Generator>
23 <SubType>Designer</SubType>
24 </Page>
25 </ItemGroup>
26 <ItemGroup>
27 <Reference Include="PresentationCore">
28 <RequiredTargetFramework>3.0</RequiredTargetFramework>
29 </Reference>
30 <Reference Include="PresentationFramework">
31 <RequiredTargetFramework>3.0</RequiredTargetFramework>
32 </Reference>
33 <Reference Include="System" />
34 <Reference Include="System.Core">
35 <RequiredTargetFramework>3.5</RequiredTargetFramework>
36 </Reference>
37 <Reference Include="System.Xml" />
38 <Reference Include="WindowsBase">
39 <RequiredTargetFramework>3.0</RequiredTargetFramework>
40 </Reference>
41 </ItemGroup>
42 <ItemGroup>
43 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
44 <Project>{24121677-0ed0-41b5-833f-1b9a18e87bf4}</Project>
45 <Name>WixToolset.Dtf.WindowsInstaller</Name>
46 </ProjectReference>
47 </ItemGroup>
48
49 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
50<!--
51 <PropertyGroup>
52 <PostBuildEvent>"$(TargetDir)..\x86\MakeSfxCA.exe" "$(TargetPath)" "$(TargetDir)SfxCA.dll" "$(IntermediateOutputPath)$(TargetFileName)" "$(TargetDir)WixToolset.Dtf.WindowsInstaller.dll"</PostBuildEvent>
53 </PropertyGroup>
54-->
55
56</Project>
diff --git a/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
new file mode 100644
index 00000000..df77e106
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/InstallProgressCounter.cs
@@ -0,0 +1,176 @@
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
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using WixToolset.Dtf.WindowsInstaller;
7
8 /// <summary>
9 /// Tracks MSI progress messages and converts them to usable progress.
10 /// </summary>
11 public class InstallProgressCounter
12 {
13 private int total;
14 private int completed;
15 private int step;
16 private bool moveForward;
17 private bool enableActionData;
18 private int progressPhase;
19 private double scriptPhaseWeight;
20
21 public InstallProgressCounter() : this(0.3)
22 {
23 }
24
25 public InstallProgressCounter(double scriptPhaseWeight)
26 {
27 if (!(0 <= scriptPhaseWeight && scriptPhaseWeight <= 1))
28 {
29 throw new ArgumentOutOfRangeException("scriptPhaseWeight");
30 }
31
32 this.scriptPhaseWeight = scriptPhaseWeight;
33 }
34
35 /// <summary>
36 /// Gets a number between 0 and 1 that indicates the overall installation progress.
37 /// </summary>
38 public double Progress { get; private set; }
39
40 public void ProcessMessage(InstallMessage messageType, Record messageRecord)
41 {
42 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
43
44 switch (messageType)
45 {
46 case InstallMessage.ActionStart:
47 if (this.enableActionData)
48 {
49 this.enableActionData = false;
50 }
51 break;
52
53 case InstallMessage.ActionData:
54 if (this.enableActionData)
55 {
56 if (this.moveForward)
57 {
58 this.completed += this.step;
59 }
60 else
61 {
62 this.completed -= this.step;
63 }
64
65 this.UpdateProgress();
66 }
67 break;
68
69 case InstallMessage.Progress:
70 this.ProcessProgressMessage(messageRecord);
71 break;
72 }
73 }
74
75 private void ProcessProgressMessage(Record progressRecord)
76 {
77 // This MSI progress-handling code was mostly borrowed from burn and translated from C++ to C#.
78
79 if (progressRecord == null || progressRecord.FieldCount == 0)
80 {
81 return;
82 }
83
84 int fieldCount = progressRecord.FieldCount;
85 int progressType = progressRecord.GetInteger(1);
86 string progressTypeString = String.Empty;
87 switch (progressType)
88 {
89 case 0: // Master progress reset
90 if (fieldCount < 4)
91 {
92 return;
93 }
94
95 this.progressPhase++;
96
97 this.total = progressRecord.GetInteger(2);
98 if (this.progressPhase == 1)
99 {
100 // HACK!!! this is a hack courtesy of the Windows Installer team. It seems the script planning phase
101 // is always off by "about 50". So we'll toss an extra 50 ticks on so that the standard progress
102 // doesn't go over 100%. If there are any custom actions, they may blow the total so we'll call this
103 // "close" and deal with the rest.
104 this.total += 50;
105 }
106
107 this.moveForward = (progressRecord.GetInteger(3) == 0);
108 this.completed = (this.moveForward ? 0 : this.total); // if forward start at 0, if backwards start at max
109 this.enableActionData = false;
110
111 this.UpdateProgress();
112 break;
113
114 case 1: // Action info
115 if (fieldCount < 3)
116 {
117 return;
118 }
119
120 if (progressRecord.GetInteger(3) == 0)
121 {
122 this.enableActionData = false;
123 }
124 else
125 {
126 this.enableActionData = true;
127 this.step = progressRecord.GetInteger(2);
128 }
129 break;
130
131 case 2: // Progress report
132 if (fieldCount < 2 || this.total == 0 || this.progressPhase == 0)
133 {
134 return;
135 }
136
137 if (this.moveForward)
138 {
139 this.completed += progressRecord.GetInteger(2);
140 }
141 else
142 {
143 this.completed -= progressRecord.GetInteger(2);
144 }
145
146 this.UpdateProgress();
147 break;
148
149 case 3: // Progress total addition
150 this.total += progressRecord.GetInteger(2);
151 break;
152 }
153 }
154
155 private void UpdateProgress()
156 {
157 if (this.progressPhase < 1 || this.total == 0)
158 {
159 this.Progress = 0;
160 }
161 else if (this.progressPhase == 1)
162 {
163 this.Progress = this.scriptPhaseWeight * Math.Min(this.completed, this.total) / this.total;
164 }
165 else if (this.progressPhase == 2)
166 {
167 this.Progress = this.scriptPhaseWeight +
168 (1 - this.scriptPhaseWeight) * Math.Min(this.completed, this.total) / this.total;
169 }
170 else
171 {
172 this.Progress = 1;
173 }
174 }
175 }
176}
diff --git a/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
new file mode 100644
index 00000000..9b26bef5
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SampleEmbeddedUI.cs
@@ -0,0 +1,132 @@
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
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Configuration;
8 using System.Threading;
9 using System.Windows;
10 using System.Windows.Threading;
11 using WixToolset.Dtf.WindowsInstaller;
12 using Application = System.Windows.Application;
13
14 public class SampleEmbeddedUI : IEmbeddedUI
15 {
16 private Thread appThread;
17 private Application app;
18 private SetupWizard setupWizard;
19 private ManualResetEvent installStartEvent;
20 private ManualResetEvent installExitEvent;
21
22 /// <summary>
23 /// Initializes the embedded UI.
24 /// </summary>
25 /// <param name="session">Handle to the installer which can be used to get and set properties.
26 /// The handle is only valid for the duration of this method call.</param>
27 /// <param name="resourcePath">Path to the directory that contains all the files from the MsiEmbeddedUI table.</param>
28 /// <param name="internalUILevel">On entry, contains the current UI level for the installation. After this
29 /// method returns, the installer resets the UI level to the returned value of this parameter.</param>
30 /// <returns>True if the embedded UI was successfully initialized; false if the installation
31 /// should continue without the embedded UI.</returns>
32 /// <exception cref="InstallCanceledException">The installation was canceled by the user.</exception>
33 /// <exception cref="InstallerException">The embedded UI failed to initialize and
34 /// causes the installation to fail.</exception>
35 public bool Initialize(Session session, string resourcePath, ref InstallUIOptions internalUILevel)
36 {
37 if (session != null)
38 {
39 if ((internalUILevel & InstallUIOptions.Full) != InstallUIOptions.Full)
40 {
41 // Don't show custom UI when the UI level is set to basic.
42 return false;
43
44 // An embedded UI could display an alternate dialog sequence for reduced or
45 // basic modes, but it's not implemented here. We'll just fall back to the
46 // built-in MSI basic UI.
47 }
48
49 if (String.Equals(session["REMOVE"], "All", StringComparison.OrdinalIgnoreCase))
50 {
51 // Don't show custom UI when uninstalling.
52 return false;
53
54 // An embedded UI could display an uninstall wizard, it's just not imlemented here.
55 }
56 }
57
58 // Start the setup wizard on a separate thread.
59 this.installStartEvent = new ManualResetEvent(false);
60 this.installExitEvent = new ManualResetEvent(false);
61 this.appThread = new Thread(this.Run);
62 this.appThread.SetApartmentState(ApartmentState.STA);
63 this.appThread.Start();
64
65 // Wait for the setup wizard to either kickoff the install or prematurely exit.
66 int waitResult = WaitHandle.WaitAny(new WaitHandle[] { this.installStartEvent, this.installExitEvent });
67 if (waitResult == 1)
68 {
69 // The setup wizard set the exit event instead of the start event. Cancel the installation.
70 throw new InstallCanceledException();
71 }
72 else
73 {
74 // Start the installation with a silenced internal UI.
75 // This "embedded external UI" will handle message types except for source resolution.
76 internalUILevel = InstallUIOptions.NoChange | InstallUIOptions.SourceResolutionOnly;
77 return true;
78 }
79 }
80
81 /// <summary>
82 /// Processes information and progress messages sent to the user interface.
83 /// </summary>
84 /// <param name="messageType">Message type.</param>
85 /// <param name="messageRecord">Record that contains message data.</param>
86 /// <param name="buttons">Message box buttons.</param>
87 /// <param name="icon">Message box icon.</param>
88 /// <param name="defaultButton">Message box default button.</param>
89 /// <returns>Result of processing the message.</returns>
90 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
91 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
92 {
93 // Synchronously send the message to the setup wizard window on its thread.
94 object result = this.setupWizard.Dispatcher.Invoke(DispatcherPriority.Send,
95 new Func<MessageResult>(delegate()
96 {
97 return this.setupWizard.ProcessMessage(messageType, messageRecord, buttons, icon, defaultButton);
98 }));
99 return (MessageResult) result;
100 }
101
102 /// <summary>
103 /// Shuts down the embedded UI at the end of the installation.
104 /// </summary>
105 /// <remarks>
106 /// If the installation was canceled during initialization, this method will not be called.
107 /// If the installation was canceled or failed at any later point, this method will be called at the end.
108 /// </remarks>
109 public void Shutdown()
110 {
111 // Wait for the user to exit the setup wizard.
112 this.setupWizard.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
113 new Action(delegate()
114 {
115 this.setupWizard.EnableExit();
116 }));
117 this.appThread.Join();
118 }
119
120 /// <summary>
121 /// Creates the setup wizard and runs the application thread.
122 /// </summary>
123 private void Run()
124 {
125 this.app = new Application();
126 this.setupWizard = new SetupWizard(this.installStartEvent);
127 this.setupWizard.InitializeComponent();
128 this.app.Run(this.setupWizard);
129 this.installExitEvent.Set();
130 }
131 }
132}
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
new file mode 100644
index 00000000..a43059e8
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml
@@ -0,0 +1,17 @@
1
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
5<Window x:Class="WixToolset.Dtf.Samples.EmbeddedUI.SetupWizard"
6 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8 Title="Sample Embedded UI" Height="400" Width="540" Visibility="Visible">
9 <Grid>
10 <TextBox Margin="8,8,8,63" Name="messagesTextBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" FontFamily="Lucida Console" FontSize="10" />
11 <Button Height="23" HorizontalAlignment="Right" Name="installButton" VerticalAlignment="Bottom" Width="75" Click="installButton_Click" Margin="0,0,91,8">Install</Button>
12 <Button Height="23" HorizontalAlignment="Right" Name="exitButton" VerticalAlignment="Bottom" Width="75" Visibility="Hidden" Click="exitButton_Click" Margin="0,0,8,8">Exit</Button>
13 <Button Height="23" Margin="0,0,8,8" Name="cancelButton" VerticalAlignment="Bottom" Width="75" HorizontalAlignment="Right" Click="cancelButton_Click">Cancel</Button>
14 <ProgressBar Height="16" Margin="8,0,8,39" Name="progressBar" VerticalAlignment="Bottom" Visibility="Hidden" IsIndeterminate="False" />
15 <Label Height="28" HorizontalAlignment="Left" Margin="8,0,0,4.48" Name="progressLabel" VerticalAlignment="Bottom" Width="120" Visibility="Hidden">0%</Label>
16 </Grid>
17</Window>
diff --git a/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
new file mode 100644
index 00000000..b25b8a9e
--- /dev/null
+++ b/src/samples/Dtf/EmbeddedUI/SetupWizard.xaml.cs
@@ -0,0 +1,111 @@
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
3namespace WixToolset.Dtf.Samples.EmbeddedUI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Threading;
10 using System.Windows;
11 using System.Windows.Controls;
12 using System.Windows.Data;
13 using System.Windows.Documents;
14 using System.Windows.Input;
15 using System.Windows.Media;
16 using System.Windows.Media.Imaging;
17 using System.Windows.Navigation;
18 using System.Windows.Shapes;
19 using WixToolset.Dtf.WindowsInstaller;
20
21 /// <summary>
22 /// Interaction logic for SetupWizard.xaml
23 /// </summary>
24 public partial class SetupWizard : Window
25 {
26 private ManualResetEvent installStartEvent;
27 private InstallProgressCounter progressCounter;
28 private bool canceled;
29
30 public SetupWizard(ManualResetEvent installStartEvent)
31 {
32 this.installStartEvent = installStartEvent;
33 this.progressCounter = new InstallProgressCounter(0.5);
34 }
35
36 public MessageResult ProcessMessage(InstallMessage messageType, Record messageRecord,
37 MessageButtons buttons, MessageIcon icon, MessageDefaultButton defaultButton)
38 {
39 try
40 {
41 this.progressCounter.ProcessMessage(messageType, messageRecord);
42 this.progressBar.Value = this.progressBar.Minimum +
43 this.progressCounter.Progress * (this.progressBar.Maximum - this.progressBar.Minimum);
44 this.progressLabel.Content = "" + (int) Math.Round(100 * this.progressCounter.Progress) + "%";
45
46 switch (messageType)
47 {
48 case InstallMessage.Error:
49 case InstallMessage.Warning:
50 case InstallMessage.Info:
51 string message = String.Format("{0}: {1}", messageType, messageRecord);
52 this.LogMessage(message);
53 break;
54 }
55
56 if (this.canceled)
57 {
58 this.canceled = false;
59 return MessageResult.Cancel;
60 }
61 }
62 catch (Exception ex)
63 {
64 this.LogMessage(ex.ToString());
65 this.LogMessage(ex.StackTrace);
66 }
67
68 return MessageResult.OK;
69 }
70
71 private void LogMessage(string message)
72 {
73 this.messagesTextBox.Text += Environment.NewLine + message;
74 this.messagesTextBox.ScrollToEnd();
75 }
76
77 internal void EnableExit()
78 {
79 this.progressBar.Visibility = Visibility.Hidden;
80 this.progressLabel.Visibility = Visibility.Hidden;
81 this.cancelButton.Visibility = Visibility.Hidden;
82 this.exitButton.Visibility = Visibility.Visible;
83 }
84
85 private void installButton_Click(object sender, RoutedEventArgs e)
86 {
87 this.installButton.Visibility = Visibility.Hidden;
88 this.progressBar.Visibility = Visibility.Visible;
89 this.progressLabel.Visibility = Visibility.Visible;
90 this.installStartEvent.Set();
91 }
92
93 private void exitButton_Click(object sender, RoutedEventArgs e)
94 {
95 this.Close();
96 }
97
98 private void cancelButton_Click(object sender, RoutedEventArgs e)
99 {
100 if (this.installButton.Visibility == Visibility.Visible)
101 {
102 this.Close();
103 }
104 else
105 {
106 this.canceled = true;
107 this.cancelButton.IsEnabled = false;
108 }
109 }
110 }
111}