// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma warning disable 1634, 1691
#pragma warning disable 56506

using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Internal;
using System.Management.Automation.Host;
using System.Resources;
using System.Diagnostics.CodeAnalysis; // for fxcop
using System.Security.AccessControl;

namespace System.Management.Automation.Provider
{

    /// <summary>
    /// This interface needs to be implemented by providers that want users to see
    /// provider-specific help.
    /// </summary>
#nullable enable
    public interface ICmdletProviderSupportsHelp
    {
        /// <summary>
        /// Called by the help system to get provider-specific help from the provider.
        /// </summary>
        /// <param name="helpItemName">
        /// Name of command that the help is requested for.
        /// </param>
        /// <param name="path">
        /// Full path to the current location of the user or the full path to
        /// the location of the property that the user needs help about.
        /// </param>
        /// <returns>
        /// The MAML help XML that should be presented to the user.
        /// </returns>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Maml", Justification = "Maml is an acronym.")]
        string GetHelpMaml(string helpItemName, string path);
    }
#nullable restore
    #region CmdletProvider

    /// <summary>
    /// The base class for Cmdlet provider.
    /// </summary>
    /// <remarks>
    /// Although it is possible to derive from this base class to implement a Cmdlet Provider, in most
    /// cases one should derive from <see cref="System.Management.Automation.Provider.ItemCmdletProvider"/>,
    /// <see cref="System.Management.Automation.Provider.ContainerCmdletProvider"/>, or
    /// <see cref ="System.Management.Automation.Provider.NavigationCmdletProvider"/>
    /// </remarks>
    public abstract partial class CmdletProvider : IResourceSupplier
    {
        #region private data

        /// <summary>
        /// The context under which the provider is running. This will change between each
        /// invocation of a method in this class or derived classes.
        /// </summary>
        private CmdletProviderContext _contextBase = null;

        /// <summary>
        /// The information that the Monad engine stores on behalf of the provider.
        /// </summary>
        private ProviderInfo _providerInformation = null;

        #endregion private data

        #region internal members

        #region Trace object

        /// <summary>
        /// An instance of the PSTraceSource class used for trace output
        /// using "CmdletProviderClasses" as the category.
        /// </summary>
        [TraceSourceAttribute(
             "CmdletProviderClasses",
             "The namespace provider base classes tracer")]
        internal static readonly PSTraceSource providerBaseTracer = PSTraceSource.GetTracer(
                                                               "CmdletProviderClasses",
                                                               "The namespace provider base classes tracer");

        #endregion Trace object

        /// <summary>
        /// Sets the provider information that is stored in the Monad engine into the
        /// provider base class.
        /// </summary>
        /// <param name="providerInfoToSet">
        /// The provider information that is stored by the Monad engine.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="providerInformation"/> is null.
        /// </exception>
        internal void SetProviderInformation(ProviderInfo providerInfoToSet)
        {
            if (providerInfoToSet == null)
            {
                throw PSTraceSource.NewArgumentNullException(nameof(providerInfoToSet));
            }

            _providerInformation = providerInfoToSet;
        }

        /// <summary>
        /// Checks whether the filter of the provider is set.
        /// Can be overridden by derived class when additional filters are defined.
        /// </summary>
        /// <returns>
        /// Whether the filter of the provider is set.
        /// </returns>
        internal virtual bool IsFilterSet()
        {
            bool filterSet = !string.IsNullOrEmpty(Filter);
            return filterSet;
        }

        #region CmdletProvider method wrappers

        /// <summary>
        /// Gets or sets the context for the running command.
        /// </summary>
        /// <exception cref="NotSupportedException">
        /// On set, if the context contains credentials and the provider
        /// doesn't support credentials, or if the context contains a filter
        /// parameter and the provider does not support filters.
        /// </exception>
        internal CmdletProviderContext Context
        {
            get
            {
                return _contextBase;
            }

            set
            {
                if (value == null)
                {
                    throw PSTraceSource.NewArgumentNullException("value");
                }

                // Check that the provider supports the use of credentials
                if (value.Credential != null &&
                    value.Credential != PSCredential.Empty &&
                    !CmdletProviderManagementIntrinsics.CheckProviderCapabilities(ProviderCapabilities.Credentials, _providerInformation))
                {
                    throw PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.Credentials_NotSupported);
                }

                // Supplying Credentials for the FileSystemProvider is supported only for New-PSDrive Command.
                if (_providerInformation != null && !string.IsNullOrEmpty(_providerInformation.Name) && _providerInformation.Name.Equals("FileSystem") &&
                    value.Credential != null &&
                    value.Credential != PSCredential.Empty &&
                    !value.ExecutionContext.CurrentCommandProcessor.Command.GetType().Name.Equals("NewPSDriveCommand"))
                {
                    throw PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.FileSystemProviderCredentials_NotSupported);
                }

                // Check that the provider supports the use of filters
                if ((!string.IsNullOrEmpty(value.Filter)) &&
                    (!CmdletProviderManagementIntrinsics.CheckProviderCapabilities(ProviderCapabilities.Filter, _providerInformation)))
                {
                    throw PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.Filter_NotSupported);
                }

                // Check that the provider supports the use of transactions if the command
                // requested it
                if ((value.UseTransaction) &&
                   (!CmdletProviderManagementIntrinsics.CheckProviderCapabilities(ProviderCapabilities.Transactions, _providerInformation)))
                {
                    throw PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.Transactions_NotSupported);
                }

                _contextBase = value;
                _contextBase.ProviderInstance = this;
            }
        }

        /// <summary>
        /// Called when the provider is first initialized. It sets the context
        /// of the call and then calls the derived providers Start method.
        /// </summary>
        /// <param name="providerInfo">
        /// The information about the provider.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        internal ProviderInfo Start(ProviderInfo providerInfo, CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;
            return Start(providerInfo);
        }

        /// <summary>
        /// Gets an object that defines the additional parameters for the Start implementation
        /// for a provider.
        /// </summary>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object StartDynamicParameters(CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            return StartDynamicParameters();
        }

        /// <summary>
        /// Called when the provider is being removed. It sets the context
        /// of the call and then calls the derived providers Stop method.
        /// </summary>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        internal void Stop(CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;
            Stop();
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.StopProcessing" />
        protected internal virtual void StopProcessing()
        {
        }

        #endregion CmdletProvider method wrappers

        #region IPropertyCmdletProvider method wrappers

        /// <summary>
        /// Internal wrapper for the GetProperty protected method. This method will
        /// only be called if the provider implements the IPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item to retrieve properties from.
        /// </param>
        /// <param name="providerSpecificPickList">
        /// A list of properties that should be retrieved. If this parameter is null
        /// or empty, all properties should be retrieved.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        internal void GetProperty(
            string path,
            Collection<string> providerSpecificPickList,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.GetProperty(path, providerSpecificPickList);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the get-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="providerSpecificPickList">
        /// A list of properties that should be retrieved. If this parameter is null
        /// or empty, all properties should be retrieved.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object GetPropertyDynamicParameters(
            string path,
            Collection<string> providerSpecificPickList,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.GetPropertyDynamicParameters(path, providerSpecificPickList);
        }

        /// <summary>
        /// Internal wrapper for the SetProperty protected method. This method will
        /// only be called if the provider implements the IPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item to set the properties on.
        /// </param>
        /// <param name="propertyValue">
        /// A PSObject which contains a collection of the name, type, value
        /// of the properties to be set.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        internal void SetProperty(
            string path,
            PSObject propertyValue,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.SetProperty(path, propertyValue);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the set-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="propertyValue">
        /// A PSObject which contains a collection of the name, type, value
        /// of the properties to be set.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object SetPropertyDynamicParameters(
            string path,
            PSObject propertyValue,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.SetPropertyDynamicParameters(path, propertyValue);
        }

        /// <summary>
        /// Internal wrapper for the ClearProperty protected method. This method will
        /// only be called if the provider implements the IPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item from which the property should be cleared.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property that should be cleared.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic clearing of properties.
        /// </remarks>
        internal void ClearProperty(
            string path,
            Collection<string> propertyName,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.ClearProperty(path, propertyName);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the clear-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="providerSpecificPickList">
        /// A list of properties that should be cleared. If this parameter is null
        /// or empty, all properties should be cleared.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object ClearPropertyDynamicParameters(
            string path,
            Collection<string> providerSpecificPickList,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.ClearPropertyDynamicParameters(path, providerSpecificPickList);
        }

        #endregion IPropertyCmdletProvider

        #region IDynamicPropertyCmdletProvider

        /// <summary>
        /// Internal wrapper for the NewProperty protected method. This method will
        /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item on which the new property should be created.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property that should be created.
        /// </param>
        /// <param name="propertyTypeName">
        /// The type of the property that should be created.
        /// </param>
        /// <param name="value">
        /// The new value of the property that should be created.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic creation of properties.
        /// </remarks>
        internal void NewProperty(
            string path,
            string propertyName,
            string propertyTypeName,
            object value,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IDynamicPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.NewProperty(path, propertyName, propertyTypeName, value);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the new-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property that should be created.
        /// </param>
        /// <param name="propertyTypeName">
        /// The type of the property that should be created.
        /// </param>
        /// <param name="value">
        /// The new value of the property that should be created.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object NewPropertyDynamicParameters(
            string path,
            string propertyName,
            string propertyTypeName,
            object value,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.NewPropertyDynamicParameters(path, propertyName, propertyTypeName, value);
        }

        /// <summary>
        /// Internal wrapper for the RemoveProperty protected method. This method will
        /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item on which the property should be removed.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property to be removed
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic removal of properties.
        /// </remarks>
        internal void RemoveProperty(
            string path,
            string propertyName,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IDynamicPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.RemoveProperty(path, propertyName);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the remove-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property that should be removed.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object RemovePropertyDynamicParameters(
            string path,
            string propertyName,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.RemovePropertyDynamicParameters(path, propertyName);
        }

        /// <summary>
        /// Internal wrapper for the RenameProperty protected method. This method will
        /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item on which the property should be renamed.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property that should be renamed.
        /// </param>
        /// <param name="newPropertyName">
        /// The new name for the property.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic renaming of properties.
        /// </remarks>
        internal void RenameProperty(
                    string path,
            string propertyName,
            string newPropertyName,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IDynamicPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.RenameProperty(path, propertyName, newPropertyName);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the rename-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="sourceProperty">
        /// The name of the property that should be renamed.
        /// </param>
        /// <param name="destinationProperty">
        /// The name of the property to rename it to.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object RenamePropertyDynamicParameters(
            string path,
            string sourceProperty,
            string destinationProperty,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.RenamePropertyDynamicParameters(path, sourceProperty, destinationProperty);
        }

        /// <summary>
        /// Internal wrapper for the CopyProperty protected method. This method will
        /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="sourcePath">
        /// The path to the item from which the property should be copied.
        /// </param>
        /// <param name="sourceProperty">
        /// The name of the property that should be copied.
        /// </param>
        /// <param name="destinationPath">
        /// The path to the item to which the property should be copied.
        /// </param>
        /// <param name="destinationProperty">
        /// The name of the property that should be copied to.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic copying of properties.
        /// </remarks>
        internal void CopyProperty(
            string sourcePath,
            string sourceProperty,
            string destinationPath,
            string destinationProperty,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IDynamicPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.CopyProperty(sourcePath, sourceProperty, destinationPath, destinationProperty);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the copy-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="sourceProperty">
        /// The name of the property that should be copied.
        /// </param>
        /// <param name="destinationPath">
        /// The path to the item to which the property should be copied.
        /// </param>
        /// <param name="destinationProperty">
        /// The name of the property that should be copied to.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object CopyPropertyDynamicParameters(
            string path,
            string sourceProperty,
            string destinationPath,
            string destinationProperty,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.CopyPropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty);
        }

        /// <summary>
        /// Internal wrapper for the MoveProperty protected method. This method will
        /// only be called if the provider implements the IDynamicPropertyCmdletProvider interface.
        /// </summary>
        /// <param name="sourcePath">
        /// The path to the item from which the property should be moved.
        /// </param>
        /// <param name="sourceProperty">
        /// The name of the property that should be moved.
        /// </param>
        /// <param name="destinationPath">
        /// The path to the item to which the property should be moved.
        /// </param>
        /// <param name="destinationProperty">
        /// The name of the property that should be moved to.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <remarks>
        /// Implement this method when you are providing access to a data store
        /// that allows dynamic moving of properties.
        /// </remarks>
        internal void MoveProperty(
            string sourcePath,
            string sourceProperty,
            string destinationPath,
            string destinationProperty,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IDynamicPropertyCmdletProvider_NotSupported);
            }

            // Call interface method

            propertyProvider.MoveProperty(sourcePath, sourceProperty, destinationPath, destinationProperty);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the move-itemproperty cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="sourceProperty">
        /// The name of the property that should be copied.
        /// </param>
        /// <param name="destinationPath">
        /// The path to the item to which the property should be copied.
        /// </param>
        /// <param name="destinationProperty">
        /// The name of the property that should be copied to.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object MovePropertyDynamicParameters(
            string path,
            string sourceProperty,
            string destinationPath,
            string destinationProperty,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IDynamicPropertyCmdletProvider propertyProvider))
            {
                return null;
            }

            return propertyProvider.MovePropertyDynamicParameters(path, sourceProperty, destinationPath, destinationProperty);
        }

        #endregion IDynamicPropertyCmdletProvider method wrappers

        #region IContentCmdletProvider method wrappers

        /// <summary>
        /// Internal wrapper for the GetContentReader protected method. This method will
        /// only be called if the provider implements the IContentCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item to retrieve content from.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An instance of the IContentReader for the specified path.
        /// </returns>
        internal IContentReader GetContentReader(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IContentCmdletProvider_NotSupported);
            }

            // Call interface method

            return contentProvider.GetContentReader(path);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the get-content cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object GetContentReaderDynamicParameters(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                return null;
            }

            return contentProvider.GetContentReaderDynamicParameters(path);
        }

        /// <summary>
        /// Internal wrapper for the GetContentWriter protected method. This method will
        /// only be called if the provider implements the IContentCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item to set content on.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An instance of the IContentWriter for the specified path.
        /// </returns>
        internal IContentWriter GetContentWriter(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IContentCmdletProvider_NotSupported);
            }

            // Call interface method

            return contentProvider.GetContentWriter(path);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the add-content and set-content cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object GetContentWriterDynamicParameters(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                return null;
            }

            return contentProvider.GetContentWriterDynamicParameters(path);
        }

        /// <summary>
        /// Internal wrapper for the ClearContent protected method. This method will
        /// only be called if the provider implements the IContentCmdletProvider interface.
        /// </summary>
        /// <param name="path">
        /// The path to the item to clear the content from.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        internal void ClearContent(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                throw
                    PSTraceSource.NewNotSupportedException(
                        SessionStateStrings.IContentCmdletProvider_NotSupported);
            }

            // Call interface method

            contentProvider.ClearContent(path);
        }

        /// <summary>
        /// Gives the provider a chance to attach additional parameters to
        /// the clear-content cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item to get the dynamic parameters for.
        /// </param>
        /// <param name="cmdletProviderContext">
        /// The context under which this method is being called.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        internal object ClearContentDynamicParameters(
            string path,
            CmdletProviderContext cmdletProviderContext)
        {
            Context = cmdletProviderContext;

            if (!(this is IContentCmdletProvider contentProvider))
            {
                return null;
            }

            return contentProvider.ClearContentDynamicParameters(path);
        }

        #endregion IContentCmdletProvider method wrappers

        #endregion internal members

        #region protected members

        /// <summary>
        /// Gives the provider the opportunity to initialize itself.
        /// </summary>
        /// <param name="providerInfo">
        /// The information about the provider that is being started.
        /// </param>
        /// <remarks>
        /// The default implementation returns the ProviderInfo instance that
        /// was passed.
        ///
        /// To have session state maintain persisted data on behalf of the provider,
        /// the provider should derive from <see cref="System.Management.Automation.ProviderInfo"/>
        /// and add any properties or
        /// methods for the data it wishes to persist.  When Start gets called the
        /// provider should construct an instance of its derived ProviderInfo using the
        /// providerInfo that is passed in and return that new instance.
        /// </remarks>
        protected virtual ProviderInfo Start(ProviderInfo providerInfo)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                return providerInfo;
            }
        }

        /// <summary>
        /// Gets an object that defines the additional parameters for the Start implementation
        /// for a provider.
        /// </summary>
        /// <returns>
        /// Overrides of this method should return an object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class or a
        /// <see cref="System.Management.Automation.RuntimeDefinedParameterDictionary"/>.
        ///
        /// The default implementation returns null. (no additional parameters)
        /// </returns>
        protected virtual object StartDynamicParameters()
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                return null;
            }
        }

        /// <summary>
        /// Called by session state when the provider is being removed.
        /// </summary>
        /// <remarks>
        /// A provider should override this method to free up any resources that the provider
        /// was using.
        ///
        /// The default implementation does nothing.
        /// </remarks>
        protected virtual void Stop()
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
            }
        }

        /// <summary>
        /// Indicates whether stop has been requested on this provider.
        /// </summary>
        public bool Stopping
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Stopping;
                }
            }
        }

        /// <summary>
        /// Gets the instance of session state for the current runspace.
        /// </summary>
        public SessionState SessionState
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return new SessionState(Context.ExecutionContext.EngineSessionState);
                }
            }
        }

        /// <summary>
        /// Gets the instance of the provider interface APIs for the current runspace.
        /// </summary>
        public ProviderIntrinsics InvokeProvider
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return new ProviderIntrinsics(Context.ExecutionContext.EngineSessionState);
                }
            }
        }

        /// <summary>
        /// Gets the instance of the command invocation APIs for the current runspace.
        /// </summary>
        public CommandInvocationIntrinsics InvokeCommand
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return new CommandInvocationIntrinsics(Context.ExecutionContext);
                }
            }
        }

        /// <summary>
        /// Gets the credentials under which the operation should run.
        /// </summary>
        public PSCredential Credential
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Credential;
                }
            }
        }

        /// <summary>
        /// The information about the provider that is stored in the runspace
        /// on behalf of the provider.
        /// </summary>
        /// <remarks>
        /// If a derived type of ProviderInfo was returned from the Start method, it
        /// will be set here in all subsequent calls to the provider.
        /// </remarks>
        protected internal ProviderInfo ProviderInfo
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    return _providerInformation;
                }
            }
        }

        /// <summary>
        /// The drive information associated with the context of the current operation.
        /// </summary>
        protected PSDriveInfo PSDriveInfo
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Drive;
                }
            }
        }

        /// <summary>
        /// The dynamic parameters object populated with the values as specified
        /// by the user.
        /// </summary>
        protected object DynamicParameters
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.DynamicParameters;
                }
            }
        }

        /// <summary>
        /// Gets the force property.
        /// </summary>
        /// <remarks>
        /// Gives the provider guidance on how vigorous it should be about performing
        /// the operation. If true, the provider should do everything possible to perform
        /// the operation. If false, the provider should attempt the operation but allow
        /// even simple errors to terminate the operation.
        /// For example, if the user tries to copy a file to a path that already exists and
        /// the destination is read-only, if force is true, the provider should copy over
        /// the existing read-only file. If force is false, the provider should write an error.
        /// </remarks>
        public SwitchParameter Force
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Force;
                }
            }
        }

        /// <summary>
        /// Gets the provider specific filter that was supplied by the caller.
        /// </summary>
        public string Filter
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Filter;
                }
            }
        }

        /// <summary>
        /// Gets the include wildcard patterns which is used to determine which items
        /// will be included when taking an action.
        /// </summary>
        public Collection<string> Include
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Include;
                }
            }
        }

        /// <summary>
        /// Gets the exclude wildcard patterns which is used to determine which items
        /// will be excluded when taking an action.
        /// </summary>
        public Collection<string> Exclude
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.Exclude;
                }
            }
        }

        /// <summary>
        /// Gets the host interaction APIs.
        /// </summary>
        public PSHost Host
        {
            get
            {
                using (PSTransactionManager.GetEngineProtectionScope())
                {
                    Diagnostics.Assert(
                        Context != null,
                        "The context should always be set");

                    return Context.ExecutionContext.EngineHostInterface;
                }
            }
        }

        /// <summary>
        /// Gets the default item separator character for this provider.
        /// </summary>
        public virtual char ItemSeparator => Path.DirectorySeparatorChar;

        /// <summary>
        /// Gets the alternate item separator character for this provider.
        /// </summary>
        public virtual char AltItemSeparator =>
#if UNIX
            '\\';
#else
            Path.AltDirectorySeparatorChar;
#endif

        #region IResourceSupplier
        /// <summary>
        /// Gets the resource string corresponding to baseName and
        /// resourceId from the current assembly. You should override
        /// this if you require a different behavior.
        /// </summary>
        /// <param name="baseName">
        /// the base resource name
        /// </param>
        /// <param name="resourceId">
        /// the resource id
        /// </param>
        /// <returns>
        /// the resource string corresponding to baseName and resourceId
        /// </returns>
        /// <remarks>
        /// When overriding this method, the resource string for the specified
        /// resource should be retrieved from a localized resource assembly.
        /// </remarks>
        public virtual string GetResourceString(string baseName, string resourceId)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                if (string.IsNullOrEmpty(baseName))
                {
                    throw PSTraceSource.NewArgumentException(nameof(baseName));
                }

                if (string.IsNullOrEmpty(resourceId))
                {
                    throw PSTraceSource.NewArgumentException(nameof(resourceId));
                }

                ResourceManager manager =
                    ResourceManagerCache.GetResourceManager(
                        this.GetType().Assembly,
                        baseName);

                string retValue = null;

                try
                {
                    retValue = manager.GetString(resourceId,
                                                  System.Globalization.CultureInfo.CurrentUICulture);
                }
                catch (MissingManifestResourceException)
                {
                    throw PSTraceSource.NewArgumentException(nameof(baseName), GetErrorText.ResourceBaseNameFailure, baseName);
                }

                if (retValue == null)
                {
                    throw PSTraceSource.NewArgumentException(nameof(resourceId), GetErrorText.ResourceIdFailure, resourceId);
                }

                return retValue;
            }
        }
        #endregion IResourceSupplier

        #region ThrowTerminatingError
        /// <Content contentref="System.Management.Automation.Cmdlet.ThrowTerminatingError" />
        [System.Diagnostics.CodeAnalysis.DoesNotReturn]
        public void ThrowTerminatingError(ErrorRecord errorRecord)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                if (errorRecord == null)
                {
                    throw PSTraceSource.NewArgumentNullException(nameof(errorRecord));
                }

                if (errorRecord.ErrorDetails != null
                    && errorRecord.ErrorDetails.TextLookupError != null)
                {
                    Exception textLookupError = errorRecord.ErrorDetails.TextLookupError;
                    errorRecord.ErrorDetails.TextLookupError = null;
                    MshLog.LogProviderHealthEvent(
                        this.Context.ExecutionContext,
                        ProviderInfo.Name,
                        textLookupError,
                        Severity.Warning);
                }

                // We can't play the same game as Cmdlet.ThrowTerminatingError
                //  and save the exception in the "pipeline".  We need to pass
                //  the actual exception as a thrown exception.  So, we wrap
                //  it in ProviderInvocationException.

                ProviderInvocationException providerInvocationException =
                    new ProviderInvocationException(ProviderInfo, errorRecord);

                // Log a provider health event

                MshLog.LogProviderHealthEvent(
                    this.Context.ExecutionContext,
                    ProviderInfo.Name,
                    providerInvocationException,
                    Severity.Warning);

                throw providerInvocationException;
            }
        }
        #endregion ThrowTerminatingError

        #region User feedback mechanisms

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldProcess" />
        public bool ShouldProcess(
            string target)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldProcess(target);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldProcess" />
        public bool ShouldProcess(
            string target,
            string action)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldProcess(target, action);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldProcess" />
        public bool ShouldProcess(
            string verboseDescription,
            string verboseWarning,
            string caption)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldProcess(
                    verboseDescription,
                    verboseWarning,
                    caption);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldProcess" />
        public bool ShouldProcess(
            string verboseDescription,
            string verboseWarning,
            string caption,
            out ShouldProcessReason shouldProcessReason)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldProcess(
                    verboseDescription,
                    verboseWarning,
                    caption,
                    out shouldProcessReason);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldContinue" />
        public bool ShouldContinue(
            string query,
            string caption)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldContinue(query, caption);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.ShouldContinue" />
        public bool ShouldContinue(
            string query,
            string caption,
            ref bool yesToAll,
            ref bool noToAll)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                return Context.ShouldContinue(
                    query, caption, ref yesToAll, ref noToAll);
            }
        }

        #region Transaction Support

        /// <summary>
        /// Returns true if a transaction is available and active.
        /// </summary>
        public bool TransactionAvailable()
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                if (Context == null)
                    return false;
                else
                    return Context.TransactionAvailable();
            }
        }

        /// <summary>
        /// Gets an object that surfaces the current PowerShell transaction.
        /// When this object is disposed, PowerShell resets the active transaction.
        /// </summary>
        public PSTransactionContext CurrentPSTransaction
        {
            get
            {
                if (Context == null)
                    return null;
                else
                    return Context.CurrentPSTransaction;
            }
        }
        #endregion Transaction Support

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteVerbose" />
        public void WriteVerbose(string text)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                Context.WriteVerbose(text);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteWarning" />
        public void WriteWarning(string text)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                Context.WriteWarning(text);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteProgress" />
        public void WriteProgress(ProgressRecord progressRecord)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                if (progressRecord == null)
                {
                    throw PSTraceSource.NewArgumentNullException(nameof(progressRecord));
                }

                Context.WriteProgress(progressRecord);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteDebug" />
        public void WriteDebug(string text)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                Context.WriteDebug(text);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteInformation" />
        public void WriteInformation(InformationRecord record)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                Context.WriteInformation(record);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteInformation" />
        public void WriteInformation(object messageData, string[] tags)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                Context.WriteInformation(messageData, tags);
            }
        }

        /// <summary>
        /// Converts the incoming object to a PSObject and then adds extra
        /// data as notes. Then it writes the shell object to the context.
        /// </summary>
        /// <param name="item">
        /// The item being written out.
        /// </param>
        /// <param name="path">
        /// The path of the item being written out.
        /// </param>
        /// <param name="isContainer">
        /// True if the item is a container, false otherwise.
        /// </param>
        private void WriteObject(
            object item,
            string path,
            bool isContainer)
        {
            PSObject result = WrapOutputInPSObject(item, path);

            // Now add the IsContainer

            result.AddOrSetProperty("PSIsContainer", isContainer ? Boxed.True : Boxed.False);
            providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSIsContainer", isContainer);

            Diagnostics.Assert(
                Context != null,
                "The context should always be set");

            Context.WriteObject(result);
        }

        /// <summary>
        /// Converts the incoming object to a PSObject and then adds extra
        /// data as notes. Then it writes the shell object to the context.
        /// </summary>
        /// <param name="item">
        /// The item being written out.
        /// </param>
        /// <param name="path">
        /// The path of the item being written out.
        /// </param>
        private void WriteObject(
            object item,
            string path)
        {
            PSObject result = WrapOutputInPSObject(item, path);

            Diagnostics.Assert(
                Context != null,
                "The context should always be set");

            Context.WriteObject(result);
        }

        /// <summary>
        /// Wraps the item in a PSObject and attaches some notes to the
        /// object that deal with path information.
        /// </summary>
        /// <param name="item">
        /// The item to be wrapped.
        /// </param>
        /// <param name="path">
        /// The path to the item.
        /// </param>
        /// <returns>
        /// A PSObject that wraps the item and has path information attached
        /// as notes.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// if <paramref name="item"/> is null.
        /// </exception>
        private PSObject WrapOutputInPSObject(
            object item,
            string path)
        {
            if (item == null)
            {
                throw PSTraceSource.NewArgumentNullException(nameof(item));
            }

            PSObject result = new PSObject(item);

            Diagnostics.Assert(
                ProviderInfo != null,
                "The ProviderInfo should always be set");

            // Move the TypeNames to the wrapping object if the wrapped object
            // was an PSObject

            PSObject mshObj = item as PSObject;
            if (mshObj != null)
            {
                result.InternalTypeNames = new ConsolidatedString(mshObj.InternalTypeNames);
            }

            // Construct a provider qualified path as the Path note

            string providerQualifiedPath =
                LocationGlobber.GetProviderQualifiedPath(path, ProviderInfo);

            result.AddOrSetProperty("PSPath", providerQualifiedPath);
            providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSPath", providerQualifiedPath);

            // Now get the parent path and child name

            NavigationCmdletProvider navProvider = this as NavigationCmdletProvider;
            if (navProvider != null && path != null)
            {
                // Get the parent path

                string parentPath = null;

                if (PSDriveInfo != null)
                {
                    parentPath = navProvider.GetParentPath(path, PSDriveInfo.Root, Context);
                }
                else
                {
                    parentPath = navProvider.GetParentPath(path, string.Empty, Context);
                }

                string providerQualifiedParentPath = string.Empty;

                if (!string.IsNullOrEmpty(parentPath))
                {
                    providerQualifiedParentPath =
                        LocationGlobber.GetProviderQualifiedPath(parentPath, ProviderInfo);
                }

                result.AddOrSetProperty("PSParentPath", providerQualifiedParentPath);
                providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSParentPath", providerQualifiedParentPath);

                // Get the child name

                string childName = navProvider.GetChildName(path, Context);

                result.AddOrSetProperty("PSChildName", childName);
                providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSChildName", childName);
#if UNIX

                // Add a commonstat structure to file system objects
                if (ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider))
                {
                    try
                    {
                        // Use LStat because if you get a link, you want the information about the
                        // link, not the file.
                        var commonStat = Platform.Unix.GetLStat(path);
                        result.AddOrSetProperty("UnixStat", commonStat);
                    }
                    catch
                    {
                        // If there is *any* problem in retrieving the stat information
                        // set the property to null. There is no specific exception which
                        // would result in different behavior.
                        result.AddOrSetProperty("UnixStat", value: null);
                    }
                }
#endif
            }

            // PSDriveInfo

            if (PSDriveInfo != null)
            {
                result.AddOrSetProperty(this.PSDriveInfo.GetNotePropertyForProviderCmdlets("PSDrive"));
                providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSDrive", this.PSDriveInfo);
            }

            // ProviderInfo

            result.AddOrSetProperty(this.ProviderInfo.GetNotePropertyForProviderCmdlets("PSProvider"));
            providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSProvider", this.ProviderInfo);

            return result;
        }

        /// <summary>
        /// Writes an item to the output as a PSObject with extra data attached
        /// as notes.
        /// </summary>
        /// <param name="item">
        /// The item to be written.
        /// </param>
        /// <param name="path">
        /// The path of the item being written.
        /// </param>
        /// <param name="isContainer">
        /// True if the item is a container, false otherwise.
        /// </param>
        /// <!--
        /// If streaming is on and the writeObjectHandler was specified then the object
        /// gets written to the writeObjectHandler. If streaming is on and the writeObjectHandler
        /// was not specified and the command object was specified, the object gets written to
        /// the WriteObject method of the command object.
        /// If streaming is off the object gets written to an accumulator collection. The collection
        /// of written object can be retrieved using the AccumulatedObjects method.
        /// -->
        public void WriteItemObject(
            object item,
            string path,
            bool isContainer)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                WriteObject(item, path, isContainer);
            }
        }

        /// <summary>
        /// Writes a property object to the output as a PSObject with extra data attached
        /// as notes.
        /// </summary>
        /// <param name="propertyValue">
        /// The properties to be written.
        /// </param>
        /// <param name="path">
        /// The path of the item being written.
        /// </param>
        /// <!--
        /// If streaming is on and the writeObjectHandler was specified then the object
        /// gets written to the writeObjectHandler. If streaming is on and the writeObjectHandler
        /// was not specified and the command object was specified, the object gets written to
        /// the WriteObject method of the command object.
        /// If streaming is off the object gets written to an accumulator collection. The collection
        /// of written object can be retrieved using the AccumulatedObjects method.
        /// -->
        public void WritePropertyObject(
            object propertyValue,
            string path)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                WriteObject(propertyValue, path);
            }
        }

        /// <summary>
        /// Writes a Security Descriptor object to the output as a PSObject with extra data attached
        /// as notes.
        /// </summary>
        /// <param name="securityDescriptor">
        /// The Security Descriptor to be written.
        /// </param>
        /// <param name="path">
        /// The path of the item from which the Security Descriptor was retrieved.
        /// </param>
        /// <!--
        /// If streaming is on and the writeObjectHandler was specified then the object
        /// gets written to the writeObjectHandler. If streaming is on and the writeObjectHandler
        /// was not specified and the command object was specified, the object gets written to
        /// the WriteObject method of the command object.
        /// If streaming is off the object gets written to an accumulator collection. The collection
        /// of written object can be retrieved using the AccumulatedObjects method.
        /// -->
        public void WriteSecurityDescriptorObject(
            ObjectSecurity securityDescriptor,
            string path)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                WriteObject(securityDescriptor, path);
            }
        }

        /// <Content contentref="System.Management.Automation.Cmdlet.WriteError" />
        public void WriteError(ErrorRecord errorRecord)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                Diagnostics.Assert(
                    Context != null,
                    "The context should always be set");

                if (errorRecord == null)
                {
                    throw PSTraceSource.NewArgumentNullException(nameof(errorRecord));
                }

                if (errorRecord.ErrorDetails != null
                    && errorRecord.ErrorDetails.TextLookupError != null)
                {
                    MshLog.LogProviderHealthEvent(
                        this.Context.ExecutionContext,
                        ProviderInfo.Name,
                        errorRecord.ErrorDetails.TextLookupError,
                        Severity.Warning);
                }

                Context.WriteError(errorRecord);
            }
        }

        #endregion User feedback mechanisms

        #endregion protected members
    }

    #endregion CmdletProvider
}

#pragma warning restore 56506

