Building Windows Installer -1

Madhusudan
Cppaddiction
Published in
5 min readMay 24, 2024

This is my understanding while working in Windows Installer development using WiX . WIX has a pretty steep learning curve and actually it is not WiX, it shows how windows as an OS behaves with its users. WiX is opensource but firegaint commands monopoly in educating it.

Installing Postgres

Installing a DB as is a very common use case for a software application. Installing it as part of the Application installer(Bundling ) itself is not very straight forward . A Child installer, in this case postgres installer (exe or msi) can be invoked in GUI or silent mode from the parent installer(Application installer ) .Invoke the child installer (could be an msi or exe ) from the parent installer (needs to be an exe ) .either in silent fashion or in full fledged GUI version . Generally the application author wants to customize the child component according to the application’s need and hence it is not common to invoke the child installer in GUI mode. Now coming to silent installation option ,you will be lucky if the customization option that you are seeking are available as silent options command line arguments. But that is not always the case and then only option is left is to FileCopy

<Component Id="myid" Directory="INSTALLFOLDER" Guid="*">
<File Id="myid" KeyPath="yes" Source="SourceDir\vertag-1.0.19.0.txt" />
</Component>

the binary files and then modify the required files. Coming to this particular case of postgres one good way is to keep the binaries downloaded into a folder and the harvest it .Upon completion of InstallFiles Custom Action the required files can be modified using a hand written custom action and then service registration with SCM(service control manager) can be done using Service Install element and can be configured using Servicecontrol element.Here is the configuration snippet.

 <Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="AppDataFolder">
<Directory Id="ManufacturerFolder" Name="$(var.Name)">
<Directory Id="ProductRuntimeDataFolder" Name="$(var.ProductName)">
<Directory Id="INSTALLDIR" Name="!(wix.installSubDir)">
<Directory Id="DataFolder" Name="$(var.DataDirectoryName)">
<Component Id="DataFolderComponent" Guid="!(wix.newDataFolderComponentGuid)" Win64="yes">
<CreateFolder>
<util:PermissionEx User="[SERVICEACCOUNT]" GenericAll="yes"></util:PermissionEx>
<!-- [LoggedonUser] needs permission to call initdb.exe, if CustomAction InitdbQuietly runs with impersonation. -->
<?if $(var.ImpersonateForInitdb) != "no" ?>
<util:PermissionEx User="[LoggedonUser]" GenericAll="yes"></util:PermissionEx>
<?endif ?>
</CreateFolder>
<util:User Id="GrantLogonAsServicePrivilege" Name="[ACCOUNTTOGRANTLOGONASSERVICEPRIV]" LogonAsService="yes" CreateUser="no" UpdateIfExists="yes"/>
<ServiceControl Id="Service" Name="[SERVICENAME]" Start="install" Remove="uninstall" Stop="uninstall" Wait="yes" />
<util:ServiceConfig ServiceName="[SERVICENAME]" FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="restart" RestartServiceDelayInSeconds="60" ResetPeriodInDays="1"/>

</Component>

</Directory>
</Directory>
</Directory>
</Directory>

Here the util:User element will create the service account user upon installtion if embedded under a component. Installation also can be done by using sc (service control) like below.

<SetProperty Id="InstallThisService"
Value="&quot;sc.exe&quot; create [SERVICENAME] binPath= &quot;\&quot;[DIR_BIN]pg_ctl.exe\&quot; runservice -N [SERVICENAME] -D \&quot;[INSTALLDIR]$(var.DataDirectoryName)\&quot; -w&quot; start= auto depend= RpcSS obj= &quot;[PGSQLSERVICEACCOUNT]&quot; DisplayName= &quot;!(wix.serviceDisplayName)&quot;"
Before="InstallThisService" Sequence="execute" />

There is also another way to create the service account via custom action using C# .Now the irony is that it is not very obvious to create services before initializing the database data folder, but during data folder creation we would like the permissions to be set correctly to the service’s virtual account, which is only “created” when the service is installed. If we are creating service using a custom action then it has to be scheduled before InstallServices in the InstallExecute Sequence. If ServiceInstall element is used to create the service then it would execute as part of InstallService . Note that here pg_ctl.exe actually does the starting part of service by assigning the Data directory and service account name.

Similarly uninstallation has to be done using sc.exe delete

<SetProperty Id="Uninstall_ThisService"
Value="&quot;sc.exe&quot; delete [SERVICENAME]"
Before="Uninstall_ThisService" Sequence="execute" />

but it is not that straight forward. Simply executing above custom action often will not offer a clean uninstallation and most of the time complains of database files in use or they are left out causing subsequent installation issues. The reason could be one of the below

Some thought provoking links are

So the ideal way is to stop the postgres service before StopServices Install Sequence and Delete Services before DeleteServices Install Sequence. One apt question is why can’t the WiX provided Stop Services and Delete Services not work . These are meant to handle in ideal scenarios , where the service responds with in default wait time or there is no active connection . For ex directly issuing sc delete service marks the service for deletion and the actual stop command to DB is issued much latter by SCM, the reason is beyond my understanding .Hence here a gracefull stop followed by delete service should be done. Luckily pg_ctl.exe also has a stop argument which accepts three modes of shutting down postgress ( smart,fast,immediate) . The default is fast . When there is no such option provided by the vendor , the only way left is to make the hands dirty and play with the service control table.

While doing this we have to explicitly take care of the lingering processes and wait till they release resources or apply some brute force way. There is not straight answer to it. But all sane vendors provide a way for graceful shutdown and while installing and uninstalling via the binaries (the case we are discussing here ) it is essential to use the correct tool in correct order. A graceful shutdown logs of postgres would look like this

, , 2024-05-24 18:07:52.072 ISTLOG:  received fast shutdown request
, , 2024-05-24 18:07:52.076 ISTLOG: aborting any active transactions
user1, user1, 2024-05-24 18:07:52.076 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.076 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.076 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.077 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.077 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.078 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.078 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.080 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.080 ISTFATAL: terminating connection due to administrator command
user1, digital_asset, 2024-05-24 18:07:52.080 ISTFATAL: terminating connection due to administrator command
user1, user1, 2024-05-24 18:07:52.080 ISTFATAL: terminating connection due to administrator command
, , 2024-05-24 18:07:52.253 ISTLOG: background worker "logical replication launcher" (PID 50744) exited with exit code 1
, , 2024-05-24 18:07:52.279 ISTLOG: shutting down
, , 2024-05-24 18:07:52.492 ISTLOG: database system is shut down

Bottom line is Shutdown is much more important than Startup and always look for a way for graceful shutdown of services. Using sc.exe delete directly to stop a service is rarely the correct way.

--

--