Skip to content

Commit

Permalink
feat(installer): improved error handling and rollback in Windows inst…
Browse files Browse the repository at this point in the history
…aller (#397)

PowerShell configuration commands are now executed as custom actions instead of WixSilentExec. Errors are tracked and, if the installer is running with UI, an appropriate error message is shown to the user. For first time installs, if the installation fails, files that may have been created by the configuration process are cleaned up.

PowerShell command output is redirected to a temporary file; in the case of an error we provide the user the path to that file. A general command execution error will display a string error value. 

Custom actions are refactored slightly for consistency and readability:

- Internal functions now only return `void`, `BOOL`, or `HRESULT` where possible. Errors are always handled as `HRESULT` and other results (e.g. Win32 error codes, `LSTATUS`, null references) are converted to `HRESULT` and handled with the different WiX macros (e.g. `ExitOnWin32Error`).
- Consolidate on `WixGetProperty` instead of `MsiGetProperty` and be careful to release the resulting strings (`ReleaseStr`)
- Consolidate on `nullptr` instead of `NULL`

Issue: DGW-76
Issue: DGW-78
  • Loading branch information
thenextman authored Mar 13, 2023
1 parent 08d42bb commit 2766e5f
Show file tree
Hide file tree
Showing 8 changed files with 661 additions and 310 deletions.
905 changes: 612 additions & 293 deletions package/Windows/Actions/CustomAction.cpp

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package/Windows/Actions/CustomAction.def
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ EXPORTS
ValidateListeners
ValidateCertificate
ValidatePublicKey
ConfigureAccessUri
ConfigureListeners
ConfigureCert
ConfigurePublicKey
RollbackConfig
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;Shlwapi.lib;advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;Shlwapi.lib;advapi32.lib;pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
Expand Down Expand Up @@ -154,7 +154,7 @@
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;Shlwapi.lib;advapi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>msi.lib;dutil.lib;wcautil.lib;Version.lib;Shlwapi.lib;advapi32.lib;pathcch.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(WIX)sdk\$(WixPlatformToolset)\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<ModuleDefinitionFile>CustomAction.def</ModuleDefinitionFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
Expand Down
2 changes: 2 additions & 0 deletions package/Windows/Actions/stdafx.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
#include <commdlg.h>
#include <wininet.h>
#include "strutil.h"
#include "shlobj.h"
#include "pathcch.h"
36 changes: 23 additions & 13 deletions package/Windows/DevolutionsGateway.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -100,51 +100,61 @@
Before="CA.SetProgramDataPermissions" />
<SetProperty Id="CA.InitConfigAfterFinalize" Value="&quot;[INSTALLDIR]\DevolutionsGateway.exe&quot; --config-init-only" Sequence="execute" Before="CA.InitConfigAfterFinalize" />
<SetProperty Id="CA.SetGatewayStartupType" Value="[P.SERVICE_START]" Sequence="execute" Before="CA.SetGatewayStartupType" />
<SetProperty Id="CA.ConfigHostname" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.ACCESSURI_CMD]"' Sequence="execute" Before="CA.ConfigHostname" />
<SetProperty Id="CA.ConfigAccessUri" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.ACCESSURI_CMD]"' Sequence="execute" Before="CA.ConfigAccessUri" />
<SetProperty Id="CA.ConfigListeners" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.LISTENER_CMD]"' Sequence="execute" Before="CA.ConfigListeners" />
<SetProperty Id="CA.ConfigCert" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.CERT_CMD]"' Sequence="execute" Before="CA.ConfigCert" />
<SetProperty Id="CA.ConfigPk" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.PK_CMD]"' Sequence="execute" Before="CA.ConfigPk" />
<SetProperty Id="CA.ConfigPublicKey" Value='&quot;[P.POWERSHELLEXE]&quot; -ep Bypass -Command "&amp; Import-Module &apos;[INSTALLDIR]PowerShell\Modules\DevolutionsGateway&apos;; [P.PK_CMD]"' Sequence="execute" Before="CA.ConfigPublicKey" />

<!-- Reinstall the DGatewayService feature on maintenance installations
This forces the service to be stopped and started,
re-reading any configuration updated by the installer -->
<CustomAction Id="CA.SetREINSTALL" Property="REINSTALL" Value="F.DGatewayService" />
<CustomAction Id="CA.SetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />

<CustomAction Id="CA.InitConfigAfterFinalize" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="check" />
<CustomAction Id="CA.SetProgramDataPermissions" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="ignore"/>
<CustomAction Id="CA.CheckPowerShellVersion" BinaryKey="B.HELPER" DllEntry="CheckPowerShellVersion" Execute="immediate" Return="ignore" />

<!-- Validation actions: validate the input and generate the final PowerShell command -->
<CustomAction Id="CA.ValidateAccessUri" BinaryKey="B.HELPER" DllEntry="ValidateAccessUri" Execute="immediate" Return="check" />
<CustomAction Id="CA.ValidateListeners" BinaryKey="B.HELPER" DllEntry="ValidateListeners" Execute="immediate" Return="check" />
<CustomAction Id="CA.ValidateCertificate" BinaryKey="B.HELPER" DllEntry="ValidateCertificate" Execute="immediate" Return="check" />
<CustomAction Id="CA.ValidatePublicKey" BinaryKey="B.HELPER" DllEntry="ValidatePublicKey" Execute="immediate" Return="check" />
<CustomAction Id="CA.GenerateSummary" BinaryKey="B.HELPER" DllEntry="GenerateSummary" Execute="immediate" Return="check" />

<CustomAction Id="CA.BrowseForCertificate" BinaryKey="B.HELPER" DllEntry="BrowseForCertificate" Execute="immediate" Return="check" />
<CustomAction Id="CA.BrowseForPrivateKey" BinaryKey="B.HELPER" DllEntry="BrowseForPrivateKey" Execute="immediate" Return="check" />
<CustomAction Id="CA.BrowseForPublicKey" BinaryKey="B.HELPER" DllEntry="BrowseForPublicKey" Execute="immediate" Return="ignore" />

<CustomAction Id="CA.QueryGatewayStartupType" BinaryKey="B.HELPER" DllEntry="QueryGatewayStartupType" Execute="immediate" Return="ignore" />
<CustomAction Id="CA.SetGatewayStartupType" BinaryKey="B.HELPER" DllEntry="SetGatewayStartupType" Execute="deferred" Impersonate="no" Return="ignore" />
<CustomAction Id="CA.StartGatewayIfNeeded" BinaryKey="B.HELPER" DllEntry="StartGatewayIfNeeded" Execute="deferred" Impersonate="no" Return="ignore" />
<CustomAction Id="CA.ConfigHostname" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="check"/>
<CustomAction Id="CA.ConfigListeners" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="check"/>
<CustomAction Id="CA.ConfigCert" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="check" HideTarget="yes" />
<CustomAction Id="CA.ConfigPk" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Impersonate="no" Return="check"/>

<!-- Config actions: execute the PowerShell command -->
<CustomAction Id="CA.ConfigAccessUri" BinaryKey="B.HELPER" DllEntry="ConfigureAccessUri" Execute="deferred" Impersonate="no" Return="check"/>
<CustomAction Id="CA.ConfigListeners" BinaryKey="B.HELPER" DllEntry="ConfigureListeners" Execute="deferred" Impersonate="no" Return="check"/>
<!-- NOTE HideTarget="yes" to prevent CustomActionData leaking into logs -->
<CustomAction Id="CA.ConfigCert" BinaryKey="B.HELPER" DllEntry="ConfigureCert" Execute="deferred" Impersonate="no" Return="check" HideTarget="yes" />
<CustomAction Id="CA.ConfigPublicKey" BinaryKey="B.HELPER" DllEntry="ConfigurePublicKey" Execute="deferred" Impersonate="no" Return="check"/>

<!-- Rollback files generated during first installation -->
<CustomAction Id="CA.RollbackConfig" BinaryKey="B.HELPER" DllEntry="RollbackConfig" Execute="rollback" Impersonate="no" Return="ignore" />

<InstallUISequence>
<Custom Action='CA.CheckPowerShellVersion' After='LaunchConditions'/>
<Custom Action='CA.CheckPowerShellVersion' After='LaunchConditions' />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action='CA.SetProgramDataPermissions' After='CreateFolders'/>
<Custom Action="CA.SetARPINSTALLLOCATION" After="InstallValidate"/>
<Custom Action="CA.SetREINSTALL" Before="CostInitialize">
Maintenance AND NOT REINSTALL
</Custom>
<Custom Action="CA.SetREINSTALL" Before="CostInitialize">Maintenance AND NOT REINSTALL</Custom>
<Custom Action="CA.QueryGatewayStartupType" Before="RemoveExistingProducts">1</Custom>
<Custom Action="CA.SetGatewayStartupType" Before="StartServices">1</Custom>
<Custom Action="CA.RollbackConfig" Before="CA.InitConfigAfterFinalize">(NOT Installed OR REINSTALL)</Custom>
<Custom Action="CA.InitConfigAfterFinalize" Before="StartServices">(NOT Installed OR REINSTALL)</Custom>
<Custom Action="CA.ConfigHostname" After="CA.InitConfigAfterFinalize">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigListeners" After="CA.ConfigHostname">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigAccessUri" After="CA.InitConfigAfterFinalize">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigListeners" After="CA.ConfigAccessUri">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigCert" After="CA.ConfigListeners">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigPk" After="CA.ConfigCert">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.ConfigPublicKey" After="CA.ConfigCert">(NOT Installed OR REINSTALL) AND (P.CONFIGURE = "0")</Custom>
<Custom Action="CA.StartGatewayIfNeeded" After="StartServices">(NOT Uninstalling) AND (NOT P.DGW.NO_START_SERVICE)</Custom>
</InstallExecuteSequence>

Expand Down
7 changes: 6 additions & 1 deletion package/Windows/DevolutionsGateway_en-us.wxl
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,14 @@
<String Id="SummaryDlgCertificatePasswordLabel">Certificate Password: [P.CERT_PASS_MASKED]</String>
<String Id="SummaryDlgCertificateKeyLabel">Certificate Private Key: [P.CERT_PK_FILE]</String>

<String Id="WelcomeDlgTitle" Overridable="yes"><!-- _locID_text="WelcomeDlgTitle" _locComment="WelcomeDlgTitle" -->{\WixUI_Font_Bigger}Welcome to the [ProductName] 20[ProductVersion] Setup Wizard</String>
<String Id="WelcomeDlgTitle" Overridable="yes">{\WixUI_Font_Bigger}Welcome to the [ProductName] 20[ProductVersion] Setup Wizard</String>
<String Id="WelcomeEulaDlgTitle">{\MyWixUI_Font_Title}Please read the [ProductName] License Agreement</String>

<String Id="Error29989">Failed to configure the provisioner public key.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29990">Failed to configure the certificate.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29991">Failed to configure the listeners.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29992">Failed to configure the access URI.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29993">Failed to execute a configuration command.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29994">Failed to query existing service configuration</String>
<String Id="Error29995">You must provide a valid certificate file and either a password or private key file</String>
<String Id="Error29996">The specified file was invalid or not accessible</String>
Expand Down
7 changes: 6 additions & 1 deletion package/Windows/DevolutionsGateway_fr-fr.wxl
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
<String Id="SummaryDlgTitle">{\MyWixUI_Font_Title}Résumé</String>
<String Id="SummaryDlgDescription">{\MyWixUI_Font_Description}Cliquez Suivant pour utiliser cette configuration ou Précédent pour revenir en arrière.</String>
<String Id="SummaryDlgAccessUriLabel">URI d'accès: [P.ACCESSURI_SCHEME]://[P.ACCESSURI_HOST]:[P.ACCESSURI_PORT]</String>
<String Id="SummaryDlgHTTPLabel">ÉCouteur HTTP: [P.HTTPURI_SCHEME]://*:[P.HTTPURI_PORT]</String>
<String Id="SummaryDlgHTTPLabel">Écouteur HTTP: [P.HTTPURI_SCHEME]://*:[P.HTTPURI_PORT]</String>
<String Id="SummaryDlgTCPLabel">Écouteur TCP: [P.TCPURI_SCHEME]://*:[P.TCPURI_PORT]</String>
<String Id="SummaryDlgListenersLabel">Écouteurs Devolutions Gateway</String>
<String Id="SummaryDlgKeyPairLabel">Configuration de paire de clés Devolutions Gateway</String>
Expand All @@ -157,6 +157,11 @@
<String Id="WelcomeEulaDlgTitle">{\MyWixUI_Font_Title}Veuillez lire la Convention de licence de [ProductName]</String>
<String Id="WelcomeDlgTitle" Overridable="yes">{\WixUI_Font_Bigger}Bienvenue dans l'assistant d'installation de [ProductName] 20[ProductVersion]</String>

<String Id="Error29989">Failed to configure the provisioner public key.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29990">Failed to configure the certificate.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29991">Failed to configure the listeners.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29992">Failed to configure the access URI.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29993">Failed to execute a configuration command.&#xa;&#xa; Error details: [2]</String>
<String Id="Error29994">Échec de lecture de la configuration existante du service</String>
<String Id="Error29995">Vous devez fournir un fichier de certificat valide avec mot de passe ou fichier de clée privée</String>
<String Id="Error29996">Le chemin de fichier spécifié est invalide ou inaccessible</String>
Expand Down
5 changes: 5 additions & 0 deletions package/Windows/WixUI_CustomInstallDir.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ Patch dialog sequence:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI Id="WixUI_CustomInstallDir">
<Error Id="29989">!(loc.Error29989)</Error>
<Error Id="29990">!(loc.Error29990)</Error>
<Error Id="29991">!(loc.Error29991)</Error>
<Error Id="29992">!(loc.Error29992)</Error>
<Error Id="29993">!(loc.Error29993)</Error>
<Error Id="29994">!(loc.Error29994)</Error>
<Error Id="29995">!(loc.Error29995)</Error>
<Error Id="29996">!(loc.Error29996)</Error>
Expand Down

0 comments on commit 2766e5f

Please sign in to comment.