File: Tasks\MsDeploy\MSDeploy.cs
Web Access
Project: ..\..\..\src\WebSdk\Publish\Tasks\Microsoft.NET.Sdk.Publish.Tasks.csproj (Microsoft.NET.Sdk.Publish.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
using CultureInfo = System.Globalization.CultureInfo;
using Framework = Microsoft.Build.Framework;
using Utilities = Microsoft.Build.Utilities;
 
namespace Microsoft.NET.Sdk.Publish.Tasks.MsDeploy
{
    /// <summary>
    /// The MSDeploy task, which is a wrapper around msdeploy.exe
    /// </summary>
    public class MSDeploy : Utilities.ToolTask
    {
        /* 
         *Microsoft (R) Web Deployment Command Line Tool (MSDeploy.exe)
            Version 7.1.495.0
            Copyright (c) Microsoft Corporation. All rights reserved.
 
            MSDeploy.exe <-verb:<name>> <-source:<object>> [-dest:<object>] [args ...]
 
              -verb:<name>                   Action to perform (required).
              -source:<object>               The source object for the operation 
                                             (required).
              -dest:<object>                 The destination object for the operation. 
              -declareParam:<parms>          Declares a parameter for synchronization.
              -setParam:<parms>              Sets a parameter for synchronization.
              -disableLink:<name>            Disables the specified link extension(s).
              -enableLink:<name>             Enables the specified link extension(s).
              -disableRule:<name>            Disables the specified synchronization 
                                             rule(s).
              -enableRule:<name>             Enables the specified synchronization rule(s).
              -replace:<arg settings>        Specifies an attribute replacement rule.
              -retryAttempts                 The number of times a provider will retry 
                                             after a failed action (not all providers 
                                             support retrying). Defaults to 5.
              -retryInterval                 Interval in milliseconds between retry 
                                             attempts (-retryAttempts). The default is 
                                             1000.
              -skip:<arg settings>           Specifies an object to skip during 
                                             synchronization.
              -disableSkipDirective:<name>   Disables the specified skip directive.
              -enableSkipDirective:<name>    Enables the specified skip directive.
              -useAdminShares                When possible, use UNC admin shares for file 
                                             synchronization.
              -verbose                       Enables more verbose output.
              -whatif                        Displays what would have happened without 
                                             actually performing any operations.
              -xpath:<path>                  An XPath expression to apply to XML output.
              -xml                           Return results in XML format.
              -allowUntrusted                Allow untrusted server certificate when using 
                                             SSL.
              -showSecure                    Show secure attributes in XML output instead 
                                             of hiding them.^
              -preSync:<command>             A command to execute before the 
                                             synchronization on the destination.  For 
                                             instance, net stop a service.
              -postSync:<command>            A command to execute after the 
                                             synchronization on the destination.  For 
                                             instance, net start a service.
         * 
         * 
         *         // not documented, part of IISExpress
              public const string AppHostConfigDirectory = "-appHostConfigDir";
         *    public const string WebServerDirectory = "-webServerDir";
              public const string WebServerManifest = "-webServerManifest";
 
 
              
            Supported Verbs:
 
              dump                           Displays the details of the specified source 
                                             object.
              migrate                        Migrates the source object to the destination 
                                             object.
              sync                           Synchronizes the destination object with the 
                                             source object.
              delete                         Deletes specified destination object.
              getDependencies                Retrieve dependencies for given object
              getParameters                  Return parameters supported by object
              getSystemInfo                  Retrieve system information associated with 
                                             given object
 
            <object> format:
 
              provider-type=[provider-path],[provider settings],...
              
            Supported provider-types (and sample paths, if applicable):
 
              appHostConfig                  IIS 7+ configuration
              appHostSchema                  IIS 7+ configuration schema
              appPoolConfig                  IIS 7+ Application Pool
              archiveDir                     Archive directory
              auto                           Automatic destination
              cert                           Certificate
              comObject32                    32-bit COM object
              comObject64                    64-bit COM object
              contentPath                    File System Content
              dbFullSql                      Deploy SQL database
              dbMySql                        Deploy MySql database
              delete                         Special source-only provider used to delete a 
                                             given destination.
              fcgiExtConfig                  FcgiExt.ini settings or fastCgi section 
                                             configuration
              gacAssembly                    GAC assembly
              iisApp                         Web Application
              machineConfig32                .NET 32-bit machine configuration
              machineConfig64                .NET 64-bit machine configuration
              manifest                       Custom manifest file
              metaKey                        Metabase key
              package                        A .zip file package
              regKey                         Registry key
              regValue                       Registry value
              rootWebConfig32                .NET 32-bit root Web configuration
              rootWebConfig64                .NET 64-bit root Web configuration
              runCommand                     Runs a command on the destination when sync 
                                             is called.
              setAcl                         Grant permissions
              urlScanConfig                  UrlScan.ini settings or requestFiltering 
                                             section configuration
              webServer                      Full IIS 7+ Web server
              webServer60                    Full IIS 6.0 Web server
 
 
            Common settings (can be used with all providers):
 
                computerName=<name>       Name of remote computer or proxy-URL
                wmsvc=<name>              Name of remote computer or proxy-URL for the Web 
                                          Management Service (WMSvc). Assumes that the 
                                          service is listening on port 8172.
                authtype=<name>           Authentication scheme to use. NTLM is the 
                                          default setting. If the wmsvc option is 
                                          specified, then Basic is the default setting.
                userName=<name>           User name to authenticate for remote connections 
                                          (required if using Basic authentication).
                password=<password>       Password of the user for remote connections 
                                          (required if using Basic authentication).
                encryptPassword=<pwd>     Password to use for encrypting/decrypting any 
                                          secure data.
                includeAcls=<bool>        If true, include ACLs in the operation (applies 
                                          to the file system, registry, and metabase).
                useStatusRequest=<bool>   Controls whether remote destination 
                                          synchronization status should appear 
                                          immediately. The default setting is true.
                tempAgent=<bool>          Temporarily install the remote agent for the 
                                          duration of a remote operation
                
    
        */
 
        private string? m_exePath;
        private string? m_disableRule;
        private string? m_verb;
        private string? m_failureLevel;
        private string? m_xpath;
        private string? m_enableRule;
        private string? m_replace;
        private string? m_skip;
        private string? m_disableLink;
        private string? m_enableLink;
        private string? m_disableSkipDirective;
        private string? m_enableSkipDirective;
        private string? m_lastCommandLine;
        private bool m_xml;
        private bool m_whatif;
        private bool m_useChecksum;
        private bool m_verbose;
        private bool m_allowUntrusted;
        private bool m_enableTransaction;
        private int m_retryAttempts;
        private int m_retryInterval;
        private bool m_useDoubleQuoteForValue = false;
        private string? m_strValueQuote = null;
 
        //public const string AppHostConfigDirectory = "-appHostConfigDir";
        // *    public const string WebServerDirectory = "-webServerDir";
        //      public const string WebServerManifest = "-webServerManifest";
 
        public string? WebServerAppHostConfigDirectory { get; set; }
        public string? WebServerDirectory { get; set; }
        public string? WebServerManifest { get; set; }
 
        private Framework.ITaskItem[]? m_sourceITaskItem = null;
        private Framework.ITaskItem[]? m_destITaskItem = null;
        private Framework.ITaskItem[]? m_replaceRuleItemsITaskItem = null;
        private Framework.ITaskItem[]? m_skipRuleItemsITaskItem = null;
        private Framework.ITaskItem[]? m_declareParameterItems = null;
        private Framework.ITaskItem[]? m_importDeclareParametersItems = null;
        private Framework.ITaskItem[]? m_simpleSetParameterItems = null;
        private Framework.ITaskItem[]? m_importSetParametersItems = null;
        private Framework.ITaskItem[]? m_setParameterItems = null;
 
        private bool m_previewOnly = false;
 
        public class Provider
        {
            public static readonly string Unknown = "Unknown";
            public static readonly string Package = "Package";
            public static readonly string ArchiveDir = "ArchiveDir";
            public static readonly string DbDacFx = "DbDacFx";
            public static readonly string MetaKey = "MetaKey";
            public static readonly string AppHostConfig = "AppHostConfig";
            public static readonly string DBFullSql = "DBFullSql";
            public static readonly string DBCodeFirst = "DBCodeFirst";
        }
 
        public class TypeName
        {
            public static readonly string DeploymentWellKnownProvider = "Microsoft.Web.Deployment.DeploymentWellKnownProvider";
            public static readonly string DeploymentEncryptionException = "Microsoft.Web.Deployment.DeploymentEncryptionException";
            public static readonly string DeploymentException = "Microsoft.Web.Deployment.DeploymentException";
        }
 
        public class Extensions
        {
            public static readonly string DbFullSql = ".sql";
            public static readonly string DbDacFx = ".dacpac";
        }
 
        /// <summary>
        /// Location for the MSdeploy.exe path
        /// </summary>
        public string? ExePath
        {
            get
            {
#if NET472
                if (string.IsNullOrEmpty(m_exePath))
                {
                    // if path is not set, we optimize to latest version of msdeploy
                    using (Win32.RegistryKey registryKey = Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\IIS Extensions\MSDeploy"))
                    {
                        if (registryKey != null)
                        {
                            string[] strVersions = registryKey.GetSubKeyNames();
                            if (strVersions != null)
                            {
                                int [] versions = registryKey.GetSubKeyNames().Select(p => Convert.ToInt32(p)).ToArray();
                                Array.Sort(versions);
 
                                for (int i = versions.Length -1; i >= 0; i--)
                                {
                                    int version = versions[i];
                                    using (Win32.RegistryKey versionRegistry = registryKey.OpenSubKey(version.ToString(CultureInfo.InvariantCulture)))
                                    {
                                        if (versionRegistry != null)
                                        {
                                            m_exePath = versionRegistry.GetValue(@"InstallPath").ToString();
                                            if (!string.IsNullOrEmpty(m_exePath))
                                            {
                                                // found the valid m_exePath
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
#else
                if (string.IsNullOrEmpty(m_exePath))
                {
                    string programFiles = Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%");
                    string msdeployExePath = Path.Combine("IIS", "Microsoft Web Deploy V3");
                    m_exePath = Path.Combine(programFiles, msdeployExePath);
                    if (!File.Exists(Path.Combine(m_exePath, ToolName)))
                    {
                        /// On 32-bit Operating Systems, this will return C:\Program Files
                        /// On 64-bit Operating Systems - regardless of process bitness, this will return C:\Program Files
                        if (!Environment.Is64BitOperatingSystem || Environment.Is64BitProcess)
                        {
                            programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
                            m_exePath = Path.Combine(programFiles, msdeployExePath);
                        }
                        else
                        {
                            // 32 bit process on a 64 bit OS can't use SpecialFolder.ProgramFiles to get the 64-bit program files folder
                            programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
                            m_exePath = Path.Combine(programFiles, msdeployExePath);
                        }
                    }
                }
#endif
                return m_exePath;
            }
            set { m_exePath = value; }
        }
 
        public string? DisableRule
        {
            get { return m_disableRule; }
            set { m_disableRule = value; }
        }
 
        [Framework.Required]
        public string? Verb
        {
            get { return m_verb; }
            set { m_verb = value; }
        }
 
        [Framework.Required]
        public Framework.ITaskItem[]? Source
        {
            get { return m_sourceITaskItem; }
            set { m_sourceITaskItem = value; }
        }
 
        public Framework.ITaskItem[]? Destination
        {
            get { return m_destITaskItem; }
            set { m_destITaskItem = value; }
        }
        public bool WhatIf
        {
            get { return m_whatif; }
            set { m_whatif = value; }
        }
 
        public bool OptimisticParameterDefaultValue { get; set; }
 
        public bool UseChecksum
        {
            get { return m_useChecksum; }
            set { m_useChecksum = value; }
        }
 
        public bool AllowUntrusted
        {
            get { return m_allowUntrusted; }
            set { m_allowUntrusted = value; }
        }
 
        public bool Verbose
        {
            get { return m_verbose; }
            set { m_verbose = value; }
        }
        public string? FailureLevel
        {
            get { return m_failureLevel; }
            set { m_failureLevel = value; }
        }
        public bool Xml
        {
            get { return m_xml; }
            set { m_xml = value; }
        }
        public string? XPath
        {
            get { return m_xpath; }
            set { m_xpath = value; }
        }
        public string? EnableRule
        {
            get { return m_enableRule; }
            set { m_enableRule = value; }
        }
        public string? Replace
        {
            get { return m_replace; }
            set { m_replace = value; }
        }
        public string? Skip
        {
            get { return m_skip; }
            set { m_skip = value; }
        }
        public string? DisableLink
        {
            get { return m_disableLink; }
            set { m_disableLink = value; }
        }
 
        public string? EnableLink
        {
            get { return m_enableLink; }
            set { m_enableLink = value; }
        }
 
        public bool EnableTransaction
        {
            get { return m_enableTransaction; }
            set { m_enableTransaction = value; }
        }
        public int RetryAttempts
        {
            get { return m_retryAttempts; }
            set { m_retryAttempts = value; }
        }
        public int RetryInterval
        {
            get { return m_retryInterval; }
            set { m_retryInterval = value; }
        }
 
        public bool UseDoubleQuoteForValue
        {
            get { return m_useDoubleQuoteForValue; }
            set
            {
                m_useDoubleQuoteForValue = value;
                m_strValueQuote = (m_useDoubleQuoteForValue) ? "\"" : null;
            }
        }
 
        public Framework.ITaskItem[]? ReplaceRuleItems
        {
            get { return m_replaceRuleItemsITaskItem; }
            set { m_replaceRuleItemsITaskItem = value; }
        }
 
        public Framework.ITaskItem[]? SkipRuleItems
        {
            get { return m_skipRuleItemsITaskItem; }
            set { m_skipRuleItemsITaskItem = value; }
        }
 
        public string? DisableSkipDirective
        {
            get { return m_disableSkipDirective; }
            set { m_disableSkipDirective = value; }
        }
 
        public string? EnableSkipDirective
        {
            get { return m_enableSkipDirective; }
            set { m_enableSkipDirective = value; }
        }
 
        public Framework.ITaskItem[]? DeclareParameterItems
        {
            get { return m_declareParameterItems; }
            set { m_declareParameterItems = value; }
        }
        public Framework.ITaskItem[]? ImportDeclareParametersItems
        {
            get { return m_importDeclareParametersItems; }
            set { m_importDeclareParametersItems = value; }
        }
 
        public Framework.ITaskItem[]? ImportSetParametersItems
        {
            get { return m_importSetParametersItems; }
            set { m_importSetParametersItems = value; }
        }
 
        public Framework.ITaskItem[]? SimpleSetParameterItems
        {
            get { return m_simpleSetParameterItems; }
            set { m_simpleSetParameterItems = value; }
        }
 
        public Framework.ITaskItem[]? SetParameterItems
        {
            get { return m_setParameterItems; }
            set { m_setParameterItems = value; }
        }
 
        public Framework.ITaskItem[]? AdditionalDestinationProviderOptions { get; set; }
 
        private string? _userAgent;
        public string? UserAgent
        {
            get { return _userAgent; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    _userAgent = Utility.GetFullUserAgentString(value);
                }
            }
        }
 
        [Framework.Output]
        public string CommandLine
        {
            get
            {
                return string.Concat("\"", GenerateFullPathToTool(), "\" ", GenerateCommandLineCommands());
            }
        }
 
        [Framework.Output]
        public string CommandLineArguments
        {
            get
            {
                return GenerateCommandLineCommands();
            }
        }
 
        [Framework.Output]
        public string MSDeployToolPath
        {
            get
            {
                return string.Concat("\"", GenerateFullPathToTool(), "\" ");
            }
        }
 
        /// <summary>
        /// This enable us not to execute the msdeploy, but just have the output of PreviewMSDeploy
        /// </summary>
        public bool PreviewCommandLineOnly
        {
            get
            {
                return m_previewOnly;
            }
            set
            {
                m_previewOnly = value;
            }
        }
 
        // controlling whether a task should be execute.
        protected override bool SkipTaskExecution()
        {
            if (PreviewCommandLineOnly)
                return true;
            else
                return base.SkipTaskExecution();
        }
 
        protected override Framework.MessageImportance StandardOutputLoggingImportance
        {
            get
            {
                return Framework.MessageImportance.High;
            }
        }
 
        /// <summary>
        /// Override the Execute method to be able to send ExternalProjectStarted/Finished events.
        /// </summary>
        /// <returns></returns>
        public override bool Execute()
        {
            bool bSuccess = false;
            if (PreviewCommandLineOnly)
            {
                // useful information about what was wrong with the parameters.
                if (!ValidateParameters())
                {
                    return false;
                }
                Log.LogMessage(Framework.MessageImportance.Low, Resources.MSDEPLOY_EXE_PreviewOnly);
                return true;
            }
 
            try
            {
                Log.LogMessage(Framework.MessageImportance.Normal, Resources.MSDEPLOY_EXE_Start);
                bSuccess = base.Execute();
                if (bSuccess)
                    Log.LogMessage(Framework.MessageImportance.Normal, Resources.MSDEPLOY_EXE_Succeeded);
            }
            catch (Exception ex)
            {
                // Log Failure
                Log.LogMessage(Framework.MessageImportance.High, Resources.MSDEPLOY_EXE_Failed);
                Log.LogErrorFromException(ex);
                bSuccess = false;
            }
            if (bSuccess)
            {
                string type = string.Empty;
                string path = string.Empty;
                Framework.ITaskItem? taskItem = null;
                if (Destination != null && Destination.GetLength(0) == 1)
                {
                    taskItem = Destination[0];
                }
                else
                {
                    if (Source != null)
                        taskItem = Source[0];
                }
                if (taskItem != null)
                {
                    type = taskItem.ItemSpec;
                    path = taskItem.GetMetadata("Path");
                }
                Utility.MsDeployExeEndOfExecuteMessage(bSuccess, type, path, Log);
            }
            return bSuccess;
        }
 
        // utility function to add the replace rule for the option
        public static void AddReplaceRulesToOptions(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? replaceRuleItems, string? valueQuoteChar)
        {
            if (commandLineBuilder is not null && replaceRuleItems is not null)// Dev10 bug 496639 foreach will throw the exception if the replaceRuleItem is null
            {
                List<string> arguments = new(6);
 
                foreach (Framework.ITaskItem item in replaceRuleItems)
                {
                    arguments.Clear();
                    Utility.BuildArgumentsBaseOnEnumTypeName(item, arguments, typeof(ReplaceRuleMetadata), valueQuoteChar);
                    commandLineBuilder.AppendSwitchUnquotedIfNotNull("-replace:", arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
                }
            }
        }
 
        public static void AddSkipDirectiveToBaseOptions(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? skipRuleItems, string? valueQuoteChar)
        {
            if (commandLineBuilder is not null && skipRuleItems is not null)// Dev10 bug 496639 foreach will throw the exception if the replaceRuleItem is null
            {
                List<string> arguments = new(6);
 
                foreach (Framework.ITaskItem item in skipRuleItems)
                {
                    arguments.Clear();
                    Utility.BuildArgumentsBaseOnEnumTypeName(item, arguments, typeof(SkipRuleMetadata), valueQuoteChar);
                    commandLineBuilder.AppendSwitchUnquotedIfNotNull("-skip:", arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
                }
            }
        }
 
        public static void AddDeclareParameterToCommandArgument(List<string> arguments,
                                                                Framework.ITaskItem? item,
                                                                string? valueQuote,
                                                                Dictionary<string, string> lookupDictionary)
        {
            if (arguments is not null && item is not null && lookupDictionary is not null)
            {
                // special for the name
                arguments.Clear();
                List<string> idenities = new(6);
 
                string name = item.ItemSpec;
                if (!string.IsNullOrEmpty(name))
                {
                    string element = item.GetMetadata(ExistingParameterValidationMetadata.Element.ToString());
                    if (string.IsNullOrEmpty(element))
                        element = "parameterEntry";
                    if (string.Compare(element, "parameterEntry", StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        idenities.Add(name);
                        foreach (string dPIdentity in Enum.GetNames(typeof(ExistingDeclareParameterMetadata)))
                        {
                            idenities.Add(item.GetMetadata(dPIdentity));
                        }
 
                        string identity = string.Join(",", idenities.ToArray());
                        if (!lookupDictionary.ContainsKey(identity))
                        {
                            string nameValue = Utility.PutValueInQuote(name, valueQuote);
                            arguments.Add(string.Concat("name=", nameValue));
                            Type enumType = lookupDictionary.ContainsValue(name) ? typeof(ExistingDeclareParameterMetadata) : typeof(DeclareParameterMetadata);
                            // the rest, build on the Enum Name
                            Utility.BuildArgumentsBaseOnEnumTypeName(item, arguments, enumType, valueQuote);
                            lookupDictionary.Add(identity, name);
                        }
                    }
                    else if (string.Compare(element, "parameterValidation", StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        System.Diagnostics.Debug.Assert(false, "msdeploy.exe doesn't support parameterValidation entry in the command line for declareParameter yet.");
                    }
                }
            }
        }
 
        /// <summary>
        /// Utility to build DeclareParameterOptions
        /// </summary>
        /// <param name="commandLineBuilder"></param>
        /// <param name="items"></param>
        public static void AddDeclareParametersOptions(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? originalItems, string? valueQuote, bool foptimisticParameterDefaultValue)
        {
            IList<Framework.ITaskItem> items = Utility.SortParametersTaskItems(originalItems, foptimisticParameterDefaultValue, DeclareParameterMetadata.DefaultValue.ToString());
            if (commandLineBuilder is not null && items is not null)
            {
                List<string> arguments = new(6);
                Dictionary<string, string> lookupDictionary = new(items.Count);
 
                foreach (Framework.ITaskItem item in items)
                {
                    AddDeclareParameterToCommandArgument(arguments, item, valueQuote, lookupDictionary);
                    commandLineBuilder.AppendSwitchUnquotedIfNotNull("-declareParam:", arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
                }
            }
        }
 
        public static void AddImportDeclareParametersFilesOptions(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? items)
        {
            AddImportParametersFilesOptions(commandLineBuilder, "-declareParamFile:", items);
        }
 
        public static void AddImportSetParametersFilesOptions(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? items)
        {
            AddImportParametersFilesOptions(commandLineBuilder, "-setParamFile:", items);
        }
 
        internal static void AddImportParametersFilesOptions(Utilities.CommandLineBuilder commandLineBuilder, string parameterFlag, Framework.ITaskItem[]? items)
        {
            if (commandLineBuilder is not null && !string.IsNullOrEmpty(parameterFlag) && items is not null)// Dev10 bug 496639 foreach will throw the exception if the replaceRuleItem is null
            {
                foreach (Framework.ITaskItem item in items)
                {
                    string fileName = item.ItemSpec;
                    if (!string.IsNullOrEmpty(fileName))
                    {
                        commandLineBuilder.AppendSwitch(string.Concat(parameterFlag, "\"", fileName, "\""));
                    }
                }
            }
        }
 
        /// <summary>
        /// Utility function to set SimpleSyncParameter Name/Value
        /// </summary>
        /// <param name="commandLineBuilder"></param>
        /// <param name="items"></param>
        public static void AddSimpleSetParametersToObject(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? originalItems, string? valueQuoteChar, bool foptimisticParameterDefaultValue)
        {
            IList<Framework.ITaskItem> items = Utility.SortParametersTaskItems(originalItems, foptimisticParameterDefaultValue, SimpleSyncParameterMetadata.Value.ToString());
            if (commandLineBuilder is not null && items is not null)
            {
                List<string> arguments = new(6);
                foreach (Framework.ITaskItem item in items)
                {
                    arguments.Clear();
 
                    // special for name
                    string name = item.ItemSpec;
                    if (!string.IsNullOrEmpty(name))
                    {
                        string valueData = Utility.PutValueInQuote(name, valueQuoteChar);
                        arguments.Add(string.Concat("name=", valueData));
                    }
                    // the rest, build on the enum name
 
                    Utility.BuildArgumentsBaseOnEnumTypeName(item, arguments, typeof(SimpleSyncParameterMetadata), valueQuoteChar);
                    commandLineBuilder.AppendSwitchUnquotedIfNotNull("-setParam:", arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
                }
            }
        }
 
        /// <summary>
        /// Utility function to setParameters in type, scope, match, value of SyncParameter
        /// </summary>
        /// <param name="commandLineBuilder"></param>
        /// <param name="items"></param>
        public static void AddSetParametersToObject(Utilities.CommandLineBuilder commandLineBuilder, Framework.ITaskItem[]? originalItems, string? valueQuote, bool foptimisticParameterDefaultValue)
        {
            IList<Framework.ITaskItem> items = Utility.SortParametersTaskItems(originalItems, foptimisticParameterDefaultValue, ExistingSyncParameterMetadata.Value.ToString());
            if (commandLineBuilder is not null && items is not null)
            {
                List<string> arguments = new(6);
                Dictionary<string, string> lookupDictionary = new(items.Count);
                Dictionary<string, string> nameValueDictionary = new(items.Count, StringComparer.OrdinalIgnoreCase);
 
                foreach (Framework.ITaskItem item in items)
                {
                    arguments.Clear();
 
                    List<string> identities = new(6);
 
                    string name = item.ItemSpec;
                    if (!string.IsNullOrEmpty(name))
                    {
                        string element = item.GetMetadata(ExistingParameterValidationMetadata.Element.ToString());
                        if (string.IsNullOrEmpty(element))
                            element = "parameterEntry";
 
                        if (string.Compare(element, "parameterEntry", StringComparison.OrdinalIgnoreCase) == 0)
                        {
 
                            identities.Add(name);
                            foreach (string dPIdentity in Enum.GetNames(typeof(ExistingDeclareParameterMetadata)))
                            {
                                identities.Add(item.GetMetadata(dPIdentity));
                            }
 
                            string identity = string.Join(",", identities.ToArray());
                            if (!lookupDictionary.ContainsKey(identity))
                            {
                                string? data = null;
                                bool fExistingName = nameValueDictionary.ContainsKey(name);
 
                                if (nameValueDictionary.ContainsKey(name))
                                {
                                    data = nameValueDictionary[name];
                                }
                                else
                                {
                                    data = item.GetMetadata(ExistingSyncParameterMetadata.Value.ToString());
                                    nameValueDictionary.Add(name, data);
                                }
 
                                // the rest, build on the Enum Name
                                Utility.BuildArgumentsBaseOnEnumTypeName(item, arguments, typeof(ExistingDeclareParameterMetadata), valueQuote);
                                if (arguments.Count > 0 && !string.IsNullOrEmpty(data))
                                {
#if NET472
                                    arguments.Add(string.Concat(ExistingSyncParameterMetadata.Value.ToString().ToLower(CultureInfo.InvariantCulture),
                                                            "=", Utility.PutValueInQuote(data, valueQuote)));
#else
                                    arguments.Add(string.Concat(ExistingSyncParameterMetadata.Value.ToString().ToLower(),
                                                            "=", Utility.PutValueInQuote(data, valueQuote)));
#endif
                                }
                                lookupDictionary.Add(identity, name);
 
                            }
                        }
                        else if (string.Compare(element, "parameterValidation", StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            System.Diagnostics.Debug.Assert(false, "msdeploy.exe doesn't support parameterValidation entry in the command line for declareParameter yet.");
                        }
                    }
                    commandLineBuilder.AppendSwitchUnquotedIfNotNull("-setParam:", arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
                }
            }
        }
 
        public static void AddDestinationProviderSettingToObject(Utilities.CommandLineBuilder commandLineBuilder, string? dest, Framework.ITaskItem[]? items, string? valueQuoteChar,
                                                                Framework.ITaskItem[]? additionalProviderItems, MSDeploy msdeploy)
        {
            //commandLineBuilder.AppendSwitchUnquotedIfNotNull("-source:", m_source);
            //commandLineBuilder.AppendSwitchUnquotedIfNotNull("-dest:", m_dest);
            List<string> arguments = new(6);
 
            if (items != null && items.GetLength(0) == 1)
            {
                Framework.ITaskItem taskItem = items[0];
                string provider = taskItem.ItemSpec;
                string path = taskItem.GetMetadata("Path");
                string valueData = Utility.PutValueInQuote(path, valueQuoteChar);
                string setData = (string.IsNullOrEmpty(path)) ? provider : string.Concat(provider, "=", valueData);
                arguments.Add(setData);
 
                //Commonly supported provider settings:
                //    computerName=<name>     Name of remote computer or proxy-URL
                //    wmsvc=<name>            Name of remote computer or proxy-URL for the Web
                //                            Management Service (wmsvc)
                //    userName=<name>         User name to authenticate
                //    password=<password>     Password of user name
                //    encryptPassword=<pwd>   Password to use for encryption related operations
                //    includeAcls=<bool>      If true, include ACLs in the operation for the
                //                            specified path
 
                foreach (string name in taskItem.MetadataNames)
                {
                    if (!Utility.IsInternalMsdeployWellKnownItemMetadata(name))
                    {
                        string value = taskItem.GetMetadata(name);
                        if (!string.IsNullOrEmpty(value))
                        {
                            valueData = Utility.PutValueInQuote(value, valueQuoteChar);
                            setData = string.Concat(name, "=", valueData);
                            arguments.Add(setData);
                        }
                    }
                    else
                    {
                        Utility.IISExpressMetadata expressMetadata;
                        if (Enum.TryParse(name, out expressMetadata))
                        {
                            string value = taskItem.GetMetadata(name);
                            if (!string.IsNullOrEmpty(value))
                            {
                                switch (expressMetadata)
                                {
                                    case Utility.IISExpressMetadata.WebServerAppHostConfigDirectory:
                                        msdeploy.WebServerAppHostConfigDirectory = value;
                                        break;
                                    case Utility.IISExpressMetadata.WebServerDirectory:
                                        msdeploy.WebServerDirectory = value;
                                        break;
                                    case Utility.IISExpressMetadata.WebServerManifest:
                                        msdeploy.WebServerManifest = value;
                                        break;
                                }
                            }
                        }
                    }
                }
            }
 
            // If additional parameters are specified, we add these too. the itemSpec will be something like iisApp, contentPath, etc and 
            // each item should have a name\value pair defined as metadata. Each provider will be written as itemSpec.Name=Value
            if (additionalProviderItems != null)
            {
                foreach (Framework.ITaskItem item in additionalProviderItems)
                {
                    if (!string.IsNullOrEmpty(item.ItemSpec))
                    {
                        string settingName = item.GetMetadata("Name");
                        string settingValue = item.GetMetadata("Value");
                        if (!string.IsNullOrEmpty(settingName) && !string.IsNullOrEmpty(settingValue))
                        {
                            string providerString = string.Concat(item.ItemSpec, ".", settingName, "=", settingValue);
                            arguments.Add(providerString);
                        }
                    }
                }
            }
 
            commandLineBuilder.AppendSwitchUnquotedIfNotNull(dest, arguments.Count == 0 ? null : string.Join(",", arguments.ToArray()));
            return;
        }
 
        /// <summary>
        /// Utility function to help to generate Switch per item
        /// </summary>
        /// <param name="commandLine"></param>
        /// <param name="strSwitch"></param>
        /// <param name="args"></param>
        private static void GenerateSwitchPerItem(Utilities.CommandLineBuilder commandLine, string strSwitch, string? args)
        {
            if (args is not null && args.Length != 0)
            {
                foreach (string dl in args.Split(new char[] { ';' }))
                {
                    if (!string.IsNullOrEmpty(dl))
                    {
                        commandLine.AppendSwitchUnquotedIfNotNull(strSwitch, dl);
                    }
                }
            }
        }
 
        internal static void IncorporateSettingsFromHostObject(ref Framework.ITaskItem[]? skipRuleItems, Framework.ITaskItem[]? destProviderSetting, IEnumerable<Framework.ITaskItem>? hostObject)
        {
            if (hostObject != null)
            {
                //retrieve user credentials
                Framework.ITaskItem? credentialItem = hostObject.FirstOrDefault(p => p.ItemSpec == VSMsDeployTaskHostObject.CredentialItemSpecName);
                if (credentialItem != null && destProviderSetting != null && destProviderSetting[0] != null)
                {
                    Framework.ITaskItem destSettings = destProviderSetting[0];
                    string userName = credentialItem.GetMetadata(VSMsDeployTaskHostObject.UserMetaDataName);
                    if (!string.IsNullOrEmpty(userName))
                    {
                        destSettings.SetMetadata(VSMsDeployTaskHostObject.UserMetaDataName, userName);
                        destSettings.SetMetadata(VSMsDeployTaskHostObject.PasswordMetaDataName, credentialItem.GetMetadata(VSMsDeployTaskHostObject.PasswordMetaDataName));
                    }
                }
 
                //retrieve skip rules
                IEnumerable<Framework.ITaskItem> skips = hostObject.Where(item => item.ItemSpec == VSMsDeployTaskHostObject.SkipFileItemSpecName);
                if (skips != null)
                {
                    if (skipRuleItems != null)
                    {
                        skipRuleItems = skips.Concat(skipRuleItems).ToArray();
                    }
                    else
                    {
                        skipRuleItems = skips.ToArray();
                    }
                }
            }
        }
 
        /// <summary>
        /// Generates command line arguments for msdeploy.exe
        /// </summary>
        protected override string GenerateCommandLineCommands()
        {
            Utilities.CommandLineBuilder commandLine = new();
            IncorporateSettingsFromHostObject(ref m_skipRuleItemsITaskItem, Destination, HostObject as IEnumerable<Framework.ITaskItem>);
            AddDestinationProviderSettingToObject(commandLine, "-source:", Source, m_strValueQuote, null, this);
            AddDestinationProviderSettingToObject(commandLine, "-dest:", Destination, m_strValueQuote, AdditionalDestinationProviderOptions, this);
 
            commandLine.AppendSwitchUnquotedIfNotNull("-verb:", m_verb);
            commandLine.AppendSwitchUnquotedIfNotNull("-failureLevel:", m_failureLevel);
            commandLine.AppendSwitchUnquotedIfNotNull("-xpath:", m_xpath);
            commandLine.AppendSwitchUnquotedIfNotNull("-replace:", m_replace);
            commandLine.AppendSwitchUnquotedIfNotNull("-skip:", m_skip);
 
            GenerateSwitchPerItem(commandLine, "-enableRule:", m_enableRule);
            GenerateSwitchPerItem(commandLine, "-disableRule:", m_disableRule);
            GenerateSwitchPerItem(commandLine, "-enableLink:", m_enableLink);
            GenerateSwitchPerItem(commandLine, "-disableLink:", m_disableLink);
            GenerateSwitchPerItem(commandLine, "-disableSkipDirective:", m_disableSkipDirective);
            GenerateSwitchPerItem(commandLine, "-enableSkipDirective:", m_enableSkipDirective);
 
            // this allow multiple replace rule to happen, we should consider do the same thing for skip:
            AddReplaceRulesToOptions(commandLine, m_replaceRuleItemsITaskItem, m_strValueQuote);
            AddSkipDirectiveToBaseOptions(commandLine, m_skipRuleItemsITaskItem, m_strValueQuote);
            AddImportDeclareParametersFilesOptions(commandLine, m_importDeclareParametersItems);
            AddDeclareParametersOptions(commandLine, m_declareParameterItems, m_strValueQuote, OptimisticParameterDefaultValue);
 
            AddImportSetParametersFilesOptions(commandLine, m_importSetParametersItems);
            AddSimpleSetParametersToObject(commandLine, m_simpleSetParameterItems, m_strValueQuote, OptimisticParameterDefaultValue);
            AddSetParametersToObject(commandLine, m_setParameterItems, m_strValueQuote, OptimisticParameterDefaultValue);
 
            if (m_xml) commandLine.AppendSwitch("-xml");
            if (m_whatif) commandLine.AppendSwitch("-whatif");
            if (m_verbose) commandLine.AppendSwitch("-verbose");
            if (m_allowUntrusted) commandLine.AppendSwitch("-allowUntrusted");
            if (m_useChecksum) commandLine.AppendSwitch("-useChecksum");
 
            if (m_enableTransaction) commandLine.AppendSwitch("-enableTransaction");
            if (m_retryAttempts > 0) commandLine.AppendSwitchUnquotedIfNotNull("-retryAttempts=", m_retryAttempts.ToString(CultureInfo.InvariantCulture));
            if (m_retryInterval > 0) commandLine.AppendSwitchUnquotedIfNotNull("-retryInterval=", m_retryInterval.ToString(CultureInfo.InvariantCulture));
 
            if (!string.IsNullOrEmpty(UserAgent))
            {
                commandLine.AppendSwitchUnquotedIfNotNull("-userAgent=", string.Concat("\"", UserAgent, "\""));
            }
 
            //IISExpress support
            //public const string AppHostConfigDirectory = "-appHostConfigDir";
            // *    public const string WebServerDirectory = "-webServerDir";
            //      public const string WebServerManifest = "-webServerManifest";
            commandLine.AppendSwitchIfNotNull("-appHostConfigDir:", WebServerAppHostConfigDirectory);
            commandLine.AppendSwitchIfNotNull("-webServerDir:", WebServerDirectory);
            // bug in msdeploy.exe currently only take the file name
            commandLine.AppendSwitchIfNotNull("-webServerManifest:", Path.GetFileName(WebServerManifest));
 
            m_lastCommandLine = commandLine.ToString();
 
            // show arguments in the output 
            Log.LogMessage(Framework.MessageImportance.Low, string.Concat("\"", GenerateFullPathToTool(), "\" ", m_lastCommandLine));
            return m_lastCommandLine;
        }
 
        /// <summary>
        /// The name of the tool to execute
        /// </summary>
        protected override string ToolName
        {
            get { return "msdeploy.exe"; }
        }
 
        /// <summary>
        /// Determine the path to msdeploy.exe
        /// </summary>
        /// <returns>path to aspnet_merge.exe, null if not found</returns>
        protected override string GenerateFullPathToTool()
        {
            string result = ExePath is null ? string.Empty : Path.Combine(ExePath, ToolName);
 
            if (string.Compare(ExePath, "%MSDeployPath%", StringComparison.OrdinalIgnoreCase) == 0)
            {
                // if it comes in as %msdeploypath% don't use Path.Combine because it will add a \ which is 
                // not necessary since reg key for MSDeployPath already contains it
                result = string.Format("{0}{1}", ExePath, ToolName);
            }
 
            return result;
        }
 
        /// <summary>
        /// Validate the task arguments, log any warnings/errors
        /// </summary>
        /// <returns>true if arguments are current enough to continue processing, false otherwise</returns>
        protected override bool ValidateParameters()
        {
            if (Source is not null && Source.GetLength(0) > 1)
            {
                Log.LogError(string.Format(CultureInfo.CurrentCulture, Resources.MSDEPLOY_InvalidSourceCount, Source.GetLength(0)), null);
                return false;
            }
 
            if (Destination != null && Destination.GetLength(0) > 1)
            {
                Log.LogError(string.Format(CultureInfo.CurrentCulture, Resources.MSDEPLOY_InvalidDestinationCount, Destination.GetLength(0)), null);
                return false;
            }
            else
            {
                string[]? validVerbs = null;
                bool fNullDestination = false;
                if (Destination == null || Destination.GetLength(0) == 0)
                {
                    fNullDestination = true;
                    validVerbs = new string[] {
                        "dump",
                        "getDependencies",
                        "getParameters",
                        "getSystemInfo",
                    };
                }
                if (Source == null || Source.GetLength(0) == 0)
                {
                    validVerbs = new string[] {
                        "delete",
                    };
                }
                else
                {
                    validVerbs = new string[] {
                        "sync",
                        "migrate",
                    };
                }
                if (validVerbs != null)
                {
                    foreach (string verb in validVerbs)
                    {
                        if (string.Compare(Verb, verb, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            return true;
                        }
                    }
                }
                Log.LogError(string.Format(CultureInfo.CurrentCulture, Resources.MSDEPLOY_InvalidVerbForTheInput, Verb, Source?[0].ItemSpec, (fNullDestination) ? null : Destination?[0].ItemSpec), null);
                return false;
            }
        }
    }
}