| File: src\StabilizeWixFileId.cs | Web Access |
| Project: src\arcade\src\Microsoft.DotNet.Build.Tasks.Installers\Microsoft.DotNet.Build.Tasks.Installers.csproj (Microsoft.DotNet.Build.Tasks.Installers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Framework; using System; using System.Linq; using System.Xml.Linq; namespace Microsoft.DotNet.Build.Tasks.Installers { /// <summary> /// In a WiX source file, replaces the Id of a File with some given string in order to stabilize /// it. This allows external tooling such as signature validators to rely on a stable identifier /// for certain files. /// </summary> public class StabilizeWixFileId : BuildTask { /// <summary> /// File to read from. This is expected to be an output from heat.exe. /// /// Expected format: /// /// <?xml version="1.0" encoding="utf-8"?> /// <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> /// <Fragment> /// <ComponentGroup Id="InstallFiles"> /// <Component Id="cmp680C9..." Directory="dir14B9F..." Guid="{C31...}"> /// <File Id="filE57B7..." KeyPath="yes" Source="$(var.PackSrc)\packs\...\native\apphost.exe" /> /// ... /// </summary> [Required] public string SourceFile { get; set; } /// <summary> /// File to write to. May be the same as SourceFile. /// </summary> [Required] public string OutputFile { get; set; } /// <summary> /// Set of files to stabilize. This matches the end of the "Source" attribute in the WiX /// source file. If exactly one match isn't found in the WiX source file, this task fails. /// /// %(Identity): The file source to replace. /// %(ReplacementId): The replacement for Id that won't change per-build. /// </summary> [Required] public ITaskItem[] FileElementToStabilize { get; set; } public override bool Execute() { XDocument content = XDocument.Load(SourceFile); XNamespace rootNamespace = content.Root.GetDefaultNamespace(); XName GetQualifiedName(string name) => rootNamespace.GetName(name); foreach (var file in FileElementToStabilize) { string replacement = file.GetMetadata("ReplacementId"); if (string.IsNullOrEmpty(replacement)) { Log.LogError($"{nameof(FileElementToStabilize)} {file.ItemSpec} has null/empty ReplacementId metadata."); continue; } XElement[] matchingFileElements = content.Element(GetQualifiedName("Wix")) .Elements(GetQualifiedName("Fragment")) .SelectMany(f => f.Elements(GetQualifiedName("ComponentGroup"))) .SelectMany(cg => cg.Elements(GetQualifiedName("Component"))) .SelectMany(c => c.Elements(GetQualifiedName("File"))) .Where(f => f.Attribute("Source")?.Value ?.EndsWith(file.ItemSpec, StringComparison.OrdinalIgnoreCase) == true) .ToArray(); if (matchingFileElements.Length != 1) { Log.LogError( $"Expected 1 match for '{file.ItemSpec}', found {matchingFileElements.Length}: " + string.Join(", ", matchingFileElements.Select(e => e.ToString()))); continue; } XAttribute nameAttribute = matchingFileElements[0].Attribute("Id"); if (nameAttribute is null) { Log.LogError($"Match has no Id attribute: {matchingFileElements[0]}"); continue; } Log.LogMessage( $"Setting '{file.ItemSpec}' Id to '{replacement}' for File with Source " + matchingFileElements[0].Attribute("Source").Value); nameAttribute.Value = replacement; } content.Save(OutputFile); return !Log.HasLoggedErrors; } } }