I am using WIX to create MSI installers for C# services. The MSI does 3 jobs :
a) Copy solution file from bin to a particular location.
b) Create a folder where the service writes it's logs.
c) install the service on the machine if it previously does not exist.
The want these to execute in the similar order. But, when the condition to check if the service is installed fails the previous step does not seem to be executing, i.e, copying and creating steps fail too.
Here is the snippet of the code.
<Directory Id="TARGETDIR" Name="SourceDir">
<!--Creating folder hierarchy for storing project solution files; reference defined in fragments-->
<Directory Id="ProgramFilesFolder" Name="PFiles"/>
<!--Creating folder hierarchy for storing logs; reference defined in fragments-->
<Directory Id="LOGS" Name="Logs"/>
</Directory>
<InstallExecuteSequence>
<LaunchConditions After='AppSearch' />
<Custom Action='CMDInstallService' Before='InstallFinalize'></Custom>
</InstallExecuteSequence>
<Property Id="MYSERVICE">
<RegistrySearch Id="SERVICE_CHECK" Root="HKLM" Name="Install" Type="raw"
Key="SYSTEM\CurrentControlSet\services\Service"/>
</Property>
<Condition Message="Service is already installed on your system">
<![CDATA[Installed OR MYSERVICE]]>
</Condition>
<CustomAction
Id='CMDInstallService' Directory='PROJECT_INSTALL' Execute='deferred' Impersonate='no'
ExeCommand='[SystemFolder]cmd.exe /K "C:\Windows\Microsoft.NET\Framework\v4.0.30319\installutil.exe Service.exe"'
Return ='check'/>
This will not work because the files and folder are not committed before InstallFinalize. To install a service, you should use the following command:
<Component Id="Component_WinService" Directory="Directory_WindowsService" Guid="*">
<File Id="File_WindowsService" KeyPath="yes"
Source="WindowsService.exe" />
<ServiceInstall Id="ServiceInstall_WindowsService"
Type="ownProcess"
Vital="yes"
Name="My Windows service"
Description="Windows service."
Start="auto"
Account="LocalSystem"
ErrorControl="ignore"
Interactive="no"
/>
<ServiceControl Id="ServiceControl_WindowsService"
Start="install"
Stop="both"
Remove="uninstall"
Name="My Windows Service"
Wait="no"
/>
</Component>
Related
I use wix toolset to install an ASP.NET Core application as a windows service. When doing a major upgrade, the installer can take very long (5 mins) while it usually is a couple of seconds. This is because the InstallValidate action gives errors because files are in use by the service, I noticed that if I stop the service before starting the installer, it always runs smoothly. I use the following code to install and start/stop the service: (this is generated using heat)
<Component Id="ApiEndpoint.exe" Guid="*">
<File Id="ApiEndpoint.exe" KeyPath="yes" Source="$(var.publishDir)\ApiEndpoint.exe" />
<wix:ServiceInstall Id="ApiEndpointInstall" DisplayName="ApiEndpoint" Name="ApiEndpoint" ErrorControl="normal" Start="auto" Type="ownProcess" Vital="yes" xmlns:wix="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<util:ServiceConfig RestartServiceDelayInSeconds="60" ResetPeriodInDay="1" FirstFailureActionType="restart" SecondFailureActionType="restart" ThirdFailureActionType="restart" />
</wix:ServiceInstall>
<wix:ServiceControl Id="ApiEndpointControl" Name="ApiEndpoint" Start="install" Stop="both" Remove="uninstall" xmlns:wix="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" />
</Component>
Shouldn't the files in use by the ApiEndpoint be excluded because the service will be stopped anyway? Is there a way I can stop the service before the InstallValidate, I tried this with a custom action, but this wasn't possible because no admin privileges are present at that point.
I also tried to ignore the exe file from heat and make a component for it myself, like below, this results in the same issue.
<Fragment xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<ComponentGroup Id="ExeComponents" Directory="INSTALLFOLDER">
<Component Win64="yes" Id="ExeComponent" Guid="*">
<File Id="ExecFile" Source="$(exeSource)" KeyPath="yes" />
<ServiceInstall Id="ApiEndpointInstall" DisplayName="ApiEndpoint" Name="ApiEndpoint" ErrorControl="normal" Start="auto" Type="ownProcess" Vital="yes">
<util:ServiceConfig RestartServiceDelayInSeconds='60' ResetPeriodInDays='1' FirstFailureActionType='restart' SecondFailureActionType='restart' ThirdFailureActionType='restart' />
<util:PermissionEx
User="Everyone"
GenericAll="yes"
ServiceChangeConfig="yes"
ServiceEnumerateDependents="yes"
ChangePermission="yes"
ServiceInterrogate="yes"
ServicePauseContinue="yes"
ServiceQueryConfig="yes"
ServiceQueryStatus="yes"
ServiceStart="yes"
ServiceStop="yes" />
</ServiceInstall>
<ServiceControl Id="ApiEndpointControl" Name="ApiEndpoint" Start="install" Stop="both" Remove="uninstall" />
</Component>
</ComponentGroup>
</Fragment>
I'm very new at wix so I'm not sure if I'm missing something obvious or if this is just a limit of Wix.
I can see that the service stops eventually when the files are being copied, but it just seems like it is stopped either too late, or the installer should ignore the files in use by the service that will be stopped anyway.
I have the below Wix XML code that I have written for Single Package Authoring. The problem is when "Install for all unders of this machine" mode is selected in the UI(WixUI_Advanced), the default location that shows up in the UI is "C:\Users\XXXX\AppData\Local\Programs\MyApp\ ". How can I change this, so that the default location is ..\Program Files (x86)\MyApp....
If I change <Property Id="MSIINSTALLPERUSER" Value=" "/>,
then the default location is ..\Program Files(x86).., but the per user does not work due to lack of admin privileges.
Much appreciated.
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<!--Product Information-->
<Product Id="*"
Name="$(var.ApplicationName)"
Language="1033" Version="!(bind.FileVersion.$(var.ExecutableName))" Manufacturer="$(var.ManufacturerName)"
UpgradeCode="33bc2348-****-****-****-ebcde0d14afe">
<!--MSI Package Information-->
<Package InstallerVersion="500"
Compressed="yes" />
<!--Single Package Authoring-->
<Property Id="MSIINSTALLPERUSER" Value="1"/>
<Property Id="ALLUSERS" Value="2"/>
<!--Upgrade Information-->
<MajorUpgrade DowngradeErrorMessage="A newer version of $(var.ApplicationName) is already installed." />
<MediaTemplate EmbedCab="yes" />
<!--Application Features-->
<Feature Id="CoreFeature" Title="$(var.ApplicationName)" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<ComponentRef Id="ApplicationShortcut" />
<ComponentRef Id="RegisterApplicationAutoStart" />
</Feature>
<!--Required .NET Framework for the Application-->
<PropertyRef Id="NETFRAMEWORK35" />
<Condition Message="This application requires Microsoft .NET Framework 3.5 or greater. Please install the .NET Framework then run this installer again.">
<![CDATA[Installed OR NETFRAMEWORK35]]>
</Condition>
<!--Advanced UI-->
<Property Id="ApplicationFolderName" Value="$(var.ApplicationName)" />
<Property Id="WixAppFolder" Value="WixPerMachineFolder" />
<UIRef Id="WixUI_Advanced"/>
<InstallExecuteSequence>
<Custom Action="LaunchApplication" After="InstallFinalize">NOT Installed</Custom>
</InstallExecuteSequence>
</Product>
<Fragment>
<!-- Define the Target Directory. The individual files will be filled in via a Heat generated fragment. -->
<Directory Id="TARGETDIR" Name="SourceDir">
<!--Define the directory when the application will be installed-->
<Directory Id="ProgramFilesFolder">
<Directory Id="APPLICATIONFOLDER" Name="$(var.ApplicationName)" />
</Directory>
</Fragment>
<Fragment>
<Component Id="RegisterApplicationAutoStart" Directory="ApplicationProgramsFolder" Guid="*">
<RegistryValue Root="HKMU"
Key="Software\Microsoft\Windows\CurrentVersion\Run"
Name="$(var.ApplicationName)"
Type="string"
Value="[APPLICATIONFOLDER]$(var.ExecutableName)"
KeyPath="yes" />
</Component>
</Fragment>
</Wix>
The OP described it perfectly. With single authoring enabled in the following way, there seems to be no way to obtain to "actual" ProgramFilesFolder at C:\Program Files (x86)\.
<Package InstallerVersion="200" ... /> <!-- do not specify InstallScope or InstallPrivileges! -->
<Property Id="ALLUSERS" Value="2" />
<Property Id="MSIINSTALLPERUSER" Value="1" />
Even if MSIINSTALLPERUSER is reset, this does not change the value of ProgramFilesFolder, it's still C:\Users\XXX\AppData\Local\Programs (I assume the folder is initialised through SHGetFolderPath at installer startup, and doesn't change thereafter).
Setting the ProgramFilesFolderexplicitly like the OP showed in his answer would work I guess, but it's an ugly hack. What worked for me in the end was to start out in 'perMachine'-Mode:
<Property Id="ALLUSERS" Value="2" />
<Property Id="MSIINSTALLPERUSER" />
Afterwards, if the installer chooses the 'perUser'-Mode, I set the variables accordingly:
<Publish Dialog="MyWelcomeDlg" Control="Next" Property="MSIINSTALLPERUSER" Value="1">1</Publish>
<Publish Dialog="MyWelcomeDlg" Control="Next" Property="ALLUSERS" Value="{}">1</Publish>
This way, the folders will be correctly set.
I consider the underlying problem to be that ProgramFilesFolder would ever get set to something in AppData, that makes little to no sense.
If you want to install to the Program Files folder then you are required to be administrator. Limited users cannot create or update files in that folder, and running an MSI install does not break security just because it's an install. So the answer is that you cannot install to ProgramFiles unless you cause the MSI to ask for elevated privileges. Your question is essentially "how can a limited user break security by adding or replacing files in the Program Files folder", and there's no way to answer that.
Does your app require elevated privileges to run? Does it have an elevation manifest? If the answer is yes, then I suspect you're stuck with the requirement that elevated privileges are also needed to install it.
I included the below line in the WiX installer that fixed my issue.
<SetDirectory Id="ProgramFilesFolder" Value="C:\Program Files (x86)\"></SetDirectory>
This changed the value of ProgramFilesFolder that was being set to C:\Users\XXX\AppData\Local..... to C:\Program Files (x86) for per machine installation.
Also the above line of code does not make any difference to per user installation and installation happens at that user folder only (as desired.)
Please follow the link below
http://wixtoolset.org/documentation/manual/v3/wixui/dialog_reference/wixui_advanced.html
For a per-machine installation, the default installation location will be
[ProgramFilesFolder][ApplicationFolderName]
and the user will be able to change it in the setup UI.
For a per-user installation, the default installation location will be
[LocalAppDataFolder]Apps[ApplicationFolderName]
and the user will not be able to change it in the setup UI.
you can set the per user to 0 to enforce installation per machine - or in Program files
<WixVariable Id="WixUISupportPerUser" Value="0" />
For per-machine installation you will need admin privileges, if the user will not have admin privileges, he only has access to his local app data folder, hence that location cannot be changed.
I am trying to create a WIX installer that will install a Windows Service. For the Windows service, I create a service exactly how it's outlined http://tech.pro/tutorial/895/creating-a-simple-windows-service-in-csharp.
In my wxs installer file, I have the following markup specified -
<Component Id="MyCompanyWindowsServiceComponent" Guid="*">
<File Id="MyCompanyWindowsServiceFile" Name="SimpleWindowsService.exe" DiskId="1"
Source="..\SimpleWindowsService\bin\debug\SimpleWindowsService.exe"/>
<ServiceInstall Id="MyCompanyServiceInstall" Type="ownProcess" Vital="yes"
Name="MyCompany:MyProduct"
DisplayName="MyCompany:MyProduct"
Description="MyCompany Windows Service"
Start="auto"
Account="LocalSystem"
ErrorControl="critical"
Interactive="yes"/>
<ServiceControl Id="StartService"
Start="install"
Stop="both"
Remove="uninstall"
Name="MyCompany:MyProduct"
Wait="no"/>
</Component>
and I have the component referenced like -
<Feature Id="Complete" Level="1">
::
<ComponentRef Id="MyCompanyWindowsServiceComponent"/>
</Feature>
When I finally run my installer, I see the file has been copied to the right location but the service itself hasn't been started.
What am I missing?
Regards
When I run my installer I get the following issue.
I'm doing some custom actions which require to access the registry and I can only think that its because the WiX configuration doesn't make it request admin priveleges. I've looked at some posts on SO and tried to use.
InstallPriveleges="elevated"
within the package element however this does not make the installer have the admin shield nor request it therefore still producing the error.
Extra information about test project.
The name of my application is :WindowsFormsApplication33, the name of the custom action project is CustomAction1 and name of the Setup project is SetupProject1.
This is my current wix xml file.
<Package InstallerVersion="200" Compressed="yes" InstallPrivileges="elevated" InstallScope="perUser" />
<Binary Id="CustomAction1.CA.dll" SourceFile ="..\CustomAction1\bin\$(var.Configuration)\CustomAction1.CA.dll" />
<CustomAction Id="disableTaskManager"
Return="check"
Execute="immediate"
BinaryKey="CustomAction1.CA.dll"
DllEntry="disableTaskManager" />
<CustomAction Id="enableTaskManager"
Return="check"
Execute="immediate"
BinaryKey="CustomAction1.CA.dll"
DllEntry="enableTaskManager" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="SetupProject1" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<InstallExecuteSequence>
<Custom Action="disableTaskManager" Before="InstallFinalize" />
<Custom Action="enableTaskManager" After="InstallInitialize"><![CDATA[(NOT UPGRADINGPRODUCTCODE)]]></Custom>
</InstallExecuteSequence>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Form Test Application" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Guid="{EDA315F6-A115-4348-8607-981C252EA317}">
<File Source="$(var.WindowsFormsApplication33.TargetPath)" KeyPath ="yes" />
</Component>
<Component Guid="{E3182F61-F563-4C13-82B5-8CC39D9DB380}">
<File Source="$(var.CustomAction1.TargetPath)" KeyPath ="yes" />
</Component>
<Component Guid="{E4AF325E-B244-47F5-855A-5B40DBC425D2}">
<File Source="..\WindowsFormsApplication33\bin\Release\WindowsFormsApplication33.exe.config" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
Update : changing the InstallScope value from perUser to "perMachine" does make a UAC prompt however the DLL error still exists..
Your custom action is immediate, that means it will not run with elevation. It must be deferred to run with elevation. It's got nothing to do with WiX particularly, it's just that immediate custom actions run as the user but limited.
I struggled to get rid of the dll error however an alternative I found was to NOT use Custom Action and use the XML in the wix file to create the registry and then delete the key when uninstalling via the use of :
ForceDeleteOnUninstall="yes"
You have to use this in the
Example :
<!-- Register windows autostart registry -->
<Component Id="RegistryEntries" Guid="45C7AC46-1101-4301-83E1-D24392283A60">
<RegistryValue Type="string"
Name="FooStartup"
Value="[#FooMainApp]"
Root="HKLM"
Key="Software\Microsoft\Windows\CurrentVersion\Run"
Action="write"/>
</Component>
As found on : Registry change upon installing application C#
I really hope this helps someone new to WiX as it did to me.
Use these three attributes inside custom action tag.
<CustomAction ....
Execute="deferred"
Impersonate="no"
Return="ignore" />
These fields will make the custom action to run with admin priveleges.
I've created a merge module following the instructions in the Getting Started Wix guide located at: http://wix.sourceforge.net/manual-wix2/authoring_merge_modules.htm.
Here is the merge module wxs:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Module Id="SomeRepositoryMergeModule" Language="1033" Version="1.0.0.0">
<Package Id="f11e7321-a687-4d53-8be7-21a8ae0721a6" Manufacturer="SomeCompany Technologies" InstallerVersion="200" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="MODULEINSTALLLOCATION" Name="Some Repository">
<Component Id="ServicesHostWindowsService" Guid="257D1FAE-4AFF-4155-BDB8-D81F50E5862B">
<File Id="ServicesHostInstallerExecutable" KeyPath="yes" DiskId="1" Name="WindowsServiceHost.exe" Source="..\WindowsServiceHost\bin\Output_For_Installer\WindowsServiceHost.exe" />
<File Id="ServicesHostConfig" KeyPath="no" DiskId="1" Name="WindowsServiceHost.exe.config" Source="..\WindowsServiceHost\bin\Output_For_Installer\WindowsServiceHost.exe.config" />
<File Id="SomeCompanyCommon" KeyPath="no" DiskId="1" Name="SomeCompany.Common.dll" Source="..\WindowsServiceHost\bin\Output_For_Installer\SomeCompany.Common.dll" />
<File Id="SomeRepositorySqlScript" KeyPath="no" DiskId="1" Name="SomeRepository.sql" Source="..\..\..\..\..\DB\SomeRepository\SomeRepository.sql" />
<File Id="LogConfigXml" KeyPath="no" DiskId="1" Name="log.config.xml" Source="..\WindowsService\log.config.xml" />
<ServiceInstall Id="ServicesHostInstallElement" ErrorControl="normal" Start="auto" Type="ownProcess" Vital="yes"
Name="AServer WindowsService Host"
Description="The windows service responsible for hosting SomeCompany Some Repository's WindowsService."
/>
<ServiceControl Id="ServicesHostController" Name="AServer WindowsService Host" Remove="uninstall" Start="install" Stop="uninstall" Wait="no" />
</Component>
</Directory>
</Directory>
</Directory>
<ComponentGroupRef Id="Product.Generated" /><!-- Harvested by heat -->
</Module>
</Wix>
And here is the main product wxs:
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="SomeCompanyGlobalDir" Name="SomeCompany Technologies">
<Directory Id="INSTALLLOCATION" Name="Some Repository">
<Merge Id='SomeRepositoryPrimaryModule' Language='1033' SourceFile='..\SomeRepositoryMergeModule\bin\Output_For_Installer\SomeRepositoryMergeModule.msm' DiskId='1' />
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id="ProductFeature" Title="SomeRepositoryStandaloneInstaller" Level="1">
<!-- Note: The following ComponentGroupRef is required to pull in generated authoring from project references. -->
<MergeRef Id="SomeRepositoryPrimaryModule"/>
</Feature>
<UIRef Id="WixUI_InstallDir" />
<UIRef Id="WixUI_ErrorProgressText" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
When I build the installer and run it, the files from the merge module are not going into the directory selected by the user from within the installer UI. No matter what, they go into the "[Program Files]\SomeCompany Technologies\Some Repository\" directory.
If I remove the reference to Program Files from the merge module directory path and use a root dir with a name of "." to pick up the parent directory of the parent MSI then the merge module picks up the user selected directory just fine. But then Visual Studio throws an error on build that harvesting won't work because the path must be rooted in one of the standard directories in order to use automatically generated Guids.
So how can I get the merge module to take the directory selected by the user at install time while still keeping the merge module path rooted in a standard directory?
You are confusing MSI because ProgramFilesFolder is a reserved property name. Change that value to "MergeRedirectFolder" and by virtue of the Merge element under the Directory element with Id of INSTALLLOCATION MergeRedirectFolder will become associated with INSTALLLOCATION. The directory Some Repository ([MODULEINSTALLLOCATION]) will then be a subdirectory of INSTALLLOCATION.
Also feel free to checkout the ISWIX project on CodePlex. It's useful for authoring and maintaining merge modules and has sample source code relevant to what you are trying to do here.