File: Vsix\FinalizeInsertionVsixFile.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.VisualStudio\Microsoft.DotNet.Build.Tasks.VisualStudio.csproj (Microsoft.DotNet.Build.Tasks.VisualStudio)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
 
namespace Microsoft.DotNet.Build.Tasks.VisualStudio
{
    /// <summary>
    /// Post-processing necessary to convert VSIXes produced by the build to VSIXes suitable for insertion into VS.
    /// 
    /// Replaces Experimental="true" attribute of the Installation element with SystemComponent="true" in the VSIX manifest file.
    /// </summary>
    public sealed class FinalizeInsertionVsixFile : Microsoft.Build.Utilities.Task
    {
        private const string VsixManifestPartName = "/extension.vsixmanifest";
        private const string VsixNamespace = "http://schemas.microsoft.com/developer/vsx-schema/2011";
 
        [Required]
        public string VsixFilePath { get; set; }
 
        public override bool Execute()
        {
            ExecuteImpl();
            return !Log.HasLoggedErrors;
        }
 
        private void ExecuteImpl()
        {
            using (var package = Package.Open(VsixFilePath))
            {
                UpdatePartHashInManifestJson(package, VsixManifestPartName, UpdateExtensionVsixManifest(package));
            }
        }
 
        private byte[] UpdateExtensionVsixManifest(Package package)
        {
            var part = package.GetPart(new Uri(VsixManifestPartName, UriKind.Relative));
 
            byte[] hash;
            using (var stream = part.GetStream(FileMode.Open))
            {
                var document = XDocument.Load(stream);
                UpdateInstallationElement(document);
 
                using (var newContent = new MemoryStream())
                {
                    document.Save(newContent);
 
                    // overwrite the content of the part in VSIX:
                    stream.Position = 0;
                    stream.SetLength(newContent.Length);
                    newContent.Position = 0;
                    newContent.CopyTo(stream);
 
                    // calculate new hash:
                    newContent.Seek(0, SeekOrigin.Begin);
                    using (var sha = SHA256.Create())
                    {
                        hash = sha.ComputeHash(newContent);
                    }
                }
            }
 
            return hash;
        }
 
        internal void UpdateInstallationElement(XDocument document)
        {
            var installationElement = document.Element(XName.Get("PackageManifest", VsixNamespace))?.Element(XName.Get("Installation", VsixNamespace));
            if (installationElement == null)
            {
                Log.LogError($"PackageManifest.Installation element not found in manifest of '{VsixFilePath}'");
                return;
            }
 
            var experimental = installationElement.Attribute("Experimental");
            if (experimental == null || experimental.Value != "true")
            {
                Log.LogWarning($"PackageManifest.Installation element of the manifest does not have Experimental=\"true\": '{VsixFilePath}'");
            }
 
            experimental?.Remove();
 
            var systemComponentName = XName.Get("SystemComponent");
            var systemComponent = installationElement.Attribute(systemComponentName);
            if (systemComponent != null)
            {
                Log.LogWarning($"PackageManifest.Installation element of the manifest specifies SystemComponent attribute: '{VsixFilePath}'");
                systemComponent.SetValue(true);
            }
            else
            {
                systemComponent = new XAttribute(systemComponentName, true);
                installationElement.Add(systemComponent);
            }
        }
 
        private static void UpdatePartHashInManifestJson(Package package, string partName, byte[] partHash)
        {
            var part = package.GetPart(new Uri("/manifest.json", UriKind.Relative));
 
            using (var stream = part.GetStream(FileMode.Open))
            {
                string jsonStr;
                using (var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 2048, leaveOpen: true))
                {
                    jsonStr = reader.ReadToEnd();
                }
 
                var json = JObject.Parse(jsonStr);
 
                var file = ((JArray)json["files"]).Where(f => (string)f["fileName"] == partName).Single();
                file["sha256"] = BitConverter.ToString(partHash).Replace("-", "");
 
                stream.Position = 0;
                stream.SetLength(0);
 
                using (var writer = new StreamWriter(stream, Encoding.UTF8, bufferSize: 2048, leaveOpen: false))
                {
                    writer.Write(json.ToString(Formatting.None));
                }
            }
        }
    }
}