How to deploy MSI that installs to localappdata, via GPO, system session? - wix

I need my app to be installed to local app data of the active console session, or even to all the machine's users.
But when deploying via GPO, a system session is the one running the MSI.
So how can I create an MSI via WiX Toolset that achieves that purpose?
I tried using this guide: https://learn.microsoft.com/en-us/windows/win32/msi/msiinstallperuser
So I set these properties in the wxs file:
<Property Id='ALLUSERS' Value='2' />
<Property Id='MSIINSTALLPERUSER' Value='1' />
And the dir structure is:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="LocalAppDataFolder">
<Directory Id="APPLICATIONFOLDER" Name="MyApp">
Then, I ran the msi via system session using psexec, but as the log says:
PROPERTY CHANGE: Adding APPLICATIONFOLDER property. Its value is 'C:\WINDOWS\SysWOW64\config\systemprofile\AppData\Local\MyApp\'.
And it tries to install to the default local app data
I've tried several other methods, like changing the APPLICATIONFOLDER via a c# custom action, or by modifying the database via c# custom action, but I still didn't find a working solution.
Any help will be greatly appreciated

What you did to the MSI was correct. But, you used psexec to "install" (instead of GPO) and as you stated, "via system session", so you got the system account's local app data directory.
You need to assign (or maybe publish, but you were describing assign) your MSI to your users in GPO. Then the MSI will be installed as you desire. If you assign to the computer in GPO, the MSI should be setup to be "per-machine", in which case ProgramFiles would be the better directory to use (and ALLUSERS should be "1" in that case).
Use Group Policy to remotely install software

Related

Wix Toolset - File for admin not appearing when installer is an admin

I am trying to use a component condition for an admin manual pdf. If the installer is in the administrators group I want the admin manual to be installed. Here's how I'm setting this up but it's not being installed even if installer is an administrator. What am I missing?
Requirement:
InstallScope="perUser" />
<Condition>Privileged</Condition>
Find below:
<Component Id="cmp_ManualForAdmins.pdf" Guid="4C28B047-74D2-4642-A180-0039B4C2C5BC">
<File Id="fil_ManualForAdmins.pdf" Name="ManualForAdmins.pdf" Source="$(var.WindowsFormsApp1_TargetDir)ManualForAdmins.pdf">
<Shortcut Id="startMenuAdminManual" Directory="ProgramMenuSubFolder" Name="AdminManual"></Shortcut>
</File>
<Condition>Privileged</Condition>
</Component>
I just spent an hour investigating this and there really aren't any good answers. Because the MSI is invoked from a standard user process and doesn't require elevation, MSI never knows the user can elevate so the Privileged property isn't set.
I figured a custom action might help to work around this but searching up C# Detect Admin revealed various classes and API calls that all had the same behavior.
If I install a protoype MSI from an elevated command prompt the condition evaluates to true and the 'admin.txt' file is installed. From a non elevated it is not installed.
So what would I do? One of two things:
1) Make a second Docs MSI that is a permachine install that requires elevation
or
2) Build and deploy a docs.exe which is manifested to require admin. If the program successfully elevates then have it extract the PDF from an embedded resource to the temp directory and do a ShellExecute to launch the default PDF viewer with that file.
Administrative Installation: Admins typically perform an administrative installation (file extraction) of a setup - at least if they work in big companies that do application packaging. Hence I tend to try to make files like that easily visible on the extracted source media - instead of installing them during normal installation (or both or either, doesn't matter which).
Sample administrative installation (glorified file extraction):
msiexec /a Test.msi TARGETDIR=D:\ExtractedFiles\
More about Administrative Installations.
Admin.pdf: Here is a quick hack that I haven't tested extensively. The admin.pdf will show up during file extraction and not during installation:
<..>
<Feature Id="MainApplication" Title="MainApplication" Level="1">
<Feature Id="SomeFiles" Title="SomeFiles" Level="1" />
<!-- Remove "Display" attibute to show Admin feature in normal setup GUI -->
<Feature Id="Admin" Title="Admin" Level="1001" Display="hidden" />
</Feature>
<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="AdminManual" Feature="Admin" Guid="{00000000-0000-0000-0000-0000DBFB0000}">
<File Source="D:\Admin.pdf" />
</Component>
<..>
Running administrative installation will extract the admin.pdf to the top level extraction directory:
msiexec /a Test.msi TARGETDIR=D:\ExtractedFiles\
The Admin feature is hidden from the normal installation GUI. Change the attribute "Display" to change this. Just remove it for example - for testing purposes.

How to change directory in wxs file where all dll and other files will be installed?

I am quite new in creating setup project using .wxs file in .NET project. I knew that by default when you run MSI file it creates folder with this project and it's reference files (dll, exe, etc...) in C:\Program Files (x86). My question is, can I change this location in my .wxs file to another using XML.
ConfigurableDirectory: You can use the ConfigurableDirectory attribute of the Feature Element to set a configurable feature directory. See down the page in the screenshot section here: How to assign path value to Directory in WIX?.
Mock-up only:
Note: I am basing myself on the standard WiX Mondo dialog set. To hook up the Mondo dialogs, see this answer. Essentially, add reference to WixUIExtension.dll and insert the <UIRef Id="WixUI_Mondo" /> element. This will compile a default WiX dialog set into your MSI.
<Feature Id="MyFeature" Title="MyFeature" Level="1" ConfigurableDirectory="INSTALLFOLDER"></Feature>
<..>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="InstallDirConfigurableTesting">
Remember Property: Note that you must persist the custom directory location yourself to the registry and read back for major upgrades or else your whole product gets "moved" during upgrades (I know, it is weird). This persisting does not happen auto-magically in any way that I know about. You can persist the property and read it back using the "Remember Pattern" as described by WiX creator and benevolency Rob Mensching here: The WiX toolset's "Remember Property" pattern.
Implementation Tip: Testing the read-back of the directory property for major upgrade scenarios can be a bit fiddly. If you create a test project in Visual Studio using Votive you can use the trick to just compile version 1 of your MSI (now suffix your MSI file in the build output folder with _Version1.0.0.msi or similar), and then kick up one of the first three digits of the version number property and build an upgrade version (suffix with _Version2.0.0.msi or similar). Then you install in sequence selecting a custom installation directory and check whether your second setup correctly detects the modified path. Just use a mockup or test harness project with a single component in it to get this working, or else you could drive yourself mad if you have to compile your whole setup. Then just inject your finished markup into the main project. Obvious, yes - just mentioning.
Some Further Links:
How to use ProgramFiles64Folder in ConfigurableDirectory
What is the meaning of WiX configurable directory?

How can I keep WIX from resetting my users settings on updates?

I have a WIX installer set up for my application and it installs correctly and updates correctly except that it re-writes the default user settings (i.e. defined in properties-> settings) that are defined in my application and corresponding dll's. How can I have WIX update the application, but not update the user settings?
Whether it's ini, registry or xml the concept is simple. Only have the installer responsible for installing defaults settings. Then on first run on your application copy the default settings to the user settings one time. Now the installer will never harm user settings because it doesn't even know of their existence.
Properties -> Settings are mapped to the app.config file for your application, so you can setup the install for that particular file to NeverOverwrite, meaning updates will not overwrite your .config file.
Working example:
<Component Id="SPECTRAVIEW.WPF.MAINAPPLICATION.EXE.CONFIG" Win64="$(var.Win64)" Guid="89E2C6C0-18FB-428B-A9EE-C2FAB3418CB2" NeverOverwrite="yes">
<File Id="SPECTRAVIEW.WPF.MAINAPPLICATION.EXE.CONFIG" Name="SpectraView.WPF.MainApplication.exe.config" Source="$(var.MainApplication.TargetDir)\SpectraView.WPF.MainApplication.exe.config" KeyPath="yes" />
</Component>

Change install location in code behind with custom UI code

I am merging our installer projects from VS 2008 to VS 2012 and subsequently from the basic Windows Installers to Wix installers. I've created a standard Wix installer project which outputs a MSI that does all I want.
Now I've created a custom UI in WPF/XAML (Win 8 style) and a bootstrapper project that installs our projects using our own UI. The basics work just fine, i.e. installing and uninstalling.
I want to let the user change the install directory and the install scope (per user or per machine) through an 'advanced' panel. I can capture the settings in code behind, but I am unable to pass these parameters to my installer.
My directory tree is defined as follows:
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='INSTALLDIR' Name='$(var.ProductName) $(var.CustomerAcronym) v$(var.Version)'>
<Component Id='MainService' Guid="5E68A5A7-E9B3-4156-B84C-E8D7AA3DCBCC">
Any tips on how to handle this, so how to pass the user-defined path and scope to the Wix installer?
In your UI, set the install path in a bootstrapper string variable, something like:
BootstrapperApplication.Engine.StringVariables["INSTALLPATH"] = "C:\somePath\someDir";
Then in your bundle.wxs, pass the string variable in as a property, something like:
<MsiPackage SourceFile="MyPackage.msi" Id="MyPackage">
<MsiProperty Name="INSTALLDIR" Value="[INSTALLPATH]" />
</MsiPackage>
UPDATE
If directory path contains symbols [ and ], Engine resolves this part of the path as variable:
BootstrapperApplication.Engine.StringVariables["INSTALLPATH"] = "C:\[somePath]\someDir";
In this case, variable [somePath] doesn't exist in Wix Bundle, so INSTALLPATH will be "C:\someDir"
If you assing directory path from UI, it's better to Escape variable:
string directoryPathFromUser = "C:\[somePath]\someDir";
BootstrapperApplication.Engine.StringVariables["INSTALLPATH"] =
BootstrapperApplication.Engine.EscapeString(directoryPathFromUser);

Can a .msi file install itself (presumably via a Custom Action)?

I wand to construct an MSI which, in its installation process, will deploy itself along with its contained Files/Components, to the TargetDir.
So MyApp.msi contains MyApp.exe and MyAppBootstrapperEmpty.exe (with no resources) in its File Table.
The user launches a MyAppBootstrapperPackaged.exe (containing MyApp.msi as a resource, obtained from the internet somewhere, or email or otherwise). MyAppBootStrapperPackaged.exe extracts MyApp.msi to a temp folder and executes it via msiexec.exe.
After the msiexec.exe process completes, I want MyApp.msi, MyBootstrapperEmpty.exe (AND MyApp.exe in %ProgramFiles%\MyApp folder so MyApp.exe can be assured access to MyApp.msi when it runs (for creating the below-mentioned packaged content).
MyAppBootstrapper*.exe could try and copy MyApp.msi to %ProgramFiles%\MyApp folder, but would need elevation to do so, and would not allow for its removal via Windows Installer uninstall process (from Add/Remove Programs or otherwise), which should be preserved.
Obviously (I think it's obvious - am I wrong?) I can't include the MSI as a file in my Media/CAB (chicken and egg scenario), so I believe it would have to be done via a Custom Action before the install process, adding the original MSI to the MSI DB's Media/CAB and the appropriate entry in the File table on the fly. Can this be done and if so how?
Think of a content distribution model where content files are only ever to be distributed together with the App. Content is produced by the end user via the App at run time, and packaged into a distributable EXE which includes both the App and the content.
MyApp's installer must remain an MSI, but may be executed by a Bootstrapper EXE. The installed MyApp.exe must have access to both MyApp.msi and EXE is to be "assembled" at runtime by the App from a base (empty) MyAppBootstrapper.exe, which is also installed by the MSI, and the content created by the end-user. The EXE's resource MSI must be the same as that used to install the App which is doing the runtime packaging.
WIX is not to be installed with MyApp.
There can be no network dependencies at run-/packaging- time (i.e. can't do the packaging via a Webservice - must be done locally).
I am familiar with (and using) Custom Actions (managed and unmanaged, via DTF and otherwise).
Add an uncompressed medium to your wxs like this:
<Media Id='2'/>
And then create a component with a File element like this:
<File Source='/path/to/myinstaller.msi' Compressed='no' DiskId='2' />
This will make the installer look for a file called "myinstaller.msi" on the installation medium, in the same folder as the msi that is being installed. The source path above should point to a dummy file, it is only there to appease wix.
Edit: The following sample test.wxs demonstrates that it works. It produces a test.msi file which installs itself to c:\program files\test. Note that you need to put a dummy test.msi file in the same folder as text.wxs to appease wix.
<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Name='ProductName'
Id='*'
Language='1033'
Version='0.0.1'
Manufacturer='ManufacturerName' >
<Package
Keywords='Installer'
Description='Installer which installs itself'
Manufacturer='ManufactererName'
InstallerVersion='100'
Languages='1033'
Compressed='yes'
SummaryCodepage='1252'/>
<Media Id='1' Cabinet='test.cab' EmbedCab='yes'/>
<Media Id='2' />
<Directory Id='TARGETDIR' Name="SourceDir">
<Directory Id='ProgramFilesFolder'>
<Directory Id='TestFolder' Name='Test' >
<Component Id="InstallMyself">
<File Source="./test.msi" Compressed="no" DiskId="2" />
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Complete'
Display='expand'
Level='1'
Title='Copy msi file to program files folder'
Description='Test'>
<ComponentRef Id="InstallMyself" />
</Feature>
</Product>
</Wix>
Having one .MSI package launch another .MSI package from "within" itself is called a nested install, and it's bad juju (see Rule 20). Windows Installer has some global data that it uses to manage the current install, and it doesn't handle well multiple installs at the same time. For the same reason, if you start one install and then try to start another while the first is still in progress, you'll usually see a pop-up to the effect of "another install in progress, please wait until it's done".
You can have a program, usually called a bootstrapper (I think that's what you're referring to) which is itself not an install package, but which contains an install package (such as an .MSI or an .EXE) as a resource, possibly compressed. The action of the bootstrapper program is to extract/expand the resource to a file, commonly in a %TEMP% directory, then either launch the extracted .EXE or run MSIEXEC on the extracted .MSI. The bootstrapper can contain multiple resources and extract+install them one by one, if you need to install prerequisites before the main package. Or you can ship multiple packages as separate files, and have the bootstrapper execute/install them directly from the distribution media one by one, or copy them down to the target machine and run the series of install from there, or...
WiX itself does not get installed, no. It's a tool with which .MSI packages can be built. The WiX project has on its wishlist a generic bootstrapper program, but it hasn't been implemented yet. There are other bootstrappers available, e.g. this one.
You won't need a custom action -- in fact, since the bootstrapper isn't itself a Windows Installer installation package, "custom action" has no meaning to it. And, if you're familiar enough with CAs to know about managed/unmanaged/DTF, then you know enough to avoid custom actions whenever you can. (grin)
I think it's much easier for your bootstrapper to extract MSI file to some predefined location rather than to the temp folder. For example, to C:\Documents and Settings\All Users\Application Data\My Company\My Product Install Cache. After installation finishes bootstrapper would leave MSI file sitting there. If at some stage user decides to reinstall your product Windows Installer will be able to locate source MSI file.
Also, add path to this file to RemoveFile table so that it gets deleted on uninstall. You can use RemoveFile element in WiX for that.
So if I understand, then I think I would have the app create a transform (MST) that has the content files and apply that to the base MSI. I'm still not convinced that I understand though. :)
I'd configure the MSI cache path to a known location.
Then at runtime if you need to "edit" the MSI use VBScript or similar.
But still, I ask WHY!?!
I am also working on a way to deploy multiple MSI files. I have a bootstrapper.exe program that bundles the MSI files and runs them one at a time. This solves my problem for most cases.
The case it does not solve is GPO (Global Policy Object) distribution of the install. GPO requires a dot-msi file to run an install.
To do this here's what I did which almost solved the problem (but not quite). I put the dot-msi files in the file table of an installer and put my bootstrapper in the binary table and run it from a custom action inserted after InstallFinalize in the InstallExecuteSequence. Of course the bootstrapper won't be able to run other MSI's because the top level MSI holds the _MSIExecute mutex.
It was pretty easy to get a little further. I made the bootstrapper return control to the top level installer and continute. And then I added a WaitForSingleObject call to wait for the top level install to finish, and the bootstrapper can then continue to finish the install.
My problem is that the GPO installation happens at boot time and the top level install completes before the sub installers are done and GPO reboots the machine.
The toplevel install also returns a success status when the install may actually fail later on.
I'm still looking for a way to block the top level install from completing until after the bootstrapper completes.