Skip to main content

Installers, setup wizards and packaged applications

How SPART-Desk drives what installs an application and what the install leaves behind: an installer's own wizard (NSIS, Windows Installer), a ClickOnce deployment and its maintenance dialog, and a packaged (MSIX) application started by its app ID. For anyone whose tests install the application they test, or test the installer itself.

The kinds supported:

  • NSIS (Modern UI 2) installers and their uninstallers.
  • Windows Installer packages (MSI, WixUI_InstallDir dialogs).
  • ClickOnce deployments: the security dialog, the installed application, the maintenance dialog.
  • MSIX / packaged applications, a packaged WinUI 3 application among them, registered and started by their app ID.

Launch, or attach​

An installer's wizard is the window of the process the launch spawned, found as any launched program's window is:

  • NSIS: launch({ executable: "setup.exe" }); the uninstaller is launched from where it lies with _?=<folder>, so that the process the test started is the one showing the wizard rather than a copy in %TEMP%.
  • Windows Installer: launch({ executable: "msiexec.exe", args: ["/i", package] }). The client process shows the dialogs; the install itself runs in the Windows Installer service, and the Completed dialog follows by itself. desktop.run("msiexec.exe", ["/x", package, "/qn"]) removes a package silently, its exit code the result.
  • ClickOnce: the ClickOnce service (dfsvc.exe) shows every dialog and starts the application in a process of its own, so the launch names what it looks for: launch({ executable: "rundll32.exe", args: ["dfshim.dll,ShOpenVerbApplication", deployment], target: { processName: "dfsvc", windowTitle: "Application Install - Security Warning" }, joinExisting: true }) opens a deployment manifest as a double click does (the service lingers after an operation, hence joinExisting); after Install, desktop.attach({ processName }) reaches the application. The Start menu reference the install makes (.appref-ms) is launched as an executable with the application's process named: launch({ executable: appref, target: { processName: "ClickOnceForm" } }); one that names no process is refused at once, and the sandbox refuses the reference as it refuses a shortcut. The deployment is removed through its maintenance dialog (rundll32.exe dfshim.dll,ShArpMaintain <identity>, the UninstallString ClickOnce registers under HKCU\…\Uninstall): "Remove the application from this computer", OK. There is no silent uninstall.
  • Packaged applications: launch({ appId }), with args; launch({ uri, appId }) for a protocol the package declares. Registering a package is the test's business, not the engine's.

The dialogs​

NSIS and Windows Installer draw their wizards with Win32 dialogs and common controls: buttons by their names (Next, Back, Install, Finish, NSIS's I Agree), a licence as a rich edit or a check box ("I accept the terms in the License Agreement", WixUI), the folder as the one text box of the Destination Folder dialog (Windows Installer appends a backslash), components as a check box tree (NSIS), the log as a list view. Windows Installer's dialogs are titled <ProductName> Setup. ClickOnce's dialogs are the service's: the security warning names the application and its publisher ("Unknown Publisher" for a certificate the machine does not trust), with Install and Don't Install; the maintenance dialog has two radio buttons and OK.

Elevation and scope​

SPART-Desk never elevates (elevated.md). An installer that asks for elevation (a per-machine MSI, an NSIS script with RequestExecutionLevel admin) raises the consent prompt on the secure desktop, which no automation reaches: run the test runner elevated for it. Install per user where you can: NSIS under %LOCALAPPDATA%, an MSI with Scope="perUser" (a registry value under HKCU as its component's key path), ClickOnce in the user's ClickOnce store, an MSIX layout registered for the user. Leave the machine as the test found it: remove what was installed, before and after a run, whatever happened.

close() on a wizard​

app.close() asks the window to close, as the close button does, and stops the process when it is still there three seconds later. An NSIS wizard closes; Windows Installer's client answers with "Are you sure you want to cancel?", keeps its window, and is stopped: the service rolls the install back and nothing is left installed. A ClickOnce dialog belongs to the service, which is never stopped: close() on a dialog joined with joinExisting stops nothing, and the dialog is left to its own buttons.

In the Windows Sandbox​

Experimental

The Windows Sandbox runtime may change in minor versions until its prerequisites are validated.

An installer's executable is copied into the guest and run there as any program is (runtime-windows-sandbox.md); an MSI is run through desktop.run("msiexec.exe", ["/i", …]) from a folder a launch copied in, and its dialogs attached to by their title. A .appref-ms names a deployment installed for this machine's user, and is refused in the guest as a shortcut is. A packaged application is registered in the guest by the test and started by its app ID.