So I have a huge file structure which I want the installer, I am building using wix, to copy on the client location. Currently I am typing it out like:
<Directory Id="xyz" Name = "abc FileSource = "sdfsdf">
<Component Id="asdas" Guid="asdasd">
<File Id = "asdfgrs" Name="name" />
</Component>
</Directory>
As the number of files have increased I would like this to be done in an automated way. Using heat I am able to generate:
<ComponentGroup Id="weqw">
<Component Id="2132312" Directory="Some random string (cause of concern>" Guid="asdasd">
<File Id="sdqwdqwd> keyPath="yes" Source = "Correct source path" />
</Component>
<ComponentGroup>
My concern is that due to the presence of some random string in Directory field of Component generated by heat, I wont get the directory structure replicated. Is this true? Is there a way around this?
From the heat /? output:
-dr directory reference to root directories (cannot contains spaces
e.g. -dr MyAppDirRef)
If you use heat to recursively harvest a directory structure, then the -dr switch will set the ID of the root target folder. This ID should match the ID of a Directory element you have specified elsewhere in your wxs files.
For the harvested subfolders, heat will still generate a random ID. A given ID will appear multiple times in the generated XML file:
In the Directory element generated by heat for that subfolder, as the Id attribute.
In the Component elements associated with that folder, As the Directory attribute.
The ID is only used to link Component elements to Directory elements. It is not the folder name as it appears after installation. It is only used as a key in the Windows Installer database.
Related
I want to be able to compare the WXS file - generated by heat - with the previous version so that I can verify the changes.
But I can't find any logic behind the component order. The generated components are neither sorted by Id nor by directory name. And always when I regenerate my WXS files and some files have been added, the order is totally different, which makes the comparation very difficult.
Is there any way to control the sort order? Ideal would be, if the components would be sorted by the source of inner file node. But any constant order would be good enough for me.
E.g. for the following example, it would be great, when the components are sorted by file source. I.E. AutoMapper before Log4Net
<Component Id="cmpB83..." Directory="DIR.MYDIR" Guid="{2A49...}">
<File Id="filA272..." KeyPath="yes" source="$(var.MYVAR)\AutoMapper.xml" />
</Component>
<Component Id="cmp445..." Directory="DIR.MYDIR" Guid="{1C34...}">
<File Id="filB356..." KeyPath="yes" source="$(var.MYVAR)\Log4Net.xml" />
</Component>
I start heat with following parameters:
heat.exe dir MYDIR -gg -dr DIR.MYDIR -srd -sreg -ke -cg compGroup.MYGROUP -var var.MYVAR -out ..\MYOUTPUT.wxs
I use WiX 3.11.
Since there is no answer since two years, I will show what I have done:
I use this code to re-order the components via "source" in the generated file and store it to a copy:
XDocument xdoc = XDocument.Load(filename);
XElement componentsFragment = xdoc.Root.Elements().ElementAt(1);
XElement componentGroup = componentsFragment.Elements().ElementAt(0);
List<XElement> components = componentGroup.Elements().ToList();
List<XElement> sortedComponents = components.OrderBy(one => one.Elements().ElementAt(0).Attribute("Source").Value).ToList();
componentGroup.RemoveNodes();
foreach (XElement component in sortedComponents)
{
componentGroup.Add(component);
}
xdoc.Save(resultFilename);
I am looking for a way to add File(s) to an existing directory that has a random name as part of a Visual Studio Setup Project and I hoped someone might be able to help me solve this puzzle please.
I have been attempting to obtain the discovered path property of the directory using a Launch Condition; Unfortunately this method returns the full file path including the filename, which cannot be used as a directory property.
The directory in question takes the form [AppDataFolder]Company\Product\aaaaaaaaaaaa\
where aaaaaaaaaaaa is a random installation string.
Within the Launch Condition Setup I check for the directory's existence by searching for a file that would appear inside it,
Search Target Machine
(Name): File marker
Filename: sample.txt
Folder: [AppDataFolder]Company\Product\
Property: DIRFILE
Launch Condition
(Name): File marker exists
Condition: DIRFILE
In the Setup Project I add the file I wish to insert, with the details
Condition: DIRFILE
Folder: 'Installation folder'
Then in File System Setup I add a new folder entry for the random directory aaaaaaaaaaaa
(Name): Installation folder
Condition: DIRFILE
DefaultLocation: [DIRFILE]\..\ *Incorrect*
Property [DIRLOCATION]
As you can see the installer detects the existence of the marker file but, instead of placing my file at the same location, when using [DIRFILE] the installer would incorrectly try and insert it INTO the file;
This is because the file path was returned
[AppDataFolder]Company\Product\aaaaaaaaaaaa\sample.txt
where I instead need the directory path
[AppDataFolder]Company\Product\aaaaaaaaaaaa
Therefore I was wondering if it was possible to return the directory the file was found in from Search Target Machine (as opposed to the file location of the file), if I could extract the directory path by performing a string replace of the filename on the file location DIRFILE within the DefaultLocation field in File System Setup, or if perhaps there is even another method I am missing?
I'm also very interested in a simple solution for this, inside the setup project.
The way I did solve it was to install the files to a temporary location and then copy them to the final location in an AfterInstall event handler. Not a very elegant solution! Since it no longer care about the user selected target path I removed that dialog. Also I needed to take special care when uninstalling.
public override void OnAfterInstall(IDictionary savedState)
{
base.OnAfterInstall(savedState);
// Get original file folder
string originDir = Context.Parameters["targetdir"];
// Get new file folder based on the dir of sample.txt
string newDir = Path.GetDirectoryName(Context.Parameters["dirfile"]);
// Application executable file name
// (or loop for all files on the path instead)
string filename = "ApplicationName.exe";
// Move or copy the file
File.Move(Path.Combine(originDir, filename), Path.Combine(newDir, filename)));
}
Is there any way to specify in .vsprops file paths relative to .vsprops file directory?
For example, I have the followind directory stucture:
largesolution.sln
a/a.vcproj
b/c/c.vcproj
common/common.vsprops
Both a.vcproj and c.vcproj include common.vsprops, and I want to add some macro or set include directory relative to common folder regardless the solution directory both projects are included to. I've tried using $(InputDir) in .vsprops file, but it seems this macro is expanded as directory containing .vcproj, not .vsprops file.
Setting absolute paths or setting global include path in Visual C++ Directories is not a solution because different developers have different location of the source tree root. Setting paths relative to $(SolutionDir) does not suit because it is useful to have smaller solutions containig some subset ob projects (for example, a.vcproj only) somewhere outside main sources tree.
Of course, setting include directory in a.vcproj to $(ProjectDir)..\common works fine, but the result to be achieved is only including .vsprops and having paths set correctly.
You can use MSBuildThisFileDirectory macro.
For example:
You set Include Directories to "$(MSBuildThisFileDirectory)\include;$(IncludePath)" in common.props.
See http://msdn.microsoft.com/en-us/library/vstudio/ms164309.aspx for details.
Tested with MSVS2012 and MSVS2013.
I've just begun using Chirpy, and it's frigging amazing. One problem I'm having is that I'm unable to get it to update the mashed file(s) when an edit is made in one of the "sub" files.
IE: If I have a mashed file called "site.css" (via my js.chirp.config file) which contains "elements.css", "master.css", "misc.css" etc. And I make an edit to one of them (say, master.css), I want chirpy to kick in and redo the site.css with the new edits.
Is this at all possible?
Chirpy does this - just make sure your paths use backslashes rather than forward slashes.
<root>
<FileGroup Name="site.css">
<File Path="css\elements.css" />
<File Path="css\master.css" />
<File Path="css\misc.css" />
</FileGroup>
</root>
Chirpity chirp chirp chirp.
I have
<Folder Pattern="*.min.css" Minify="false" />
And with that wildcard in there, it doesnt run when min.css files are updated. I have to update the config and save for the mash to occur
All I want is a command-line tool that can extract files from a merge module (.msm) onto disk. Said differently, I want the same "administrative install" functionality that is possible for an MSI:
msiexec /a myProduct.msi TARGETDIR="C:\myInstallation" /qn
The above only works on an msi (near as I can tell). So to get the same effect for a merge module, I'm trying msidb.exe and orca.exe The documentation for orca states:
Many merge module options can be
specified from the command line...
Extracting Files from a Merge Module
Orca supports three different methods
for extracting files contained in a
merge module. Orca can extract the
individual CAB file, extract the files
into a module tree and extract the
files into a source image once it has
been merged into a target database...
Extracting Files
To extract the individual files from a
merge module, use the
... -x ... option on the
command line, where is the
desired path to the new directory
tree.
The specified path is used as the root
path for the extracted files. All
files are extracted from the CAB file
embedded in the module and placed in
the specified path. The directory
layout for the extracted files is
based on the directory tree of the
merge module.
It sounds like what I need. But when I try it, orca simply opens up an editor (with info on the msm I specified) and then does nothing. I've tried a variety of command lines, usually starting with this:
orca -x theDirectory theModule.msm
I use "theDirectory" as whatever empty folder I want. Like I said - it didn't do anything.
Then I tried msidb, where a couple of attempts I've made look like this:
msidb -d theModule.msm -w {storage}
msidb -d theModule.msm -x MergeModule.CABinet
In the first case, I don't know what to put for {storage}. In the second case, it turns out that the literal string "MergeModule.CABinet" is necessary (it is a reserved name). However, the extracted cabinet does not preserve the file hierarchy or "normal" file names; so I can't use it for my purposes.
Can someone explain what I'm doing wrong with the command line options? Is there any other tool that can do this?
You can use the decompiler tool included with WiX (called Dark) to decompile the merge module and extract the files:
dark.exe myMergeModule.msm -x "path_to_extracted_files"
The files will get extraced to the path specified in the -x parameter.
Note: The files will get extracted using the names specified in the File table of the installation database, which may not actually be the file names used when the files actually get installed. If you need extract the files using the actual file names, see my other answer to this question: Extracting files from merge module
I just had to do this by creating a blank msi and then use Orca to attempt to merge the module into my msi and then extract the files.
Create a blank .msi. I used WiX 3.6 to create the .msi and below is the minimal source. I named it "blank.msi".
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="blank" Language="1033" Version="1.0.0.0" Manufacturer="blank" UpgradeCode="298878d0-5e7b-4b2e-84f9-45bb66541b10">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MediaTemplate />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder"/>
</Directory>
<ComponentGroup Id="ProductComponents" Directory="ProgramFilesFolder" />
<Feature Id="ProductFeature" Title="blank" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
</Wix>
Use Orca to extract the files from the merge module.
orca -m "myModule.msm" -f ProductFeature -x .\xdir blank.msi
The files will be extracted to the directory specified by the -x parameter (in this case .\xdir).
Note that the value for the -f parameter "ProductFeature" matches the name of the feature specified in msi file above.
The DeploymentToolsFoundation class library in WiX, has an InstallPackage class with an ExtractFiles() method that should do just what you want, but fails for Merge Modules. This appears to be a bug.
The following PowerShell script, which uses DTF to access the CAB in the mergemodule, should do what you want. Apologies if the scripting is a bit wonky, I'm new to PowerShell.
[Reflection.Assembly]::LoadFrom("[InsertPath]\Microsoft.Deployment.WindowsInstaller.dll")
function ExtractMSM([string]$file, [string]$targetDir)
{
write-host "Extracting files from merge module: "$file
if(![IO.Directory]::Exists($targetDir)) { new-item -type directory -path $targetDir }
$cabFile = join-path $targetDir "temp.cab"
if([IO.File]::Exists($cabFile)) { remove-item $cabFile }
$db = new-object Microsoft.Deployment.WindowsInstaller.DataBase($file, [Microsoft.Deployment.WindowsInstaller.DataBaseOpenMode]::ReadOnly)
$view = $db.OpenView("SELECT `Name`,`Data` FROM _Streams WHERE `Name`= 'MergeModule.CABinet'")
$view.Execute()
$record = $view.Fetch()
$record.GetStream(2, $cabFile)
$view.Dispose()
expand -F:* $cabFile $targetDir
remove-item $cabFile
$extractedFiles = get-childitem $targetDir
$hashFiles = #{}
foreach($extracted in $extractedFiles)
{
try
{
$longName = $db.ExecuteScalar("SELECT `FileName` FROM `File` WHERE `File`='{0}'", $extracted.Name)
}
catch
{
write-host "$($extracted.Name) is not in the MSM file"
}
if($longName)
{
$longName = $LongName.SubString($LongName.IndexOf("|") + 1)
Write-host $longName
#There are duplicates in the
if($hashFiles.Contains($longName))
{
write-host "Removing duplicate of $longName"
remove-item $extracted.FullName
}
else
{
write-host "Rename $($extracted.Name) to $longName"
$hashFiles[$longName] = $extracted
$targetFilePath = join-path $targetDir $longName
if([IO.File]::Exists($targetFilePath)) {remove-item $targetFilePath}
rename-item $extracted.FullName -NewName $longName
}
}
}
$db.Dispose()
}
I had a similar problem, but I went at it from a different direction.
I installed InstallSheild Express that came with an earlier version of Visual Studio, created a new project, but I only added the MSM file that I required.
After compiling and running my new install I was able to retrieve the files that the MSM file contained.
MSI2XML