oleg-shilo/wixsharp

ComponentIDs for files change depending on folder that source is built from.

cameron-moore opened this issue · 5 comments

Hi,

I'm using WixSharp 1.25.3 to build an installer and noticed that the component Ids of files included in the install package changed depending on where the VS solution that includes the WixSharp project was located. For example, if the solution was in a folder under C:\projects" the wxs file would have one set of ComponentIDs generated. If the solution was built on the build server, in a different location, it'd have a different set of component IDs. This led to issues where files are unexpected deleted on an upgrade because there are multiple component IDs for the same installed file.

Absolute paths are being used to define the source for the files to include and I'm using the default component ID generation algorithm that creates a hash from the destination folder and filename.

What I noticed is that the GetTargetPathOf method in Project uses the full file name, including any path, to generate the target path. For example, if file xyx.txt was being installed to the folder %ProgramFiles%\My Company\My Product from source folder C:\sourceFiles the GetTargetPath method would return a target path of %ProgramFiles%\My Company\My Product\C:\sourceFiles\xyz.txt for that file.

If the GetTargetPathOf method is meant to get the target path of the file then I believe it should be stripping any path information from the source filename before using it to construct the target path of the file. When this is done the hash and component ID generated for each file is consistent regardless of what path the source file is located in.

Below is an updated version of GetTargetPathOf that does strips path information from the source file and just combines the source filename with the destination folders.

 public string GetTargetPathOf(File file)
 {
     var filename = file?.Name.Split(System.IO.Path.DirectorySeparatorChar).LastOrDefault() ?? string.Empty;
     var dir = this.AllDirs.FirstOrDefault(d => d.Files.Contains(file));
     var path = new List<string> { filename };

     while (dir != null)
     {
         path.Insert(0, dir.Name);
         dir = this.AllDirs.FirstOrDefault(d => d.Dirs.Contains(dir));
     }
     return path.JoinBy($"{System.IO.Path.DirectorySeparatorChar}");
 }

Hope this helps and thanks for a great library!

Cameron.

Yes, indeed the component ID generation is based on the path and in the case of an absolute path it can lead to unintended id changes. Even though still valid msi.

I will process your suggestion and integrate it if it does not cause any implications.

Thank you for sharing.

Thanks @oleg-shilo. Much appreciated.
Cameron.

Hi Cameron,

May I ask you why you did not use GetFileName? Is there a reason? Am I missing something?

var filename = file?.Name.Path.PathGetFileName() ?? string.Empty;
// instead of 
var filename = file?.Name.Split(System.IO.Path.DirectorySeparatorChar).LastOrDefault() ?? string.Empty;

Hi Cameron,

May I ask you why you did not use GetFileName? Is there a reason? Am I missing something?

var filename = file?.Name.Path.PathGetFileName() ?? string.Empty;
// instead of 
var filename = file?.Name.Split(System.IO.Path.DirectorySeparatorChar).LastOrDefault() ?? string.Empty;

Hi @oleg-shilo,
Path.GetFileName() would definitely be fine to use. There wasn't any reason why I didn't use that & you're not missing anything. I just didn't think of that at the time.

Thanks,
Cameron.

Perfect. Then we are fine.