I have adapted a program to stop needing administrator rights or writing data files into its installation programfiles directory. These files are not created by the program but modified: they exist in a fresh installation, even if they were empty (not really the case anyway).
Now I need to adapt the installer, for the additional requirement that, if there are files from a previous version, I must preserve them somehow, so the program uses them instead of the default ones. The previous MSI (not made with WiX) leaves these files behind after un-installation.
My solution for the program itself has been to install the default data files directly into INSTALLFOLDER/programfiles as before, but check within the program at every run if the files exist in appdata, otherwise copy the fresh ones from programfiles, then start using the new path. I do this because I still want this to be per-user data, but I can't rely on an installation to store files in every current and future user profile; and I think that would not be something an MSI should do. (BTW am I wrong here?)
Now for installation the effect I thought would be best is to keep installing the fresh data files into programfiles, but have the MSI move existing files, if any (if there are no data files in programfiles installation should just go on), into appdata (so they will be found and used by the program, for the user who installed). The icing on the cake would be resolving the conflict in case files are found both in programfiles and appdata (I should assume the latter would be more recent).
This is my first try:
<Directory Id="AppDataFolder">
<Directory Id="appdataDirAuthor" Name="authorName">
<Directory Id="appdataDir" Name="productName">
<Component Id="dbPreserve" Permanent="yes" Guid="XXXXXX">
<CopyFile Id="dbPreserveFoo" Delete="yes" DestinationDirectory="appdataDir" SourceDirectory="dbDir" SourceName="Foo*.*" />
</Component>
</Directory>
</Directory>
</Directory>
Of course I get errors that I must use a registry KeyPath, and that I'm missing RemoveFile. But what I want is a one-time copy, and I don't need or want the OS to track it, because I'm just mimicking how files would be left over in appdata by the software itself, that un-installation would not touch.
Perhaps this is better suited for an ad hoc script outside the MSI run via a custom action?
what is the best way to accomplish this file copying (via MSI standard actions or not)?
Is this copying the best or least bad way to accomplish my original goal?
Many thanks in advance.
Well after reading the authoritative answers to this other question, I am reaffirmed that this kind of thing is not appropriate to be done by the installer.
Just for info this is the Windows batch script I'll use instead (before InstallFiles):
#echo off
if exist "%APPDATA%\authorName\prodName\Foo.bar" exit /b 0
set dirOrig=0
if exist "%ProgramFiles(x86)%\prodNameOld\DATABASE\Foo.bar" set dirOrig=%ProgramFiles(x86)%\prodNameOld\DATABASE
if exist "%ProgramFiles(x86)%\prodName\DATABASE\Foo.bar" set dirOrig=%ProgramFiles(x86)%\prodName\DATABASE
if "%dirOrig%"=="0" exit /b 0
xcopy "%dirOrig%\Foo.bar" "%APPDATA%\authorName\prodName\" /y
xcopy "%dirOrig%\etc.*" "%APPDATA%\authorName\prodName\" /y
del /f/q "%dirOrig%\*.*"
exit /b 0
Related
Lots of pages in the internet and books about Windows Installer are citing the following Microsoft page ROOTDRIVE for:
If ROOTDRIVE is not set at a command line or authored into the Property table, the installer sets this property. During an administrative installation the installer sets ROOTDRIVE to the first connected network drive it finds that can be written to. If it is not an administrative installation, or if the installer can find no network drives, the installer sets ROOTDRIVE to the local drive that can be written to having the most free space.
Well, on my customer's machine C: has lots of more free space than Y:, but the application gets installed into Y:\MyApp instead of C:\Program Files (x86)\MyApp. Of course the WiX and Directory structure are standard-style:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="$(var.CompanyName)">
<Directory Id="MyAppFolder" Name="MyApp" />
</Directory>
</Directory>
</Directory>
So nothing special. On other machines it installs to Program Files folder as expected.
From my daily experience the rule with the "most free space" cannot be true, since on lots of machines I am dealing with have a rather small SSD as C: local drive and a much, much more bigger data drive D: with Terabytes more free space than C:. If that rule would fit then nowadays most of the computers would have (all) their applications being installed to their Data Drive root directory (e.g. D:\) and not underneath the specially-protected %ProgramFiles%/%ProgramFiles (x86)% folders. On all my machines with the configuration small SSD vs. big data HDD with more space all the stuff gets installed into my expected Program Files folders in C:. So the rule of the "most free space" is definitely NOT TRUE!!! And the MSIs are not especially defining any TARGETDIR or ROOTDIR property to C:\ on command line neither MSI table in any case!
So there must be another evalution rule. Which one is it? Who can explain the odd behaviour?
EDIT
Thanks to Stein I did a closer look at the logs and found out that ROOTDRIVE actually really points to the larger local disks but due to the higher priority of the System Folder Properties it always gets installed into the right place on System Drive. The log file I also looked into when asking the question was the one from Dell Data Protection which explicitly must have set ROOTDRIVE to C:\ or System Drive. Thus The ROOTDRIVE rule from MSDN seems to be true but it does not have any effect in most of the times.
When I looked on the problem computer myself I saw that Y: was a network drive, then I searched for the word ADMIN et voilĂ : it was an administrative installation although my customer told me that he just double-clicked on the MSI and Y: was a local drive => my airing grievance this year for the festivus will be that one should not never believe what the customer says and vows ;-)
System Folder Properties: I think the answer can be found in this Symantec Article. Essentially: System Folder Properties are not affected by the ROOTDRIVE property.
Incidentally my D:\ drive has more space than my C:\ drive and ROOTDRIVE is set to D:\ in the MSI log file. Installation writes no files to D:\ though. I would think this is due to ample space on C:\?
UPDATE: Also check the documentation on TARGETDIR, with focus on this section:
"...if the TARGETDIR property is defined, the destination directory
is resolved to the property's value. If the TARGETDIR property is
undefined, the ROOTDRIVE property is used to resolve the path."
Problem Computer: The above does not really explain what happens on your problem computer, does it? Did you ever install manually to this custom location? Did you use a "Remember Property" pattern to persist the installation folder? It must be reading back that old path? Or maybe the disk is very low on space? Or are you installing using an admin image? AdminProperties. No Set Property custom actions in there? Could there be constructs in the GUI that could affect this? I know set property is used in some of WiX's default GUI sets. And you can make directories modifiable as "feature directories". See the screenshot down the page here.
Verbose Log: I would suggest you make a log file on a system where the installs go to a secondary drive and check what is written into the log with regards to ROOTDRIVE and directory resolution in general. In corporate environments where target computers are uniform many actually hard code ROOTDRIVE to C:\ - not great, but they do. They wouldn't do that unless they are trying to avoid some random side effect along the lines of what you describe.
Festivus Grievance: I have always disliked this ROOTDRIVE issue, and never fully understood it to be honest. I hear you in other words. It is in fact one of my Festivus grievances against MSI. Did I answer? Not really :-). You have some pointers at least. Maybe the WiX guys, Chris Painter or PhilDW can provide a complete answer.
Do Not Use the below construct:
<!--ROOTDRIVE explicitly set to prevent it from defaulting to drive with most space-->
<Property Id="ROOTDRIVE" Value="$(env.SystemDrive)" />
<!-- NO SOLUTION -->
The above will be a compile time resolution, and not a runtime resolution of the folder. In other words it is no solution at all. ROOTDRIVE will be set to the system drive letter of the build computer, not of the computer you install to.
Construct you can try to force ROOTDRIVE to be set to the system drive:
<CustomAction Id='SetRootDrive' Property='ROOTDRIVE' Value='[%SystemDrive]\' />
<InstallUISequence>
<Custom Action="SetRootDrive" Before="CostInitialize"></Custom>
</InstallUISequence>
Some Links:
In WiX files, what does Name="SourceDir" refer to?
How to use FileSearch result as condition in Component section.
I want to get something like this:
<Property Id=\"CONFIG_XML_EXISTS\">
<DirectorySearch Id="CheckForConfigXml" Path="[INSTALLDIR]\">'
<FileSearch Id="ConfigXmlSearch" Name="config.xml" />
</DirectorySearch>
</Property>
...
<Component Id="c_DefaultConfig.xml" Guid="{1AAB0AFD-B763-4A55-8585-B0AD4D8CE23C}">
<File Id="f_default_config.xml"
Name="default-config.xml"
Source="$(var.SourceRoot)\config.xml"/>
<Condition>CONFIG_XML_EXISTS</Condition>
</Component>
I don't know why but property wix doesn't want to evaluate CONFIG_XML_EXISTS.
Because that search happens very early in the install, the most likely reason is that INSTALLDIR has no value. You haven't said whether you're doing a fresh install or an upgrade, so it's not clear where you think it might be getting its value from.
I'd also point out that the purpose of that source code is apparently to prevent the install of a file if there is one there already, so:
If INSTALLDIR turns out to be the application folder (typically program files) where your files are installed then users can usually change this location, so it's not clear the file is going to be where you expect it to be.
The file overwrite rules prevent incoming files from overwriting modified data files (modify date > creation date) so if that config file has been changed it won't be overwritten and you don't need to do the check.
In your comment you say "My installer must create file config.xml only if there is no such file in target(install) directory. If such file exists, my installer must create file with name template.xml". I think that perhaps the easiest way to do this is in the application after the install has finished, or possibly in a custom action after all the files have been installed. There seems to be no good way to do this before the install because INSTALLDIR is unpredictable. I've seen this kind of problem solved by installing the XML files to (say) User's Application Data, and after the files are installed then the application or a custom action can see what files are there (or not) and get them from User's Application Data.
Can I change the default directory (%Temp%) of msi log file?
I want to move the log directory to ProgramData folder.
Thanks
The problem here is, that the related property MsiLogFileLocation is read-only, i.e. you have no chance to change the location of the logfile within a Windows Installer package.
There are some alternatives though:
You can define the location of the logfile if you run the Windows Installer with the /L-parameter, e.g. msiexec.exe /i MyPackage.msi /l*v "C:\path\to\log\logfile.log"
To ensure that e.g. a customer will invoke this with the correct parameters you would have to create a bootstrapper around your MSI-file, using e.g. burn
Still another, more simple solution (that I used myself) would be: let the Windows Installer log the whole installation process to wherever it needs to. As you always have access to the complete path of the created logfile using the MsiLogFileLocation-property, simply copy the logfile as last step in your installation process to wherever you want to. The only drawback here: sometimes the last lines are missing as it is copied before the installation finished completely.
So if the very last lines are not always important (because you parse the logfile further etc.), the last solution would probably be the most simple one.
I scheduled it after InstallFinalize (and used the condition NOT (REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE), btw, else it overwrote or copied too much or... (can't remember)).
I am about to create a MSI package.
During the installation (launched e.g. via double-click on the MSI) some files contained in the MSI are deposited deeper under c:\ProgramData (resp. the CommonAppDataFolder), e.g. c:\ProgramData\myCompany\myApplication.
Later when the installed application is run by the user the application may need to modify these file.
The problem is that neither the running application nor the user e.g. via Windows Explorer has the right to modify any files under c:\ProgramData\myCompany\myApplication created during installation.
The files do not have a readonly attribute set.
The strange thing for me now is: If I install the MSI via msiexec /q /i then I have write permissions on these files.
My MSI is created with WiX, my os is Win 7, the user is member of the administrator group.
Can anyone tell me why that is so, and how I can gain write permissions to these files w/o having to use /q /i?
Thanks
Jan
EDIT 2014-03-24: Damned. I missed to specify the InstallPrivileges attribute on my element, I just didn't know about it.
Setting it to "limited" does not show up a UAC prompt when installing to ProgramData! And now the user / my application is allowed to overwrite files in the destination folder :)
I don't know why that behavior would be different regarding access to that folder unless you have a custom action that does something that is only in the UI sequence. That's the only functional difference I can think of - the UI sequence is suppressed in a silent install.
However the common app data folder is not normally writeable to limited users. I'm not sure how much you know about UAC, but it doesn't matter if the user running the program is an admin or not because by default admins run with limited privileges. If the app needs admin privilege to run then it needs building with an elevation manifest so it asks for elevation to admin privilege. Either that or run it as administrator from a shortcut.
Windows access control is extremely complicated to deal with. In every version of Windows there is something new and changed to deal with, and it looks like the default write access in ProgramData now includes some sort of special ACL / DACL. If this is indeed the problem you can apply new permissions there and open up for regular users to write. Please note that I am not quite aware of exactly what newer versions of Windows apply as default. The tool I generally use to set permissioning is subinacl.exe. A real handful of a tool with a command line that can scare the most experienced system administrators. Some command line samples are here. You can also use the LockPermissions table in MSI, but somehow there are some issues with how these permissions are applied to the file system object. subinacl.exe is more complex, and more capable.
More on file and folder permissioning:
http://technet.microsoft.com/en-us/library/bb727008.aspx
http://support.microsoft.com/kb/308419
With regards to where you should put different types of files on the system, please check this thread.
I have multiple config files (for different environments). During install user get to select the environment, and based on that correct files are copied. I want to delete the extra files that are not used.
I am using but it doesn't seem to be working. I don't get any errors as such, in the log I see action getting executed but files are not deleted. Can anyone please point what I am doing wrong?
<Component Id="RemoveFiles" Guid="C5D634C2-744E-4CA5-BB44-F3DE88482AB5">
<RemoveFile Id="RemoveConfigs" Name="???_*.config" On="install" />
</Component>
My RemoveFile table also looks like
FileKey Component FileName DirProperty InstallMode
RemoveExtraConfigFiles RemoveExtraFiles p6wjlh9a.con|Web_*.config INSTALLDIR 1
Still it's not deleting anything
RemoveFile or CopyFile always run before InstallFiles, and it finds no files in the install directory, hence it fails.
The RemoveFiles action will try to find the files you specified in the parent directory of the component, in case you don't override it in the RemoveFile element itself (according to your sample, you don't). Make sure that it is really a folder containing that file. If the file is not found, the action won't fail - it will silently continue.