Skip to content

Commit 826a3a4

Browse files
committed
Add FGBA documentation
1 parent 66f194e commit 826a3a4

File tree

5 files changed

+382
-0
lines changed

5 files changed

+382
-0
lines changed

astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export default defineConfig({
5353
'heatwave/build-tools',
5454
'heatwave/build-tools/firegiant-licensing',
5555
{ label: 'Advanced Harvesting', collapsed: true, autogenerate: { directory: '/heatwave/build-tools/harvesting' } },
56+
{ label: 'Bundle Application Framework', collapsed: true, autogenerate: { directory: '/heatwave/build-tools/fgba' } },
5657
{ label: 'MSIX', collapsed: true, autogenerate: { directory: '/heatwave/build-tools/msix' } },
5758
'heatwave/build-tools/driver',
5859
'heatwave/build-tools/protected-services',
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
title: Building a BundleUI
3+
sidebar:
4+
order: 2
5+
---
6+
7+
The Bundle UI is the central object in the FireGiant Bundle Application Framework that you create to manage your UI. It inherits from [`FireGiant.BundleApplicationFramework.BundleUIBase`](/firegiant/api/firegiantbundleapplicationframework/bundleuibase/) and must implement three methods: CreateWindow, Run, and Stop.
8+
* `CreateWindow()` is called to get the Bundle UI's window handle and appropriate threading context. A window handle is _always_ required because some Burn operations must pump Windows messages. The window may not be displayed to the user (and should be created hidden) but Burn requires it.
9+
* `Run()` is called for the Bundle UI to enter its main event loop. When the Bundle UI exits this function, the bundle application exits.
10+
* `Stop()` is called when the Bundle UI should exit. This method sends the quit message to the Bundle UI's main event loop.
11+
12+
## Minimal Bundle UI
13+
14+
Here is the minimal WPF-based Bundle UI. It stores a reference to the [`FireGiant.BundleApplicationFramework.IBundleApplication`](/firegiant/api/firegiantbundleapplicationframework/ibundleapplication/) to communicate with Burn and its WPF-based `MainWindow` starts out hidden.
15+
16+
```cs title=ExampleWpfBundleUI.cs
17+
using FireGiant.BundleApplicationFramework;
18+
19+
public class ExampleWpfBundleUI : BundleUIBase
20+
{
21+
private readonly IBundleApplication _bundleApplication;
22+
private MainWindow _window;
23+
24+
public ExampleWpfBundleUI(IBundleApplication bundleApplication)
25+
{
26+
_bundleApplication = bundleApplication;
27+
}
28+
29+
public override BundleUIWindowContext CreateWindow()
30+
{
31+
_window = new MainWindow();
32+
33+
return new BundleUIWindowContext
34+
{
35+
WindowHandle = new WindowInteropHelper(_window).EnsureHandle(),
36+
SynchronizationContext = new DispatcherSynchronizationContext(),
37+
};
38+
}
39+
40+
public override void Run()
41+
{
42+
Dispatcher.Run();
43+
}
44+
45+
public override void Stop()
46+
{
47+
Dispatcher.CurrentDispatcher.InvokeShutdown();
48+
}
49+
```
50+
51+
:::note[IBundleApplication]
52+
Technically speaking, this minimal Bundle UI does not need the `IBundleApplication` reference. But as we'll see, every realistic Bundle UI will use the `_bundleApplication`. The `IBundleApplication` provides methods to read/write variables, log messages, cancel progress, and much more.
53+
:::
54+
55+
With the most minimal Bundle UI created, let's look at the callbacks that are available to customize your Bundle UI's behavior.
56+
57+
## Bundle UI callbacks
58+
59+
Without overriding any other Bundle UI methods, your Bundle UI supports all bundle actions (Install, Modify, Repair, Uninstall, etc) and all phases (Detect, Plan, Apply) without displaying any UI or customization. Since the goal of implementing a Bundle UI is to customize the bundle experience and/or display some UI, let's explore the available customization callbacks.
60+
61+
### Bundle action handling
62+
63+
A Bundle UI has three callbacks for each bundle action: Initialize, Start and Start Progress.
64+
* `OnInititialize{Action}()` is called before any action related operations have occured. It is not very common to use this callback because very little is known at this time. In Burn terms, the `Initialize` callbacks happen before the Detect phase is executed.
65+
* `OnStart{Action}(bool fullUI)` is called after the bundle has completed the Detect phase so the current state of the machine is known. The Bundle UI should set any configuration at this time. It can display UI to interact with the user if `fullUI` is true. Otherwise, it should do any configuration silently then call `IBundleApplication.Go()`. In Burn terms, the `Start` callbacks happen after the Detect phase completes and `Go()` starts the Plan phase.
66+
* `OnStartProgress{Action}(bool showUI)` is called after the bundle has planned its work and is ready to start. The BundleUI should display UI (such as a progress bar) if `showUI` is true. In Burn terms, the `StartProgress` callbacks, happen at the beginning of the Apply phase.
67+
68+
The following is a example of a Bundle UI that shows UI for install and uninstall.
69+
70+
```cs title=ExampleWpfBundleUI.cs
71+
public class ExampleWpfBundleUI : BundleUIBase
72+
{
73+
private readonly IBundleApplication _bundleApplication;
74+
private MainWindow _window;
75+
76+
...
77+
78+
public override bool OnStartInstall(bool fullUI)
79+
{
80+
if (fullUI)
81+
{
82+
_window.Model = new ExampleInstallViewModel(_bundleApplication);
83+
_window.Show();
84+
85+
// Let FGBA know that we're handling this callback because the
86+
// window and viewmodel will call _bundleApplication.Go() when
87+
// the user presses the "Install" button.
88+
return true;
89+
}
90+
91+
// Let the default handle the non-UI case.
92+
return base.OnStartInstall(fullUI);
93+
}
94+
95+
public override bool OnStartUninstall(bool fullUI)
96+
{
97+
if (fullUI)
98+
{
99+
_window.Model = new ExampleUninstallViewModel(_bundleApplication);
100+
_window.Show();
101+
102+
// Let FGBA know that we're handling this callback because the
103+
// window and viewmodel will call _bundleApplication.Go() when
104+
// the user presses the "Ok" button to start the uninstall.
105+
return true;
106+
}
107+
108+
// Let the default handle the non-UI case.
109+
return base.OnStartUninstall(fullUI);
110+
}
111+
112+
public override void OnStartProgressInstall(bool showUI)
113+
{
114+
if (showUI)
115+
{
116+
_window.Model = new ExampleProgressViewModel(_bundleApplication, "Installing...");
117+
_window.Show();
118+
}
119+
}
120+
121+
public override void OnStartProgressUninstall(bool showUI)
122+
{
123+
if (showUI)
124+
{
125+
_window.Model = new ExampleProgressViewModel(_bundleApplication, "Uninstalling...");
126+
_window.Show();
127+
}
128+
}
129+
130+
// Other callbacks.
131+
132+
// Initialization, these are rarely needed.
133+
// OnInitializeHelp()
134+
// OnInitializeLayout()
135+
// OnInitializeInstall()
136+
// OnInitializeModify()
137+
// OnInitializeRepair()
138+
// OnInitializeUninstall()
139+
140+
// Show a custom UI for any of these actions.
141+
// OnStartHelp()
142+
// OnStartLayout()
143+
// OnStartInstall() - seen above
144+
// OnStartModify()
145+
// OnStartRepair()
146+
// OnStartUninstall() - seen above
147+
148+
// Show progress page and handle progress updates.
149+
// OnStartProgressLayout
150+
// OnStartProgressInstall - seen above
151+
// OnStartProgressModify
152+
// OnStartProgressRepair
153+
// OnStartProgressUninstall - seen above
154+
```
155+
156+
:::note[Callback code duplication]
157+
You may find there is some duplication between callbacks for a particular action. Feel free to refactor as you see fit.
158+
:::
159+
160+
### Progress callbacks
161+
162+
During the Apply phase of the bundle, there are several callbacks that can report what is happening in Burn. These callbacks can be useful to show progress and potentially prompt the user for input.
163+
164+
```cs title=ExampleWpfBundleUI.cs
165+
public virtual void OnProgress(BundleOverallProgress progress)
166+
{
167+
if (_window.Model is ExampleProgressViewModel vm)
168+
{
169+
vm.ProgressBarPercentage = progress.OverallPercentage;
170+
}
171+
172+
base.OnProgress(progress);
173+
}
174+
175+
public virtual void OnPackageProgress(BundlePackageProgress packageProgress)
176+
{
177+
if (_window.Model is ExampleProgressViewModel vm)
178+
{
179+
vm.CurrentPackageName = packageProgress.Package.DisplayName;
180+
}
181+
182+
base.OnPackageProgress(packageProgress);
183+
}
184+
185+
public virtual void OnPackageComplete(
186+
BundlePackage package,
187+
BundleProgressAction action,
188+
int errorCode)
189+
{
190+
if (_window.Model is ExampleProgressViewModel vm)
191+
{
192+
vm.CurrentPackageName = null;
193+
}
194+
195+
base.OnPackageComplete(package, action, errorCode);
196+
}
197+
198+
199+
// Other useful callbacks.
200+
// OnMsiMessage
201+
// OnResolveSource
202+
```
203+
204+
### Completion callbacks
205+
206+
The FireGiant Bundle Application Framework calls the Bundle UI when the bundle action has completed with success, error or was canceled by the user. You can use the following callbacks to show the appropriate UI.
207+
208+
```cs title=ExampleWpfBundleUI.cs
209+
public override void OnCompleteCancel(bool restartRequired, bool fullUI)
210+
{
211+
if (fullUI)
212+
{
213+
// ExampleDoneViewModel will need to call this.Stop() when the user clicks "Ok" or
214+
// closes the dialog.
215+
_window.Model = new ExampleDoneViewModel(this, _bundleApplication, restartRequired,
216+
"Canceled...");
217+
_window.Show();
218+
}
219+
else
220+
{
221+
base.OnCompleteCancel(restartRequired, fullUI);
222+
}
223+
}
224+
225+
public override void OnCompleteFailure(bool restartRequired, int errorCode, bool fullUI)
226+
{
227+
if (fullUI)
228+
{
229+
// ExampleDoneViewModel will need to call this.Stop() when the user clicks "Ok" or
230+
// closes the dialog.
231+
_window.Model = new ExampleDoneViewModel(this, _bundleApplication, restartRequired,
232+
"Failed :(", errorCode);
233+
_window.Show();
234+
}
235+
else
236+
{
237+
base.OnCompleteCancel(restartRequired, fullUI);
238+
}
239+
}
240+
241+
public override void OnCompleteSuccess(bool restartRequired, bool fullUI)
242+
{
243+
if (fullUI)
244+
{
245+
// ExampleDoneViewModel will need to call this.Stop() when the user clicks "Ok" or
246+
// closes the dialog.
247+
_window.Model = new ExampleDoneViewModel(this, _bundleApplication, restartRequired,
248+
"Success!!!");
249+
_window.Show();
250+
}
251+
else
252+
{
253+
base.OnCompleteCancel(restartRequired, fullUI);
254+
}
255+
}
256+
```
257+
258+
:::tip[Show UI]
259+
Notice in the example that the `_window` will be shown in `fullUI`. This is important becuase it is possible the bundle will complete without calling any of the OnStart callbacks. This usually happens when there is an error very early in the initialization of the bundle.
260+
:::
261+
262+
Feel free to explore the other callbacks that a Bundle UI can override in the [`BundleUIBase`](/firegiant/api/firegiantbundleapplicationframework/bundleuibase/).
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: Debugging a Bundle UI
3+
sidebar:
4+
order: 3
5+
---
6+
7+
Bundle UIs are challenging to debug. They are an executable carried as a payload in a bundle that is extracted then launched from Burn. That means every change to your Bundle UI requires rebuilding the Bundle UI .csproj then your bundle .wixproj. Additionally, the process you need to debug is not the produced bundle .exe but a process started by that process. Fortunately, there is a mechanism to make the Bundle UI process pause at startup and display a message box so you can attach a debugger.
8+
9+
## One-time debug configuration
10+
11+
1. Set a **system** environment variable named `WixDebugBootstrapperApplication` to the file name of the Bundle UI executable.
12+
13+
For example, if your Bundle UI is named `mybundleui.csproj` then the output is probably `mybundleui.exe` (unless you customize the .csproj). Set a system environment variable `WixDebugBootstrapperApplication` to `mybundleui.exe`.
14+
15+
2. Restart Visual Studio to pick up the new environment variable.
16+
17+
18+
## Debugging
19+
20+
1. Make your Bundle UI changes
21+
2. Build the Bundle UI .csproj with those changes
22+
3. Build the bundle .wixproj
23+
4. Start the output bundle .exe
24+
5. A message box will appear with the process id for the Bundle UI .exe
25+
6. Attach a debugger to the Bundle UI process
26+
7. Set a breakpoint in the Bundle UI code to debug
27+
8. Continue in the debugger to hit the break point
28+
29+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: Implementing a BundleUIFactory
3+
sidebar:
4+
order: 1
5+
---
6+
7+
In addition to communicating with Burn, [`FireGiantBundleApplication.Run()`](/firegiant/api/firegiantbundleapplicationframework/firegiantbundleapplication/) finds an implementation of [`IBundleUIFactory`](/firegiant/api/firegiantbundleapplicationframework/ibundleui/) and calls that to create your Bundle UI object. The following is an implementation that should be sufficient for 99.999% of Bundle UIs. This factory pattern exists for those 0.001% of cases that need to do something special.
8+
9+
## Standard Bundle UI factory
10+
11+
```cs title=ExampleBundleUIFactory.cs
12+
using FireGiant.BundleApplicationFramework;
13+
14+
public class ExampleBundleUIFactory : IBundleUIFactory
15+
{
16+
public IBundleUI CreateBundleUI(IBundleApplication bundleApplication)
17+
{
18+
return new ExampleWpfBundleUI(bundleApplication);
19+
}
20+
}
21+
```
22+
23+
Now that we can create Bundle UI using our factory, let's look at how to implement the Bundle UI itself.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: FireGiant Bundle Application Framework
3+
sidebar:
4+
order: 0
5+
---
6+
7+
FireGiant Bundle Application Framework (FGBA) makes it easy to create custom bundle experiences, better known as "Bootstrapper Applications". Bootstrapper Applications are responsible for driving the bundle process and must handle many different scenarios. FGBA handles the complex edge cases for you so your bundle always installs and (often more importantly) uninstalls. It does this by abstracting away many of the low level details of Bootstrapper Applications allowing you to focus on the "Bundle UI".
8+
9+
:::caution[Licensed feature]
10+
Like the other features in HeatWave Build Tools, a license file is required to use FireGiant Bundle Application. See the [FireGiant licensing documentation for more information](../firegiant-licensing/).
11+
:::
12+
13+
Before digging into everything that FGBA provides, let's do a quick review of the key features of bundles from the Bootstrapper and Bundle Application point of view.
14+
15+
Part of the build process for a [`<Bundle/>`](/wix/schema/wxs/bundle/) is to embed the [`<BootstrapperApplication/>`](/wix/schema/wxs/bootstrapperapplication/) and all of its child [`<Payload/>`](/wix/schema/wxs/payload/) files into the bundle engine provided by WiX, called "Burn", to create your final bundle executable. When your customer runs your bundle, Burn unpacks your Bootstrapper Application and all of its payloads then launches your Bootstrapper Application executable. Since Bootstrapper Applications are executables that run in a separate process from Burn, they must connect back to Burn communicate about the bundle process.
16+
17+
FGBA makes this easy with one line of code:
18+
19+
```cs title=Program.cs {7}
20+
using FireGiant.BundleApplicationFramework;
21+
22+
public static class Program
23+
{
24+
public static int Main(string[] args)
25+
{
26+
FireGiantBundleApplication.Run();
27+
28+
return 0;
29+
}
30+
}
31+
```
32+
33+
:::note[Exit code]
34+
Always `return 0` from your Bootstrapper Application's entry point. That signals to Burn that your Bootstrapper Application executed properly. It is NOT how you communicate the final exit code for the overall process. We'll see how to communicate that later.
35+
:::
36+
37+
Before we dig deeper into how you define your Bundle Application, let's complete our introduction to a few more key features of bundles.
38+
39+
## Bundle actions
40+
41+
Users can request six different actions from a bundle: Install, Modify, Repair, Uninstall, Layout, and Help.
42+
* Install is the most well known action that typically has users select what options to install, possibly provide some configuration, then actually install the appropriate packages in the bundle.
43+
* Modify is the same but occurs when the bundle is already installed.
44+
* Repair is selected by users to fix a broken application.
45+
* Uninstall is pretty self-explanatory; it is used to remove the bundle from the computer.
46+
* Layout copies the bundle and all external payloads into a folder that can then be installed without network access.
47+
* Help is the outlier, as it displays help UI and nothing else.
48+
49+
## Bundle phases
50+
51+
Bundle actions (except Help) go through three phases: Detect, Plan, and Apply.
52+
* During the Detect phase, the Burn engine scans the user's machine and reports the current status of the bundle and all of its chained packages.
53+
* During the Plan phase, the Burn engine takes a high-level directive (such as "install", "repair", or "uninstall") and determines what operations need to be done for each package.
54+
* During the Apply phase, the operations from the Plan phase are executed. The Apply phase has two sub-phases Cache and Execute.
55+
* During Cache, packages are downloaded, copied, and/or extracted to a secure package store.
56+
* During Execute, the packages are installed, repaired, or uninstalled as per the plan.
57+
58+
## Bundle UI modes
59+
60+
Finally, bundles must handle the three UI modes: Full, Passive and Quiet.
61+
* In Full UI mode, the user expects to be presented with the interactive user interface. Historically, this UI has been a wizard-like experience.
62+
* In Passive UI mode, the user expects to be provided enough UI to know that the process is underway but with no other interaction. Commonly this is a simple UI that starts immediately, presents a progress bar then completes without any additional prompts.
63+
* The Quiet UI mode is like Passive in that it starts immediately and does not prompt the user at all, but also shows no UI at all.
64+
65+
The six operations, three phases and three UI modes create many different permutations Bootstrapper Applications must handle. There are almost 100 events Burn can fire to transmit all the information about the active operation, phase and UI mode. Again, FGBA simplifies your UI development by capturing all Burn events to track the bundle state in an easy to use API and provide default implementations for the events that require responses. The result is your Bundle UI only overrides the callbacks it wants to customize.
66+
67+
Let's look at how to instantiate your Bundle UI next.

0 commit comments

Comments
 (0)