| File: ManifestUtil\DeployManifest.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// 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.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Xml; using System.Xml.Serialization; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName; #nullable disable namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities { /// <summary> /// Specifies how the application checks for updates. /// </summary> [ComVisible(false)] public enum UpdateMode { /// <summary> /// Check for updates in the background, after the application starts. /// </summary> Background, /// <summary> /// Check for updates in the foreground, before the application starts. /// </summary> Foreground } /// <summary> /// Specifies the units for the update interval. /// </summary> [ComVisible(false)] public enum UpdateUnit { /// <summary> /// Update interval is in hours. /// </summary> Hours, /// <summary> /// Update interval is in days. /// </summary> Days, /// <summary> /// Update interval is in weeks. /// </summary> Weeks } /// <summary> /// Describes a ClickOnce deployment manifest. /// </summary> [ComVisible(false)] [XmlRoot("DeployManifest")] public sealed class DeployManifest : Manifest { private string _createDesktopShortcut; private string _deploymentUrl; private string _disallowUrlActivation; private AssemblyReference _entryPoint; private string _errorReportUrl; private string _install = "true"; private string _mapFileExtensions; private string _minimumRequiredVersion; private string _product; private string _publisher; private string _suiteName; private string _supportUrl; private string _trustUrlParameters; private string _updateEnabled; private string _updateInterval = "0"; private string _updateMode; private string _updateUnit = "days"; private CompatibleFrameworkCollection _compatibleFrameworkList; private List<CompatibleFramework> _compatibleFrameworks = new List<CompatibleFramework>(); private string _targetFrameworkMoniker; private const string _redistListFolder = "RedistList"; private const string _redistListFile = "FrameworkList.xml"; /// <summary> /// Initializes a new instance of the DeployManifest class. /// </summary> public DeployManifest() { } /// <summary> /// Initializes a new instance of the DeployManifest class. /// </summary> public DeployManifest(string targetFrameworkMoniker) { DiscoverCompatFrameworks(targetFrameworkMoniker); } private void DiscoverCompatFrameworks(string moniker) { if (!string.IsNullOrEmpty(moniker)) { var frameworkName = new FrameworkNameVersioning(moniker); if (frameworkName.Version.Major >= 4) { _compatibleFrameworks.Clear(); DiscoverCompatibleFrameworks(frameworkName); } } } private void DiscoverCompatibleFrameworks(FrameworkNameVersioning frameworkName) { FrameworkNameVersioning installableFrameworkName = GetInstallableFrameworkName(frameworkName); // if profile is null or empty. if (string.IsNullOrEmpty(installableFrameworkName.Profile)) { _compatibleFrameworks.Add(GetFullCompatFramework(installableFrameworkName)); } else { _compatibleFrameworks.Add(GetSubsetCompatFramework(installableFrameworkName)); _compatibleFrameworks.Add(GetFullCompatFramework(installableFrameworkName)); } } /// <summary> /// codes from GetInstallableFrameworkForTargetFxInternal in /// env/vscore/package/FxMultiTargeting/FrameworkMultiTargetingInternal.cs /// </summary> private static FrameworkNameVersioning GetInstallableFrameworkName(FrameworkNameVersioning frameworkName) { string installableFramework = null; FrameworkNameVersioning installableFrameworkObj; IList<string> referenceAssemblyPaths = GetPathToReferenceAssemblies(frameworkName); if (referenceAssemblyPaths?.Count > 0) { // the first one in the list is the reference assembly path for the requested TFM string referenceAssemblyPath = referenceAssemblyPaths[0]; // Get the redistlist file path string redistListFilePath = GetRedistListFilePath(referenceAssemblyPath); if (FileSystems.Default.FileExists(redistListFilePath)) { installableFramework = GetInstallableFramework(redistListFilePath); } } // If the installable framework value is not in the redist, there was no redist, or no matching FX we return the sent TFM, // this means frameworks that are installable themselves don't need to specify this property // and that all unknown frameworks are assumed to be installable. if (installableFramework == null) { installableFrameworkObj = frameworkName; } else { try { installableFrameworkObj = new FrameworkNameVersioning(installableFramework); } catch (ArgumentException) { // Redist list data was invalid, behave as if it was not defined. installableFrameworkObj = frameworkName; } } return installableFrameworkObj; } private static string GetRedistListFilePath(string referenceAssemblyPath) { string redistListPath = Path.Combine(referenceAssemblyPath, _redistListFolder); return Path.Combine(redistListPath, _redistListFile); } private static IList<string> GetPathToReferenceAssemblies(FrameworkNameVersioning targetFrameworkMoniker) { IList<string> targetFrameworkPaths = null; try { targetFrameworkPaths = ToolLocationHelper.GetPathToReferenceAssemblies(targetFrameworkMoniker); // this returns the chained reference assemblies folders of the framework // ordered from highest to lowest version } catch (InvalidOperationException) { // The chained dirs does not exist // or could not read redistlist for chain } return targetFrameworkPaths; } /// <summary> /// Gets the InstallableFramework by reading the 'InstallableFramework' attribute in the redist file of the target framework /// </summary> /// <param name="redistListFilePath">the path to the redistlist file</param> /// <returns>InstallableFramework</returns> private static string GetInstallableFramework(string redistListFilePath) { string installableFramework = null; try { var doc = new XmlDocument(); var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true }; FileStream fs = File.OpenRead(redistListFilePath); using (XmlReader xr = XmlReader.Create(fs, xrSettings)) { doc.Load(xr); XmlNode fileListNode = doc.DocumentElement; XmlAttribute nameattr = fileListNode?.Attributes["InstallableFramework"]; if (!String.IsNullOrEmpty(nameattr?.Value)) { installableFramework = nameattr.Value; } } } catch (Exception) { } return installableFramework; } private static CompatibleFramework GetSubsetCompatFramework(FrameworkNameVersioning frameworkName) { CompatibleFramework compat = GetFullCompatFramework(frameworkName); compat.Profile = frameworkName.Profile; return compat; } private static CompatibleFramework GetFullCompatFramework(FrameworkNameVersioning frameworkName) { var compat = new CompatibleFramework { Version = frameworkName.Version.ToString(), SupportedRuntime = PatchCLRVersion(Util.GetClrVersion(frameworkName.Version.ToString())), Profile = "Full" }; return compat; } /// <summary> /// conver (MajorVersion).(MinorVersion).(Build).(Revision) to (MajorVersion).(MinorVersion).(Build) /// </summary> /// <param name="version"></param> /// <returns></returns> private static string PatchCLRVersion(string version) { try { var ver = new Version(version); var result = new Version(ver.Major, ver.Minor, ver.Build); return result.ToString(); } catch (ArgumentException) { // continue } catch (FormatException) { // continue } catch (OverflowException) { // continue } return version; } /// <summary> /// Specifies whether the application install will create a shortcut on the desktop /// If True, the installation will create a shortcut to the application on the desktop. /// The default is False /// If Install is False, this value will be ignored /// </summary> [XmlIgnore] public bool CreateDesktopShortcut { get => ConvertUtil.ToBoolean(_createDesktopShortcut); set => _createDesktopShortcut = (value ? "true" : null); } /// <summary> /// Specifies the target framework moniker of this project. /// </summary> [XmlIgnore] public string TargetFrameworkMoniker { get => _targetFrameworkMoniker; set { _targetFrameworkMoniker = value; DiscoverCompatFrameworks(_targetFrameworkMoniker); } } /// <summary> /// A collection of CompatibleFrameworks /// </summary> [XmlIgnore] public CompatibleFrameworkCollection CompatibleFrameworks { get { if (_compatibleFrameworkList == null && _compatibleFrameworks != null) { _compatibleFrameworkList = new CompatibleFrameworkCollection(_compatibleFrameworks); } return _compatibleFrameworkList; } } /// <summary> /// Specifies the update location for the application. /// If this input is not specified then no update location will be defined for the application. /// However, if application updates are specified then the update location must be specified. /// The specified value should be a fully qualified URL or UNC path. /// </summary> [XmlIgnore] public string DeploymentUrl { get => _deploymentUrl; set => _deploymentUrl = value; } /// <summary> /// Specifies whether the application should be blocked from being activated via a URL. /// If this option is True then application can only be activated from the user's Start menu. /// The default is False. /// This option is ignored if the Install property is False. /// </summary> [XmlIgnore] public bool DisallowUrlActivation { get => ConvertUtil.ToBoolean(_disallowUrlActivation); set => _disallowUrlActivation = value ? "true" : null; // NOTE: disallowUrlActivation=false is implied, and Fusion prefers the false case to be unspecified } [XmlIgnore] public override AssemblyReference EntryPoint { get => _entryPoint; set => _entryPoint = value; } /// <summary> /// Specifies the link to use if there is a failure launching the application. /// The specified value should be a fully qualified URL or UNC path. /// </summary> [XmlIgnore] public string ErrorReportUrl { get => _errorReportUrl; set => _errorReportUrl = value; } /// <summary> /// Specifies whether the application is an installed application or an online only application. /// If this flag is True the application will be installed on the user's Start menu, and can be removed from the Add/Remove Programs dialog. /// If this flag is False then the application is intended for online use from a web page. /// The default is True. /// </summary> [XmlIgnore] public bool Install { get => ConvertUtil.ToBoolean(_install); set => _install = Convert.ToString(value, CultureInfo.InvariantCulture); } /// <summary> /// Specifies whether or not the ".deploy" file extension mapping is used. /// If this flag is true then every application file is published with a ".deploy" file extension. /// This option is useful for web server security to limit the number of file extensions that need to be unblocked to enable ClickOnce application deployment. /// The default is false. /// </summary> [XmlIgnore] public bool MapFileExtensions { get => ConvertUtil.ToBoolean(_mapFileExtensions); set => _mapFileExtensions = value ? "true" : null; // NOTE: mapFileExtensions=false is implied, and Fusion prefers the false case to be unspecified } /// <summary> /// Specifies whether or not the user can skip the update. /// If the user has a version less than the minimum required, he or she will not have the option to skip the update. /// The default is to have no minimum required version. /// This input only applies when Install is True. /// </summary> [XmlIgnore] public string MinimumRequiredVersion { get => _minimumRequiredVersion; set => _minimumRequiredVersion = value; } internal override void OnAfterLoad() { base.OnAfterLoad(); if (_entryPoint == null && AssemblyReferences?.Count > 0) { _entryPoint = AssemblyReferences[0]; _entryPoint.ReferenceType = AssemblyReferenceType.ClickOnceManifest; } } internal override void OnBeforeSave() { base.OnBeforeSave(); if (AssemblyIdentity != null && String.IsNullOrEmpty(AssemblyIdentity.PublicKeyToken)) { AssemblyIdentity.PublicKeyToken = "0000000000000000"; } } /// <summary> /// Specifies the name of the application. /// If this input is not specified then the name is inferred from the identity of the generated manifest. /// This name is used for the shortcut name on the Start menu and is part of the name that appears in the Add/Remove Programs dialog. /// </summary> [XmlIgnore] public string Product { get => _product; set => _product = value; } /// <summary> /// Specifies the publisher of the application. /// If this input is not specified then the name is inferred from the registered user, or the identity of the generated manifest. /// This name is used for the folder name on the Start menu and is part of the name that appears in the Add/Remove Programs dialog. /// </summary> [XmlIgnore] public string Publisher { get => _publisher; set => _publisher = value; } /// <summary> /// Specifies the suite name of the application. /// This name is used for the sub-folder name on the Start menu (as a child of the publisher) /// </summary> [XmlIgnore] public string SuiteName { get => _suiteName; set => _suiteName = value; } /// <summary> /// Specifies the link that appears in the Add/Remove Programs dialog for the application. /// The specified value should be a fully qualified URL or UNC path. /// </summary> [XmlIgnore] public string SupportUrl { get => _supportUrl; set => _supportUrl = value; } /// <summary> /// Specifies whether or not URL query-string parameters should be made available to the application. /// The default is False indicating that parameters will not be available to the application. /// </summary> [XmlIgnore] public bool TrustUrlParameters { get => ConvertUtil.ToBoolean(_trustUrlParameters); set => _trustUrlParameters = value ? "true" : null; // NOTE: trustUrlParameters=false is implied, and Fusion prefers the false case to be unspecified } /// <summary> /// Indicates whether or not the application is updatable. /// The default is False. /// This input only applies when Install is True. /// </summary> [XmlIgnore] public bool UpdateEnabled { get => ConvertUtil.ToBoolean(_updateEnabled); set => _updateEnabled = Convert.ToString(value, CultureInfo.InvariantCulture); } /// <summary> /// Specifies the update interval for the application. /// The default is zero. /// This input only applies when Install and UpdateEnabled are both True. /// </summary> [XmlIgnore] public int UpdateInterval { get { try { return Convert.ToInt32(_updateInterval, CultureInfo.InvariantCulture); } catch (ArgumentException) { return 1; } catch (FormatException) { return 1; } } set => _updateInterval = Convert.ToString(value, CultureInfo.InvariantCulture); } /// <summary> /// Specifies whether updates should be checked in the foreground before starting the application, or in the background as the application is running. /// The default is "Background". /// This input only applies when Install and UpdateEnabled are both True. /// </summary> [XmlIgnore] public UpdateMode UpdateMode { get { try { return (UpdateMode)Enum.Parse(typeof(UpdateMode), _updateMode, true); } catch (FormatException) { return UpdateMode.Foreground; } catch (ArgumentException) { return UpdateMode.Foreground; } } set => _updateMode = value.ToString(); } /// <summary> /// Specifies the units for UpdateInterval input. /// This input only applies when Install and UpdateEnabled are both True. /// </summary> [XmlIgnore] public UpdateUnit UpdateUnit { get { try { return (UpdateUnit)Enum.Parse(typeof(UpdateUnit), _updateUnit, true); } catch (FormatException) { return UpdateUnit.Days; } catch (ArgumentException) { return UpdateUnit.Days; } } set => _updateUnit = value.ToString(); } [UnconditionalSuppressMessage("TrimAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Validation reads the entry-point ClickOnce manifest with XmlSerializer; the manifest model is inherently incompatible with trimming.")] [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", Justification = "Validation reads the entry-point ClickOnce manifest with XmlSerializer and XslCompiledTransform; the manifest model is inherently incompatible with Native AOT.")] public override void Validate() { base.Validate(); ValidateDeploymentProvider(); ValidateMinimumRequiredVersion(); ValidatePlatform(); ValidateEntryPoint(); } private void ValidateDeploymentProvider() { if (!String.IsNullOrEmpty(_deploymentUrl) && PathUtil.IsLocalPath(_deploymentUrl)) { OutputMessages.AddWarningMessage("GenerateManifest.InvalidDeploymentProvider"); } } [RequiresUnreferencedCode("Reads the entry-point ClickOnce manifest with XmlSerializer; members may be trimmed.")] [RequiresDynamicCode("Reads the entry-point ClickOnce manifest with XmlSerializer and XslCompiledTransform; both require runtime code generation not supported with Native AOT.")] private void ValidateEntryPoint() { if (_entryPoint != null) { if (!String.IsNullOrEmpty(_entryPoint.TargetPath) && !_entryPoint.TargetPath.EndsWith( ".manifest", StringComparison.OrdinalIgnoreCase)) { OutputMessages.AddErrorMessage("GenerateManifest.InvalidEntryPoint", _entryPoint.ToString()); } string manifestPath = _entryPoint.ResolvedPath; if (manifestPath == null) { manifestPath = Path.Combine(Path.GetDirectoryName(SourcePath), _entryPoint.TargetPath); } if (FileSystems.Default.FileExists(manifestPath)) { ApplicationManifest entryPointManifest = ManifestReader.ReadManifest(manifestPath, false) as ApplicationManifest; if (entryPointManifest != null) { if (Install) { if (entryPointManifest.HostInBrowser) { OutputMessages.AddErrorMessage("GenerateManifest.HostInBrowserNotOnlineOnly"); } } else { if (entryPointManifest.FileAssociations?.Count > 0) { OutputMessages.AddErrorMessage("GenerateManifest.FileAssociationsNotInstalled"); } } } } } } private void ValidateMinimumRequiredVersion() { if (!String.IsNullOrEmpty(_minimumRequiredVersion)) { var v1 = new Version(_minimumRequiredVersion); var v2 = new Version(AssemblyIdentity.Version); if (v1 > v2) { OutputMessages.AddErrorMessage("GenerateManifest.GreaterMinimumRequiredVersion"); } } } #region " XmlSerializer " [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("CreateDesktopShortcut")] public string XmlCreateDesktopShortcut { get => _createDesktopShortcut?.ToLowerInvariant(); set => _createDesktopShortcut = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlArray("CompatibleFrameworks")] public CompatibleFramework[] XmlCompatibleFrameworks { get => _compatibleFrameworks.Count > 0 ? _compatibleFrameworks.ToArray() : null; set => _compatibleFrameworks = new List<CompatibleFramework>(value); } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("DeploymentUrl")] public string XmlDeploymentUrl { get => _deploymentUrl; set => _deploymentUrl = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("DisallowUrlActivation")] public string XmlDisallowUrlActivation { get => _disallowUrlActivation?.ToLowerInvariant(); set => _disallowUrlActivation = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("ErrorReportUrl")] public string XmlErrorReportUrl { get => _errorReportUrl; set => _errorReportUrl = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("Install")] public string XmlInstall { get => !String.IsNullOrEmpty(_install) ? _install.ToLower(CultureInfo.InvariantCulture) : "true"; // NOTE: Install attribute shouldn't be null in the manifest, so specify install="true" by default set => _install = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("MapFileExtensions")] public string XmlMapFileExtensions { get => _mapFileExtensions?.ToLowerInvariant(); set => _mapFileExtensions = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("MinimumRequiredVersion")] public string XmlMinimumRequiredVersion { get => _minimumRequiredVersion; set => _minimumRequiredVersion = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("Product")] public string XmlProduct { get => _product; set => _product = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("Publisher")] public string XmlPublisher { get => _publisher; set => _publisher = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("SuiteName")] public string XmlSuiteName { get => _suiteName; set => _suiteName = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("SupportUrl")] public string XmlSupportUrl { get => _supportUrl; set => _supportUrl = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("TrustUrlParameters")] public string XmlTrustUrlParameters { get => _trustUrlParameters?.ToLowerInvariant(); set => _trustUrlParameters = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("UpdateEnabled")] public string XmlUpdateEnabled { get => _updateEnabled?.ToLower(CultureInfo.InvariantCulture); set => _updateEnabled = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("UpdateInterval")] public string XmlUpdateInterval { get => _updateInterval; set => _updateInterval = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("UpdateMode")] public string XmlUpdateMode { get => _updateMode; set => _updateMode = value; } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [XmlAttribute("UpdateUnit")] public string XmlUpdateUnit { get => _updateUnit?.ToLower(CultureInfo.InvariantCulture); set => _updateUnit = value; } #endregion } }