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

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Provider;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Text;
using System.Xml;
using System.Xml.XPath;

using Microsoft.Win32.SafeHandles;

using Dbg = System.Management.Automation;

namespace Microsoft.PowerShell.Commands
{
    #region FileSystemProvider

    /// <summary>
    /// Defines the implementation of a File System Provider.  This provider
    /// allows for stateless namespace navigation of the file system.
    /// </summary>
    [CmdletProvider(FileSystemProvider.ProviderName, ProviderCapabilities.Credentials | ProviderCapabilities.Filter | ProviderCapabilities.ShouldProcess)]
    [OutputType(typeof(FileSecurity), ProviderCmdlet = ProviderCmdlet.SetAcl)]
    [OutputType(typeof(string), typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.ResolvePath)]
    [OutputType(typeof(PathInfo), ProviderCmdlet = ProviderCmdlet.PushLocation)]
    [OutputType(typeof(byte), typeof(string), ProviderCmdlet = ProviderCmdlet.GetContent)]
    [OutputType(typeof(FileInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
    [OutputType(typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetChildItem)]
    [OutputType(typeof(FileSecurity), typeof(DirectorySecurity), ProviderCmdlet = ProviderCmdlet.GetAcl)]
    [OutputType(typeof(bool), typeof(string), typeof(FileInfo), typeof(DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItem)]
    [OutputType(typeof(bool), typeof(string), typeof(DateTime), typeof(System.IO.FileInfo), typeof(System.IO.DirectoryInfo), ProviderCmdlet = ProviderCmdlet.GetItemProperty)]
    [OutputType(typeof(string), typeof(System.IO.FileInfo), ProviderCmdlet = ProviderCmdlet.NewItem)]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This coupling is required")]
    public sealed partial class FileSystemProvider : NavigationCmdletProvider,
                                                     IContentCmdletProvider,
                                                     IPropertyCmdletProvider,
                                                     ISecurityDescriptorCmdletProvider,
                                                     ICmdletProviderSupportsHelp
    {
#if UNIX
        // This is the errno returned by the rename() syscall
        // when an item is attempted to be renamed across filesystem mount boundaries.
        private const int UNIX_ERRNO_EXDEV = 18;
#endif

        // 4MB gives the best results without spiking the resources on the remote connection for file transfers between pssessions.
        // NOTE: The script used to copy file data from session (PSCopyFromSessionHelper) has a
        // maximum fragment size value for security.  If FILETRANSFERSIZE changes make sure the
        // copy script will accomodate the new value.
        private const int FILETRANSFERSIZE = 4 * 1024 * 1024;

        // The name of the key in an exception's Data dictionary when attempting
        // to copy an item onto itself.
        private const string SelfCopyDataKey = "SelfCopy";

        /// <summary>
        /// An instance of the PSTraceSource class used for trace output
        /// using "FileSystemProvider" as the category.
        /// </summary>
        [Dbg.TraceSourceAttribute("FileSystemProvider", "The namespace navigation provider for the file system")]
        private static Dbg.PSTraceSource s_tracer =
            Dbg.PSTraceSource.GetTracer("FileSystemProvider", "The namespace navigation provider for the file system");

        /// <summary>
        /// Gets the name of the provider.
        /// </summary>
        public const string ProviderName = "FileSystem";

        /// <summary>
        /// Initializes a new instance of the FileSystemProvider class. Since this
        /// object needs to be stateless, the constructor does nothing.
        /// </summary>
        public FileSystemProvider()
        {
        }

        private Collection<WildcardPattern> _excludeMatcher = null;

        private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions
        {
            MatchType = MatchType.Win32,
            MatchCasing = MatchCasing.CaseInsensitive,
            AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior
        };

        /// <summary>
        /// Converts all / in the path to \
        /// </summary>
        /// <param name="path">
        /// The path to normalize.
        /// </param>
        /// <returns>
        /// The path with all / normalized to \
        /// </returns>
        private static string NormalizePath(string path)
        {
            return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));
        }

        /// <summary>
        /// Get the correct casing for a path.  This method assumes it's being called by NormalizePath()
        /// so that the path is already normalized.
        /// </summary>
        /// <param name="path">
        /// The path to retrieve.
        /// </param>
        /// <returns>
        /// The path with accurate casing if item exists, otherwise it returns path that was passed in.
        /// </returns>
        private static string GetCorrectCasedPath(string path)
        {
            // Only apply to directories where there are issues with some tools if the casing
            // doesn't match the source like git
            if (Directory.Exists(path))
            {
                string exactPath = string.Empty;
                int itemsToSkip = 0;
                if (Utils.PathIsUnc(path))
                {
                    // With the Split method, a UNC path like \\server\share, we need to skip
                    // trying to enumerate the server and share, so skip the first two empty
                    // strings, then server, and finally share name.
                    itemsToSkip = 4;
                }

                foreach (string item in path.Split(StringLiterals.DefaultPathSeparator))
                {
                    if (itemsToSkip-- > 0)
                    {
                        // This handles the UNC server and share and 8.3 short path syntax
                        exactPath += item + StringLiterals.DefaultPathSeparator;
                        continue;
                    }
                    else if (string.IsNullOrEmpty(exactPath))
                    {
                        // This handles the drive letter or / root path start
                        exactPath = item + StringLiterals.DefaultPathSeparator;
                    }
                    else if (string.IsNullOrEmpty(item))
                    {
                        // This handles the trailing slash case
                        if (!exactPath.EndsWith(StringLiterals.DefaultPathSeparator))
                        {
                            exactPath += StringLiterals.DefaultPathSeparator;
                        }

                        break;
                    }
                    else if (item.Contains('~'))
                    {
                        // This handles short path names
                        exactPath += StringLiterals.DefaultPathSeparator + item;
                    }
                    else
                    {
                        // Use GetFileSystemEntries to get the correct casing of this element
                        try
                        {
                            var entries = Directory.GetFileSystemEntries(exactPath, item);
                            if (entries.Length > 0)
                            {
                                exactPath = entries[0];
                            }
                            else
                            {
                                // If previous call didn't return anything, something failed so we just return the path we were given
                                return path;
                            }
                        }
                        catch
                        {
                            // If we can't enumerate, we stop and just return the original path
                            return path;
                        }
                    }
                }

                return exactPath;
            }
            else
            {
                return path;
            }
        }

        /// <summary>
        /// Checks if the item exist at the specified path. if it exists then creates
        /// appropriate directoryinfo or fileinfo object.
        /// </summary>
        /// <param name="path">
        /// Refers to the item for which we are checking for existence and creating filesysteminfo object.
        /// </param>
        /// <param name="isContainer">
        /// Return true if path points to a directory else returns false.
        /// </param>
        /// <returns>FileInfo or DirectoryInfo object.</returns>
        /// <exception cref="System.ArgumentNullException">
        /// The path is null.
        /// </exception>
        /// <exception cref="System.IO.IOException">
        /// I/O error occurs.
        /// </exception>
        /// <exception cref="System.UnauthorizedAccessException">
        /// An I/O error or a specific type of security error.
        /// </exception>
        private static FileSystemInfo GetFileSystemInfo(string path, out bool isContainer)
        {
            // We use 'FileInfo.Attributes' (not 'FileInfo.Exist')
            // because we want to get exceptions
            // like UnauthorizedAccessException or IOException.
            FileSystemInfo fsinfo = new FileInfo(path);
            var attr = fsinfo.Attributes;
            var exists = (int)attr != -1;
            isContainer = exists && attr.HasFlag(FileAttributes.Directory);

            if (exists)
            {
                if (isContainer)
                {
                    return new DirectoryInfo(path);
                }
                else
                {
                    return fsinfo;
                }
            }

            return null;
        }

        /// <summary>
        /// Overrides the method of CmdletProvider, considering the additional
        /// dynamic parameters of FileSystemProvider.
        /// </summary>
        /// <returns>
        /// whether the filter or attribute filter is set.
        /// </returns>
        internal override bool IsFilterSet()
        {
            bool attributeFilterSet = false;
            GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
            if (fspDynamicParam != null)
            {
                attributeFilterSet = (
                    (fspDynamicParam.Attributes != null)
                        || (fspDynamicParam.Directory)
                        || (fspDynamicParam.File)
                        || (fspDynamicParam.Hidden)
                        || (fspDynamicParam.ReadOnly)
                        || (fspDynamicParam.System));
            }

            return (attributeFilterSet || base.IsFilterSet());
        }

        /// <summary>
        /// Gets the dynamic parameters for get-childnames on the
        /// FileSystemProvider.
        /// We currently only support one dynamic parameter,
        /// "Attributes" that returns an enum evaluator for the
        /// given expression.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        protected override object GetChildNamesDynamicParameters(string path)
        {
            return new GetChildDynamicParameters();
        }

        /// <summary>
        /// Gets the dynamic parameters for get-childitems on the
        /// FileSystemProvider.
        /// We currently only support one dynamic parameter,
        /// "Attributes" that returns an enum evaluator for the
        /// given expression.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </param>
        /// <param name="recurse">
        /// Ignored.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        protected override object GetChildItemsDynamicParameters(string path, bool recurse)
        {
            return new GetChildDynamicParameters();
        }

        /// <summary>
        /// Gets the dynamic parameters for Copy-Item on the FileSystemProvider.
        /// </summary>
        /// <param name="path">Source for the copy operation.</param>
        /// <param name="destination">Destination for the copy operation.</param>
        /// <param name="recurse">Whether to recurse.</param>
        /// <returns></returns>
        protected override object CopyItemDynamicParameters(string path, string destination, bool recurse)
        {
            return new CopyItemDynamicParameters();
        }

        #region ICmdletProviderSupportsHelp members

        /// <summary>
        /// Implementation of ICmdletProviderSupportsHelp interface.
        /// Gets provider-specific help content for the corresponding cmdlet.
        /// </summary>
        /// <param name="helpItemName">
        /// Name of command that the help is requested for.
        /// </param>
        /// <param name="path">
        /// Not used here.
        /// </param>
        /// <returns>
        /// The MAML help XML that should be presented to the user.
        /// </returns>
        public string GetHelpMaml(string helpItemName, string path)
        {
            // Get the verb and noun from helpItemName
            //
            string verb = null;
            string noun = null;
            XmlReader reader = null;

            try
            {
                if (!string.IsNullOrEmpty(helpItemName))
                {
                    CmdletInfo.SplitCmdletName(helpItemName, out verb, out noun);
                }
                else
                {
                    return string.Empty;
                }

                if (string.IsNullOrEmpty(verb) || string.IsNullOrEmpty(noun))
                {
                    return string.Empty;
                }

                // Load the help file from the current UI culture subfolder
                XmlDocument document = new XmlDocument();
                CultureInfo currentUICulture = CultureInfo.CurrentUICulture;
                string fullHelpPath = Path.Combine(
                    string.IsNullOrEmpty(this.ProviderInfo.ApplicationBase) ? string.Empty : this.ProviderInfo.ApplicationBase,
                    currentUICulture.ToString(),
                    string.IsNullOrEmpty(this.ProviderInfo.HelpFile) ? string.Empty : this.ProviderInfo.HelpFile);

                XmlReaderSettings settings = new XmlReaderSettings();
                settings.XmlResolver = null;
                reader = XmlReader.Create(fullHelpPath, settings);
                document.Load(reader);

                // Add "msh" and "command" namespaces from the MAML schema
                XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
                nsMgr.AddNamespace("msh", HelpCommentsParser.mshURI);
                nsMgr.AddNamespace("command", HelpCommentsParser.commandURI);

                // Compose XPath query to select the appropriate node based on the cmdlet
                string xpathQuery = string.Format(
                    CultureInfo.InvariantCulture,
                    HelpCommentsParser.ProviderHelpCommandXPath,
                    "[@id='FileSystem']",
                    verb,
                    noun);

                // Execute the XPath query and return its MAML snippet
                XmlNode result = document.SelectSingleNode(xpathQuery, nsMgr);
                if (result != null)
                {
                    return result.OuterXml;
                }
            }
            catch (XmlException)
            {
                return string.Empty;
            }
            catch (PathTooLongException)
            {
                return string.Empty;
            }
            catch (IOException)
            {
                return string.Empty;
            }
            catch (UnauthorizedAccessException)
            {
                return string.Empty;
            }
            catch (NotSupportedException)
            {
                return string.Empty;
            }
            catch (SecurityException)
            {
                return string.Empty;
            }
            catch (XPathException)
            {
                return string.Empty;
            }
            finally
            {
                if (reader != null)
                {
                    ((IDisposable)reader).Dispose();
                }
            }

            return string.Empty;
        }

        #endregion

        #region CmdletProvider members

        /// <summary>
        /// Starts the File System provider. This method sets the Home for the
        /// provider to providerInfo.Home if specified, and %USERPROFILE%
        /// otherwise.
        /// </summary>
        /// <param name="providerInfo">
        /// The ProviderInfo object that holds the provider's configuration.
        /// </param>
        /// <returns>
        /// The updated ProviderInfo object that holds the provider's configuration.
        /// </returns>
        protected override ProviderInfo Start(ProviderInfo providerInfo)
        {
            // Set the home folder for the user
            if (providerInfo != null && string.IsNullOrEmpty(providerInfo.Home))
            {
                // %USERPROFILE% - indicate where a user's home directory is located in the file system.
                string homeDirectory = Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home);

                if (!string.IsNullOrEmpty(homeDirectory))
                {
                    if (Directory.Exists(homeDirectory))
                    {
                        s_tracer.WriteLine("Home = {0}", homeDirectory);
                        providerInfo.Home = homeDirectory;
                    }
                    else
                    {
                        s_tracer.WriteLine("Not setting home directory {0} - does not exist", homeDirectory);
                    }
                }
            }

            // OneDrive placeholder support (issue #8315)
            // make it so OneDrive placeholders are perceived as such with *all* their attributes accessible
#if !UNIX
            // The placeholder mode management APIs Rtl(Set|Query)(Process|Thread)PlaceholderCompatibilityMode
            // are only supported starting with Windows 10 version 1803 (build 17134)
            Version minBuildForPlaceHolderAPIs = new Version(10, 0, 17134, 0);
            if (Environment.OSVersion.Version >= minBuildForPlaceHolderAPIs)
            {
                // let's be safe, don't change the PlaceHolderCompatibilityMode if the current one is not what we expect
                if (NativeMethods.PHCM_DISGUISE_PLACEHOLDER == NativeMethods.RtlQueryProcessPlaceholderCompatibilityMode())
                {
                    NativeMethods.RtlSetProcessPlaceholderCompatibilityMode(NativeMethods.PHCM_EXPOSE_PLACEHOLDERS);
                }
            }
#endif

            return providerInfo;
        }

        #endregion CmdletProvider members

        #region DriveCmdletProvider members

        /// <summary>
        /// Determines if the specified drive can be mounted.
        /// </summary>
        /// <param name="drive">
        /// The drive that is going to be mounted.
        /// </param>
        /// <returns>
        /// The same drive that was passed in, if the drive can be mounted.
        /// null if the drive cannot be mounted.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// drive is null.
        /// </exception>
        /// <exception cref="System.ArgumentException">
        /// drive root is null or empty.
        /// </exception>
        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            // verify parameters
            if (drive == null)
            {
                throw PSTraceSource.NewArgumentNullException(nameof(drive));
            }

            if (string.IsNullOrEmpty(drive.Root))
            {
                throw PSTraceSource.NewArgumentException("drive.Root");
            }

            // -Persist switch parameter is supported only for Network paths.
            if (drive.Persist && !PathIsNetworkPath(drive.Root))
            {
                ErrorRecord er = new ErrorRecord(new NotSupportedException(FileSystemProviderStrings.PersistNotSupported), "DriveRootNotNetworkPath", ErrorCategory.InvalidArgument, drive);
                ThrowTerminatingError(er);
            }

            if (IsNetworkMappedDrive(drive))
            {
                // MapNetworkDrive facilitates to map the newly
                // created PS Drive to a network share.
                this.MapNetworkDrive(drive);
            }

            // The drive is valid if the item exists or the
            // drive is not a fixed drive.  We want to allow
            // a drive to exist for floppies and other such\
            // removable media, even if the media isn't in place.
            bool driveIsFixed = true;
            PSDriveInfo result = null;

            try
            {
                // See if the drive is a fixed drive.
                string pathRoot = Path.GetPathRoot(drive.Root);
                DriveInfo driveInfo = new DriveInfo(pathRoot);

                if (driveInfo.DriveType != DriveType.Fixed)
                {
                    driveIsFixed = false;
                }

                // The current drive is a network drive.
                if (driveInfo.DriveType == DriveType.Network)
                {
                    drive.IsNetworkDrive = true;
                }
            }
            catch (ArgumentException) // swallow ArgumentException incl. ArgumentNullException
            {
            }

            bool validDrive = true;

            if (driveIsFixed)
            {
                // Since the drive is fixed, ensure the root is valid.
                validDrive = Directory.Exists(drive.Root);
            }

            if (validDrive)
            {
                result = drive;
            }
            else
            {
                string error = StringUtil.Format(FileSystemProviderStrings.DriveRootError, drive.Root);
                Exception e = new IOException(error);
                WriteError(new ErrorRecord(e, "DriveRootError", ErrorCategory.ReadError, drive));
            }

            drive.Trace();

            return result;
        }

        /// <summary>
        /// MapNetworkDrive facilitates to map the newly created PS Drive to a network share.
        /// </summary>
        /// <param name="drive">The PSDrive info that would be used to create a new PS drive.</param>
        private void MapNetworkDrive(PSDriveInfo drive)
        {
            // Porting note: mapped network drives are only supported on Windows
            if (Platform.IsWindows)
            {
                WinMapNetworkDrive(drive);
            }
            else
            {
                throw new PlatformNotSupportedException();
            }
        }

        private static bool _WNetApiAvailable = true;

        private void WinMapNetworkDrive(PSDriveInfo drive)
        {
            if (drive != null && !string.IsNullOrEmpty(drive.Root))
            {
                const int CONNECT_UPDATE_PROFILE = 0x00000001;
                const int CONNECT_NOPERSIST = 0x00000000;
                const int RESOURCE_GLOBALNET = 0x00000002;
                const int RESOURCETYPE_ANY = 0x00000000;
                const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
                const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
                const int ERROR_NO_NETWORK = 1222;

                // By default the connection is not persisted.
                int CONNECT_TYPE = CONNECT_NOPERSIST;

                string driveName = null;
                byte[] passwd = null;
                string userName = null;

                if (drive.Persist)
                {
                    if (IsSupportedDriveForPersistence(drive))
                    {
                        CONNECT_TYPE = CONNECT_UPDATE_PROFILE;
                        driveName = drive.Name + ":";
                        drive.DisplayRoot = drive.Root;
                    }
                    else
                    {
                        // error.
                        ErrorRecord er = new ErrorRecord(new InvalidOperationException(FileSystemProviderStrings.InvalidDriveName), "DriveNameNotSupportedForPersistence", ErrorCategory.InvalidOperation, drive);
                        ThrowTerminatingError(er);
                    }
                }

                // If alternate credentials is supplied then use them to get connected to network share.
                if (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty))
                {
                    userName = drive.Credential.UserName;

                    passwd = SecureStringHelper.GetData(drive.Credential.Password);
                }

                try
                {
                    NetResource resource = new NetResource();
                    resource.Comment = null;
                    resource.DisplayType = RESOURCEDISPLAYTYPE_GENERIC;
                    resource.LocalName = driveName;
                    resource.Provider = null;
                    resource.RemoteName = drive.Root;
                    resource.Scope = RESOURCE_GLOBALNET;
                    resource.Type = RESOURCETYPE_ANY;
                    resource.Usage = RESOURCEUSAGE_CONNECTABLE;

                    int code = ERROR_NO_NETWORK;

                    if (_WNetApiAvailable)
                    {
                        try
                        {
                            code = NativeMethods.WNetAddConnection2(ref resource, passwd, userName, CONNECT_TYPE);
                        }
                        catch (System.DllNotFoundException)
                        {
                            _WNetApiAvailable = false;
                        }
                    }

                    if (code != 0)
                    {
                        ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldNotMapNetworkDrive", ErrorCategory.InvalidOperation, drive);
                        ThrowTerminatingError(er);
                    }

                    if (CONNECT_TYPE == CONNECT_UPDATE_PROFILE)
                    {
                        // Update the current PSDrive to be a persisted drive.
                        drive.IsNetworkDrive = true;

                        // PsDrive.Root is updated to the name of the Drive for
                        // drives targeting network path and being persisted.
                        drive.Root = driveName + @"\";
                    }
                }
                finally
                {
                    // Clear the password in the memory.
                    if (passwd != null)
                    {
                        Array.Clear(passwd, 0, passwd.Length - 1);
                    }
                }
            }
        }

        /// <summary>
        /// ShouldMapNetworkDrive is a helper function used to detect if the
        /// requested PSDrive to be created has to be mapped to a network drive.
        /// </summary>
        /// <param name="drive"></param>
        /// <returns></returns>
        private bool IsNetworkMappedDrive(PSDriveInfo drive)
        {
            bool shouldMapNetworkDrive = (drive != null && !string.IsNullOrEmpty(drive.Root) && PathIsNetworkPath(drive.Root)) &&
                                         (drive.Persist || (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty)));

            return shouldMapNetworkDrive;
        }

        /// <summary>
        /// RemoveDrive facilitates to remove network mapped persisted PSDrvie.
        /// </summary>
        /// <param name="drive">
        /// PSDrive info.
        /// </param>
        /// <returns>PSDrive info.
        /// </returns>
        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
#if UNIX
            return drive;
#else
            return WinRemoveDrive(drive);
#endif
        }

        private PSDriveInfo WinRemoveDrive(PSDriveInfo drive)
        {
            if (IsNetworkMappedDrive(drive))
            {
                const int CONNECT_UPDATE_PROFILE = 0x00000001;
                const int ERROR_NO_NETWORK = 1222;

                int flags = 0;
                string driveName;
                if (drive.IsNetworkDrive)
                {
                    // Here we are removing only persisted network drives.
                    flags = CONNECT_UPDATE_PROFILE;
                    driveName = drive.Name + ":";
                }
                else
                {
                    // OSGTFS: 608188 PSDrive leaves a connection open after the drive is removed
                    // if a drive is not persisted or networkdrive, we need to use the actual root to remove the drive.
                    driveName = drive.Root;
                }

                // You need to actually remove the drive.
                int code = ERROR_NO_NETWORK;

                if (_WNetApiAvailable)
                {
                    try
                    {
                        code = NativeMethods.WNetCancelConnection2(driveName, flags, true);
                    }
                    catch (System.DllNotFoundException)
                    {
                        _WNetApiAvailable = false;
                    }
                }

                if (code != 0)
                {
                    ErrorRecord er = new ErrorRecord(new System.ComponentModel.Win32Exception(code), "CouldRemoveNetworkDrive", ErrorCategory.InvalidOperation, drive);
                    ThrowTerminatingError(er);
                }
            }

            return drive;
        }

        /// <summary>
        /// IsSupportedDriveForPersistence is a helper method used to
        /// check if the psdrive can be persisted or not.
        /// </summary>
        /// <param name="drive">
        /// PS Drive Info.
        /// </param>
        /// <returns>True if the drive can be persisted or else false.</returns>
        private bool IsSupportedDriveForPersistence(PSDriveInfo drive)
        {
            bool isSupportedDriveForPersistence = false;
            if (drive != null && !string.IsNullOrEmpty(drive.Name) && drive.Name.Length == 1)
            {
                char driveChar = Convert.ToChar(drive.Name, CultureInfo.InvariantCulture);

                if (char.ToUpperInvariant(driveChar) >= 'A' && char.ToUpperInvariant(driveChar) <= 'Z')
                {
                    isSupportedDriveForPersistence = true;
                }
            }

            return isSupportedDriveForPersistence;
        }

        /// <summary>
        /// Return the UNC path for a given network drive
        /// using the Windows API.
        /// </summary>
        /// <param name="driveName"></param>
        /// <returns></returns>
        internal static string GetUNCForNetworkDrive(string driveName)
        {
#if UNIX
            return driveName;
#else
            return WinGetUNCForNetworkDrive(driveName);
#endif
        }

        private static string WinGetUNCForNetworkDrive(string driveName)
        {
            const int ERROR_NO_NETWORK = 1222;
            string uncPath = null;
            if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)
            {
                // By default buffer size is set to 300 which would generally be sufficient in most of the cases.
                int bufferSize = 300;
#if DEBUG
                // In Debug mode buffer size is initially set to 3 and if additional buffer is required, the
                // required buffer size is allocated and the WNetGetConnection API is executed with the newly
                // allocated buffer size.
                bufferSize = 3;
#endif

                StringBuilder uncBuffer = new StringBuilder(bufferSize);
                driveName += ':';

                // Call the windows API
                int errorCode = ERROR_NO_NETWORK;

                try
                {
                    errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize);
                }
                catch (System.DllNotFoundException)
                {
                    return null;
                }

                // error code 234 is returned whenever the required buffer size is greater
                // than the specified buffer size.
                if (errorCode == 234)
                {
                    uncBuffer = new StringBuilder(bufferSize);
                    errorCode = NativeMethods.WNetGetConnection(driveName, uncBuffer, ref bufferSize);
                }

                if (errorCode != 0)
                {
                    throw new System.ComponentModel.Win32Exception(errorCode);
                }

                uncPath = uncBuffer.ToString();
            }

            return uncPath;
        }

        /// <summary>
        /// Get the substituted path of a NetWork type MS-DOS device that is created by 'subst' command.
        /// When a MS-DOS device is of NetWork type, it could be:
        ///   1. Substitute a path in a drive that maps to a network location. For example:
        ///         net use z: \\scratch2\scratch\
        ///         subst y: z:\abc\
        ///   2. Substitute a network location directly. For example:
        ///         subst y: \\scratch2\scratch\
        /// </summary>
        /// <param name="driveName"></param>
        /// <returns></returns>
        internal static string GetSubstitutedPathForNetworkDosDevice(string driveName)
        {
#if UNIX
            throw new PlatformNotSupportedException();
#else
            return WinGetSubstitutedPathForNetworkDosDevice(driveName);
#endif
        }

        private static string WinGetSubstitutedPathForNetworkDosDevice(string driveName)
        {
            string associatedPath = null;
            if (!string.IsNullOrEmpty(driveName) && driveName.Length == 1)
            {
                // By default buffer size is set to 300 which would generally be sufficient in most of the cases.
                int bufferSize = 300;
                var pathInfo = new StringBuilder(bufferSize);
                driveName += ':';

                // Call the windows API
                while (true)
                {
                    pathInfo.EnsureCapacity(bufferSize);
                    int retValue = NativeMethods.QueryDosDevice(driveName, pathInfo, bufferSize);
                    if (retValue > 0)
                    {
                        // If the drive letter is a substed path, the result will be in the format of
                        //  - "\??\C:\RealPath" for local path
                        //  - "\??\UNC\RealPath" for network path
                        associatedPath = pathInfo.ToString();
                        if (associatedPath.StartsWith("\\??\\", StringComparison.OrdinalIgnoreCase))
                        {
                            associatedPath = associatedPath.Remove(0, 4);
                            if (associatedPath.StartsWith("UNC", StringComparison.OrdinalIgnoreCase))
                            {
                                associatedPath = associatedPath.Remove(0, 3);
                                associatedPath = "\\" + associatedPath;
                            }
                            else if (associatedPath.EndsWith(':'))
                            {
                                // The substed path is the root path of a drive. For example: subst Y: C:\
                                associatedPath += Path.DirectorySeparatorChar;
                            }
                        }
                        else
                        {
                            // The drive name is not a substed path, then we return the root path of the drive
                            associatedPath = driveName + "\\";
                        }

                        break;
                    }

                    // Windows API call failed
                    int errorCode = Marshal.GetLastWin32Error();
                    if (errorCode != 122)
                    {
                        // ERROR_INSUFFICIENT_BUFFER = 122
                        // For an error other than "insufficient buffer", throw it
                        throw new Win32Exception((int)errorCode);
                    }

                    // We got the "insufficient buffer" error. In this case we extend
                    // the buffer size, unless it's unreasonably too large.
                    if (bufferSize >= 32767)
                    {
                        // "The Windows API has many functions that also have Unicode versions to permit
                        // an extended-length path for a maximum total path length of 32,767 characters"
                        // See https://msdn.microsoft.com/library/aa365247.aspx#maxpath
                        string errorMsg = StringUtil.Format(FileSystemProviderStrings.SubstitutePathTooLong, driveName);
                        throw new InvalidOperationException(errorMsg);
                    }

                    // Extend the buffer size and try again.
                    bufferSize *= 10;
                    if (bufferSize > 32767)
                    {
                        bufferSize = 32767;
                    }
                }
            }

            return associatedPath;
        }

        /// <summary>
        /// Get the root path for a network drive or MS-DOS device.
        /// </summary>
        /// <param name="driveInfo"></param>
        /// <returns></returns>
        internal static string GetRootPathForNetworkDriveOrDosDevice(DriveInfo driveInfo)
        {
            Dbg.Diagnostics.Assert(driveInfo.DriveType == DriveType.Network, "Caller should make sure it is a network drive.");

            string driveName = driveInfo.Name.Substring(0, 1);
            string rootPath = null;

            try
            {
                rootPath = GetUNCForNetworkDrive(driveName);
            }
            catch (Win32Exception)
            {
                if (driveInfo.IsReady)
                {
                    // The drive is ready but we failed to find the UNC path based on the drive name.
                    // In this case, it's possibly a MS-DOS device created by 'subst' command that
                    //  - substitutes a network location directly, or
                    //  - substitutes a path in a drive that maps to a network location
                    rootPath = GetSubstitutedPathForNetworkDosDevice(driveName);
                }
                else
                {
                    throw;
                }
            }

            return rootPath;
        }

        /// <summary>
        /// Returns a collection of all logical drives in the system.
        /// </summary>
        /// <returns>
        /// A collection of PSDriveInfo objects, one for each logical drive returned from
        /// System.Environment.GetLogicalDrives().
        /// </returns>
        protected override Collection<PSDriveInfo> InitializeDefaultDrives()
        {
            Collection<PSDriveInfo> results = new Collection<PSDriveInfo>();

            DriveInfo[] logicalDrives = DriveInfo.GetDrives();
            if (logicalDrives != null)
            {
                foreach (DriveInfo newDrive in logicalDrives)
                {
                    // Making sure to obey the StopProcessing.
                    if (Stopping)
                    {
                        results.Clear();
                        break;
                    }

                    // cover everything by the try-catch block, because some of the
                    // DriveInfo properties may throw exceptions
                    try
                    {
                        string newDriveName = newDrive.Name.Substring(0, 1);

                        string description = string.Empty;
                        string root = newDrive.Name;
                        string displayRoot = null;

                        if (newDrive.DriveType == DriveType.Fixed)
                        {
                            try
                            {
                                description = newDrive.VolumeLabel;
                            }
                            // trying to read the volume label may cause an
                            // IOException or SecurityException. Just default
                            // to an empty description.
                            catch (IOException)
                            {
                            }
                            catch (System.Security.SecurityException)
                            {
                            }
                            catch (System.UnauthorizedAccessException)
                            {
                            }
                        }

                        if (newDrive.DriveType == DriveType.Network)
                        {
                            // Platform notes: This is important because certain mount
                            // points on non-Windows are enumerated as drives by .NET, but
                            // the platform itself then has no real network drive support
                            // as required by this context. Solution: check for network
                            // drive support before using it.
#if UNIX
                            continue;
#else
                            displayRoot = GetRootPathForNetworkDriveOrDosDevice(newDrive);
#endif
                        }

                        if (newDrive.DriveType == DriveType.Fixed)
                        {
                            if (!newDrive.RootDirectory.Exists)
                            {
                                continue;
                            }

                            root = newDrive.RootDirectory.FullName;
                        }

#if UNIX
                        // Porting notes: On platforms with single root filesystems, ensure
                        // that we add a filesystem with the root "/" to the initial drive list,
                        // otherwise path handling will not work correctly because there
                        // is no : available to separate the filesystems from each other
                        if (root != StringLiterals.DefaultPathSeparatorString
                            && newDriveName == StringLiterals.DefaultPathSeparatorString)
                        {
                            root = StringLiterals.DefaultPathSeparatorString;
                        }
#endif

                        // Porting notes: On non-windows platforms .net can report two
                        // drives with the same root, make sure to only add one of those
                        bool skipDuplicate = false;
                        foreach (PSDriveInfo driveInfo in results)
                        {
                            if (driveInfo.Root == root)
                            {
                                skipDuplicate = true;
                                break;
                            }
                        }

                        if (skipDuplicate)
                        {
                            continue;
                        }

                        // Create a new VirtualDrive for each logical drive
                        PSDriveInfo newPSDriveInfo =
                            new PSDriveInfo(
                                newDriveName,
                                ProviderInfo,
                                root,
                                description,
                                null,
                                displayRoot);

                        // The network drive is detected when PowerShell is launched.
                        // Hence it has been persisted during one of the earlier sessions,
                        if (newDrive.DriveType == DriveType.Network)
                        {
                            newPSDriveInfo.IsNetworkDrive = true;
                        }

                        if (newDrive.DriveType != DriveType.Fixed)
                        {
                            newPSDriveInfo.IsAutoMounted = true;
                        }

                        // Porting notes: on the non-Windows platforms, the drive never
                        // uses : as a separator between drive and path
                        if (!Platform.IsWindows)
                        {
                            newPSDriveInfo.VolumeSeparatedByColon = false;
                        }

                        results.Add(newPSDriveInfo);
                    }
                    // If there are issues accessing properties of the DriveInfo, do
                    // not add the drive
                    catch (IOException)
                    {
                    }
                    catch (System.Security.SecurityException)
                    {
                    }
                    catch (System.UnauthorizedAccessException)
                    {
                    }
                }
            }

            results.Add(
                new PSDriveInfo(
                    DriveNames.TempDrive,
                    ProviderInfo,
                    Path.GetTempPath(),
                    SessionStateStrings.TempDriveDescription,
                    credential: null,
                    displayRoot: null)
            );

            return results;
        }

        #endregion DriveCmdletProvider methods

        #region ItemCmdletProvider methods

        /// <summary>
        /// Retrieves the dynamic parameters required for the Get-Item cmdlet.
        /// </summary>
        /// <param name="path">The path of the file to process.</param>
        /// <returns>An instance of the FileSystemProviderGetItemDynamicParameters class that represents the dynamic parameters.</returns>
        protected override object GetItemDynamicParameters(string path)
        {
            return new FileSystemProviderGetItemDynamicParameters();
        }

        /// <summary>
        /// Determines if the specified path is syntactically and semantically valid.
        /// An example path looks like this
        ///     C:\WINNT\Media\chimes.wav.
        /// </summary>
        /// <param name="path">
        /// The fully qualified path to validate.
        /// </param>
        /// <returns>
        /// True if the path is valid, false otherwise.
        /// </returns>
        protected override bool IsValidPath(string path)
        {
            // Path passed should be fully qualified path.
            if (string.IsNullOrEmpty(path))
            {
                return false;
            }

            // Normalize the path
            path = NormalizePath(path);
            path = EnsureDriveIsRooted(path);

#if !UNIX
            // Remove alternate data stream references
            // See if they've used the inline stream syntax. They have more than one colon.
            int firstColon = path.IndexOf(':');
            int secondColon = path.IndexOf(':', firstColon + 1);
            if (secondColon > 0)
            {
                path = path.Substring(0, secondColon);
            }
#endif

            // Make sure the path is either drive rooted or UNC Path
            if (!IsAbsolutePath(path) && !Utils.PathIsUnc(path))
            {
                return false;
            }

            // Exceptions should only deal with exceptional circumstances,
            // but unfortunately, FileInfo offers no Try() methods that
            // let us check if we _could_ open the file.
            try
            {
                FileInfo testFile = new FileInfo(path);
            }
            catch (Exception e)
            {
                if ((e is ArgumentNullException) ||
                    (e is ArgumentException) ||
                    (e is System.Security.SecurityException) ||
                    (e is UnauthorizedAccessException) ||
                    (e is PathTooLongException) ||
                    (e is NotSupportedException))
                {
                    return false;
                }
                else
                {
                    throw;
                }
            }

            return true;
        }

        /// <summary>
        /// Gets the item at the specified path.
        /// </summary>
        /// <param name="path">
        /// A fully qualified path representing a file or directory in the
        /// file system.
        /// </param>
        /// <returns>
        /// Nothing.  FileInfo and DirectoryInfo objects are written to the
        /// context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override void GetItem(string path)
        {
            // Validate the argument
            bool isContainer = false;

            if (string.IsNullOrEmpty(path))
            {
                // The parameter was null, throw an exception
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            try
            {
#if !UNIX
                bool retrieveStreams = false;
                FileSystemProviderGetItemDynamicParameters dynamicParameters = null;

                if (DynamicParameters != null)
                {
                    dynamicParameters = DynamicParameters as FileSystemProviderGetItemDynamicParameters;
                    if (dynamicParameters != null)
                    {
                        if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
                        {
                            retrieveStreams = true;
                        }
                        else
                        {
                            // See if they've used the inline stream syntax. They have more than one colon.
                            int firstColon = path.IndexOf(':');
                            int secondColon = path.IndexOf(':', firstColon + 1);
                            if (secondColon > 0)
                            {
                                string streamName = path.Substring(secondColon + 1);
                                path = path.Remove(secondColon);

                                retrieveStreams = true;
                                dynamicParameters = new FileSystemProviderGetItemDynamicParameters();
                                dynamicParameters.Stream = new string[] { streamName };
                            }
                        }
                    }
                }
#endif

                FileSystemInfo result = GetFileSystemItem(path, ref isContainer, false);
                if (result != null)
                {
#if !UNIX
                    // If we want to retrieve the file streams, retrieve them.
                    if (retrieveStreams)
                    {
                        if (!isContainer)
                        {
                            foreach (string desiredStream in dynamicParameters.Stream)
                            {
                                // See that it matches the name specified
                                WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
                                bool foundStream = false;

                                foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName))
                                {
                                    if (!p.IsMatch(stream.Stream)) { continue; }

                                    string outputPath = result.FullName + ":" + stream.Stream;
                                    WriteItemObject(stream, outputPath, isContainer);
                                    foundStream = true;
                                }

                                if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
                                {
                                    string errorMessage = StringUtil.Format(
                                        FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName);
                                    Exception e = new FileNotFoundException(errorMessage, result.FullName);

                                    WriteError(new ErrorRecord(
                                        e,
                                        "AlternateDataStreamNotFound",
                                        ErrorCategory.ObjectNotFound,
                                        path));
                                }
                            }
                        }
                    }
                    else
#endif
                    {
                        // Otherwise, return the item itself.
                        WriteItemObject(result, result.FullName, isContainer);
                    }
                }
                else
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, path);
                    Exception e = new IOException(error);
                    WriteError(new ErrorRecord(
                        e,
                        "ItemNotFound",
                        ErrorCategory.ObjectNotFound,
                        path));
                }
            }
            catch (IOException ioError)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                ErrorRecord er = new ErrorRecord(ioError, "GetItemIOError", ErrorCategory.ReadError, path);
                WriteError(er);
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "GetItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
        }

        private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool showHidden)
        {
            path = NormalizePath(path);
            FileInfo result = new FileInfo(path);

            // FileInfo.Exists is always false for a directory path, so we check the attribute for existence.
            var attributes = result.Attributes;
            if ((int)attributes == -1) { /* Path doesn't exist. */ return null; }

            bool hidden = attributes.HasFlag(FileAttributes.Hidden);
            isContainer = attributes.HasFlag(FileAttributes.Directory);

            FlagsExpression<FileAttributes> evaluator = null;
            FlagsExpression<FileAttributes> switchEvaluator = null;
            GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
            if (fspDynamicParam != null)
            {
                evaluator = fspDynamicParam.Attributes;
                switchEvaluator = FormatAttributeSwitchParameters();
            }

            bool filterHidden = false;           // "Hidden" is specified somewhere in the expression
            bool switchFilterHidden = false;     // "Hidden" is specified somewhere in the parameters

            if (evaluator != null)
            {
                filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
            }

            if (switchEvaluator != null)
            {
                switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
            }

            // if "Hidden" is specified in the attribute filter dynamic parameters
            // also return the object
            if (!isContainer)
            {
                if (!hidden || Force || showHidden || filterHidden || switchFilterHidden)
                {
                    s_tracer.WriteLine("Got file info: {0}", result);
                    return result;
                }
            }
            else
            {
                // Check to see if the path is the root of a file system drive.
                // Since all root paths are hidden we need to show the directory
                // anyway
                bool isRootPath =
                    string.Equals(
                        Path.GetPathRoot(path),
                        result.FullName,
                        StringComparison.OrdinalIgnoreCase);

                // if "Hidden" is specified in the attribute filter dynamic parameters
                // also return the object
                if (isRootPath || !hidden || Force || showHidden || filterHidden || switchFilterHidden)
                {
                    s_tracer.WriteLine("Got directory info: {0}", result);
                    return new DirectoryInfo(path);
                }
            }

            return null;
        }

        /// <summary>
        /// Invokes the item at the path using ShellExecute semantics.
        /// </summary>
        /// <param name="path">
        /// The item to invoke.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override void InvokeDefaultAction(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            string action = FileSystemProviderStrings.InvokeItemAction;

            string resource = StringUtil.Format(FileSystemProviderStrings.InvokeItemResourceFileTemplate, path);

            if (ShouldProcess(resource, action))
            {
                var invokeProcess = new System.Diagnostics.Process();
                invokeProcess.StartInfo.FileName = path;
#if UNIX
                bool useShellExecute = false;
                if (Directory.Exists(path))
                {
                    // Path points to a directory. We have to use xdg-open/open on Linux/macOS.
                    useShellExecute = true;
                }
                else
                {
                    try
                    {
                        // Try Process.Start first. This works for executables on Win/Unix platforms
                        invokeProcess.Start();
                    }
                    catch (Win32Exception ex) when (ex.NativeErrorCode == 13)
                    {
                        // Error code 13 -- Permission denied
                        // The file is possibly not an executable. We try xdg-open/open on Linux/macOS.
                        useShellExecute = true;
                    }
                }

                if (useShellExecute)
                {
                    invokeProcess.StartInfo.UseShellExecute = true;
                    invokeProcess.Start();
                }
#else
                // Use ShellExecute when it's not a headless SKU
                invokeProcess.StartInfo.UseShellExecute = Platform.IsWindowsDesktop;
                invokeProcess.Start();
#endif
            }
        }

        #endregion ItemCmdletProvider members

        #region ContainerCmdletProvider members

        #region GetChildItems
        /// <summary>
        /// Gets the child items of a given directory.
        /// </summary>
        /// <param name="path">
        /// The full path of the directory to enumerate.
        /// </param>
        /// <param name="recurse">
        /// If true, recursively enumerates the child items as well.
        /// </param>
        /// <param name="depth">
        /// Limits the depth of recursion; uint.MaxValue performs full recursion.
        /// </param>
        /// <returns>
        /// Nothing.  FileInfo and DirectoryInfo objects that match the filter are written to the
        /// context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override void GetChildItems(
            string path,
            bool recurse,
            uint depth)
        {
            GetPathItems(path, recurse, depth, false, ReturnContainers.ReturnMatchingContainers);
        }
        #endregion GetChildItems

        #region GetChildNames
        /// <summary>
        /// Gets the path names for all children of the specified
        /// directory that match the given filter.
        /// </summary>
        /// <param name="path">
        /// The full path of the directory to enumerate.
        /// </param>
        /// <param name="returnContainers">
        /// Determines if all containers should be returned or only those containers that match the
        /// filter(s).
        /// </param>
        /// <returns>
        /// Nothing.  Child names are written to the context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override void GetChildNames(
            string path,
            ReturnContainers returnContainers)
        {
            GetPathItems(path, false, uint.MaxValue, true, returnContainers);
        }
        #endregion GetChildNames

        /// <summary>
        /// Gets a new provider-specific path and filter (if any) that corresponds to the given
        /// path.
        /// </summary>
        /// <param name="path">
        /// The path to the item. Unlike most other provider APIs, this path is likely to
        /// contain PowerShell wildcards.
        /// </param>
        /// <param name="filter">
        /// The provider-specific filter currently applied.
        /// </param>
        /// <param name="updatedPath">
        /// The new path to the item.
        /// </param>
        /// <param name="updatedFilter">
        /// The new filter.
        /// </param>
        /// <returns>
        /// True if the path or filter were altered. False otherwise.
        /// </returns>
        /// <remarks>
        /// Makes no attempt to filter if the user has already specified a filter, or
        /// if the path contains directory separators. Those are not supported by the
        /// FileSystem filter.
        /// </remarks>
        protected override bool ConvertPath(
            string path,
            string filter,
            ref string updatedPath,
            ref string updatedFilter)
        {
            // Don't handle full paths, paths that the user is already trying to
            // filter, or paths they are trying to escape.
            if ((!string.IsNullOrEmpty(filter)) ||
                (path.Contains(StringLiterals.DefaultPathSeparator, StringComparison.Ordinal)) ||
                (path.Contains(StringLiterals.AlternatePathSeparator, StringComparison.Ordinal)) ||
                (path.Contains(StringLiterals.EscapeCharacter)))
            {
                return false;
            }

            // We can never actually modify the PowerShell path, as the
            // Win32 filtering support returns items that match the short
            // filename OR long filename.
            //
            // This creates tons of seemingly incorrect matches, such as:
            //
            // *~*:   Matches any file with a long filename
            // *n*:   Matches all files with a long filename, but have been
            //        mapped to a [6][~n].[3] disambiguation bucket
            // *.abc: Matches all files that have an extension that begins
            //        with ABC, since their extension is truncated in the
            //        short filename
            // *.*:   Matches all files and directories, even if they don't
            //        have a dot in their name

            // Our algorithm here is pretty simple. The filesystem can handle
            // * and ? in PowerShell wildcards, just not character ranges [a-z].
            // We replace character ranges with the single-character wildcard, '?'.
            updatedPath = path;
            updatedFilter = System.Text.RegularExpressions.Regex.Replace(path, "\\[.*?\\]", "?");

            return true;
        }

        private void GetPathItems(
            string path,
            bool recurse,
            uint depth,
            bool nameOnly,
            ReturnContainers returnContainers)
        {
            // Verify parameters
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            var fsinfo = GetFileSystemInfo(path, out bool isDirectory);

            if (fsinfo != null)
            {
                if (isDirectory)
                {
                    InodeTracker tracker = null;

                    if (recurse)
                    {
                        GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
                        if (fspDynamicParam != null && fspDynamicParam.FollowSymlink)
                        {
                            tracker = new InodeTracker(fsinfo.FullName);
                        }
                    }

                    // Enumerate the directory
                    Dir((DirectoryInfo)fsinfo, recurse, depth, nameOnly, returnContainers, tracker);
                }
                else
                {
                    FlagsExpression<FileAttributes> evaluator = null;
                    FlagsExpression<FileAttributes> switchEvaluator = null;
                    GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
                    if (fspDynamicParam != null)
                    {
                        evaluator = fspDynamicParam.Attributes;
                        switchEvaluator = FormatAttributeSwitchParameters();
                    }

                    bool attributeFilter = true;
                    bool switchAttributeFilter = true;
                    bool filterHidden = false;           // "Hidden" is specified somewhere in the expression
                    bool switchFilterHidden = false;     // "Hidden" is specified somewhere in the parameters

                    if (evaluator != null)
                    {
                        attributeFilter = evaluator.Evaluate(fsinfo.Attributes);  // expressions
                        filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
                    }

                    if (switchEvaluator != null)
                    {
                        switchAttributeFilter = switchEvaluator.Evaluate(fsinfo.Attributes);  // switch parameters
                        switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
                    }

                    bool hidden = (fsinfo.Attributes & FileAttributes.Hidden) != 0;

                    // if "Hidden" is explicitly specified anywhere in the attribute filter, then override
                    // default hidden attribute filter.
                    if ((attributeFilter && switchAttributeFilter)
                        && (filterHidden || switchFilterHidden || Force || !hidden))
                    {
                        if (nameOnly)
                        {
                            WriteItemObject(
                                fsinfo.Name,
                                fsinfo.FullName,
                                false);
                        }
                        else
                        {
                            WriteItemObject(fsinfo, path, false);
                        }
                    }
                }
            }
            else
            {
                string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                Exception e = new IOException(error);
                WriteError(new ErrorRecord(
                    e,
                    "ItemDoesNotExist",
                    ErrorCategory.ObjectNotFound,
                    path));
                return;
            }
        }

        private void Dir(
            DirectoryInfo directory,
            bool recurse,
            uint depth,
            bool nameOnly,
            ReturnContainers returnContainers,
            InodeTracker tracker)   // tracker will be non-null only if the user invoked the -FollowSymLinks and -Recurse switch parameters.
        {
            List<IEnumerable<FileSystemInfo>> target = new List<IEnumerable<FileSystemInfo>>();

            try
            {
                if (Filter != null &&
                    Filter.Length > 0)
                {
                    if (returnContainers == ReturnContainers.ReturnAllContainers)
                    {
                        // Don't filter directories
                        target.Add(directory.EnumerateDirectories());
                    }
                    else
                    {
                        // Filter the directories
                        target.Add(directory.EnumerateDirectories(Filter, _enumerationOptions));
                    }

                    // Making sure to obey the StopProcessing.
                    if (Stopping)
                    {
                        return;
                    }

                    // Use the specified filter when retrieving the
                    // children
                    target.Add(directory.EnumerateFiles(Filter, _enumerationOptions));
                }
                else
                {
                    target.Add(directory.EnumerateDirectories());

                    // Making sure to obey the StopProcessing.
                    if (Stopping)
                    {
                        return;
                    }

                    // Don't use a filter to retrieve the children
                    target.Add(directory.EnumerateFiles());
                }

                FlagsExpression<FileAttributes> evaluator = null;
                FlagsExpression<FileAttributes> switchEvaluator = null;

                GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
                if (fspDynamicParam != null)
                {
                    evaluator = fspDynamicParam.Attributes;
                    switchEvaluator = FormatAttributeSwitchParameters();
                }

                // Write out the items
                foreach (IEnumerable<FileSystemInfo> childList in target)
                {
                    // On some systems, this is already sorted.  For consistency, always sort again.
                    IEnumerable<FileSystemInfo> sortedChildList = childList.OrderBy(c => c.Name, StringComparer.CurrentCultureIgnoreCase);

                    foreach (FileSystemInfo filesystemInfo in sortedChildList)
                    {
                        // Making sure to obey the StopProcessing.
                        if (Stopping)
                        {
                            return;
                        }

                        try
                        {
                            bool attributeFilter = true;
                            bool switchAttributeFilter = true;
                            // 'Hidden' is specified somewhere in the expression
                            bool filterHidden = false;
                            // 'Hidden' is specified somewhere in the parameters
                            bool switchFilterHidden = false;

                            if (evaluator != null)
                            {
                                attributeFilter = evaluator.Evaluate(filesystemInfo.Attributes);
                                filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
                            }

                            if (switchEvaluator != null)
                            {
                                switchAttributeFilter = switchEvaluator.Evaluate(filesystemInfo.Attributes);
                                switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
                            }

                            bool hidden = false;
                            if (!Force)
                            {
                                hidden = (filesystemInfo.Attributes & FileAttributes.Hidden) != 0;
                            }

                            // If 'Hidden' is explicitly specified anywhere in the attribute filter, then override
                            // default hidden attribute filter.
                            // If specification is to return all containers, then do not do attribute filter on
                            // the containers.
                            bool attributeSatisfy =
                                ((attributeFilter && switchAttributeFilter) ||
                                    ((returnContainers == ReturnContainers.ReturnAllContainers) &&
                                    ((filesystemInfo.Attributes & FileAttributes.Directory) != 0)));

                            if (attributeSatisfy && (filterHidden || switchFilterHidden || Force || !hidden))
                            {
                                if (nameOnly)
                                {
                                    WriteItemObject(
                                        filesystemInfo.Name,
                                        filesystemInfo.FullName,
                                        false);
                                }
                                else
                                {
                                    if (filesystemInfo is FileInfo)
                                    {
                                        WriteItemObject(filesystemInfo, filesystemInfo.FullName, false);
                                    }
                                    else
                                    {
                                        WriteItemObject(filesystemInfo, filesystemInfo.FullName, true);
                                    }
                                }
                            }
                        }
                        catch (System.IO.FileNotFoundException ex)
                        {
                            WriteError(new ErrorRecord(ex, "DirIOError", ErrorCategory.ReadError, directory.FullName));
                        }
                        catch (UnauthorizedAccessException ex)
                        {
                            WriteError(new ErrorRecord(ex, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName));
                        }
                    }
                }

                bool isFilterHiddenSpecified = false;           // "Hidden" is specified somewhere in the expression
                bool isSwitchFilterHiddenSpecified = false;     // "Hidden" is specified somewhere in the parameters

                if (evaluator != null)
                {
                    isFilterHiddenSpecified = evaluator.ExistsInExpression(FileAttributes.Hidden);
                }

                if (switchEvaluator != null)
                {
                    isSwitchFilterHiddenSpecified = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
                }
                // Recurse into the directories
                // Grab all the directories to recurse
                // into separately from the ones that will get written
                // out.
                if (recurse)
                {
                    // Limiter for recursion
                    if (depth > 0) // this includes special case 'depth == uint.MaxValue' for unlimited recursion
                    {
                        foreach (DirectoryInfo recursiveDirectory in directory.EnumerateDirectories())
                        {
                            // Making sure to obey the StopProcessing.
                            if (Stopping)
                            {
                                return;
                            }

                            bool hidden = false;
                            if (!Force)
                            {
                                hidden = (recursiveDirectory.Attributes & FileAttributes.Hidden) != 0;
                            }

                            // if "Hidden" is explicitly specified anywhere in the attribute filter, then override
                            // default hidden attribute filter.
                            if (Force || !hidden || isFilterHiddenSpecified || isSwitchFilterHiddenSpecified)
                            {
                                // We only want to recurse into symlinks if
                                //  a) the user has asked to with the -FollowSymLinks switch parameter and
                                //  b) the directory pointed to by the symlink has not already been visited,
                                //     preventing symlink loops.
                                //  c) it is not a reparse point with a target (not OneDrive or an AppX link).
                                if (tracker == null)
                                {
                                    if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(recursiveDirectory))
                                    {
                                        continue;
                                    }
                                }
                                else if (!tracker.TryVisitPath(recursiveDirectory.FullName))
                                {
                                    WriteWarning(StringUtil.Format(FileSystemProviderStrings.AlreadyListedDirectory,
                                                                   recursiveDirectory.FullName));
                                    continue;
                                }

                                Dir(recursiveDirectory, recurse, depth - 1, nameOnly, returnContainers, tracker);
                            }
                        }
                    }
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "DirArgumentError", ErrorCategory.InvalidArgument, directory.FullName));
            }
            catch (IOException e)
            {
                // 2004/10/13-JonN removed ResourceActionFailedException wrapper
                WriteError(new ErrorRecord(e, "DirIOError", ErrorCategory.ReadError, directory.FullName));
            }
            catch (UnauthorizedAccessException uae)
            {
                // 2004/10/13-JonN removed ResourceActionFailedException wrapper
                WriteError(new ErrorRecord(uae, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName));
            }
        }

        /// <summary>
        /// Create an enum expression evaluator for user-specified attribute filtering
        /// switch parameters.
        /// </summary>
        /// <returns>
        /// If any attribute filtering switch parameters are set,
        /// returns an evaluator that evaluates these parameters.
        /// Otherwise,
        /// returns NULL
        /// </returns>
        private FlagsExpression<FileAttributes> FormatAttributeSwitchParameters()
        {
            FlagsExpression<FileAttributes> switchParamEvaluator = null;
            StringBuilder sb = new StringBuilder();

            if (((GetChildDynamicParameters)DynamicParameters).Directory)
            {
                sb.Append("+Directory");
            }

            if (((GetChildDynamicParameters)DynamicParameters).File)
            {
                sb.Append("+!Directory");
            }

            if (((GetChildDynamicParameters)DynamicParameters).System)
            {
                sb.Append("+System");
            }

            if (((GetChildDynamicParameters)DynamicParameters).ReadOnly)
            {
                sb.Append("+ReadOnly");
            }

            if (((GetChildDynamicParameters)DynamicParameters).Hidden)
            {
                sb.Append("+Hidden");
            }

            string switchParamString = sb.ToString();

            if (!string.IsNullOrEmpty(switchParamString))
            {
                // Remove unnecessary PLUS sign
                switchParamEvaluator = new FlagsExpression<FileAttributes>(switchParamString.Substring(1));
            }

            return switchParamEvaluator;
        }

        /// <summary>
        /// Provides a mode property for FileSystemInfo.
        /// </summary>
        /// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
        /// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        public static string Mode(PSObject instance) => Mode(instance, excludeHardLink: false);

        /// <summary>
        /// Provides a ModeWithoutHardLink property for FileSystemInfo, without HardLinks for performance reasons.
        /// </summary>
        /// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
        /// <returns>A string representation of the FileAttributes, with one letter per attribute.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        public static string ModeWithoutHardLink(PSObject instance) => Mode(instance, excludeHardLink: true);

        private static string Mode(PSObject instance, bool excludeHardLink)
        {
            string ToModeString(FileSystemInfo fileSystemInfo)
            {
                FileAttributes fileAttributes = fileSystemInfo.Attributes;

                bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileSystemInfo);
                bool isLink = isReparsePoint || (excludeHardLink ? false : InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileSystemInfo));
                if (!isLink)
                {
                    // special casing for the common cases - no allocations
                    switch (fileAttributes)
                    {
                        case FileAttributes.Archive:
                            return "-a---";
                        case FileAttributes.Directory:
                            return "d----";
                        case FileAttributes.Normal:
                            return "-----";
                        case FileAttributes.Directory | FileAttributes.ReadOnly:
                            return "d-r--";
                        case FileAttributes.Archive | FileAttributes.ReadOnly:
                            return "-ar--";
                    }
                }

                bool isDirectory = fileAttributes.HasFlag(FileAttributes.Directory);
                ReadOnlySpan<char> mode = stackalloc char[]
                {
                    isLink ? 'l' : isDirectory ? 'd' : '-',
                    fileAttributes.HasFlag(FileAttributes.Archive) ? 'a' : '-',
                    fileAttributes.HasFlag(FileAttributes.ReadOnly) ? 'r' : '-',
                    fileAttributes.HasFlag(FileAttributes.Hidden) ? 'h' : '-',
                    fileAttributes.HasFlag(FileAttributes.System) ? 's' : '-',
                };
                return new string(mode);
            }

            return instance?.BaseObject is FileSystemInfo fileInfo
                ? ToModeString(fileInfo)
                : string.Empty;
        }

        /// <summary>
        /// Provides a NameString property for FileSystemInfo.
        /// </summary>
        /// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
        /// <returns>Name if a file or directory, Name -> Target if symlink.</returns>
        public static string NameString(PSObject instance)
        {
            return instance?.BaseObject is FileSystemInfo fileInfo
                ? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo)
                    ? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}"
                    : fileInfo.Name
                : string.Empty;
        }

        /// <summary>
        /// Provides a LengthString property for FileSystemInfo.
        /// </summary>
        /// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
        /// <returns>Length as a string.</returns>
        public static string LengthString(PSObject instance)
        {
            return instance?.BaseObject is FileInfo fileInfo
                ? fileInfo.Attributes.HasFlag(FileAttributes.Offline)
                    ? $"({fileInfo.Length})"
                    : fileInfo.Length.ToString()
                : string.Empty;
        }

        /// <summary>
        /// Provides a LastWriteTimeString property for FileSystemInfo.
        /// </summary>
        /// <param name="instance">Instance of PSObject wrapping a FileSystemInfo.</param>
        /// <returns>LastWriteTime formatted as short date + short time.</returns>
        public static string LastWriteTimeString(PSObject instance)
        {
            return instance?.BaseObject is FileSystemInfo fileInfo
                ? string.Format(CultureInfo.CurrentCulture, "{0,10:d} {0,8:t}", fileInfo.LastWriteTime)
                : string.Empty;
        }

        #region RenameItem

        /// <summary>
        /// Renames a file or directory.
        /// </summary>
        /// <param name="path">
        /// The current full path to the file or directory.
        /// </param>
        /// <param name="newName">
        /// The new full path to the file or directory.
        /// </param>
        /// <returns>
        /// Nothing.  The renamed DirectoryInfo or FileInfo object is
        /// written to the context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        ///     newName is null or empty
        /// </exception>
        protected override void RenameItem(
            string path,
            string newName)
        {
            // Check the parameters
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

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

            // Clean up "newname" to fix some common usability problems:
            // Rename .\foo.txt .\bar.txt
            // Rename C:\temp\foo.txt C:\temp\bar.txt
            if (newName.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) ||
                newName.StartsWith("./", StringComparison.OrdinalIgnoreCase))
            {
                newName = newName.Remove(0, 2);
            }
            else if (string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(newName), StringComparison.OrdinalIgnoreCase))
            {
                newName = Path.GetFileName(newName);
            }

            // Check to see if the target specified is just filename. We dont allow rename to move the file to a different directory.
            // If a path is specified for the newName then we flag that as an error.
            if (!string.Equals(Path.GetFileName(newName), newName, StringComparison.OrdinalIgnoreCase))
            {
                throw PSTraceSource.NewArgumentException(nameof(newName), FileSystemProviderStrings.RenameError);
            }

            // Verify that the target doesn't represent a device name
            if (PathIsReservedDeviceName(newName, "RenameError"))
            {
                return;
            }

            try
            {
                bool isContainer = IsItemContainer(path);

                FileSystemInfo result = null;

                if (isContainer)
                {
                    // Get the DirectoryInfo
                    DirectoryInfo dir = new DirectoryInfo(path);

                    // Generate the new path which the directory will
                    // be renamed to.
                    string parentDirectory = dir.Parent.FullName;
                    string newPath = MakePath(parentDirectory, newName);

                    // Confirm the rename with the user
                    string action = FileSystemProviderStrings.RenameItemActionDirectory;

                    string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, dir.FullName, newPath);

                    if (ShouldProcess(resource, action))
                    {
                        // Now move the file
                        dir.MoveTo(newPath);

                        result = dir;
                        WriteItemObject(result, result.FullName, isContainer);
                    }
                }
                else
                {
                    // Get the FileInfo
                    FileInfo file = new FileInfo(path);

                    // Generate the new path which the file will be renamed to.
                    string parentDirectory = file.DirectoryName;
                    string newPath = MakePath(parentDirectory, newName);

                    // Confirm the rename with the user
                    string action = FileSystemProviderStrings.RenameItemActionFile;

                    string resource = StringUtil.Format(FileSystemProviderStrings.RenameItemResourceFileTemplate, file.FullName, newPath);

                    if (ShouldProcess(resource, action))
                    {
                        // Now move the file
                        file.MoveTo(newPath);

                        result = file;
                        WriteItemObject(result, result.FullName, isContainer);
                    }
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "RenameItemArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "RenameItemIOError", ErrorCategory.WriteError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "RenameItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
        }

        #endregion RenameItem

        #region NewItem

        /// <summary>
        /// Creates a file or directory with the given path.
        /// </summary>
        /// <param name="path">
        /// The path of the file or directory to create.
        /// </param>
        ///<param name="type">
        /// Specify "file" to create a file.
        /// Specify "directory" or "container" to create a directory.
        /// </param>
        /// <param name="value">
        /// If <paramref name="type" /> is "file" then this parameter becomes the content
        /// of the file to be created.
        /// </param>
        /// <returns>
        /// Nothing.  The new DirectoryInfo or FileInfo object is
        /// written to the context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        ///     type is null or empty.
        /// </exception>
        protected override void NewItem(
            string path,
            string type,
            object value)
        {
            ItemType itemType = ItemType.Unknown;

            // Verify parameters
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            if (string.IsNullOrEmpty(type))
            {
                type = "file";
            }

            path = NormalizePath(path);

            if (Force)
            {
                if (!CreateIntermediateDirectories(path))
                {
                    return;
                }
            }

            itemType = GetItemType(type);

            if (itemType == ItemType.Directory)
            {
                CreateDirectory(path, true);
            }
            else if (itemType == ItemType.File)
            {
                try
                {
                    FileMode fileMode = FileMode.CreateNew;

                    if (Force)
                    {
                        // If force is specified, overwrite the existing
                        // file
                        fileMode = FileMode.Create;
                    }

                    string action = FileSystemProviderStrings.NewItemActionFile;

                    string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);

                    if (ShouldProcess(resource, action))
                    {
                        // Create the file with read/write access and
                        // not allowing sharing.
                        using (FileStream newFile =
                            new FileStream(
                                path,
                                fileMode,
                                FileAccess.Write,
                                FileShare.None))
                        {
                            if (value != null)
                            {
                                StreamWriter streamWriter = new StreamWriter(newFile);
                                streamWriter.Write(value.ToString());
                                streamWriter.Flush();
                                streamWriter.Dispose();
                            }
                        }

                        FileInfo fileInfo = new FileInfo(path);
                        WriteItemObject(fileInfo, path, false);
                    }
                }
                catch (IOException exception)
                {
                    // IOException contains specific message about the error occured and so no need for errordetails.
                    WriteError(new ErrorRecord(exception, "NewItemIOError", ErrorCategory.WriteError, path));
                }
                catch (UnauthorizedAccessException accessException)
                {
                    WriteError(new ErrorRecord(accessException, "NewItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
                }
            }
            else if (itemType == ItemType.SymbolicLink || itemType == ItemType.HardLink)
            {
                string action = null;
                if (itemType == ItemType.SymbolicLink)
                {
                    action = FileSystemProviderStrings.NewItemActionSymbolicLink;
                }
                else if (itemType == ItemType.HardLink)
                {
                    action = FileSystemProviderStrings.NewItemActionHardLink;
                }

                string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);

                if (ShouldProcess(resource, action))
                {
                    bool isDirectory = false;
                    string strTargetPath = value?.ToString();

                    if (string.IsNullOrEmpty(strTargetPath))
                    {
                        throw PSTraceSource.NewArgumentNullException(nameof(value));
                    }

                    bool exists = false;

                    // It is legal to create symbolic links to non-existing targets on
                    // both Windows and Linux. It is not legal to create hard links to
                    // non-existing targets on either Windows or Linux.
                    try
                    {
                        if (itemType == ItemType.SymbolicLink)
                        {
                            exists = true;

                            var normalizedTargetPath = strTargetPath;
                            if (strTargetPath.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) ||
                                strTargetPath.StartsWith("./", StringComparison.OrdinalIgnoreCase))
                            {
                                normalizedTargetPath = Path.Join(SessionState.Internal.CurrentLocation.ProviderPath, strTargetPath.AsSpan().Slice(2));
                            }

                            GetFileSystemInfo(normalizedTargetPath, out isDirectory);

                            strTargetPath = strTargetPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
                        }
                        else
                        {
                            exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null;
                        }
                    }
                    catch (Exception e)
                    {
                        WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
                        return;
                    }

                    if (!exists)
                    {
                        string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, strTargetPath);
                        WriteError(new ErrorRecord(new ItemNotFoundException(message), "ItemNotFound", ErrorCategory.ObjectNotFound, strTargetPath));
                        return;
                    }

                    if (itemType == ItemType.HardLink)
                    {
                        // Hard links can only be to files, not directories.
                        if (isDirectory)
                        {
                            string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFile, strTargetPath);
                            WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotFile", ErrorCategory.InvalidOperation, strTargetPath));
                            return;
                        }
                    }

                    bool isSymLinkDirectory = false;
                    bool symLinkExists = false;

                    try
                    {
                        symLinkExists = GetFileSystemInfo(path, out isSymLinkDirectory) != null;
                    }
                    catch (Exception e)
                    {
                        WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, path));
                        return;
                    }

                    if (Force)
                    {
                        try
                        {
                            if (!isSymLinkDirectory && symLinkExists)
                            {
                                File.Delete(path);
                            }
                            else if (isSymLinkDirectory && symLinkExists)
                            {
                                Directory.Delete(path);
                            }
                        }
                        catch (Exception exception)
                        {
                            if ((exception is FileNotFoundException) ||
                                (exception is DirectoryNotFoundException) ||
                                (exception is UnauthorizedAccessException) ||
                                (exception is System.Security.SecurityException) ||
                                (exception is ArgumentException) ||
                                (exception is PathTooLongException) ||
                                (exception is NotSupportedException) ||
                                (exception is ArgumentNullException) ||
                                (exception is IOException))
                            {
                                WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path));
                            }
                            else
                            {
                                throw;
                            }
                        }
                    }
                    else
                    {
                        if (symLinkExists)
                        {
                            string message = StringUtil.Format(FileSystemProviderStrings.SymlinkItemExists, path);
                            WriteError(new ErrorRecord(new IOException(message), "SymLinkExists", ErrorCategory.ResourceExists, path));
                            return;
                        }
                    }

                    bool success = false;

                    if (itemType == ItemType.SymbolicLink)
                    {
#if UNIX
                        success = Platform.NonWindowsCreateSymbolicLink(path, strTargetPath);
#else
                        success = WinCreateSymbolicLink(path, strTargetPath, isDirectory);
#endif
                    }
                    else if (itemType == ItemType.HardLink)
                    {
#if UNIX
                        success = Platform.NonWindowsCreateHardLink(path, strTargetPath);
#else
                        success = WinCreateHardLink(path, strTargetPath);
#endif
                    }

                    if (!success)
                    {
                        // Porting note: The Win32Exception will report the correct error on Linux
                        int errorCode = Marshal.GetLastWin32Error();

                        Win32Exception w32Exception = new Win32Exception((int)errorCode);

#if UNIX
                        if (Platform.Unix.GetErrorCategory(errorCode) == ErrorCategory.PermissionDenied)
#else
                        if (errorCode == 1314) // ERROR_PRIVILEGE_NOT_HELD
#endif
                        {
                            string message = FileSystemProviderStrings.ElevationRequired;
                            WriteError(new ErrorRecord(new UnauthorizedAccessException(message, w32Exception), "NewItemSymbolicLinkElevationRequired", ErrorCategory.PermissionDenied, value.ToString()));
                            return;
                        }

                        if (errorCode == 1) // ERROR_INVALID_FUNCTION
                        {
                            string message = null;
                            if (itemType == ItemType.SymbolicLink)
                            {
                                message = FileSystemProviderStrings.SymbolicLinkNotSupported;
                            }
                            else
                            {
                                message = FileSystemProviderStrings.HardLinkNotSupported;
                            }

                            WriteError(new ErrorRecord(new InvalidOperationException(message, w32Exception), "NewItemInvalidOperation", ErrorCategory.InvalidOperation, value.ToString()));
                            return;
                        }

                        throw w32Exception;
                    }
                    else
                    {
                        if (isDirectory)
                        {
                            DirectoryInfo dirInfo = new DirectoryInfo(path);
                            WriteItemObject(dirInfo, path, true);
                        }
                        else
                        {
                            FileInfo fileInfo = new FileInfo(path);
                            WriteItemObject(fileInfo, path, false);
                        }
                    }
                }
            }
            else if (itemType == ItemType.Junction)
            {
                string action = FileSystemProviderStrings.NewItemActionJunction;
                string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);

                if (ShouldProcess(resource, action))
                {
                    bool isDirectory = false;
                    string strTargetPath = value?.ToString();

                    bool exists = false;

                    try
                    {
                        exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null;
                    }
                    catch (Exception e)
                    {
                        WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
                        return;
                    }

                    if (!exists)
                    {
                        string message = StringUtil.Format(FileSystemProviderStrings.ItemNotFound, strTargetPath);
                        WriteError(new ErrorRecord(new ItemNotFoundException(message), "ItemNotFound", ErrorCategory.ObjectNotFound, strTargetPath));
                        return;
                    }

                    // Junctions can only be directories.
                    if (!isDirectory)
                    {
                        string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, value);
                        WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotDirectory", ErrorCategory.InvalidOperation, value));
                        return;
                    }

                    bool isPathDirectory = false;
                    FileSystemInfo pathDirInfo;

                    try
                    {
                        pathDirInfo = GetFileSystemInfo(path, out isPathDirectory);
                    }
                    catch (Exception e)
                    {
                        WriteError(new ErrorRecord(e, "AccessException", ErrorCategory.PermissionDenied, strTargetPath));
                        return;
                    }

                    bool pathExists = pathDirInfo != null;

                    if (pathExists)
                    {
                        // Junctions can only be directories.
                        if (!isPathDirectory)
                        {
                            string message = StringUtil.Format(FileSystemProviderStrings.ItemNotDirectory, path);
                            WriteError(new ErrorRecord(new InvalidOperationException(message), "ItemNotDirectory", ErrorCategory.InvalidOperation, path));
                            return;
                        }

                        // Junctions cannot have files
                        if (DirectoryInfoHasChildItems((DirectoryInfo)pathDirInfo))
                        {
                            string message = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, path);
                            WriteError(new ErrorRecord(new IOException(message), "DirectoryNotEmpty", ErrorCategory.WriteError, path));
                            return;
                        }

                        if (Force)
                        {
                            try
                            {
                                pathDirInfo.Delete();
                            }
                            catch (Exception exception)
                            {
                                if ((exception is DirectoryNotFoundException) ||
                                    (exception is UnauthorizedAccessException) ||
                                    (exception is System.Security.SecurityException) ||
                                    (exception is IOException))
                                {
                                    WriteError(new ErrorRecord(exception, "NewItemDeleteIOError", ErrorCategory.WriteError, path));
                                }
                                else
                                {
                                    throw;
                                }
                            }
                        }
                    }
                    else
                    {
                        CreateDirectory(path, false);
                        pathDirInfo = new DirectoryInfo(path);
                    }

                    try
                    {
                        bool junctionCreated = WinCreateJunction(path, strTargetPath);

                        if (junctionCreated)
                        {
                            WriteItemObject(pathDirInfo, path, true);
                        }
                        else // rollback the directory creation if we created it.
                        {
                            if (!pathExists)
                            {
                                pathDirInfo.Delete();
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        // rollback the directory creation if it was created.
                        if (!pathExists)
                        {
                            pathDirInfo.Delete();
                        }

                        if ((exception is FileNotFoundException) ||
                                (exception is DirectoryNotFoundException) ||
                                (exception is UnauthorizedAccessException) ||
                                (exception is System.Security.SecurityException) ||
                                (exception is ArgumentException) ||
                                (exception is PathTooLongException) ||
                                (exception is NotSupportedException) ||
                                (exception is ArgumentNullException) ||
                                (exception is Win32Exception) ||
                                (exception is IOException))
                        {
                            WriteError(new ErrorRecord(exception, "NewItemCreateIOError", ErrorCategory.WriteError, path));
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
            }
            else
            {
                throw PSTraceSource.NewArgumentException(nameof(type), FileSystemProviderStrings.UnknownType);
            }
        }

        private static bool WinCreateSymbolicLink(string path, string strTargetPath, bool isDirectory)
        {
            // The new AllowUnprivilegedCreate is only available on Win10 build 14972 or newer
            var flags = isDirectory ? NativeMethods.SymbolicLinkFlags.Directory : NativeMethods.SymbolicLinkFlags.File;

            Version minBuildOfDeveloperMode = new Version(10, 0, 14972, 0);
            if (Environment.OSVersion.Version >= minBuildOfDeveloperMode)
            {
                flags |= NativeMethods.SymbolicLinkFlags.AllowUnprivilegedCreate;
            }

            var created = NativeMethods.CreateSymbolicLink(path, strTargetPath, flags);
            return created;
        }

        private static bool WinCreateHardLink(string path, string strTargetPath)
        {
            bool success = NativeMethods.CreateHardLink(path, strTargetPath, IntPtr.Zero);
            return success;
        }

        private static bool WinCreateJunction(string path, string strTargetPath)
        {
            bool junctionCreated = InternalSymbolicLinkLinkCodeMethods.CreateJunction(path, strTargetPath);
            return junctionCreated;
        }

        private enum ItemType
        {
            Unknown,
            File,
            Directory,
            SymbolicLink,
            Junction,
            HardLink
        };

        private static ItemType GetItemType(string input)
        {
            ItemType itemType = ItemType.Unknown;

            WildcardPattern typeEvaluator =
                WildcardPattern.Get(input + "*",
                                     WildcardOptions.IgnoreCase |
                                     WildcardOptions.Compiled);

            if (typeEvaluator.IsMatch("directory") ||
                typeEvaluator.IsMatch("container"))
            {
                itemType = ItemType.Directory;
            }
            else if (typeEvaluator.IsMatch("file"))
            {
                itemType = ItemType.File;
            }
            else if (typeEvaluator.IsMatch("symboliclink"))
            {
                itemType = ItemType.SymbolicLink;
            }
            else if (typeEvaluator.IsMatch("junction"))
            {
                itemType = ItemType.Junction;
            }
            else if (typeEvaluator.IsMatch("hardlink"))
            {
                itemType = ItemType.HardLink;
            }

            return itemType;
        }

        /// <summary>
        /// Creates a directory at the specified path.
        /// </summary>
        /// <param name="path">
        /// The path of the directory to create
        /// </param>
        /// <param name="streamOutput">
        /// Determines if the directory should be streamed out after being created.
        /// </param>
        private void CreateDirectory(string path, bool streamOutput)
        {
            Dbg.Diagnostics.Assert(
                !string.IsNullOrEmpty(path),
                "The caller should verify path");

            // Get the parent path
            string parentPath = GetParentPath(path, null);

            // The directory name
            string childName = GetChildName(path);

            ErrorRecord error = null;
            if (!Force && ItemExists(path, out error))
            {
                string errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path);
                Exception e = new IOException(errorMessage);

                WriteError(new ErrorRecord(
                    e,
                    "DirectoryExist",
                    ErrorCategory.ResourceExists,
                    path));

                return;
            }

            if (error != null)
            {
                WriteError(error);
                return;
            }

            try
            {
                string action = FileSystemProviderStrings.NewItemActionDirectory;

                string resource = StringUtil.Format(FileSystemProviderStrings.NewItemActionTemplate, path);

                if (ShouldProcess(resource, action))
                {
                    var result = Directory.CreateDirectory(Path.Combine(parentPath, childName));

                    if (streamOutput)
                    {
                        // Write the result to the pipeline
                        WriteItemObject(result, path, true);
                    }
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "CreateDirectoryArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // Ignore the error if force was specified
                if (!Force)
                {
                    // IOException contains specific message about the error occured and so no need for errordetails.
                    WriteError(new ErrorRecord(ioException, "CreateDirectoryIOError", ErrorCategory.WriteError, path));
                }
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "CreateDirectoryUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
        }

        private bool CreateIntermediateDirectories(string path)
        {
            bool result = false;

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

            try
            {
                // Push the paths of the missing directories onto a stack such that the highest missing
                // parent in the tree is at the top of the stack.
                Stack<string> missingDirectories = new Stack<string>();

                string previousParent = path;

                do
                {
                    string root = string.Empty;

                    if (PSDriveInfo != null)
                    {
                        root = PSDriveInfo.Root;
                    }

                    string parentPath = GetParentPath(path, root);

                    if (!string.IsNullOrEmpty(parentPath) &&
                        !string.Equals(
                            parentPath,
                            previousParent,
                            StringComparison.OrdinalIgnoreCase))
                    {
                        if (!ItemExists(parentPath))
                        {
                            missingDirectories.Push(parentPath);
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }

                    previousParent = parentPath;
                } while (!string.IsNullOrEmpty(previousParent));

                // Now create the missing directories
                foreach (string directoryPath in missingDirectories)
                {
                    CreateDirectory(directoryPath, false);
                }

                result = true;
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "CreateIntermediateDirectoriesArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "CreateIntermediateDirectoriesIOError", ErrorCategory.WriteError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "CreateIntermediateDirectoriesUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }

            return result;
        }

        #endregion NewItem

        #region RemoveItem

        /// <summary>
        /// Removes the specified file or directory.
        /// </summary>
        /// <param name="path">
        /// The full path to the file or directory to be removed.
        /// </param>
        /// <param name="recurse">
        /// Specifies if the operation should also remove child items.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override void RemoveItem(string path, bool recurse)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            try
            {
                path = NormalizePath(path);

#if !UNIX
                bool removeStreams = false;
                FileSystemProviderRemoveItemDynamicParameters dynamicParameters = null;

                if (DynamicParameters != null)
                {
                    dynamicParameters = DynamicParameters as FileSystemProviderRemoveItemDynamicParameters;
                    if (dynamicParameters != null)
                    {
                        if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
                        {
                            removeStreams = true;
                        }
                        else
                        {
                            // See if they've used the inline stream syntax. They have more than one colon.
                            int firstColon = path.IndexOf(':');
                            int secondColon = path.IndexOf(':', firstColon + 1);
                            if (secondColon > 0)
                            {
                                string streamName = path.Substring(secondColon + 1);
                                path = path.Remove(secondColon);

                                removeStreams = true;
                                dynamicParameters = new FileSystemProviderRemoveItemDynamicParameters();
                                dynamicParameters.Stream = new string[] { streamName };
                            }
                        }
                    }
                }
#endif

                FileSystemInfo fsinfo = GetFileSystemInfo(path, out bool iscontainer);
                if (fsinfo == null)
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                    Exception e = new IOException(error);
                    WriteError(new ErrorRecord(e, "ItemDoesNotExist", ErrorCategory.ObjectNotFound, path));
                    return;
                }

#if UNIX
                if (iscontainer)
                {
                    RemoveDirectoryInfoItem((DirectoryInfo)fsinfo, recurse, Force, true);
                }
                else
                {
                    RemoveFileInfoItem((FileInfo)fsinfo, Force);
                }
#else
                if ((!removeStreams) && iscontainer)
                {
                    RemoveDirectoryInfoItem((DirectoryInfo)fsinfo, recurse, Force, true);
                }
                else
                {
                    // If we want to remove the file streams, retrieve them and remove them.
                    if (removeStreams)
                    {
                        foreach (string desiredStream in dynamicParameters.Stream)
                        {
                            // See that it matches the name specified
                            WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
                            bool foundStream = false;

                            foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(fsinfo.FullName))
                            {
                                if (!p.IsMatch(stream.Stream)) { continue; }

                                foundStream = true;

                                string action = string.Format(
                                    CultureInfo.InvariantCulture,
                                    FileSystemProviderStrings.StreamAction,
                                    stream.Stream, fsinfo.FullName);
                                if (ShouldProcess(action))
                                {
                                    AlternateDataStreamUtilities.DeleteFileStream(fsinfo.FullName, stream.Stream);
                                }
                            }

                            if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream))
                            {
                                string errorMessage = StringUtil.Format(
                                    FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, fsinfo.FullName);
                                Exception e = new FileNotFoundException(errorMessage, fsinfo.FullName);

                                WriteError(new ErrorRecord(
                                    e,
                                    "AlternateDataStreamNotFound",
                                    ErrorCategory.ObjectNotFound,
                                    path));
                            }
                        }
                    }
                    else
                    {
                        RemoveFileInfoItem((FileInfo)fsinfo, Force);
                    }
                }
#endif
            }
            catch (IOException exception)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(exception, "RemoveItemIOError", ErrorCategory.WriteError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "RemoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
        }

        /// <summary>
        /// Retrieves the dynamic parameters required for the Remove-Item cmdlet.
        /// </summary>
        /// <param name="path">The path of the file to process.</param>
        /// <param name="recurse">Whether to recurse into containers.</param>
        /// <returns>An instance of the FileSystemProviderRemoveItemDynamicParameters class that represents the dynamic parameters.</returns>
        protected override object RemoveItemDynamicParameters(string path, bool recurse)
        {
            if (!recurse)
            {
                return new FileSystemProviderRemoveItemDynamicParameters();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Removes a directory from the file system.
        /// </summary>
        /// <param name="directory">
        /// The DirectoryInfo object representing the directory to be removed.
        /// </param>
        /// <param name="recurse">
        /// If true, ShouldProcess will be called for each item in the subtree.
        /// If false, ShouldProcess will only be called for the directory item.
        /// </param>
        /// <param name="force">
        /// If true, attempts to modify the file attributes in case of a failure so that
        /// the file can be removed.
        /// </param>
        /// <param name="rootOfRemoval">
        /// True if the DirectoryInfo being passed in is the root of the tree being removed.
        /// ShouldProcess will be called if this is true or if recurse is true.
        /// </param>
        private void RemoveDirectoryInfoItem(DirectoryInfo directory, bool recurse, bool force, bool rootOfRemoval)
        {
            Dbg.Diagnostics.Assert(directory != null, "Caller should always check directory");

            bool continueRemoval = true;

            // We only want to confirm the removal if this is the root of the
            // tree being removed or the recurse flag is specified.
            if (rootOfRemoval || recurse)
            {
                // Confirm the user wants to remove the directory
                string action = FileSystemProviderStrings.RemoveItemActionDirectory;
                continueRemoval = ShouldProcess(directory.FullName, action);
            }

            if (directory.Attributes.HasFlag(FileAttributes.ReparsePoint))
            {
                try
                {
                    // TODO:
                    // Different symlinks seem to vary by behavior.
                    // In particular, OneDrive symlinks won't remove without recurse,
                    // but the .NET API here does not allow us to distinguish them.
                    // We may need to revisit using p/Invokes here to get the right behavior
                    directory.Delete();
                }
                catch (Exception e)
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.CannotRemoveItem, directory.FullName, e.Message);
                    var exception = new IOException(error, e);
                    WriteError(new ErrorRecord(exception, errorId: "DeleteSymbolicLinkFailed", ErrorCategory.WriteError, directory));
                }

                return;
            }

            if (continueRemoval)
            {
                // Loop through each of the contained directories and recurse into them for
                // removal.
                foreach (DirectoryInfo childDir in directory.EnumerateDirectories())
                {
                    // Making sure to obey the StopProcessing.
                    if (Stopping)
                    {
                        return;
                    }

                    if (childDir != null)
                    {
                        RemoveDirectoryInfoItem(childDir, recurse, force, false);
                    }
                }

                // Loop through each of the contained files and remove them.
                IEnumerable<FileInfo> files = null;

                if (!string.IsNullOrEmpty(Filter))
                {
                    files = directory.EnumerateFiles(Filter);
                }
                else
                {
                    files = directory.EnumerateFiles();
                }

                foreach (FileInfo file in files)
                {
                    // Making sure to obey the StopProcessing.
                    if (Stopping)
                    {
                        return;
                    }

                    if (file != null)
                    {
                        if (recurse)
                        {
                            // When recurse is specified we need to confirm each
                            // item before removal.
                            RemoveFileInfoItem(file, force);
                        }
                        else
                        {
                            // When recurse is not specified just delete all the
                            // subitems without confirming with the user.
                            RemoveFileSystemItem(file, force);
                        }
                    }
                }

                // Check to see if the item has children
                bool hasChildren = DirectoryInfoHasChildItems(directory);

                if (hasChildren && !force)
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.DirectoryNotEmpty, directory.FullName);
                    Exception e = new IOException(error);
                    WriteError(new ErrorRecord(e, "DirectoryNotEmpty", ErrorCategory.WriteError, directory));
                }
                else // !hasChildren || force
                {
                    // Finally, remove the directory
                    RemoveFileSystemItem(directory, force);
                }
            }
        }

        /// <summary>
        /// Removes a file from the file system.
        /// </summary>
        /// <param name="file">
        /// The FileInfo object representing the file to be removed.
        /// </param>
        /// <param name="force">
        /// If true, attempts to modify the file attributes in case of a failure so that
        /// the file can be removed.
        /// </param>
        private void RemoveFileInfoItem(FileInfo file, bool force)
        {
            Dbg.Diagnostics.Assert(
                file != null,
                "Caller should always check file");

            string action = FileSystemProviderStrings.RemoveItemActionFile;

            if (ShouldProcess(file.FullName, action))
            {
                RemoveFileSystemItem(file, force);
            }
        }

        /// <summary>
        /// Removes the file system object from the file system.
        /// </summary>
        /// <param name="fileSystemInfo">
        /// The FileSystemInfo object representing the file or directory to be removed.
        /// </param>
        /// <param name="force">
        /// If true, the readonly and hidden attributes will be masked off in the case of
        /// an error, and the removal will be attempted again. If false, exceptions are
        /// written to the error pipeline.
        /// </param>
        private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force)
        {
            Dbg.Diagnostics.Assert(
                fileSystemInfo != null,
                "Caller should always check fileSystemInfo");

            // First check if we can delete this file when force is not specified.
            if (!Force &&
                (fileSystemInfo.Attributes & (FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly)) != 0)
            {
                string error = StringUtil.Format(FileSystemProviderStrings.PermissionError);
                Exception e = new IOException(error);

                ErrorDetails errorDetails =
                    new ErrorDetails(this, "FileSystemProviderStrings",
                        "CannotRemoveItem",
                        fileSystemInfo.FullName,
                        e.Message);

                ErrorRecord errorRecord = new ErrorRecord(e, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, fileSystemInfo);
                errorRecord.ErrorDetails = errorDetails;

                WriteError(errorRecord);
                return;
            }

            // Store the old attributes in case we fail to delete
            FileAttributes oldAttributes = fileSystemInfo.Attributes;
            bool attributeRecoveryRequired = false;

            try
            {
                // Try to delete the item.  Strip any problematic attributes
                // if they've specified force.
                if (force)
                {
                    fileSystemInfo.Attributes = fileSystemInfo.Attributes & ~(FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
                    attributeRecoveryRequired = true;
                }

                fileSystemInfo.Delete();

                if (force)
                {
                    attributeRecoveryRequired = false;
                }
            }
            catch (Exception fsException)
            {
                ErrorDetails errorDetails =
                    new ErrorDetails(this, "FileSystemProviderStrings",
                        "CannotRemoveItem",
                        fileSystemInfo.FullName,
                        fsException.Message);

                if ((fsException is System.Security.SecurityException) ||
                    (fsException is UnauthorizedAccessException))
                {
                    ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, fileSystemInfo);
                    errorRecord.ErrorDetails = errorDetails;

                    WriteError(errorRecord);
                }
                else if (fsException is ArgumentException)
                {
                    ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemArgumentError", ErrorCategory.InvalidArgument, fileSystemInfo);
                    errorRecord.ErrorDetails = errorDetails;

                    WriteError(errorRecord);
                }
                else if ((fsException is IOException) ||
                    (fsException is FileNotFoundException) ||
                    (fsException is DirectoryNotFoundException))
                {
                    ErrorRecord errorRecord = new ErrorRecord(fsException, "RemoveFileSystemItemIOError", ErrorCategory.WriteError, fileSystemInfo);
                    errorRecord.ErrorDetails = errorDetails;

                    WriteError(errorRecord);
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                if (attributeRecoveryRequired)
                {
                    try
                    {
                        if (fileSystemInfo.Exists)
                        {
                            fileSystemInfo.Attributes = oldAttributes;
                        }
                    }
                    catch (Exception attributeException)
                    {
                        if ((attributeException is System.IO.DirectoryNotFoundException) ||
                            (attributeException is System.Security.SecurityException) ||
                            (attributeException is System.ArgumentException) ||
                            (attributeException is System.IO.FileNotFoundException) ||
                            (attributeException is System.IO.IOException))
                        {
                            ErrorDetails attributeDetails = new ErrorDetails(
                                this, "FileSystemProviderStrings",
                                    "CannotRestoreAttributes",
                                    fileSystemInfo.FullName,
                                    attributeException.Message);

                            ErrorRecord errorRecord = new ErrorRecord(attributeException, "RemoveFileSystemItemCannotRestoreAttributes", ErrorCategory.PermissionDenied, fileSystemInfo);
                            errorRecord.ErrorDetails = attributeDetails;

                            WriteError(errorRecord);
                        }
                        else
                            throw;
                    }
                }
            }
        }

        #endregion RemoveItem

        #region ItemExists

        /// <summary>
        /// Determines if a file or directory exists at the specified path.
        /// </summary>
        /// <param name="path">
        /// The path of the item to check.
        /// </param>
        /// <returns>
        /// True if a file or directory exists at the specified path, false otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override bool ItemExists(string path)
        {
            ErrorRecord error = null;
            bool result = ItemExists(path, out error);

            if (error != null)
            {
                WriteError(error);
            }

            return result;
        }

        /// <summary>
        /// Implementation of ItemExists for the provider. This implementation
        /// allows the caller to decide if it wants to WriteError or not based
        /// on the returned ErrorRecord.
        /// </summary>
        /// <param name="path">
        /// The path of the object to check
        /// </param>
        /// <param name="error">
        /// An error record is returned in this parameter if there was an error.
        /// </param>
        /// <returns>
        /// True if an object exists at the specified path, false otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        private bool ItemExists(string path, out ErrorRecord error)
        {
            error = null;

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

            bool result = false;

            path = NormalizePath(path);

            try
            {
                var fsinfo = GetFileSystemInfo(path, out bool _);
                result = fsinfo != null;

                FileSystemItemProviderDynamicParameters itemExistsDynamicParameters =
                    DynamicParameters as FileSystemItemProviderDynamicParameters;

                // If the items see if we need to check the age of the file...
                if (result && itemExistsDynamicParameters != null)
                {
                    DateTime lastWriteTime = fsinfo.LastWriteTime;

                    if (itemExistsDynamicParameters.OlderThan.HasValue)
                    {
                        result = lastWriteTime < itemExistsDynamicParameters.OlderThan.Value;
                    }

                    if (itemExistsDynamicParameters.NewerThan.HasValue)
                    {
                        result = lastWriteTime > itemExistsDynamicParameters.NewerThan.Value;
                    }
                }
            }
            catch (System.Security.SecurityException security)
            {
                error = new ErrorRecord(security, "ItemExistsSecurityError", ErrorCategory.PermissionDenied, path);
            }
            catch (ArgumentException argument)
            {
                error = new ErrorRecord(argument, "ItemExistsArgumentError", ErrorCategory.InvalidArgument, path);
            }
            catch (UnauthorizedAccessException unauthorized)
            {
                error = new ErrorRecord(unauthorized, "ItemExistsUnauthorizedAccessError", ErrorCategory.PermissionDenied, path);
            }
            catch (PathTooLongException pathTooLong)
            {
                error = new ErrorRecord(pathTooLong, "ItemExistsPathTooLongError", ErrorCategory.InvalidArgument, path);
            }
            catch (NotSupportedException notSupported)
            {
                error = new ErrorRecord(notSupported, "ItemExistsNotSupportedError", ErrorCategory.InvalidOperation, path);
            }

            return result;
        }

        /// <summary>
        /// Adds -OlderThan, -NewerThan dynamic properties.
        /// </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>
        /// <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 override object ItemExistsDynamicParameters(string path)
        {
            using (PSTransactionManager.GetEngineProtectionScope())
            {
                return new FileSystemItemProviderDynamicParameters();
            }
        }

        #endregion ItemExists

        #region HasChildItems

        /// <summary>
        /// Determines if the given path is a directory, and has children.
        /// </summary>
        /// <param name="path">
        /// The full path to the directory.
        /// </param>
        /// <returns>
        /// True if the path refers to a directory that contains other
        /// directories or files.  False otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override bool HasChildItems(string path)
        {
            bool result = false;

            // verify parameters
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            // First check to see if it is a directory
            try
            {
                DirectoryInfo directory = new DirectoryInfo(path);

                // If the above didn't throw an exception, check to
                // see if we should proceed and if it contains any children
                if ((directory.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
                    return false;

                result = DirectoryInfoHasChildItems(directory);
            }
            catch (ArgumentNullException)
            {
                // Since we couldn't convert the path to a DirectoryInfo
                // the path could not be a file system container with
                // children
                result = false;
            }
            catch (ArgumentException)
            {
                // Since we couldn't convert the path to a DirectoryInfo
                // the path could not be a file system container with
                // children
                result = false;
            }
            catch (UnauthorizedAccessException)
            {
                // Since we couldn't convert the path to a DirectoryInfo
                // the path could not be a file system container with
                // children
                result = false;
            }
            catch (IOException)
            {
                // Since we couldn't convert the path to a DirectoryInfo
                // the path could not be a file system container with
                // children
                result = false;
            }
            catch (NotSupportedException)
            {
                // Happens when we try to access an alternate data stream
                result = false;
            }

            return result;
        }

        private static bool DirectoryInfoHasChildItems(DirectoryInfo directory)
        {
            Dbg.Diagnostics.Assert(
                directory != null,
                "The caller should verify directory.");

            bool result = false;

            IEnumerable<FileSystemInfo> children = directory.EnumerateFileSystemInfos();

            if (children.Any())
            {
                result = true;
            }

            return result;
        }

        #endregion HasChildItems

        #region CopyItem

        /// <summary>
        /// Copies an item at the specified path to the given destination.
        /// </summary>
        /// <param name="path">
        /// The path of the item to copy.
        /// </param>
        /// <param name="destinationPath">
        /// The path of the destination.
        /// </param>
        /// <param name="recurse">
        /// Specifies if the operation should also copy child items.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        ///     destination path is null or empty.
        /// </exception>
        /// <returns>
        /// Nothing.  Copied items are written to the context's pipeline.
        /// </returns>
        protected override void CopyItem(
            string path,
            string destinationPath,
            bool recurse)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

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

            path = NormalizePath(path);
            destinationPath = NormalizePath(destinationPath);

            PSSession fromSession = null;
            PSSession toSession = null;

            CopyItemDynamicParameters copyDynamicParameter = DynamicParameters as CopyItemDynamicParameters;

            if (copyDynamicParameter != null)
            {
                if (copyDynamicParameter.FromSession != null)
                {
                    fromSession = copyDynamicParameter.FromSession;
                }
                else
                {
                    toSession = copyDynamicParameter.ToSession;
                }
            }

            _excludeMatcher = SessionStateUtilities.CreateWildcardsFromStrings(Exclude, WildcardOptions.IgnoreCase);

            // if the source and destination path are same (for a local copy) then flag it as error.
            if ((toSession == null) && (fromSession == null) && InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(path, destinationPath))
            {
                string error = StringUtil.Format(FileSystemProviderStrings.CopyError, path);
                Exception e = new IOException(error);
                e.Data[SelfCopyDataKey] = destinationPath;
                WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, path));
                return;
            }
            // Copy-Item from session
            if (fromSession != null)
            {
                CopyItemFromRemoteSession(path, destinationPath, recurse, Force, fromSession);
            }
            else
            {
                // Copy-Item to session
                if (toSession != null)
                {
                    using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
                    {
                        ps.Runspace = toSession.Runspace;
                        CopyItemLocalOrToSession(path, destinationPath, recurse, Force, ps);
                    }
                }
                else // Copy-Item local
                {
                    CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null);
                }
            }

            _excludeMatcher.Clear();
            _excludeMatcher = null;
        }

        private void CopyItemFromRemoteSession(string path, string destinationPath, bool recurse, bool force, PSSession fromSession)
        {
            using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
            {
                ps.Runspace = fromSession.Runspace;

                InitializeFunctionPSCopyFileFromRemoteSession(ps);

                try
                {
                    // get info on source
                    ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
                    ps.AddParameter("getPathItems", path);

                    Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
                    if (op == null)
                    {
                        Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile, path));
                        WriteError(new ErrorRecord(e, "CopyItemRemotelyFailedToReadFile", ErrorCategory.WriteError, path));
                        return;
                    }

                    bool exists = (bool)(op["Exists"]);
                    if (!exists)
                    {
                        throw PSTraceSource.NewArgumentNullException(SessionStateStrings.PathNotFound, path);
                    }

                    if (op["Items"] != null)
                    {
                        PSObject obj = (PSObject)op["Items"];
                        ArrayList itemsList = (ArrayList)obj.BaseObject;
                        foreach (PSObject item in itemsList)
                        {
                            Hashtable ItemInfo = (Hashtable)item.BaseObject;
                            string itemName = (string)ItemInfo["Name"];
                            string itemFullName = (string)ItemInfo["FullName"];
                            bool isContainer = (bool)ItemInfo["IsDirectory"];

                            if (isContainer)
                            {
                                if (File.Exists(destinationPath))
                                {
                                    Exception e = new IOException(string.Format(
                                        CultureInfo.InvariantCulture,
                                        FileSystemProviderStrings.CopyItemRemotelyDestinationIsFile,
                                        path,
                                        destinationPath));
                                    WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath));
                                    return;
                                }

                                CopyDirectoryFromRemoteSession(
                                    itemName,
                                    itemFullName,
                                    destinationPath,
                                    force,
                                    recurse,
                                    ps);
                            }
                            else
                            {
                                bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(itemName, _excludeMatcher, false);
                                if (!excludeFile)
                                {
                                    long itemSize = (long)ItemInfo["FileSize"];
                                    CopyFileFromRemoteSession(itemName, itemFullName, destinationPath, force, ps, itemSize);
                                }
                            }
                        }
                    }
                }
                finally
                {
                    RemoveFunctionsPSCopyFileFromRemoteSession(ps);
                }
            }
        }

        private void CopyItemLocalOrToSession(string path, string destinationPath, bool recurse, bool Force, System.Management.Automation.PowerShell ps)
        {
            bool isContainer = IsItemContainer(path);

            InitializeFunctionsPSCopyFileToRemoteSession(ps);

            try
            {
                if (isContainer)
                {
                    // Get the directory info
                    DirectoryInfo dir = new DirectoryInfo(path);

                    // Now copy the directory to the destination
                    CopyDirectoryInfoItem(dir, destinationPath, recurse, Force, ps);
                }
                else // !isContainer
                {
                    // Get the file info
                    FileInfo file = new FileInfo(path);

                    CopyFileInfoItem(file, destinationPath, Force, ps);
                }
            }
            finally
            {
                RemoveFunctionPSCopyFileToRemoteSession(ps);
            }
        }

        private void CopyDirectoryInfoItem(
            DirectoryInfo directory,
            string destination,
            bool recurse,
            bool force,
            System.Management.Automation.PowerShell ps)
        {
            Dbg.Diagnostics.Assert(
                directory != null,
                "The caller should verify directory.");

            // Generate the path based on whether the destination path exists and
            // is a container.
            // If the destination exists and is a container the directory we are copying
            // will become a child of that directory.
            // If the destination doesn't exist we will just try to copy to that new
            // path.

            if (ps == null)
            {
                if (IsItemContainer(destination))
                {
                    destination = MakePath(destination, directory.Name);
                }
            }
            else
            {
                if (RemoteDirectoryExist(ps, destination))
                {
                    destination = Path.Combine(destination, directory.Name);
                }
            }

            s_tracer.WriteLine("destination = {0}", destination);

            // Confirm the copy with the user
            string action = FileSystemProviderStrings.CopyItemActionDirectory;

            string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, directory.FullName, destination);

            if (ShouldProcess(resource, action))
            {
                // Create the new directory
                // CreateDirectory does the WriteItemObject for the new DirectoryInfo
                if (ps == null)
                {
                    CreateDirectory(destination, true);
                }
                else
                {
                    // Verify that the destination is not a file on the remote end
                    if (RemoteDestinationPathIsFile(destination, ps))
                    {
                        Exception e = new IOException(string.Format(CultureInfo.InvariantCulture,
                                                                    FileSystemProviderStrings.CopyItemRemoteDestinationIsFile,
                                                                    destination));
                        WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destination));
                        return;
                    }

                    destination = CreateDirectoryOnRemoteSession(destination, force, ps);
                    if (destination == null)
                    {
                        return;
                    }
                }

                if (recurse)
                {
                    // Now copy all the files to that directory
                    IEnumerable<FileInfo> files = null;

                    if (string.IsNullOrEmpty(Filter))
                    {
                        files = directory.EnumerateFiles();
                    }
                    else
                    {
                        files = directory.EnumerateFiles(Filter);
                    }

                    foreach (FileInfo file in files)
                    {
                        // Making sure to obey the StopProcessing.
                        if (Stopping)
                        {
                            return;
                        }

                        if (file != null)
                        {
                            try
                            {
                                // CopyFileInfoItem does the WriteItemObject for the new FileInfo
                                CopyFileInfoItem(file, destination, force, ps);
                            }
                            catch (ArgumentException argException)
                            {
                                WriteError(new ErrorRecord(argException, "CopyDirectoryInfoItemArgumentError", ErrorCategory.InvalidArgument, file));
                            }
                            catch (IOException ioException)
                            {
                                // IOException contains specific message about the error occured and so no need for errordetails.
                                WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, file));
                            }
                            catch (UnauthorizedAccessException accessException)
                            {
                                WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
                            }
                        }
                    }

                    // Now copy all the directories to that directory
                    foreach (DirectoryInfo childDir in directory.EnumerateDirectories())
                    {
                        // Making sure to obey the StopProcessing.
                        if (Stopping)
                        {
                            return;
                        }

                        if (childDir != null)
                        {
                            try
                            {
                                CopyDirectoryInfoItem(childDir, destination, recurse, force, ps);
                            }
                            catch (ArgumentException argException)
                            {
                                WriteError(new ErrorRecord(argException, "CopyDirectoryInfoItemArgumentError", ErrorCategory.InvalidArgument, childDir));
                            }
                            catch (IOException ioException)
                            {
                                // IOException contains specific message about the error occured and so no need for errordetails.
                                WriteError(new ErrorRecord(ioException, "CopyDirectoryInfoItemIOError", ErrorCategory.WriteError, childDir));
                            }
                            catch (UnauthorizedAccessException accessException)
                            {
                                WriteError(new ErrorRecord(accessException, "CopyDirectoryInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, childDir));
                            }
                        }
                    }
                }
            }
        }

        private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, System.Management.Automation.PowerShell ps)
        {
            Dbg.Diagnostics.Assert(
                file != null,
                "The caller should verify file.");

            // If the destination is a container, add the file name
            // to the destination path.
            if (ps == null)
            {
                if (IsItemContainer(destinationPath))
                {
                    destinationPath = MakePath(destinationPath, file.Name);
                }

                // if the source and destination path are same then flag it as error.
                if (InternalSymbolicLinkLinkCodeMethods.IsSameFileSystemItem(destinationPath, file.FullName))
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.CopyError, destinationPath);
                    Exception e = new IOException(error);
                    e.Data[SelfCopyDataKey] = file.FullName;
                    WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destinationPath));
                    return;
                }
                // Verify that the target doesn't represent a device name
                if (PathIsReservedDeviceName(destinationPath, "CopyError"))
                {
                    return;
                }
            }

            // Confirm the copy with the user
            string action = FileSystemProviderStrings.CopyItemActionFile;

            string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, file.FullName, destinationPath);

            bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(file.Name, _excludeMatcher, defaultValue: false);

            if (!excludeFile)
            {
                if (ShouldProcess(resource, action))
                {
                    try
                    {
                        if (ps == null)
                        {
                            // Now copy the file
                            // We assume that if we get called we want to make
                            // the copy even if the destination already exists.
                            file.CopyTo(destinationPath, true);

                            FileInfo result = new FileInfo(destinationPath);
                            WriteItemObject(result, destinationPath, false);
                        }
                        else
                        {
                            PerformCopyFileToRemoteSession(file, destinationPath, ps);
                        }
                    }
                    catch (System.UnauthorizedAccessException unAuthorizedAccessException)
                    {
                        if (force)
                        {
                            try
                            {
                                if (ps == null)
                                {
                                    // If the destination exists and force is specified,
                                    // mask of the readonly and hidden attributes and
                                    // try again
                                    FileInfo destinationItem = new FileInfo(destinationPath);

                                    destinationItem.Attributes =
                                        destinationItem.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
                                }
                                else
                                {
                                    PerformCopyFileToRemoteSession(file, destinationPath, ps);
                                }
                            }
                            catch (Exception exception)
                            {
                                if ((exception is FileNotFoundException) ||
                                    (exception is DirectoryNotFoundException) ||
                                    (exception is System.Security.SecurityException) ||
                                    (exception is ArgumentException) ||
                                    (exception is IOException))
                                {
                                    // Write out the original error since we failed to force the copy
                                    WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
                                }
                                else
                                {
                                    throw;
                                }
                            }

                            file.CopyTo(destinationPath, true);

                            FileInfo result = new FileInfo(destinationPath);
                            WriteItemObject(result, destinationPath, false);
                        }
                        else
                        {
                            WriteError(new ErrorRecord(unAuthorizedAccessException, "CopyFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
                        }
                    }
                    catch (IOException ioException)
                    {
                        WriteError(new ErrorRecord(ioException, "CopyFileInfoItemIOError", ErrorCategory.WriteError, file));
                    }
                }
            }
        }

        private void CopyDirectoryFromRemoteSession(
            string sourceDirectoryName,
            string sourceDirectoryFullName,
            string destination,
            bool force,
            bool recurse,
            System.Management.Automation.PowerShell ps)
        {
            Dbg.Diagnostics.Assert((sourceDirectoryName != null && sourceDirectoryFullName != null), "The caller should verify directory.");

            if (IsItemContainer(destination))
            {
                destination = MakePath(destination, sourceDirectoryName);
            }

            s_tracer.WriteLine("destination = {0}", destination);

            // Confirm the copy with the user
            string action = FileSystemProviderStrings.CopyItemActionDirectory;
            string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, sourceDirectoryFullName, destination);

            if (ShouldProcess(resource, action))
            {
                // Create destinationPath directory. This will fail if the directory already exists
                // and Force is not selected.
                CreateDirectory(destination, false);

                // If failed to create directory
                if (!Directory.Exists(destination))
                {
                    return;
                }

                if (recurse)
                {
                    // Get all the files for that directory from the remote session
                    ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
                    ps.AddParameter("getPathDir", sourceDirectoryFullName);

                    Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
                    if (op == null)
                    {
                        Exception e = new IOException(string.Format(
                                                      CultureInfo.InvariantCulture,
                                                      FileSystemProviderStrings.CopyItemRemotelyFailedToGetDirectoryChildItems,
                                                      sourceDirectoryFullName));
                        WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, sourceDirectoryFullName));
                        return;
                    }

                    if (op["Files"] != null)
                    {
                        PSObject obj = (PSObject)op["Files"];
                        ArrayList filesList = (ArrayList)obj.BaseObject;

                        foreach (PSObject fileObject in filesList)
                        {
                            Hashtable file = (Hashtable)fileObject.BaseObject;
                            string fileName = (string)file["FileName"];
                            string filePath = (string)file["FilePath"];
                            long fileSize = (long)file["FileSize"];

                            // Making sure to obey the StopProcessing.
                            if (Stopping)
                            {
                                return;
                            }

                            bool excludeFile = SessionStateUtilities.MatchesAnyWildcardPattern(fileName, _excludeMatcher, defaultValue: false);

                            if (!excludeFile)
                            {
                                // If an exception is thrown in the remote session, it is surface to the user via PowerShell Write-Error.
                                CopyFileFromRemoteSession(fileName,
                                                          filePath,
                                                          destination,
                                                          force,
                                                          ps,
                                                          fileSize);
                            }
                        }
                    }

                    if (op["Directories"] != null)
                    {
                        PSObject obj = (PSObject)op["Directories"];
                        ArrayList directories = (ArrayList)obj.BaseObject;

                        foreach (PSObject dirObject in directories)
                        {
                            Hashtable dir = (Hashtable)dirObject.BaseObject;
                            string dirName = (string)dir["Name"];
                            string dirFullName = (string)dir["FullName"];

                            // Making sure to obey the StopProcessing.
                            if (Stopping)
                            {
                                return;
                            }

                            // If an exception is thrown in the remote session, it is surface to the user via PowerShell Write-Error.
                            CopyDirectoryFromRemoteSession(dirName,
                                                           dirFullName,
                                                           destination,
                                                           force,
                                                           recurse,
                                                           ps);
                        }
                    }
                }
            }
        }

        private ArrayList GetRemoteSourceAlternateStreams(System.Management.Automation.PowerShell ps, string path)
        {
            ArrayList streams = null;
            bool supportsAlternateStreams = false;

            ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
            ps.AddParameter("supportAltStreamPath", path);

            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
            if (op != null && op["SourceSupportsAlternateStreams"] != null)
            {
                supportsAlternateStreams = (bool)op["SourceSupportsAlternateStreams"];
            }

            if (supportsAlternateStreams)
            {
                PSObject obj = (PSObject)op["Streams"];
                streams = (ArrayList)obj.BaseObject;
            }

            return streams;
        }

        private void InitializeFunctionPSCopyFileFromRemoteSession(System.Management.Automation.PowerShell ps)
        {
            if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace))
            {
                return;
            }

            ps.AddScript(CopyFileRemoteUtils.AllCopyFromRemoteScripts);
            SafeInvokeCommand.Invoke(ps, this, null, false);
        }

        private void RemoveFunctionsPSCopyFileFromRemoteSession(System.Management.Automation.PowerShell ps)
        {
            if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace))
            {
                return;
            }

            string remoteScript = @"
                Microsoft.PowerShell.Management\Remove-Item function:PSCopyFromSessionHelper -ea SilentlyContinue -Force
                Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force
            ";
            ps.AddScript(remoteScript);
            SafeInvokeCommand.Invoke(ps, this, null, false);
        }

        private bool ValidRemoteSessionForScripting(Runspace runspace)
        {
            if (runspace is not RemoteRunspace)
            {
                return false;
            }

            PSLanguageMode languageMode = runspace.SessionStateProxy.LanguageMode;
            if (languageMode == PSLanguageMode.ConstrainedLanguage || languageMode == PSLanguageMode.NoLanguage)
            {
                // SessionStateInternal.ValidateRemotePathAndGetRoot checked for expected helper functions on the
                // restricted session and will have returned an error if they are missing.  So at this point we
                // assume the session is set up with the needed helper functions.
                return false;
            }

            return true;
        }

        private Hashtable GetRemoteFileMetadata(string filePath, System.Management.Automation.PowerShell ps)
        {
            ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
            ps.AddParameter("getMetaFilePath", filePath);
            Hashtable metadata = SafeInvokeCommand.Invoke(ps, this, null);
            return metadata;
        }

        private void SetFileMetadata(string sourceFileFullName, FileInfo destinationFile, System.Management.Automation.PowerShell ps)
        {
            Hashtable metadata = GetRemoteFileMetadata(sourceFileFullName, ps);

            if (metadata != null)
            {
                // LastWriteTime
                if (metadata["LastWriteTimeUtc"] != null)
                {
                    destinationFile.LastWriteTimeUtc = (DateTime)metadata["LastWriteTimeUtc"];
                }

                if (metadata["LastWriteTime"] != null)
                {
                    destinationFile.LastWriteTime = (DateTime)metadata["LastWriteTime"];
                }

                // Attributes
                if (metadata["Attributes"] != null)
                {
                    PSObject obj = (PSObject)metadata["Attributes"];
                    foreach (string value in (ArrayList)obj.BaseObject)
                    {
                        if (string.Equals(value, "ReadOnly", StringComparison.OrdinalIgnoreCase))
                        {
                            destinationFile.Attributes = destinationFile.Attributes | FileAttributes.ReadOnly;
                        }
                        else if (string.Equals(value, "Hidden", StringComparison.OrdinalIgnoreCase))
                        {
                            destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Hidden;
                        }
                        else if (string.Equals(value, "Archive", StringComparison.OrdinalIgnoreCase))
                        {
                            destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Archive;
                        }
                        else if (string.Equals(value, "System", StringComparison.OrdinalIgnoreCase))
                        {
                            destinationFile.Attributes = destinationFile.Attributes | FileAttributes.System;
                        }
                    }
                }
            }
        }

        private void CopyFileFromRemoteSession(
            string sourceFileName,
            string sourceFileFullName,
            string destinationPath,
            bool force,
            System.Management.Automation.PowerShell ps,
            long fileSize = 0)
        {
            Dbg.Diagnostics.Assert(sourceFileFullName != null, "The caller should verify file.");

            // If the destination is a container, add the file name
            // to the destination path.
            if (IsItemContainer(destinationPath))
            {
                destinationPath = MakePath(destinationPath, sourceFileName);
            }

            // Verify that the target doesn't represent a device name
            if (PathIsReservedDeviceName(destinationPath, "CopyError"))
            {
                return;
            }

            FileInfo destinationFile = new FileInfo(destinationPath);

            string action = FileSystemProviderStrings.CopyItemActionFile;
            string resource = StringUtil.Format(FileSystemProviderStrings.CopyItemResourceFileTemplate, sourceFileFullName, destinationPath);

            if (ShouldProcess(resource, action))
            {
                bool result = PerformCopyFileFromRemoteSession(sourceFileFullName, destinationFile, destinationPath, force, ps, fileSize, false, null);

                // Copying the file from the remote session completed successfully
                if (result)
                {
                    // Check if the remote source file has any alternate data streams
                    ArrayList remoteFileStreams = GetRemoteSourceAlternateStreams(ps, sourceFileFullName);
                    if ((remoteFileStreams != null) && (remoteFileStreams.Count > 0))
                    {
                        foreach (string streamName in remoteFileStreams)
                        {
                            result = PerformCopyFileFromRemoteSession(sourceFileFullName, destinationFile, destinationPath, force, ps, fileSize, true, streamName);
                            if (!result)
                            {
                                break;
                            }
                        }
                    }
                }

                // The file was copied successfully. Now, set the file metadata
                if (result)
                {
                    SetFileMetadata(sourceFileFullName, destinationFile, ps);
                }
            }
        }

        private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInfo destinationFile, string destinationPath, bool force, System.Management.Automation.PowerShell ps,
                                                      long fileSize, bool isAlternateDataStream, string streamName)
        {
            bool success = false;
            string activity = string.Format(CultureInfo.InvariantCulture,
                                            FileSystemProviderStrings.CopyItemRemotelyProgressActivity,
                                            sourceFileFullName,
                                            destinationFile.FullName);
            string statusDescription = string.Format(CultureInfo.InvariantCulture,
                                                        FileSystemProviderStrings.CopyItemRemotelyStatusDescription,
                                                        ps.Runspace.ConnectionInfo.ComputerName,
                                                        "localhost");

            ProgressRecord progress = new ProgressRecord(0, activity, statusDescription);
            progress.PercentComplete = 0;
            progress.RecordType = ProgressRecordType.Processing;
            WriteProgress(progress);
            FileStream wStream = null;
            bool errorWhileCopyRemoteFile = false;

            try
            {
                // The main data stream
                if (!isAlternateDataStream)
                {
                    // If force is specified, and the file already exist at the destination, mask of the readonly, hidden, and system attributes
                    if (force && File.Exists(destinationFile.FullName))
                    {
                        destinationFile.Attributes = destinationFile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System);
                    }

                    wStream = new FileStream(destinationFile.FullName, FileMode.Create);
                }

#if !UNIX
                // an alternate stream
                else
                {
                    wStream = AlternateDataStreamUtilities.CreateFileStream(destinationFile.FullName, streamName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                }
#endif
                long fragmentSize = FILETRANSFERSIZE;
                long copiedSoFar = 0;
                long currentIndex = 0;

                while (true)
                {
                    ps.AddCommand(CopyFileRemoteUtils.PSCopyFromSessionHelperName);
                    ps.AddParameter("copyFromFilePath", sourceFileFullName);
                    ps.AddParameter("copyFromStart", currentIndex);
                    ps.AddParameter("copyFromNumBytes", fragmentSize);
                    if (force)
                    {
                        ps.AddParameter(nameof(force), true);
                    }

#if !UNIX
                    if (isAlternateDataStream)
                    {
                        ps.AddParameter("isAlternateStream", true);
                        ps.AddParameter(nameof(streamName), streamName);
                    }
#endif

                    Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);

                    // Check if there was an exception when reading the remote file.
                    if (op == null)
                    {
                        errorWhileCopyRemoteFile = true;
                        Exception e = new IOException(string.Format(CultureInfo.InvariantCulture,
                                                                    FileSystemProviderStrings.CopyItemRemotelyFailedToReadFile,
                                                                    sourceFileFullName));
                        WriteError(new ErrorRecord(e, "FailedToCopyFileFromRemoteSession", ErrorCategory.WriteError, sourceFileFullName));
                        break;
                    }

                    if (op["ExceptionThrown"] != null)
                    {
                        bool failedToReadFile = (bool)(op["ExceptionThrown"]);
                        if (failedToReadFile)
                        {
                            // The error is written to the error array via SafeInvokeCommand
                            errorWhileCopyRemoteFile = true;
                            break;
                        }
                    }

                    // To accomodate empty files
                    string content = string.Empty;
                    if (op["b64Fragment"] != null)
                    {
                        content = (string)op["b64Fragment"];
                    }

                    bool more = (bool)op["moreAvailable"];
                    currentIndex += fragmentSize;
                    byte[] bytes = System.Convert.FromBase64String(content);
                    wStream.Write(bytes, 0, bytes.Length);
                    copiedSoFar += bytes.Length;

                    if (wStream.Length > 0)
                    {
                        int percentage = (int)(copiedSoFar * 100 / wStream.Length);
                        progress.PercentComplete = percentage;
                        WriteProgress(progress);
                    }

                    if (!more)
                    {
                        success = true;
                        break;
                    }
                }

                progress.PercentComplete = 100;
                progress.RecordType = ProgressRecordType.Completed;
                WriteProgress(progress);
            }
            catch (IOException ioException)
            {
                // IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException
                WriteError(new ErrorRecord(ioException, "CopyItemRemotelyIOError", ErrorCategory.WriteError, sourceFileFullName));
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "CopyItemRemotelyArgumentError", ErrorCategory.WriteError, sourceFileFullName));
            }
            catch (NotSupportedException notSupportedException)
            {
                WriteError(new ErrorRecord(notSupportedException, "CopyFileInfoRemotelyPathRefersToANonFileDevice", ErrorCategory.InvalidArgument, sourceFileFullName));
            }
            catch (SecurityException securityException)
            {
                WriteError(new ErrorRecord(securityException, "CopyFileInfoRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, sourceFileFullName));
            }
            catch (UnauthorizedAccessException unauthorizedAccessException)
            {
                WriteError(new ErrorRecord(unauthorizedAccessException, "CopyFileInfoItemRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, sourceFileFullName));
            }
            finally
            {
                if (wStream != null)
                {
                    wStream.Dispose();
                }

                // If copying the file from the remote session failed, then remove it.
                if (errorWhileCopyRemoteFile && File.Exists(destinationFile.FullName))
                {
                    if (!(destinationFile.Attributes.HasFlag(FileAttributes.ReadOnly) ||
                            destinationFile.Attributes.HasFlag(FileAttributes.Hidden) ||
                            destinationFile.Attributes.HasFlag(FileAttributes.System)))
                    {
                        RemoveFileSystemItem(destinationFile, true);
                    }
                }
            }

            return success;
        }

        private void InitializeFunctionsPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps)
        {
            if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }

            ps.AddScript(CopyFileRemoteUtils.AllCopyToRemoteScripts);
            SafeInvokeCommand.Invoke(ps, this, null, false);
        }

        private void RemoveFunctionPSCopyFileToRemoteSession(System.Management.Automation.PowerShell ps)
        {
            if ((ps == null) || !ValidRemoteSessionForScripting(ps.Runspace)) { return; }

            string remoteScript = @"
                Microsoft.PowerShell.Management\Remove-Item function:PSCopyToSessionHelper -ea SilentlyContinue -Force
                Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force
            ";
            ps.AddScript(remoteScript);
            SafeInvokeCommand.Invoke(ps, this, null, false);
        }

        // If the target supports alternate data streams the following must be true:
        // 1) The remote session must be PowerShell V3 or higher to support Streams
        // 2) The target drive must be NTFS
        private bool RemoteTargetSupportsAlternateStreams(System.Management.Automation.PowerShell ps, string path)
        {
            bool supportsAlternateStreams = false;

            ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
            ps.AddParameter("supportAltStreamPath", path);

            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
            if (op != null && op["TargetSupportsAlternateStreams"] != null)
            {
                supportsAlternateStreams = (bool)op["TargetSupportsAlternateStreams"];
            }

            return supportsAlternateStreams;
        }

        // Validate that the given remotePath exists, and do the following:
        // 1) If the remotePath is a FileInfo, then just return the remotePath.
        // 2) If the remotePath is a DirectoryInfo, then return the remotePath + the given filename.
        // 3) If the remote path does not exist, but its parent does, and it is a DirectoryInfo, then return the remotePath.
        // 4) If the remotePath or its parent do not exist, return null.
        private string MakeRemotePath(System.Management.Automation.PowerShell ps, string remotePath, string filename)
        {
            bool isFileInfo = false;
            bool isDirectoryInfo = false;
            bool parentIsDirectoryInfo = false;
            string path = null;

            ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
            ps.AddParameter(nameof(remotePath), remotePath);
            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);

            if (op != null)
            {
                if (op["IsDirectoryInfo"] != null)
                {
                    isDirectoryInfo = (bool)op["IsDirectoryInfo"];
                }

                if (op["IsFileInfo"] != null)
                {
                    isFileInfo = (bool)op["IsFileInfo"];
                }

                if (op["ParentIsDirectoryInfo"] != null)
                {
                    parentIsDirectoryInfo = (bool)op["ParentIsDirectoryInfo"];
                }
            }

            if (isFileInfo)
            {
                // The destination is a file, so we are going to overwrite it.
                path = remotePath;
            }
            else if (isDirectoryInfo)
            {
                // The destination is a directory, so append the file name to the path.
                path = Path.Combine(remotePath, filename);
            }
            else if (parentIsDirectoryInfo)
            {
                // At this point we know that the remotePath is neither a file or a directory on the remote target.
                // However, if the parent of the remotePath exists, then we are doing a copy-item operation in which
                // the destination file name is already being passed, e.g.,
                // copy-item -path c:\localDir\foo.txt -destination d:\remoteDir\bar.txt -toSession $s
                // Note that d:\remoteDir is a directory that exists on the remote target machine.
                path = remotePath;
            }

            return path;
        }

        private bool RemoteDirectoryExist(System.Management.Automation.PowerShell ps, string path)
        {
            bool pathExists = false;

            ps.AddCommand(CopyFileRemoteUtils.PSCopyRemoteUtilsName);
            ps.AddParameter("dirPathExists", path);
            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);

            if (op != null)
            {
                if (op["Exists"] != null)
                    pathExists = (bool)op["Exists"];
            }

            return pathExists;
        }

        private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath, System.Management.Automation.PowerShell ps, bool isAlternateStream, string streamName)
        {
            string activity = string.Format(CultureInfo.InvariantCulture,
                                            FileSystemProviderStrings.CopyItemRemotelyProgressActivity,
                                            file.FullName,
                                            destinationPath);
            string statusDescription = string.Format(CultureInfo.InvariantCulture,
                                                     FileSystemProviderStrings.CopyItemRemotelyStatusDescription,
                                                     "localhost",
                                                     ps.Runspace.ConnectionInfo.ComputerName);

            ProgressRecord progress = new ProgressRecord(0, activity, statusDescription);
            progress.PercentComplete = 0;
            progress.RecordType = ProgressRecordType.Processing;
            WriteProgress(progress);

            // 4MB gives the best results without spiking the resources on the remote connection.
            int fragmentSize = FILETRANSFERSIZE;
            byte[] fragment = null;
            int iteration = 0;
            bool success = false;

            FileStream fStream = null;
            try
            {
                // Main data stream
                if (!isAlternateStream)
                {
                    fStream = File.OpenRead(file.FullName);
                }
#if !UNIX
                else
                {
                    fStream = AlternateDataStreamUtilities.CreateFileStream(file.FullName, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                }
#endif
                long remainingFileSize = fStream != null ? fStream.Length : 0;
                do
                {
                    if (Stopping)
                    {
                        return false;
                    }

                    iteration++;
                    int toRead = fragmentSize;
                    if (toRead > remainingFileSize)
                    {
                        toRead = (int)remainingFileSize;
                    }

                    if (fragment == null)
                    {
                        fragment = new byte[toRead];
                    }
                    else if (toRead < fragmentSize)
                    {
                        fragment = new byte[toRead];
                    }

                    int readSoFar = 0;
                    while (readSoFar < toRead)
                    {
                        readSoFar += fStream.Read(fragment, 0, toRead);
                    }

                    remainingFileSize -= readSoFar;

                    string b64Fragment = System.Convert.ToBase64String(fragment);

                    // Main data stream
                    if (!isAlternateStream)
                    {
                        ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
                        ps.AddParameter("copyToFilePath", destinationPath);
                        ps.AddParameter("createFile", (iteration == 1));

                        if ((iteration == 1) && (b64Fragment.Length == 0))
                        {
                            // This fixes the case in which the user tries to copy an empty file between sessions.
                            // Scenario 1: The user creates an empty file using the Out-File cmdlet.
                            //             In this case the file length is 6.
                            //             "" | out-file test.txt
                            // Scenario 2: The user generates an empty file using the New-Item cmdlet.
                            //             In this case the file length is 0.
                            //             New-Item -Path test.txt -Type File
                            // Because of this, when we create the file on the remote session, we need to check
                            // the length of b64Fragment to figure out if we are creating an empty file.
                            ps.AddParameter("emptyFile", true);
                        }
                        else
                        {
                            ps.AddParameter("b64Fragment", b64Fragment);
                        }
                    }
                    else
                    {
                        ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
                        ps.AddParameter("copyToFilePath", destinationPath);
                        ps.AddParameter("b64Fragment", b64Fragment);
                        ps.AddParameter(nameof(streamName), streamName);
                    }

                    Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);
                    if (op == null || op["BytesWritten"] == null)
                    {
                        // write error to stream
                        Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file));
                        WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName));
                        return false;
                    }

                    if ((int)(op["BytesWritten"]) != toRead)
                    {
                        Exception e = new IOException(string.Format(CultureInfo.InvariantCulture, FileSystemProviderStrings.CopyItemRemotelyFailed, file));
                        WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, file.FullName));
                        return false;
                    }

                    if (fStream.Length > 0)
                    {
                        int percentage = (int)((fStream.Length - remainingFileSize) * 100 / fStream.Length);
                        progress.PercentComplete = percentage;
                        WriteProgress(progress);
                    }
                } while (remainingFileSize > 0);
                progress.PercentComplete = 100;
                progress.RecordType = ProgressRecordType.Completed;
                WriteProgress(progress);
                success = true;
            }
            catch (IOException ioException)
            {
                // IOException takes care of FileNotFoundException, DirectoryNotFoundException, and PathTooLongException
                WriteError(new ErrorRecord(ioException, "CopyItemRemotelyIOError", ErrorCategory.WriteError, file.FullName));
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "CopyItemRemotelyArgumentError", ErrorCategory.WriteError, file.FullName));
            }
            catch (NotSupportedException notSupportedException)
            {
                WriteError(new ErrorRecord(notSupportedException, "CopyFileInfoRemotelyPathRefersToANonFileDevice", ErrorCategory.InvalidArgument, file.FullName));
            }
            catch (SecurityException securityException)
            {
                WriteError(new ErrorRecord(securityException, "CopyFileInfoRemotelyUnauthorizedAccessError", ErrorCategory.PermissionDenied, file.FullName));
            }
            finally
            {
                if (fStream != null)
                {
                    fStream.Dispose();
                }
            }

            return success;
        }

        // Returns a hash table with metadata about this file info.
        //
        private Hashtable GetFileMetadata(FileInfo file)
        {
            Hashtable metadata = new Hashtable();

            // LastWriteTime
            metadata.Add("LastWriteTime", file.LastWriteTime);
            metadata.Add("LastWriteTimeUtc", file.LastWriteTimeUtc);

            // File attributes
            metadata.Add("Attributes", file.Attributes);

            return metadata;
        }

        private void SetRemoteFileMetadata(FileInfo file, string remoteFilePath, System.Management.Automation.PowerShell ps)
        {
            Hashtable metadata = GetFileMetadata(file);
            if (metadata != null)
            {
                ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
                ps.AddParameter("metaDataFilePath", remoteFilePath);
                ps.AddParameter("metaDataToSet", metadata);
                SafeInvokeCommand.Invoke(ps, this, null, false);
            }
        }

        private bool PerformCopyFileToRemoteSession(FileInfo file, string destinationPath, System.Management.Automation.PowerShell ps)
        {
            // Make the remote path
            var remoteFilePath = MakeRemotePath(ps, destinationPath, file.Name);

            if (string.IsNullOrEmpty(remoteFilePath))
            {
                Exception e = new ArgumentException(string.Format(CultureInfo.InvariantCulture, SessionStateStrings.PathNotFound, destinationPath));
                WriteError(new ErrorRecord(e, "RemotePathNotFound", ErrorCategory.WriteError, destinationPath));
                return false;
            }

            bool result = CopyFileStreamToRemoteSession(file, remoteFilePath, ps, false, null);

#if !UNIX
            bool targetSupportsAlternateStreams = RemoteTargetSupportsAlternateStreams(ps, remoteFilePath);

            // Once the file is copied successfully, check if the file has any alternate data streams
            if (result && targetSupportsAlternateStreams)
            {
                foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(file.FullName))
                {
                    if (!(string.Equals(":$DATA", stream.Stream, StringComparison.OrdinalIgnoreCase)))
                    {
                        result = CopyFileStreamToRemoteSession(file, remoteFilePath, ps, true, stream.Stream);
                        if (!result)
                        {
                            break;
                        }
                    }
                }
            }
#endif
            if (result)
            {
                SetRemoteFileMetadata(file, Path.Combine(destinationPath, file.Name), ps);
            }

            return result;
        }

        private bool RemoteDestinationPathIsFile(string destination, System.Management.Automation.PowerShell ps)
        {
            ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
            ps.AddParameter("isFilePath", destination);

            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);

            if (op == null || op["IsFileInfo"] == null)
            {
                Exception e = new IOException(string.Format(
                                                    CultureInfo.InvariantCulture,
                                                    FileSystemProviderStrings.CopyItemRemotelyFailedToValidateIfDestinationIsFile,
                                                    destination));
                WriteError(new ErrorRecord(e, "CopyError", ErrorCategory.WriteError, destination));
                return false;
            }

            return (bool)(op["IsFileInfo"]);
        }

        private string CreateDirectoryOnRemoteSession(string destination, bool force, System.Management.Automation.PowerShell ps)
        {
            ps.AddCommand(CopyFileRemoteUtils.PSCopyToSessionHelperName);
            ps.AddParameter("createDirectoryPath", destination);
            if (force)
            {
                ps.AddParameter(nameof(force), true);
            }

            Hashtable op = SafeInvokeCommand.Invoke(ps, this, null);

            // If op == null,  SafeInvokeCommand.Invoke throwns an error.
            if (op["ExceptionThrown"] != null)
            {
                // If an error is thrown on the remote session, it is written via SafeInvokeCommand.Invoke.
                if ((bool)op["ExceptionThrown"])
                    return null;
            }

            if (force && (op["DirectoryPath"] == null))
            {
                Exception e = new IOException(string.Format(CultureInfo.InvariantCulture,
                                                            FileSystemProviderStrings.CopyItemRemotelyFailedToCreateDirectory,
                                                            destination));
                WriteError(new ErrorRecord(e, "FailedToCreateDirectory", ErrorCategory.WriteError, destination));
                return null;
            }

            string path = (string)(op["DirectoryPath"]);

            if ((!force) && (bool)op["PathExists"])
            {
                Exception e = new IOException(StringUtil.Format(FileSystemProviderStrings.DirectoryExist, path));
                WriteError(new ErrorRecord(e, "DirectoryExist", ErrorCategory.ResourceExists, path));
                return null;
            }

            return path;
        }

        // Returns true if the destination path represents a device name, and write an error to the user.
        private bool PathIsReservedDeviceName(string destinationPath, string errorId)
        {
            bool pathIsReservedDeviceName = false;
            if (Utils.IsReservedDeviceName(destinationPath))
            {
                pathIsReservedDeviceName = true;
                string error = StringUtil.Format(FileSystemProviderStrings.TargetCannotContainDeviceName, destinationPath);
                Exception e = new IOException(error);
                WriteError(new ErrorRecord(e, errorId, ErrorCategory.WriteError, destinationPath));
            }

            return pathIsReservedDeviceName;
        }

        #endregion CopyItem

        #endregion ContainerCmdletProvider members

        #region NavigationCmdletProvider members

        /// <summary>
        /// Gets the parent of the given path.
        /// </summary>
        /// <param name="path">
        /// The path of which to get the parent.
        /// </param>
        /// <param name="root">
        /// The root of the drive.
        /// </param>
        /// <returns>
        /// The parent of the given path.
        /// </returns>
        protected override string GetParentPath(string path, string root)
        {
            string parentPath = base.GetParentPath(path, root);
            if (!Utils.PathIsUnc(path))
            {
                parentPath = EnsureDriveIsRooted(parentPath);
            }
#if !UNIX
            else if (parentPath.Equals(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal))
            {
                // make sure we return two backslashes so it still results in a UNC path
                parentPath = "\\\\";
            }
#endif
            return parentPath;
        }

        // Note: we don't use IO.Path.IsPathRooted as this deals with "invalid" i.e. unnormalized paths
        private static bool IsAbsolutePath(string path)
        {
            bool result = false;

            // check if we're on a single root filesystem and it's an absolute path
            if (LocationGlobber.IsSingleFileSystemAbsolutePath(path))
            {
                return true;
            }

            // Find the drive separator
            int index = path.IndexOf(':');

            if (index != -1)
            {
                result = true;
            }

            return result;
        }

        /// <summary>
        /// Determines if the specified path is a root of a UNC share
        /// by counting the path separators "\" following "\\". If only
        /// one path separator is found we know the path is in the form
        /// "\\server\share" and is a valid UNC root.
        /// </summary>
        /// <param name="path">
        /// The path to check to see if its a UNC root.
        /// </param>
        /// <returns>
        /// True if the path is a UNC root, or false otherwise.
        /// </returns>
        private static bool IsUNCRoot(string path)
        {
            bool result = false;

            if (!string.IsNullOrEmpty(path))
            {
                if (Utils.PathIsUnc(path))
                {
                    int lastIndex = path.Length - 1;

                    if (path[path.Length - 1] == '\\')
                    {
                        lastIndex--;
                    }

                    int separatorsFound = 0;
                    do
                    {
                        lastIndex = path.LastIndexOf('\\', lastIndex);
                        if (lastIndex == -1)
                        {
                            break;
                        }

                        --lastIndex;
                        if (lastIndex < 3)
                        {
                            break;
                        }

                        ++separatorsFound;
                    } while (lastIndex > 3);

                    if (separatorsFound == 1)
                    {
                        result = true;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Determines if the specified path is either a drive root or a UNC root.
        /// </summary>
        /// <param name="path">
        /// The path
        /// </param>
        /// <returns>
        /// True if the path is either a drive root or a UNC root, or false otherwise.
        /// </returns>
        private static bool IsPathRoot(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return false;
            }

            bool isDriveRoot = string.Equals(path, Path.GetPathRoot(path), StringComparison.OrdinalIgnoreCase);
            bool isUNCRoot = IsUNCRoot(path);
            bool result = isDriveRoot || isUNCRoot;
            s_tracer.WriteLine("result = {0}; isDriveRoot = {1}; isUNCRoot = {2}", result, isDriveRoot, isUNCRoot);
            return result;
        }

        /// <summary>
        /// Normalizes the path that was passed in and returns it as a normalized
        /// path relative to the given basePath.
        /// </summary>
        /// <param name="path">
        /// A fully qualifiedpath to an item. The item must exist,
        /// or the provider writes out an error.
        /// </param>
        /// <param name="basePath">
        /// The path that the normalized path should be relative to.
        /// </param>
        /// <returns>
        /// A normalized path, relative to the given basePath.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override string NormalizeRelativePath(
            string path,
            string basePath)
        {
            if (string.IsNullOrEmpty(path) || !IsValidPath(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            if (basePath == null)
            {
                basePath = string.Empty;
            }

            s_tracer.WriteLine("basePath = {0}", basePath);

            string result = path;

            do // false loop
            {
                path = NormalizePath(path);
                path = EnsureDriveIsRooted(path);

                // If it's not fully normalized, normalize it.
                path = NormalizeRelativePathHelper(path, basePath);

                basePath = NormalizePath(basePath);
                basePath = EnsureDriveIsRooted(basePath);

                result = path;
                if (string.IsNullOrEmpty(result))
                {
                    break;
                }

                try
                {
                    string originalPathComparison = path;
                    if (!originalPathComparison.EndsWith(StringLiterals.DefaultPathSeparator))
                    {
                        originalPathComparison += StringLiterals.DefaultPathSeparator;
                    }

                    string basePathComparison = basePath;
                    if (!basePathComparison.EndsWith(StringLiterals.DefaultPathSeparator))
                    {
                        basePathComparison += StringLiterals.DefaultPathSeparator;
                    }

                    if (originalPathComparison.StartsWith(basePathComparison, StringComparison.OrdinalIgnoreCase))
                    {
                        if (!Utils.PathIsUnc(result))
                        {
                            // Add the base path back on so that it can be used for
                            // processing
                            if (!result.StartsWith(basePath, StringComparison.Ordinal))
                            {
                                result = MakePath(basePath, result);
                            }
                        }

                        if (IsPathRoot(result))
                        {
                            result = EnsureDriveIsRooted(result);
                        }
                        else
                        {
                            // Now ensure that we have the proper casing by
                            // getting the names of the files and directories that match
                            string directoryPath = GetParentPath(result, string.Empty);

                            if (string.IsNullOrEmpty(directoryPath))
                            {
                                return string.Empty;
                            }

#if UNIX
                            // We don't use the Directory.EnumerateFiles() for Unix because the path
                            // may contain additional globbing patterns such as '[ab]'
                            // which Directory.EnumerateFiles() processes, giving undesireable
                            // results in this context.
                            if (!File.Exists(result) && !Directory.Exists(result))
                            {
                                string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                                Exception e = new IOException(error);
                                WriteError(new ErrorRecord(
                                    e,
                                    "ItemDoesNotExist",
                                    ErrorCategory.ObjectNotFound,
                                    path));
                                break;
                            }
#else
                            string leafName = GetChildName(result);

                            // Use the Directory class to get the real path (this will
                            // ensure the proper casing
                            IEnumerable<string> files = Directory.EnumerateFiles(directoryPath, leafName);

                            if (files == null || !files.Any())
                            {
                                files = Directory.EnumerateDirectories(directoryPath, leafName);
                            }

                            if (files == null || !files.Any())
                            {
                                string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                                Exception e = new IOException(error);
                                WriteError(new ErrorRecord(
                                    e,
                                    "ItemDoesNotExist",
                                    ErrorCategory.ObjectNotFound,
                                    path));
                                break;
                            }

                            result = files.First();
#endif

                            if (result.StartsWith(basePath, StringComparison.Ordinal))
                            {
                                result = result.Substring(basePath.Length);
                            }
                            else
                            {
                                string error = StringUtil.Format(FileSystemProviderStrings.PathOutSideBasePath, path);
                                Exception e =
                                    new ArgumentException(error);
                                WriteError(new ErrorRecord(
                                    e,
                                    "PathOutSideBasePath",
                                    ErrorCategory.InvalidArgument,
                                    null));
                                break;
                            }
                        }
                    }
                }
                catch (ArgumentException argumentException)
                {
                    WriteError(new ErrorRecord(argumentException, "NormalizeRelativePathArgumentError", ErrorCategory.InvalidArgument, path));
                    break;
                }
                catch (DirectoryNotFoundException directoryNotFound)
                {
                    WriteError(new ErrorRecord(directoryNotFound, "NormalizeRelativePathDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
                    break;
                }
                catch (IOException ioError)
                {
                    // IOException contains specific message about the error occured and so no need for errordetails.
                    WriteError(new ErrorRecord(ioError, "NormalizeRelativePathIOError", ErrorCategory.ReadError, path));
                    break;
                }
                catch (UnauthorizedAccessException accessException)
                {
                    WriteError(new ErrorRecord(accessException, "NormalizeRelativePathUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
                    break;
                }
            } while (false);

            return result;
        }

        /// <summary>
        /// Normalizes the path that was passed in and returns the normalized path
        /// as a relative path to the basePath that was passed.
        /// </summary>
        /// <param name="path">
        /// A fully qualified provider specific path to an item. The item should exist
        /// or the provider should write out an error.
        /// </param>
        /// <param name="basePath">
        /// The path that the return value should be relative to.
        /// </param>
        /// <returns>
        /// A normalized path that is relative to the basePath that was passed. The
        /// provider should parse the path parameter, normalize the path, and then
        /// return the normalized path relative to the basePath.
        /// </returns>
        /// <remarks>
        /// This method does not have to be purely syntactical parsing of the path. It
        /// is encouraged that the provider actually use the path to lookup in its store
        /// and create a relative path that matches the casing, and standardized path syntax.
        ///
        /// Note, the base class implementation uses GetParentPath, GetChildName, and MakePath
        /// to normalize the path and then make it relative to basePath. All string comparisons
        /// are done using StringComparison.InvariantCultureIgnoreCase.
        /// </remarks>
        private string NormalizeRelativePathHelper(string path, string basePath)
        {
            if (path == null)
            {
                throw PSTraceSource.NewArgumentNullException(nameof(path));
            }

            if (path.Length == 0)
            {
                return string.Empty;
            }

            if (basePath == null)
            {
                basePath = string.Empty;
            }

            s_tracer.WriteLine("basePath = {0}", basePath);

#if !UNIX
            // Remove alternate data stream references
            // See if they've used the inline stream syntax. They have more than one colon.
            string alternateDataStream = string.Empty;
            int firstColon = path.IndexOf(':');
            int secondColon = path.IndexOf(':', firstColon + 1);
            if (secondColon > 0)
            {
                string newPath = path.Substring(0, secondColon);
                alternateDataStream = path.Replace(newPath, string.Empty);
                path = newPath;
            }
#endif

            string result = path;

            do // false loop
            {
                // Convert to the correct path separators and trim trailing separators
                path = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
                string originalPath = path;

                path = path.TrimEnd(StringLiterals.DefaultPathSeparator);
                basePath = basePath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
                basePath = basePath.TrimEnd(StringLiterals.DefaultPathSeparator);

                path = RemoveRelativeTokens(path);

                // See if the base and the path are already the same. We resolve this to
                // ..\Leaf, since resolving "." to "." doesn't offer much information.
                if (string.Equals(path, basePath, StringComparison.OrdinalIgnoreCase) &&
                    (!originalPath.EndsWith(StringLiterals.DefaultPathSeparator)))
                {
                    string childName = GetChildName(path);
                    result = MakePath("..", childName);
                    break;
                }

                Stack<string> tokenizedPathStack = null;

                // If the base path isn't really a base, then we resolve to a parent
                // path (such as ../../foo)
                // For example: base = c:/temp/bar/baz
                //              path = c:/temp/foo
                if ((
                    !(path + StringLiterals.DefaultPathSeparator).StartsWith(
                    basePath + StringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase)) &&
                    (!string.IsNullOrEmpty(basePath))
                    )
                {
                    result = string.Empty;
                    string commonBase = GetCommonBase(path, basePath);

                    Stack<string> parentNavigationStack = TokenizePathToStack(basePath, commonBase);
                    int parentPopCount = parentNavigationStack.Count;

                    if (string.IsNullOrEmpty(commonBase))
                    {
                        parentPopCount--;
                    }

                    for (int leafCounter = 0; leafCounter < parentPopCount; leafCounter++)
                    {
                        result = MakePath("..", result);
                    }

                    // This is true if we get passed a base path like:
                    //    c:\directory1\directory2
                    // and an actual path of
                    //    c:\directory1
                    // Which happens when the user is in c:\directory1\directory2
                    // and wants to resolve something like:
                    // ..\..\dir*
                    // In that case (as above,) we keep the ..\..\directory1
                    // instead of ".." as would usually be returned
                    if (!string.IsNullOrEmpty(commonBase))
                    {
                        if (string.Equals(path, commonBase, StringComparison.OrdinalIgnoreCase) &&
                            (!path.EndsWith(StringLiterals.DefaultPathSeparator)))
                        {
                            string childName = GetChildName(path);
                            result = MakePath("..", result);
                            result = MakePath(result, childName);
                        }
                        else
                        {
                            string[] childNavigationItems = TokenizePathToStack(path, commonBase).ToArray();

                            for (int leafCounter = 0; leafCounter < childNavigationItems.Length; leafCounter++)
                            {
                                result = MakePath(result, childNavigationItems[leafCounter]);
                            }
                        }
                    }
                }
                // Otherwise, we resolve to a child path (such as foo/bar)
                else
                {
                    // If the path is a root, then the result will either be empty or the root depending
                    // on the value of basePath.
                    if (IsPathRoot(path))
                    {
                        if (string.IsNullOrEmpty(basePath))
                        {
                            result = path;
                            break;
                        }
                        else
                        {
                            result = string.Empty;
                            break;
                        }
                    }

                    tokenizedPathStack = TokenizePathToStack(path, basePath);

                    // Now we have to normalize the path
                    // by processing each token on the stack
                    Stack<string> normalizedPathStack;

                    try
                    {
                        normalizedPathStack = NormalizeThePath(basePath, tokenizedPathStack);
                    }
                    catch (ArgumentException argumentException)
                    {
                        WriteError(new ErrorRecord(argumentException, "NormalizeRelativePathHelperArgumentError", ErrorCategory.InvalidArgument, null));
                        result = null;
                        break;
                    }

                    // Now that the path has been normalized, create the relative path
                    result = CreateNormalizedRelativePathFromStack(normalizedPathStack);
                }
            } while (false);

#if !UNIX
            if (!string.IsNullOrEmpty(alternateDataStream))
            {
                result = result + alternateDataStream;
            }
#endif

            return result;
        }

        private string RemoveRelativeTokens(string path)
        {
            string testPath = path.Replace('/', '\\');
            if (
                !testPath.Contains('\\') ||
                testPath.StartsWith(".\\", StringComparison.Ordinal) ||
                testPath.StartsWith("..\\", StringComparison.Ordinal) ||
                testPath.EndsWith("\\.", StringComparison.Ordinal) ||
                testPath.EndsWith("\\..", StringComparison.Ordinal) ||
                testPath.Contains("\\.\\", StringComparison.Ordinal) ||
                testPath.Contains("\\..\\", StringComparison.Ordinal))
            {
                try
                {
                    Stack<string> tokenizedPathStack = TokenizePathToStack(path, string.Empty);
                    Stack<string> normalizedPath = NormalizeThePath(string.Empty, tokenizedPathStack);
                    return CreateNormalizedRelativePathFromStack(normalizedPath);
                }
                catch (UnauthorizedAccessException)
                {
                    // Catch any errors here, as we may be in an AppContainer
                }
            }

            return path;
        }

        /// <summary>
        /// Get the common base path of two paths.
        /// </summary>
        /// <param name="path1">One path.</param>
        /// <param name="path2">Another path.</param>
        private string GetCommonBase(string path1, string path2)
        {
            // Always see if the shorter path is a substring of the
            // longer path. If it is not, take the child off of the longer
            // path and compare again.
            while (!string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase))
            {
                if (path2.Length > path1.Length)
                {
                    path2 = GetParentPath(path2, null);
                }
                else
                {
                    path1 = GetParentPath(path1, null);
                }
            }

            return path1;
        }

        /// <summary>
        /// Tokenizes the specified path onto a stack.
        /// </summary>
        /// <param name="path">
        /// The path to tokenize.
        /// </param>
        /// <param name="basePath">
        /// The base part of the path that should not be tokenized.
        /// </param>
        /// <returns>
        /// A stack containing the tokenized path with leaf elements on the bottom
        /// of the stack and the most ancestral parent at the top.
        /// </returns>
        private Stack<string> TokenizePathToStack(string path, string basePath)
        {
            Stack<string> tokenizedPathStack = new Stack<string>();

            string tempPath = path;
            string previousParent = path;

            while (tempPath.Length > basePath.Length)
            {
                // Get the child name and push it onto the stack
                // if its valid
                string childName = GetChildName(tempPath);
                if (string.IsNullOrEmpty(childName))
                {
                    // Push the parent on and then stop
                    s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath);
                    tokenizedPathStack.Push(tempPath);
                    break;
                }

                s_tracer.WriteLine("tokenizedPathStack.Push({0})", childName);
                tokenizedPathStack.Push(childName);

                // Get the parent path and verify if we have to continue
                // tokenizing
                // We are done if the remaining path is:
                // - the same as the previous path
                // - a UNC path that is the root of a UNC share
                // - not a UNC path and the string length is less than or
                //   equal to 3. "C:\"
                tempPath = GetParentPath(tempPath, basePath);
                if (tempPath.Length >= previousParent.Length ||
                    IsPathRoot(tempPath))
                {
                    if (string.IsNullOrEmpty(basePath))
                    {
                        s_tracer.WriteLine("tokenizedPathStack.Push({0})", tempPath);
                        tokenizedPathStack.Push(tempPath);
                    }

                    break;
                }

                previousParent = tempPath;
            }

            return tokenizedPathStack;
        }

        /// <summary>
        /// Given the tokenized path, the relative path elements are removed.
        /// </summary>
        /// <param name="basepath">
        ///   String containing basepath for which we are trying to find the relative path.
        /// </param>
        /// <param name="tokenizedPathStack">
        /// A stack containing path elements where the leaf most element is at
        /// the bottom of the stack and the most ancestral parent is on the top.
        /// Generally this stack comes from TokenizePathToStack().
        /// </param>
        /// <returns>
        /// A stack in reverse order with the path elements normalized and all relative
        /// path tokens removed.
        /// </returns>
        private Stack<string> NormalizeThePath(string basepath, Stack<string> tokenizedPathStack)
        {
            Stack<string> normalizedPathStack = new Stack<string>();
            string currentPath = basepath;

            while (tokenizedPathStack.Count > 0)
            {
                string childName = tokenizedPathStack.Pop();

                s_tracer.WriteLine("childName = {0}", childName);

                // Ignore the current directory token
                if (childName.Equals(".", StringComparison.OrdinalIgnoreCase))
                {
                    // Just ignore it and move on.
                    continue;
                }
                else if (childName.Equals("..", StringComparison.OrdinalIgnoreCase))
                {
                    if (normalizedPathStack.Count > 0)
                    {
                        // Pop the result and continue processing
                        string poppedName = normalizedPathStack.Pop();
                        // update our currentpath to reflect the change.
                        if (currentPath.Length > poppedName.Length)
                        {
                            currentPath = currentPath.Substring(0, currentPath.Length - poppedName.Length - 1);
                        }
                        else
                        {
                            currentPath = string.Empty;
                        }

                        s_tracer.WriteLine("normalizedPathStack.Pop() : {0}", poppedName);
                        continue;
                    }
                    else
                    {
                        throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.PathOutSideBasePath);
                    }
                }
                else
                {
                    currentPath = MakePath(currentPath, childName);

                    var fsinfo = GetFileSystemInfo(currentPath, out bool _);

                    // Clean up the child name to proper casing and short-path
                    // expansion if required. Also verify that .NET hasn't over-normalized
                    // the path
                    if (fsinfo != null)
                    {
                        // This might happen if you've passed a child name of two or more dots,
                        // which the .NET APIs treat as the parent directory
                        if (fsinfo.FullName.Length < currentPath.Length)
                        {
                            throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
                        }

                        // Expand the short file name
                        if (fsinfo.Name.Length >= childName.Length)
                        {
                            childName = fsinfo.Name;
                        }
                    }
                    else
                    {
                        // We couldn't find the item
                        if (tokenizedPathStack.Count == 0)
                        {
                            throw PSTraceSource.NewArgumentException("path", FileSystemProviderStrings.ItemDoesNotExist, currentPath);
                        }
                    }
                }

                s_tracer.WriteLine("normalizedPathStack.Push({0})", childName);
                normalizedPathStack.Push(childName);
            }

            return normalizedPathStack;
        }

        /// <summary>
        /// Pops each leaf element of the stack and uses MakePath to generate the relative path.
        /// </summary>
        /// <param name="normalizedPathStack">
        /// The stack containing the leaf elements of the path.
        /// </param>
        /// <returns>
        /// A path that is made up of the leaf elements on the given stack.
        /// </returns>
        /// <remarks>
        /// The elements on the stack start from the leaf element followed by its parent
        /// followed by its parent, etc. Each following element on the stack is the parent
        /// of the one before it.
        /// </remarks>
        private string CreateNormalizedRelativePathFromStack(Stack<string> normalizedPathStack)
        {
            string leafElement = string.Empty;

            while (normalizedPathStack.Count > 0)
            {
                if (string.IsNullOrEmpty(leafElement))
                {
                    leafElement = normalizedPathStack.Pop();
                }
                else
                {
                    string parentElement = normalizedPathStack.Pop();
                    leafElement = MakePath(parentElement, leafElement);
                }
            }

            return leafElement;
        }

        /// <summary>
        /// Gets the name of the leaf element of the specified path.
        /// </summary>
        /// <param name="path">
        /// The fully qualified path to the item.
        /// </param>
        /// <returns>
        /// The leaf element of the specified path.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override string GetChildName(string path)
        {
            // Verify the parameters

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

            // Normalize the path
            path = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);

            // Trim trailing back slashes
            path = path.TrimEnd(StringLiterals.DefaultPathSeparator);

            string result = null;

            int separatorIndex = path.LastIndexOf(StringLiterals.DefaultPathSeparator);

            if (separatorIndex == -1)
            {
                // Since there was no path separator return an empty string
                result = EnsureDriveIsRooted(path);
            }
            else
            {
                result = path.Substring(separatorIndex + 1);
            }

            return result;
        }

        private static string EnsureDriveIsRooted(string path)
        {
            string result = path;

            // Find the drive separator
            int index = path.IndexOf(':');

            if (index != -1)
            {
                // if the drive separator is the end of the path, add
                // the root path separator back
                if (index + 1 == path.Length)
                {
                    result = path + StringLiterals.DefaultPathSeparator;
                }
            }

            return result;
        }

        /// <summary>
        /// Determines if the item at the specified path is a directory.
        /// </summary>
        /// <param name="path">
        /// The path to the file or directory to check.
        /// </param>
        /// <returns>
        /// True if the item at the specified path is a directory.
        /// False otherwise.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        protected override bool IsItemContainer(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            return Directory.Exists(path);
        }

        #region MoveItem

        /// <summary>
        /// Moves an item at the specified path to the given destination.
        /// </summary>
        /// <param name="path">
        /// The path of the item to move.
        /// </param>
        /// <param name="destination">
        /// The path of the destination.
        /// </param>
        /// <returns>
        /// Nothing.  Moved items are written to the context's pipeline.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        ///     destination is null or empty.
        /// </exception>
        protected override void MoveItem(
            string path,
            string destination)
        {
            // Check the parameters

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

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

            path = NormalizePath(path);
            destination = NormalizePath(destination);

            // Verify that the target doesn't represent a device name
            if (PathIsReservedDeviceName(destination, "MoveError"))
            {
                return;
            }

            try
            {
                bool isContainer = IsItemContainer(path);
                s_tracer.WriteLine("Moving {0} to {1}", path, destination);

                if (isContainer)
                {
                    // Get the DirectoryInfo
                    DirectoryInfo dir = new DirectoryInfo(path);

                    if (ItemExists(destination) &&
                        IsItemContainer(destination))
                    {
                        destination = MakePath(destination, dir.Name);
                    }

                    // Get the confirmation text
                    string action = FileSystemProviderStrings.MoveItemActionDirectory;

                    string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, dir.FullName, destination);

                    // Confirm the move with the user
                    if (ShouldProcess(resource, action))
                    {
                        // Now move the directory
                        MoveDirectoryInfoItem(dir, destination, Force);
                    }
                }
                else
                {
                    // Get the FileInfo
                    FileInfo file = new FileInfo(path);

                    Dbg.Diagnostics.Assert(
                        file != null,
                        "FileInfo should be throwing an exception but it's " +
                        "returning null instead");

                    if (IsItemContainer(destination))
                    {
                        // Construct the new file path from the destination
                        // directory and the file name
                        destination = MakePath(destination, file.Name);
                    }

                    // Get the confirmation text
                    string action = FileSystemProviderStrings.MoveItemActionFile;

                    string resource = StringUtil.Format(FileSystemProviderStrings.MoveItemResourceFileTemplate, file.FullName, destination);

                    // Confirm the move with the user
                    if (ShouldProcess(resource, action))
                    {
                        MoveFileInfoItem(file, destination, Force, true);
                    }
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "MoveItemArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "MoveItemIOError", ErrorCategory.WriteError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "MoveItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
        }

        private void MoveFileInfoItem(
            FileInfo file,
            string destination,
            bool force,
            bool output)
        {
            Dbg.Diagnostics.Assert(
                file != null,
                "The caller should verify file.");

            Dbg.Diagnostics.Assert(
                !string.IsNullOrEmpty(destination),
                "THe caller should verify destination.");

            try
            {
                // Move the file
                file.MoveTo(destination);

                if (output)
                {
                    WriteItemObject(
                        file,
                        file.FullName,
                        false);
                }
            }
            catch (System.UnauthorizedAccessException unauthorizedAccess)
            {
                // This error is thrown when the readonly bit is set.
                if (force)
                {
                    try
                    {
                        // mask off the readonly and hidden bits and try again
                        file.Attributes =
                            file.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);

                        file.MoveTo(destination);

                        if (output)
                        {
                            WriteItemObject(file, file.FullName, false);
                        }
                    }
                    catch (Exception e)
                    {
                        if ((e is IOException) ||
                            (e is ArgumentNullException) ||
                            (e is ArgumentException) ||
                            (e is System.Security.SecurityException) ||
                            (e is UnauthorizedAccessException) ||
                            (e is FileNotFoundException) ||
                            (e is DirectoryNotFoundException) ||
                            (e is PathTooLongException) ||
                            (e is NotSupportedException))
                        {
                            // If any exception occurs return the original error
                            WriteError(new ErrorRecord(unauthorizedAccess, "MoveFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
                        }
                        else
                            throw;
                    }
                }
                else
                {
                    WriteError(new ErrorRecord(unauthorizedAccess, "MoveFileInfoItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, file));
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "MoveFileInfoItemArgumentError", ErrorCategory.InvalidArgument, file));
            }
            catch (IOException ioException)
            {
                // check if destination file exists. if force is specified then we should delete the destination before moving
                if (force && File.Exists(destination))
                {
                    FileInfo destfile = new FileInfo(destination);
                    if (destfile != null)
                    {
                        try
                        {
                            // Make sure the file is not read only
                            destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
                            destfile.Delete();
                            file.MoveTo(destination);

                            if (output)
                            {
                                WriteItemObject(file, file.FullName, false);
                            }
                        }
                        catch (Exception exception)
                        {
                            if ((exception is FileNotFoundException) ||
                                (exception is DirectoryNotFoundException) ||
                                (exception is UnauthorizedAccessException) ||
                                (exception is System.Security.SecurityException) ||
                                (exception is ArgumentException) ||
                                (exception is PathTooLongException) ||
                                (exception is NotSupportedException) ||
                                (exception is ArgumentNullException) ||
                                (exception is IOException))
                            {
                                // IOException contains specific message about the error occured and so no need for errordetails.
                                WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, destfile));
                            }
                            else
                                throw;
                        }
                    }
                    else
                    {
                        // IOException contains specific message about the error occured and so no need for errordetails.
                        WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file));
                    }
                }
                else
                {
                    // IOException contains specific message about the error occured and so no need for errordetails.
                    WriteError(new ErrorRecord(ioException, "MoveFileInfoItemIOError", ErrorCategory.WriteError, file));
                }
            }
        }

        private void MoveDirectoryInfoItem(
            DirectoryInfo directory,
            string destination,
            bool force)
        {
            Dbg.Diagnostics.Assert(
                directory != null,
                "The caller should verify directory.");

            Dbg.Diagnostics.Assert(
                !string.IsNullOrEmpty(destination),
                "The caller should verify destination.");

            try
            {
                MoveDirectoryInfoUnchecked(directory, destination, force);

                WriteItemObject(
                    directory,
                    directory.FullName,
                    true);
            }
            catch (System.UnauthorizedAccessException unauthorizedAccess)
            {
                // This error is thrown when the readonly bit is set.
                if (force)
                {
                    try
                    {
                        // mask off the readonly and hidden bits and try again
                        directory.Attributes =
                            directory.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);

                        MoveDirectoryInfoUnchecked(directory, destination, force);

                        WriteItemObject(directory, directory.FullName, true);
                    }
                    catch (IOException)
                    {
                        WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
                    }
                    catch (Exception exception)
                    {
                        if ((exception is FileNotFoundException) ||
                            (exception is ArgumentNullException) ||
                            (exception is DirectoryNotFoundException) ||
                            (exception is System.Security.SecurityException) ||
                            (exception is ArgumentException))
                        {
                            WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
                        }
                        else
                            throw;
                    }
                }
                else
                {
                    WriteError(new ErrorRecord(unauthorizedAccess, "MoveDirectoryItemUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory));
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "MoveDirectoryItemArgumentError", ErrorCategory.InvalidArgument, directory));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "MoveDirectoryItemIOError", ErrorCategory.WriteError, directory));
            }
        }

        /// <summary>
        /// Implements the file move operation for directories without handling any error scenarios.
        /// In particular, this attempts to rename or copy+delete the file,
        /// but passes any exceptional behavior through to the caller.
        /// </summary>
        /// <param name="directory">The directory to move.</param>
        /// <param name="destinationPath">The destination path to move the directory to.</param>
        /// <param name="force">If true, force move the directory, overwriting anything at the destination.</param>
        private void MoveDirectoryInfoUnchecked(DirectoryInfo directory, string destinationPath, bool force)
        {
#if UNIX
            try
            {
                if (InternalTestHooks.ThrowExdevErrorOnMoveDirectory)
                {
                    throw new IOException("Invalid cross-device link", hresult: UNIX_ERRNO_EXDEV);
                }

                directory.MoveTo(destinationPath);
            }
            catch (IOException e) when (e.HResult == UNIX_ERRNO_EXDEV)
            {
                // Rather than try to ascertain whether we can rename a directory ahead of time,
                // it's both faster and more correct to try to rename it and fall back to copy/deleting it
                // See also: https://github.com/coreutils/coreutils/blob/439741053256618eb651e6d43919df29625b8714/src/mv.c#L212-L216
                CopyAndDelete(directory, destinationPath, force);
            }
#else
            // On Windows, being able to rename vs copy/delete a file
            // is just a question of the drive
            if (IsSameWindowsVolume(directory.FullName, destinationPath))
            {
                directory.MoveTo(destinationPath);
            }
            else
            {
                CopyAndDelete(directory, destinationPath, force);
            }
#endif
        }

        private void CopyAndDelete(DirectoryInfo directory, string destination, bool force)
        {
            if (!ItemExists(destination))
            {
                CreateDirectory(destination, false);
            }
            else if (ItemExists(destination) && !IsItemContainer(destination))
            {
                string errorMessage = StringUtil.Format(FileSystemProviderStrings.DirectoryExist, destination);
                Exception e = new IOException(errorMessage);

                WriteError(new ErrorRecord(
                    e,
                    "DirectoryExist",
                    ErrorCategory.ResourceExists,
                    destination));
                return;
            }

            foreach (FileInfo file in directory.EnumerateFiles())
            {
                MoveFileInfoItem(file, Path.Combine(destination, file.Name), force, false);
            }

            foreach (DirectoryInfo dir in directory.EnumerateDirectories())
            {
                CopyAndDelete(dir, Path.Combine(destination, dir.Name), force);
            }

            if (!directory.EnumerateDirectories().Any() && !directory.EnumerateFiles().Any())
            {
                RemoveItem(directory.FullName, false);
            }
        }

#if !UNIX
        private bool IsSameWindowsVolume(string source, string destination)
        {
            FileInfo src = new FileInfo(source);
            FileInfo dest = new FileInfo(destination);

            return (src.Directory.Root.Name == dest.Directory.Root.Name);
        }
#endif

        #endregion MoveItem

        #endregion NavigationCmdletProvider members

        #region IPropertyCmdletProvider

        /// <summary>
        /// Gets a property for the given item.
        /// </summary>
        /// <param name="path">The fully qualified path to the item.</param>
        /// <param name="providerSpecificPickList">
        /// The list of properties to get.  Examples include "Attributes", "LastAccessTime,"
        /// and other properties defined by
        /// <see cref="System.IO.DirectoryInfo" /> and
        /// <see cref="System.IO.FileInfo" />
        /// </param>
        public void GetProperty(string path, Collection<string> providerSpecificPickList)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            PSObject result = null;

            try
            {
                var fileSystemObject = GetFileSystemInfo(path, out bool isDirectory);

                if (fileSystemObject == null)
                {
                    string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                    Exception e = new IOException(error);
                    WriteError(new ErrorRecord(
                        e,
                        "ItemDoesNotExist",
                        ErrorCategory.ObjectNotFound,
                        path));
                }
                else
                {
                    // Finally get the properties
                    if (providerSpecificPickList == null || providerSpecificPickList.Count == 0)
                    {
                        result = PSObject.AsPSObject(fileSystemObject);
                    }
                    else
                    {
                        foreach (string property in providerSpecificPickList)
                        {
                            if (property != null && property.Length > 0)
                            {
                                try
                                {
                                    PSObject mshObject = PSObject.AsPSObject(fileSystemObject);
                                    PSMemberInfo member = mshObject.Properties[property];
                                    object value;
                                    if (member != null)
                                    {
                                        value = member.Value;
                                        if (result == null)
                                        {
                                            result = new PSObject();
                                        }

                                        result.Properties.Add(new PSNoteProperty(property, value));
                                    }
                                    else
                                    {
                                        string error =
                                            StringUtil.Format(
                                                FileSystemProviderStrings.PropertyNotFound,
                                                property);
                                        Exception e = new IOException(error);
                                        WriteError(new ErrorRecord(e, "GetValueError", ErrorCategory.ReadError, property));
                                    }
                                }
                                catch (GetValueException exception)
                                {
                                    WriteError(new ErrorRecord(exception, "GetValueError", ErrorCategory.ReadError, property));
                                }
                            }
                        }
                    }
                }
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "GetPropertyArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "GetPropertyIOError", ErrorCategory.ReadError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                WriteError(new ErrorRecord(accessException, "GetPropertyUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }

            if (result != null)
            {
                WritePropertyObject(result, path);
            }
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the get-itemproperty cmdlet.
        /// This feature is not required by the File System provider.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </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>
        /// <returns>
        /// Null.  This feature is not required by the File System provider.
        /// </returns>
        public object GetPropertyDynamicParameters(
            string path,
            Collection<string> providerSpecificPickList)
        {
            return null;
        }

        /// <summary>
        /// Sets the specified properties on the item at the given path.
        /// </summary>
        /// <param name="path">
        /// The path of the item on which to set the properties.
        /// </param>
        /// <param name="propertyToSet">
        /// A PSObject which contains a collection of the names and values
        /// of the properties to be set.  The File System provider supports setting
        /// only the "Attributes" property.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        /// <exception cref="System.ArgumentNullException">
        ///     propertyToSet is null.
        /// </exception>
        public void SetProperty(string path, PSObject propertyToSet)
        {
            // verify parameters

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

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

            path = NormalizePath(path);

            PSObject results = new PSObject();

            var fsinfo = GetFileSystemInfo(path, out bool isDirectory);

            // Create a PSObject with either a DirectoryInfo or FileInfo object at its core.
            if (fsinfo != null)
            {
                PSObject fileSystemInfoShell = PSObject.AsPSObject(fsinfo);

                bool propertySet = false;

                foreach (PSMemberInfo property in propertyToSet.Properties)
                {
                    object propertyValue = property.Value;

                    // Get the confirmation text
                    string action = null;

                    if (isDirectory)
                    {
                        action = FileSystemProviderStrings.SetPropertyActionDirectory;
                    }
                    else
                    {
                        action = FileSystemProviderStrings.SetPropertyActionFile;
                    }

                    string resourceTemplate = FileSystemProviderStrings.SetPropertyResourceTemplate;

                    string propertyValueString;

                    try
                    {
                        // Use a PSObject to get the string representation of the property value
                        PSObject propertyValuePSObject = PSObject.AsPSObject(propertyValue);
                        propertyValueString = propertyValuePSObject.ToString();
                    }
                    catch (Exception e)
                    {
                        Dbg.Diagnostics.Assert(
                            false,
                            "FileSystemProvider.SetProperty exception " + e.Message);
                        throw;
                    }

                    string resource =
                        string.Format(
                            Host.CurrentCulture,
                            resourceTemplate,
                            path,
                            property.Name,
                            propertyValueString);

                    // Confirm the set with the user
                    if (ShouldProcess(resource, action))
                    {
                        PSObject mshObject = PSObject.AsPSObject(fileSystemInfoShell);
                        PSMemberInfo member = mshObject.Properties[property.Name];

                        if (member != null)
                        {
                            if (string.Equals(property.Name, "attributes", StringComparison.OrdinalIgnoreCase))
                            {
                                FileAttributes attributes;

                                if (propertyValue is FileAttributes)
                                    attributes = (FileAttributes)propertyValue;
                                else
                                    attributes = (FileAttributes)Enum.Parse(typeof(FileAttributes), propertyValueString, true);

                                if ((attributes & ~(FileAttributes.Archive | FileAttributes.Hidden |
                                                        FileAttributes.Normal | FileAttributes.ReadOnly | FileAttributes.System)) != 0)
                                {
                                    string error =
                                        StringUtil.Format(
                                            FileSystemProviderStrings.AttributesNotSupported,
                                            property);
                                    Exception e = new IOException(error);
                                    WriteError(new ErrorRecord(e, "SetPropertyError", ErrorCategory.ReadError, property));
                                    continue;
                                }
                            }

                            member.Value = propertyValue;
                            results.Properties.Add(new PSNoteProperty(property.Name, propertyValue));
                            propertySet = true;
                        }
                        else
                        {
                            string error =
                                StringUtil.Format(
                                    FileSystemProviderStrings.PropertyNotFound,
                                    property);
                            Exception e = new IOException(error);
                            WriteError(new ErrorRecord(e, "SetPropertyError", ErrorCategory.ReadError, property));
                        }
                    }
                }

                if (propertySet)
                {
                    WritePropertyObject(results, path);
                }
            }
            else
            {
                string error = StringUtil.Format(FileSystemProviderStrings.ItemDoesNotExist, path);
                Exception e = new IOException(error);
                WriteError(new ErrorRecord(
                    e,
                    "ItemDoesNotExist",
                    ErrorCategory.ObjectNotFound,
                    path));
            }
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the set-itemproperty cmdlet.
        /// This feature is not required by the File System provider.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to set the dynamic parameters.
        /// </param>
        /// <param name="propertyValue">
        /// A PSObject which contains a collection of the name, type, value
        /// of the properties to be set.
        /// </param>
        /// <returns>
        /// Null.  This feature is not required by the File System provider.
        /// </returns>
        public object SetPropertyDynamicParameters(
            string path,
            PSObject propertyValue)
        {
            return null;
        }

        /// <summary>
        /// Clears the specified properties on the item at the given path.
        /// The File System provider supports only the "Attributes" property.
        /// </summary>
        /// <param name="path">
        /// The path of the item on which to clear the properties.
        /// </param>
        /// <param name="propertiesToClear">
        /// A collection of the names of the properties to clear.  The File System
        /// provider supports clearing only the "Attributes" property.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     Path is null or empty.
        /// </exception>
        /// <exception cref="System.ArgumentNullException">
        ///     propertiesToClear is null or count is zero.
        /// </exception>
        public void ClearProperty(
            string path,
            Collection<string> propertiesToClear)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            if (propertiesToClear == null ||
                propertiesToClear.Count == 0)
            {
                throw PSTraceSource.NewArgumentNullException(nameof(propertiesToClear));
            }

            // Only the attributes property can be cleared
            if (propertiesToClear.Count > 1 ||
                Host.CurrentCulture.CompareInfo.Compare("Attributes", propertiesToClear[0], CompareOptions.IgnoreCase) != 0)
            {
                throw PSTraceSource.NewArgumentException(nameof(propertiesToClear), FileSystemProviderStrings.CannotClearProperty);
            }

            try
            {
                // Now the only entry in the array should be the Attributes, so clear them
                FileSystemInfo fileSystemInfo = null;

                // Get the confirmation text
                string action = null;

                bool isContainer = IsItemContainer(path);
                if (isContainer)
                {
                    action = FileSystemProviderStrings.ClearPropertyActionDirectory;

                    fileSystemInfo = new DirectoryInfo(path);
                }
                else
                {
                    action = FileSystemProviderStrings.ClearPropertyActionFile;

                    fileSystemInfo = new FileInfo(path);
                }

                string resourceTemplate = FileSystemProviderStrings.ClearPropertyResourceTemplate;

                string resource =
                    string.Format(
                        Host.CurrentCulture,
                        resourceTemplate,
                        fileSystemInfo.FullName,
                        propertiesToClear[0]);

                // Confirm the set with the user
                if (ShouldProcess(resource, action))
                {
                    fileSystemInfo.Attributes = FileAttributes.Normal;
                    PSObject result = new PSObject();
                    result.Properties.Add(new PSNoteProperty(propertiesToClear[0], fileSystemInfo.Attributes));

                    // Now write out the attribute that was cleared.
                    WritePropertyObject(result, path);
                }
            }
            catch (UnauthorizedAccessException unauthorizedAccessException)
            {
                WriteError(new ErrorRecord(unauthorizedAccessException, "ClearPropertyUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "ClearPropertyArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "ClearPropertyIOError", ErrorCategory.WriteError, path));
            }
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the clear-itemproperty cmdlet.
        /// This feature is not required by the File System provider.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to set the dynamic parameters.
        /// </param>
        /// <param name="propertiesToClear">
        /// A collection of the names of the properties to clear.
        /// </param>
        /// <returns>
        /// Null.  This feature is not required by the File System provider.
        /// </returns>
        public object ClearPropertyDynamicParameters(
            string path,
            Collection<string> propertiesToClear)
        {
            return null;
        }

        #endregion IPropertyCmdletProvider

        #region IContentCmdletProvider

        /// <summary>
        /// Creates an instance of the FileSystemContentStream class, opens
        /// the specified file for reading, and returns the IContentReader interface
        /// to it.
        /// </summary>
        /// <param name="path">
        /// The path of the file to be opened for reading.
        /// </param>
        /// <returns>
        /// An IContentReader for the specified file.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        public IContentReader GetContentReader(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            // Defaults for the file read operation
            string delimiter = "\n";
            Encoding encoding = ClrFacade.GetDefaultEncoding();
            bool waitForChanges = false;

            bool streamTypeSpecified = false;
            bool usingByteEncoding = false;
            bool delimiterSpecified = false;
            bool isRawStream = false;
            string streamName = null;

            // Get the dynamic parameters.
            // They override the defaults specified above.
            if (DynamicParameters != null)
            {
                FileSystemContentReaderDynamicParameters dynParams =
                    DynamicParameters as FileSystemContentReaderDynamicParameters;

                if (dynParams != null)
                {
                    // -raw is not allowed when -first,-last or -wait is specified
                    // this call will validate that and throws.
                    ValidateParameters(dynParams.Raw);

                    isRawStream = dynParams.Raw;

                    // Get the delimiter
                    delimiterSpecified = dynParams.DelimiterSpecified;
                    if (delimiterSpecified)
                        delimiter = dynParams.Delimiter;

                    // Get the stream type
                    usingByteEncoding = dynParams.AsByteStream;
                    streamTypeSpecified = dynParams.WasStreamTypeSpecified;

                    if (usingByteEncoding && streamTypeSpecified)
                    {
                        WriteWarning(FileSystemProviderStrings.EncodingNotUsed);
                    }

                    if (streamTypeSpecified)
                    {
                        encoding = dynParams.Encoding;
                    }

                    // Get the wait value
                    waitForChanges = dynParams.Wait;

#if !UNIX
                    // Get the stream name
                    streamName = dynParams.Stream;
#endif
                }
            }

#if !UNIX
            // See if they've used the inline stream syntax. They have more than one colon.
            int firstColon = path.IndexOf(':');
            int secondColon = path.IndexOf(':', firstColon + 1);
            if (secondColon > 0)
            {
                streamName = path.Substring(secondColon + 1);
                path = path.Remove(secondColon);
            }
#endif

            FileSystemContentReaderWriter stream = null;

            try
            {
                if (Directory.Exists(path))
                {
                    string errMsg = StringUtil.Format(SessionStateStrings.GetContainerContentException, path);
                    ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "GetContainerContentException", ErrorCategory.InvalidOperation, null);
                    WriteError(error);
                    return stream;
                }

                // Users can't both read as bytes, and specify a delimiter
                if (delimiterSpecified)
                {
                    if (usingByteEncoding)
                    {
                        Exception e =
                            new ArgumentException(FileSystemProviderStrings.DelimiterError, "delimiter");
                        WriteError(new ErrorRecord(
                            e,
                            "GetContentReaderArgumentError",
                            ErrorCategory.InvalidArgument,
                            path));
                    }
                    else
                    {
                        // Initialize the file reader
                        stream = new FileSystemContentReaderWriter(path, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, delimiter, encoding, waitForChanges, this, isRawStream);
                    }
                }
                else
                {
                    stream = new FileSystemContentReaderWriter(path, streamName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, encoding, usingByteEncoding, waitForChanges, this, isRawStream);
                }
            }
            catch (PathTooLongException pathTooLong)
            {
                WriteError(new ErrorRecord(pathTooLong, "GetContentReaderPathTooLongError", ErrorCategory.InvalidArgument, path));
            }
            catch (FileNotFoundException fileNotFound)
            {
                WriteError(new ErrorRecord(fileNotFound, "GetContentReaderFileNotFoundError", ErrorCategory.ObjectNotFound, path));
            }
            catch (DirectoryNotFoundException directoryNotFound)
            {
                WriteError(new ErrorRecord(directoryNotFound, "GetContentReaderDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "GetContentReaderArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "GetContentReaderIOError", ErrorCategory.ReadError, path));
            }
            catch (System.Security.SecurityException securityException)
            {
                WriteError(new ErrorRecord(securityException, "GetContentReaderSecurityError", ErrorCategory.PermissionDenied, path));
            }
            catch (UnauthorizedAccessException unauthorizedAccess)
            {
                WriteError(new ErrorRecord(unauthorizedAccess, "GetContentReaderUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }

            return stream;
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the get-content cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        public object GetContentReaderDynamicParameters(string path)
        {
            return new FileSystemContentReaderDynamicParameters(this);
        }

        /// <summary>
        /// Creates an instance of the FileSystemContentStream class, opens
        /// the specified file for writing, and returns the IContentReader interface
        /// to it.
        /// </summary>
        /// <param name="path">
        /// The path of the file to be opened for writing.
        /// </param>
        /// <returns>
        /// An IContentWriter for the specified file.
        /// </returns>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        public IContentWriter GetContentWriter(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            // If this is true, then the content will be read as bytes
            bool usingByteEncoding = false;
            bool streamTypeSpecified = false;
            Encoding encoding = ClrFacade.GetDefaultEncoding();
            FileMode filemode = FileMode.OpenOrCreate;
            string streamName = null;
            bool suppressNewline = false;

            // Get the dynamic parameters
            if (DynamicParameters != null)
            {
                FileSystemContentWriterDynamicParameters dynParams =
                    DynamicParameters as FileSystemContentWriterDynamicParameters;

                if (dynParams != null)
                {
                    usingByteEncoding = dynParams.AsByteStream;
                    streamTypeSpecified = dynParams.WasStreamTypeSpecified;

                    if (usingByteEncoding && streamTypeSpecified)
                    {
                        WriteWarning(FileSystemProviderStrings.EncodingNotUsed);
                    }

                    if (streamTypeSpecified)
                    {
                        encoding = dynParams.Encoding;
                    }

#if !UNIX
                    streamName = dynParams.Stream;
#endif
                    suppressNewline = dynParams.NoNewline.IsPresent;
                }
            }

#if !UNIX
            // See if they've used the inline stream syntax. They have more than one colon.
            int firstColon = path.IndexOf(':');
            int secondColon = path.IndexOf(':', firstColon + 1);
            if (secondColon > 0)
            {
                streamName = path.Substring(secondColon + 1);
                path = path.Remove(secondColon);
            }
#endif

            FileSystemContentReaderWriter stream = null;

            try
            {
                if (Directory.Exists(path))
                {
                    string errMsg = StringUtil.Format(SessionStateStrings.WriteContainerContentException, path);
                    ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "WriteContainerContentException", ErrorCategory.InvalidOperation, null);
                    WriteError(error);
                    return stream;
                }

                stream = new FileSystemContentReaderWriter(path, streamName, filemode, FileAccess.Write, FileShare.ReadWrite, encoding, usingByteEncoding, false, this, false, suppressNewline);
            }
            catch (PathTooLongException pathTooLong)
            {
                WriteError(new ErrorRecord(pathTooLong, "GetContentWriterPathTooLongError", ErrorCategory.InvalidArgument, path));
            }
            catch (FileNotFoundException fileNotFound)
            {
                WriteError(new ErrorRecord(fileNotFound, "GetContentWriterFileNotFoundError", ErrorCategory.ObjectNotFound, path));
            }
            catch (DirectoryNotFoundException directoryNotFound)
            {
                WriteError(new ErrorRecord(directoryNotFound, "GetContentWriterDirectoryNotFoundError", ErrorCategory.ObjectNotFound, path));
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "GetContentWriterArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "GetContentWriterIOError", ErrorCategory.WriteError, path));
            }
            catch (System.Security.SecurityException securityException)
            {
                WriteError(new ErrorRecord(securityException, "GetContentWriterSecurityError", ErrorCategory.PermissionDenied, path));
            }
            catch (UnauthorizedAccessException unauthorizedAccess)
            {
                WriteError(new ErrorRecord(unauthorizedAccess, "GetContentWriterUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
            }

            return stream;
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the set-content and
        /// add-content cmdlets.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </param>
        /// <returns>
        /// An object that has properties and fields decorated with
        /// parsing attributes similar to a cmdlet class.
        /// </returns>
        public object GetContentWriterDynamicParameters(string path)
        {
            return new FileSystemContentWriterDynamicParameters(this);
        }

        /// <summary>
        /// Clears the content of the specified file.
        /// </summary>
        /// <param name="path">
        /// The path to the file of which to clear the contents.
        /// </param>
        /// <exception cref="System.ArgumentException">
        ///     path is null or empty.
        /// </exception>
        public void ClearContent(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw PSTraceSource.NewArgumentException(nameof(path));
            }

            path = NormalizePath(path);

            if (Directory.Exists(path))
            {
                string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path);
                WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path));
                return;
            }

            try
            {
#if !UNIX
                bool clearStream = false;
                string streamName = null;
                FileSystemClearContentDynamicParameters dynamicParameters = null;
                FileSystemContentWriterDynamicParameters writerDynamicParameters = null;

                // We get called during:
                //     - Clear-Content
                //     - Set-Content, in the phase that clears the path first.
                if (DynamicParameters != null)
                {
                    dynamicParameters = DynamicParameters as FileSystemClearContentDynamicParameters;
                    writerDynamicParameters = DynamicParameters as FileSystemContentWriterDynamicParameters;

                    if (dynamicParameters != null)
                    {
                        if ((dynamicParameters.Stream != null) && (dynamicParameters.Stream.Length > 0))
                            clearStream = true;
                        streamName = dynamicParameters.Stream;
                    }
                    else if (writerDynamicParameters != null)
                    {
                        if ((writerDynamicParameters.Stream != null) && (writerDynamicParameters.Stream.Length > 0))
                            clearStream = true;
                        streamName = writerDynamicParameters.Stream;
                    }

                    if (string.IsNullOrEmpty(streamName))
                    {
                        // See if they've used the inline stream syntax. They have more than one colon.
                        int firstColon = path.IndexOf(':');
                        int secondColon = path.IndexOf(':', firstColon + 1);
                        if (secondColon > 0)
                        {
                            streamName = path.Substring(secondColon + 1);
                            path = path.Remove(secondColon);

                            clearStream = true;
                        }
                    }
                }

                // If they're just working on the DATA stream, don't use the Alternate Data Stream
                // utils to clear the stream - otherwise, the Win32 API will trash the other streams.
                if (string.Equals(":$DATA", streamName, StringComparison.OrdinalIgnoreCase))
                {
                    clearStream = false;
                }

                if (clearStream)
                {
                    FileStream fileStream = null;

                    string streamAction = string.Format(
                        CultureInfo.InvariantCulture,
                        FileSystemProviderStrings.StreamAction,
                        streamName, path);
                    if (ShouldProcess(streamAction))
                    {
                        // If we've been called as part of Clear-Content, validate that the stream exists.
                        // This is because the core API doesn't support truncate mode.
                        if (dynamicParameters != null)
                        {
                            fileStream = AlternateDataStreamUtilities.CreateFileStream(path, streamName, FileMode.Open, FileAccess.Write, FileShare.Write);
                            fileStream.Dispose();
                        }

                        fileStream = AlternateDataStreamUtilities.CreateFileStream(
                            path, streamName, FileMode.Create, FileAccess.Write, FileShare.Write);
                        fileStream.Dispose();
                    }
                }
                else
#endif
                {
                    string action = FileSystemProviderStrings.ClearContentActionFile;
                    string resource = StringUtil.Format(FileSystemProviderStrings.ClearContentesourceTemplate, path);

                    if (!ShouldProcess(resource, action))
                        return;

                    FileStream fileStream = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.Write);
                    fileStream.Dispose();
                }

                // For filesystem once content is cleared
                WriteItemObject(string.Empty, path, false);
            }
            catch (ArgumentException argException)
            {
                WriteError(new ErrorRecord(argException, "ClearContentArgumentError", ErrorCategory.InvalidArgument, path));
            }
            catch (IOException ioException)
            {
                // IOException contains specific message about the error occured and so no need for errordetails.
                WriteError(new ErrorRecord(ioException, "ClearContentIOError", ErrorCategory.WriteError, path));
            }
            catch (UnauthorizedAccessException accessException)
            {
                if (Force)
                {
                    //// Store the old attributes so that we can recover them
                    FileAttributes oldAttributes = File.GetAttributes(path);

                    try
                    {
                        // Since a security exception was thrown, try to mask off
                        // the hidden and readonly bits and then retry.
                        File.SetAttributes(path, (File.GetAttributes(path) & ~(FileAttributes.Hidden | FileAttributes.ReadOnly)));
                        FileStream fileStream = new FileStream(path, FileMode.Truncate, FileAccess.Write, FileShare.Write);
                        fileStream.Dispose();

                        // For filesystem once content is cleared
                        WriteItemObject(string.Empty, path, false);
                    }
                    catch (UnauthorizedAccessException failure)
                    {
                        WriteError(new ErrorRecord(failure, "RemoveFileSystemItemUnAuthorizedAccess", ErrorCategory.PermissionDenied, path));
                    }
                    finally
                    {
                        //// Reset the attributes
                        File.SetAttributes(path, oldAttributes);
                    }
                }
                else
                {
                    WriteError(new ErrorRecord(accessException, "ClearContentUnauthorizedAccessError", ErrorCategory.PermissionDenied, path));
                }
            }
        }

        /// <summary>
        /// Gets the dynamic property parameters required by the clear-content cmdlet.
        /// </summary>
        /// <param name="path">
        /// If the path was specified on the command line, this is the path
        /// to the item for which to get the dynamic parameters.
        /// </param>
        /// <returns>
        /// A FileSystemClearContentDynamicParameters that provides access to the -Stream dynamic parameter.
        /// </returns>
        public object ClearContentDynamicParameters(string path)
        {
            return new FileSystemClearContentDynamicParameters();
        }

        #endregion IContentCmdletProvider

        /// <summary>
        /// -raw is not allowed when -first,-last or -wait is specified
        /// this call will validate that and throws.
        /// </summary>
        private void ValidateParameters(bool isRawSpecified)
        {
            if (isRawSpecified)
            {
                if (this.Context.MyInvocation.BoundParameters.ContainsKey("TotalCount"))
                {
                    string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "TotalCount");
                    throw new PSInvalidOperationException(message);
                }

                if (this.Context.MyInvocation.BoundParameters.ContainsKey("Tail"))
                {
                    string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Tail");
                    throw new PSInvalidOperationException(message);
                }

                if (this.Context.MyInvocation.BoundParameters.ContainsKey("Wait"))
                {
                    string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Wait");
                    throw new PSInvalidOperationException(message);
                }

                if (this.Context.MyInvocation.BoundParameters.ContainsKey("Delimiter"))
                {
                    string message = StringUtil.Format(FileSystemProviderStrings.NoFirstLastWaitForRaw, "Raw", "Delimiter");
                    throw new PSInvalidOperationException(message);
                }
            }
        }

        /// <summary>
        /// The API 'PathIsNetworkPath' is not available in CoreSystem.
        /// This implementation is based on the 'PathIsNetworkPath' API.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        internal static bool PathIsNetworkPath(string path)
        {
#if UNIX
            return false;
#else
            return WinPathIsNetworkPath(path);
#endif
        }

        internal static bool WinPathIsNetworkPath(string path)
        {
            return NativeMethods.PathIsNetworkPath(path); // call the native method
        }

        private static class NativeMethods
        {
            /// <summary>
            /// WNetAddConnection2 API makes a connection to a network resource
            /// and can redirect a local device to the network resource.
            /// This API simulates the "new Use" functionality used to connect to
            /// network resource.
            /// </summary>
            /// <param name="netResource">
            /// The netResource structure contains information
            /// about a network resource.</param>
            /// <param name="password">
            /// The password used to get connected to network resource.
            /// </param>
            /// <param name="username">
            /// The username used to get connected to network resource.
            /// </param>
            /// <param name="flags">
            /// The flags parameter is used to indicate if the created network
            /// resource has to be persisted or not.
            /// </param>
            /// <returns>If connection is established to the network resource
            /// then success is returned or else the error code describing the
            /// type of failure that occured while establishing
            /// the connection is returned.</returns>
            [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
            internal static extern int WNetAddConnection2(ref NetResource netResource, byte[] password, string username, int flags);

            /// <summary>
            /// WNetCancelConnection2 function cancels an existing network connection.
            /// </summary>
            /// <param name="driveName">
            /// PSDrive Name.
            /// </param>
            /// <param name="flags">
            /// Connection Type.
            /// </param>
            /// <param name="force">
            /// Specifies whether the disconnection should occur if there are open files or jobs
            /// on the connection. If this parameter is FALSE, the function fails
            /// if there are open files or jobs.
            /// </param>
            /// <returns>If connection is removed then success is returned or
            /// else the error code describing the type of failure that occured while
            /// trying to remove the connection is returned.
            /// </returns>
            [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
            internal static extern int WNetCancelConnection2(string driveName, int flags, bool force);

            /// <summary>
            /// WNetGetConnection function retrieves the name of the network resource associated with a local device.
            /// </summary>
            /// <param name="localName">
            /// Local name of the PSDrive.
            /// </param>
            /// <param name="remoteName">
            /// The remote name to which the PSDrive is getting mapped to.
            /// </param>
            /// <param name="remoteNameLength">
            /// length of the remote name of the created PSDrive.
            /// </param>
            /// <returns></returns>
            [DllImport("mpr.dll", CharSet = CharSet.Unicode)]
            internal static extern int WNetGetConnection(string localName, StringBuilder remoteName, ref int remoteNameLength);

#if CORECLR // TODO:CORECLR Win32 function 'PathIsNetworkPath' is in an extension API set which is currently not on CSS.
            /// <summary>
            /// Searches a path for a drive letter within the range of 'A' to 'Z' and returns the corresponding drive number.
            /// </summary>
            /// <param name="path">
            /// Path of the file being executed
            /// </param>
            /// <returns>Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has a drive letter, or -1 otherwise.</returns>
            [DllImport("api-ms-win-core-shlwapi-legacy-l1-1-0.dll", CharSet = CharSet.Unicode)]
            internal static extern int PathGetDriveNumber(string path);

            private static bool _WNetApiAvailable = true;

            /// <summary>
            /// The API 'PathIsNetworkPath' is not available in CoreSystem.
            /// This implementation is based on the 'PathIsNetworkPath' API.
            /// </summary>
            /// <param name="path"></param>
            /// <returns></returns>
            internal static bool PathIsNetworkPath(string path)
            {
                if (string.IsNullOrEmpty(path))
                {
                    return false;
                }

                if (Utils.PathIsUnc(path))
                {
                    return true;
                }

                if (!_WNetApiAvailable)
                {
                    return false;
                }

                // 0 - 25 corresponding to 'A' - 'Z'
                int driveId = PathGetDriveNumber(path);
                if (driveId >= 0 && driveId < 26)
                {
                    string driveName = (char)('A' + driveId) + ":";

                    int bufferSize = 260; // MAX_PATH from EhStorIoctl.h
                    StringBuilder uncBuffer = new StringBuilder(bufferSize);
                    int errorCode = -1;
                    try
                    {
                        errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize);
                    }
                    catch (System.DllNotFoundException)
                    {
                        _WNetApiAvailable = false;
                        return false;
                    }

                    // From the 'IsNetDrive' API.
                    // 0: success; 1201: connection closed; 31: device error
                    if (errorCode == 0 || errorCode == 1201 || errorCode == 31)
                    {
                        return true;
                    }
                }

                return false;
            }
#else
            /// <summary>
            /// Facilitates to validate if the supplied path exists locally or on the network share.
            /// </summary>
            /// <param name="path">
            /// Path of the file being executed.
            /// </param>
            /// <returns>True if the path is a network path or else returns false.</returns>
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool PathIsNetworkPath(string path);
#endif

            /// <summary>
            /// The function can obtain the current mapping for a particular MS-DOS device name.
            ///
            /// If lpDeviceName is non-NULL, the function retrieves information about the particular MS-DOS device specified by lpDeviceName.
            /// The first null-terminated string stored into the buffer is the current mapping for the device.
            /// The other null-terminated strings represent undeleted prior mappings for the device.
            /// </summary>
            /// <param name="lpDeviceName">
            /// The particular MS-DOS device name.
            /// </param>
            /// <param name="lpTargetPath">
            /// The buffer to receive the result of the query.
            /// </param>
            /// <param name="ucchMax">
            /// The maximum number of characters that can be stored into the buffer
            /// </param>
            /// <returns></returns>
            [DllImport(PinvokeDllNames.QueryDosDeviceDllName, CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);

            /// <summary>
            /// Creates a symbolic link using the native API.
            /// </summary>
            /// <param name="name">Path of the symbolic link.</param>
            /// <param name="destination">Path of the target of the symbolic link.</param>
            /// <param name="symbolicLinkFlags">Flag values from SymbolicLinkFlags enum.</param>
            /// <returns>1 on successful creation.</returns>
            [DllImport(PinvokeDllNames.CreateSymbolicLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.I1)]
            internal static extern bool CreateSymbolicLink(string name, string destination, SymbolicLinkFlags symbolicLinkFlags);

            /// <summary>
            /// Flags used when creating a symbolic link.
            /// </summary>
            [Flags]
            internal enum SymbolicLinkFlags
            {
                /// <summary>
                /// Symbolic link is a file.
                /// </summary>
                File = 0,

                /// <summary>
                /// Symbolic link is a directory.
                /// </summary>
                Directory = 1,

                /// <summary>
                /// Allow creation of symbolic link without elevation.  Requires Developer mode.
                /// </summary>
                AllowUnprivilegedCreate = 2,
            }

            /// <summary>
            /// Creates a hard link using the native API.
            /// </summary>
            /// <param name="name">Name of the hard link.</param>
            /// <param name="existingFileName">Path to the target of the hard link.</param>
            /// <param name="SecurityAttributes"></param>
            /// <returns></returns>
            [DllImport(PinvokeDllNames.CreateHardLinkDllName, CharSet = CharSet.Unicode, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool CreateHardLink(string name, string existingFileName, IntPtr SecurityAttributes);

            // OneDrive placeholder support
#if !UNIX
            /// <summary>
            /// Returns the placeholder compatibility mode for the current process.
            /// </summary>
            /// <returns>The process's placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx).</returns>
            [DllImport("ntdll.dll")]
            internal static extern sbyte RtlQueryProcessPlaceholderCompatibilityMode();

            /// <summary>
            /// Sets the placeholder compatibility mode for the current process.
            /// </summary>
            /// <param name="pcm">The placeholder compatibility mode to set.</param>
            /// <returns>The process's previous placeholder compatibily mode (PHCM_xxx), or a negative value on error (PCHM_ERROR_xxx).</returns>
            [DllImport("ntdll.dll")]
            internal static extern sbyte RtlSetProcessPlaceholderCompatibilityMode(sbyte pcm);

            internal const sbyte PHCM_APPLICATION_DEFAULT = 0;
            internal const sbyte PHCM_DISGUISE_PLACEHOLDER = 1;
            internal const sbyte PHCM_EXPOSE_PLACEHOLDERS = 2;
            internal const sbyte PHCM_MAX = 2;
            internal const sbyte PHCM_ERROR_INVALID_PARAMETER = -1;
            internal const sbyte PHCM_ERROR_NO_TEB = -2;
#endif
        }

        /// <summary>
        /// Managed equivalent of NETRESOURCE structure of WNet API.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        private struct NetResource
        {
            public int Scope;
            public int Type;
            public int DisplayType;
            public int Usage;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string LocalName;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string RemoteName;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string Comment;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string Provider;
        }

        #region InodeTracker
        /// <summary>
        /// Tracks visited files/directories by caching their device IDs and inodes.
        /// </summary>
        private class InodeTracker
        {
            private HashSet<(UInt64, UInt64)> _visitations;

            /// <summary>
            /// Construct a new InodeTracker with an initial path.
            /// </summary>
            internal InodeTracker(string path)
            {
                _visitations = new HashSet<(UInt64, UInt64)>();

                if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
                {
                    _visitations.Add(inodeData);
                }
            }

            /// <summary>
            /// Attempt to mark a path as having been visited.
            /// </summary>
            /// <param name="path">
            /// Path to the file system item to be visited.
            /// </param>
            /// <returns>
            /// True if the path had not been previously visited and was
            /// successfully marked as visited, false otherwise.
            /// </returns>
            internal bool TryVisitPath(string path)
            {
                bool returnValue = false;

                if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
                {
                    returnValue = _visitations.Add(inodeData);
                }

                return returnValue;
            }
        }

        #endregion
    }

    internal static class SafeInvokeCommand
    {
        public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileSystemProvider fileSystemContext, CmdletProviderContext cmdletContext)
        {
            return Invoke(ps, fileSystemContext, cmdletContext, true);
        }

        public static Hashtable Invoke(System.Management.Automation.PowerShell ps, FileSystemProvider fileSystemContext, CmdletProviderContext cmdletContext, bool shouldHaveOutput)
        {
            bool useFileSystemProviderContext = (cmdletContext == null);

            if (useFileSystemProviderContext)
            {
                Dbg.Diagnostics.Assert(fileSystemContext != null, "The caller should verify FileSystemProvider context.");
            }

            Collection<Hashtable> output;
            try
            {
                output = ps.Invoke<Hashtable>();
            }
            catch (Exception e)
            {
                if (useFileSystemProviderContext)
                {
                    fileSystemContext.WriteError(new ErrorRecord(e, "CopyFileRemoteExecutionError", ErrorCategory.InvalidOperation, ps));
                    ps.Commands.Clear();
                }
                else
                {
                    cmdletContext.WriteError(new ErrorRecord(e, "CopyFileRemoteExecutionError", ErrorCategory.InvalidOperation, ps));
                    ps.Commands.Clear();
                }

                return null;
            }

            if (ps.HadErrors)
            {
                foreach (var error in ps.Streams.Error)
                {
                    if (useFileSystemProviderContext)
                    {
                        fileSystemContext.WriteError(error);
                    }
                    else
                    {
                        cmdletContext.WriteError(error);
                    }
                }
            }

            ps.Commands.Clear();

            if (shouldHaveOutput)
            {
                if (output.Count != 1 || output[0].GetType() != typeof(Hashtable))
                {
                    // unexpected output
                    Dbg.Diagnostics.Assert(output[0] != null, "Expected an output from the remote call.");
                    return null;
                }

                return (Hashtable)output[0];
            }

            return null;
        }
    }

    #endregion

    #region Dynamic Parameters

    internal sealed class CopyItemDynamicParameters
    {
        [Parameter]
        [ValidateNotNullOrEmpty]
        public PSSession FromSession { get; set; }

        [Parameter]
        [ValidateNotNullOrEmpty]
        public PSSession ToSession { get; set; }
    }

    /// <summary>
    /// Defines the container cmdlet dynamic providers.
    /// </summary>
    internal sealed class GetChildDynamicParameters
    {
        /// <summary>
        /// Gets or sets the attribute filtering enum evaluator.
        /// </summary>
        [Parameter]
        public FlagsExpression<FileAttributes> Attributes { get; set; }

        /// <summary>
        /// Gets or sets the flag to follow symbolic links when recursing.
        /// </summary>
        [Parameter]
        public SwitchParameter FollowSymlink { get; set; }

        /// <summary>
        /// Gets or sets the filter directory flag.
        /// </summary>
        [Parameter]
        [Alias("ad", "d")]
        public SwitchParameter Directory
        {
            get { return _attributeDirectory; }

            set { _attributeDirectory = value; }
        }

        private bool _attributeDirectory;

        /// <summary>
        /// Gets or sets the filter file flag.
        /// </summary>
        [Parameter]
        [Alias("af")]
        public SwitchParameter File
        {
            get { return _attributeFile; }

            set { _attributeFile = value; }
        }

        private bool _attributeFile;

        /// <summary>
        /// Gets or sets the filter hidden flag.
        /// </summary>
        [Parameter]
        [Alias("ah", "h")]
        public SwitchParameter Hidden
        {
            get { return _attributeHidden; }

            set { _attributeHidden = value; }
        }

        private bool _attributeHidden;

        /// <summary>
        /// Gets or sets the filter readonly flag.
        /// </summary>
        [Parameter]
        [Alias("ar")]
        public SwitchParameter ReadOnly
        {
            get { return _attributeReadOnly; }

            set { _attributeReadOnly = value; }
        }

        private bool _attributeReadOnly;

        /// <summary>
        /// Gets or sets the filter system flag.
        /// </summary>
        [Parameter]
        [Alias("as")]
        public SwitchParameter System
        {
            get { return _attributeSystem; }

            set { _attributeSystem = value; }
        }

        private bool _attributeSystem;
    }

    /// <summary>
    /// Defines the dynamic parameters used by both the content reader and writer.
    /// </summary>
    public class FileSystemContentDynamicParametersBase
    {
        internal FileSystemContentDynamicParametersBase(FileSystemProvider provider)
        {
            _provider = provider;
        }

        private FileSystemProvider _provider;

        /// <summary>
        /// Gets or sets the encoding method used when
        /// reading data from the file.
        /// </summary>
        [Parameter]
        [ArgumentToEncodingTransformationAttribute()]
        [ArgumentEncodingCompletionsAttribute]
        [ValidateNotNullOrEmpty]
        public Encoding Encoding
        {
            get
            {
                return _encoding;
            }

            set
            {
                // Check for UTF-7 by checking for code page 65000
                // See: https://docs.microsoft.com/en-us/dotnet/core/compatibility/corefx#utf-7-code-paths-are-obsolete
                if (value != null && value.CodePage == 65000)
                {
                    _provider.WriteWarning(PathUtilsStrings.Utf7EncodingObsolete);
                }
                _encoding = value;
                // If an encoding was explicitly set, be sure to capture that.
                WasStreamTypeSpecified = true;
            }
        }

        private Encoding _encoding = ClrFacade.GetDefaultEncoding();

        /// <summary>
        /// Return file contents as a byte stream or create file from a series of bytes.
        /// </summary>
        [Parameter]
        public SwitchParameter AsByteStream { get; set; }

#if !UNIX
        /// <summary>
        /// A parameter to return a stream of an item.
        /// </summary>
        [Parameter]
        public string Stream { get; set; }
#endif

        /// <summary>
        /// Gets the status of the StreamType parameter.  Returns true
        /// if the stream was opened with a user-specified encoding, false otherwise.
        /// </summary>
        public bool WasStreamTypeSpecified { get; private set; }
    }

    /// <summary>
    /// Defines the dynamic parameters used by the Clear-Content cmdlet.
    /// </summary>
    public class FileSystemClearContentDynamicParameters
    {
#if !UNIX
        /// <summary>
        /// A parameter to return a stream of an item.
        /// </summary>
        [Parameter]
        public string Stream { get; set; }
#endif
    }

    /// <summary>
    /// Defines the dynamic parameters used by the set-content and
    /// add-content cmdlets.
    /// </summary>
    public class FileSystemContentWriterDynamicParameters : FileSystemContentDynamicParametersBase
    {
        internal FileSystemContentWriterDynamicParameters(FileSystemProvider provider) : base(provider) { }

        /// <summary>
        /// False to add a newline to the end of the output string, true if not.
        /// </summary>
        [Parameter]
        public SwitchParameter NoNewline
        {
            get
            {
                return _suppressNewline;
            }

            set
            {
                _suppressNewline = value;
            }
        }

        private bool _suppressNewline = false;
    }

    /// <summary>
    /// Defines the dynamic parameters used by the get-content cmdlet.
    /// </summary>
    public class FileSystemContentReaderDynamicParameters : FileSystemContentDynamicParametersBase
    {
        internal FileSystemContentReaderDynamicParameters(FileSystemProvider provider) : base (provider) { }

        /// <summary>
        /// Gets or sets the delimiter to use when reading the file.  Custom delimiters
        /// may not be used when the file is opened with a "Byte" encoding.
        /// </summary>
        [Parameter]
        public string Delimiter
        {
            get
            {
                return _delimiter;
            }

            set
            {
                DelimiterSpecified = true;
                _delimiter = value;
            }
        }

        private string _delimiter = "\n";

        /// <summary>
        /// Gets or sets the Wait flag.  The wait flag determines if we want
        /// the read-content call to poll (and wait) for changes to the file,
        /// rather than exit after the content has been read.
        /// </summary>
        [Parameter]
        public SwitchParameter Wait
        {
            get
            {
                return _wait;
            }

            set
            {
                _wait = value;
            }
        }

        private bool _wait;

        /// <summary>
        /// When the Raw switch is present, we don't do any breaks on newlines,
        /// and only emit one object to the pipeline: all of the content.
        /// </summary>
        [Parameter]
        public SwitchParameter Raw
        {
            get
            {
                return _isRaw;
            }

            set
            {
                _isRaw = value;
            }
        }

        private bool _isRaw;

        /// <summary>
        /// Gets the status of the delimiter parameter.  Returns true
        /// if the delimiter was explicitly specified by the user, false otherwise.
        /// </summary>
        public bool DelimiterSpecified
        {
            get; private set;
            // get
        }
    }

    /// <summary>
    /// Provides the dynamic parameters for test-path on the file system.
    /// </summary>
    public class FileSystemItemProviderDynamicParameters
    {
        /// <summary>
        /// A parameter to test if a file is older than a certain time or date.
        /// </summary>
        [Parameter]
        public DateTime? OlderThan { get; set; }

        /// <summary>
        /// A parameter to test if a file is newer than a certain time or date.
        /// </summary>
        [Parameter]
        public DateTime? NewerThan { get; set; }
    }

    /// <summary>
    /// Provides the dynamic parameters for Get-Item on the file system.
    /// </summary>
    public class FileSystemProviderGetItemDynamicParameters
    {
#if !UNIX
        /// <summary>
        /// A parameter to return the streams of an item.
        /// </summary>
        [Parameter]
        [ValidateNotNullOrEmpty()]
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public string[] Stream { get; set; }
#endif
    }

    /// <summary>
    /// Provides the dynamic parameters for Remove-Item on the file system.
    /// </summary>
    public class FileSystemProviderRemoveItemDynamicParameters
    {
#if !UNIX
        /// <summary>
        /// A parameter to return the streams of an item.
        /// </summary>
        [Parameter]
        [ValidateNotNullOrEmpty()]
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public string[] Stream { get; set; }
#endif
    }

    #endregion

    #region Symbolic Link

    /// <summary>
    /// Class to find the symbolic link target.
    /// </summary>
    public static class InternalSymbolicLinkLinkCodeMethods
    {
        // This size comes from measuring the size of the header of REPARSE_GUID_DATA_BUFFER
        private const int REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24;

        // Maximum reparse buffer info size. The max user defined reparse
        // data is 16KB, plus there's a header.
        private const int MAX_REPARSE_SIZE = (16 * 1024) + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE;

        private const int ERROR_NOT_A_REPARSE_POINT = 4390;

        private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;

        private const int FSCTL_SET_REPARSE_POINT = 0x000900A4;

        private const int FSCTL_DELETE_REPARSE_POINT = 0x000900AC;

        private const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C;

        private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003;

        private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B;

        private const string NonInterpretedPathPrefix = @"\??\";

        private const int MAX_PATH = 260;

        [Flags]
        // dwDesiredAccess of CreateFile
        internal enum FileDesiredAccess : uint
        {
            GenericZero = 0,
            GenericRead = 0x80000000,
            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000,
        }

        [Flags]
        // dwShareMode of CreateFile
        internal enum FileShareMode : uint
        {
            None = 0x00000000,
            Read = 0x00000001,
            Write = 0x00000002,
            Delete = 0x00000004,
        }

        // dwCreationDisposition of CreateFile
        internal enum FileCreationDisposition : uint
        {
            New = 1,
            CreateAlways = 2,
            OpenExisting = 3,
            OpenAlways = 4,
            TruncateExisting = 5,
        }

        [Flags]
        // dwFlagsAndAttributes
        internal enum FileAttributes : uint
        {
            Readonly = 0x00000001,
            Hidden = 0x00000002,
            System = 0x00000004,
            Archive = 0x00000020,
            Encrypted = 0x00004000,
            Write_Through = 0x80000000,
            Overlapped = 0x40000000,
            NoBuffering = 0x20000000,
            RandomAccess = 0x10000000,
            SequentialScan = 0x08000000,
            DeleteOnClose = 0x04000000,
            BackupSemantics = 0x02000000,
            PosixSemantics = 0x01000000,
            OpenReparsePoint = 0x00200000,
            OpenNoRecall = 0x00100000,
            SessionAware = 0x00800000,
            Normal = 0x00000080
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_DATA_BUFFER_SYMBOLICLINK
        {
            public uint ReparseTag;
            public ushort ReparseDataLength;
            public ushort Reserved;
            public ushort SubstituteNameOffset;
            public ushort SubstituteNameLength;
            public ushort PrintNameOffset;
            public ushort PrintNameLength;
            public uint Flags;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            public byte[] PathBuffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_DATA_BUFFER_MOUNTPOINT
        {
            public uint ReparseTag;
            public ushort ReparseDataLength;
            public ushort Reserved;
            public ushort SubstituteNameOffset;
            public ushort SubstituteNameLength;
            public ushort PrintNameOffset;
            public ushort PrintNameLength;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            public byte[] PathBuffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_DATA_BUFFER_APPEXECLINK
        {
            public uint ReparseTag;
            public ushort ReparseDataLength;
            public ushort Reserved;
            public uint StringCount;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
            public byte[] StringList;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct BY_HANDLE_FILE_INFORMATION
        {
            public uint FileAttributes;
            public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
            public uint VolumeSerialNumber;
            public uint FileSizeHigh;
            public uint FileSizeLow;
            public uint NumberOfLinks;
            public uint FileIndexHigh;
            public uint FileIndexLow;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct GUID
        {
            public uint Data1;
            public ushort Data2;
            public ushort Data3;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public char[] Data4;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct REPARSE_GUID_DATA_BUFFER
        {
            public uint ReparseTag;
            public ushort ReparseDataLength;
            public ushort Reserved;
            public GUID ReparseGuid;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)]
            public char[] DataBuffer;
        }

        [DllImport(PinvokeDllNames.DeviceIoControlDllName, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
        private static extern bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode,
            IntPtr InBuffer, int nInBufferSize,
            IntPtr OutBuffer, int nOutBufferSize,
            out int pBytesReturned, IntPtr lpOverlapped);

        [DllImport(PinvokeDllNames.GetFileInformationByHandleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetFileInformationByHandle(
                IntPtr hFile,
                out BY_HANDLE_FILE_INFORMATION lpFileInformation);

        [DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern IntPtr CreateFile(
            string lpFileName,
            FileDesiredAccess dwDesiredAccess,
            FileShareMode dwShareMode,
            IntPtr lpSecurityAttributes,
            FileCreationDisposition dwCreationDisposition,
            FileAttributes dwFlagsAndAttributes,
            IntPtr hTemplateFile);

        internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            private SafeFindHandle() : base(true) { }

            protected override bool ReleaseHandle()
            {
                return FindClose(this.handle);
            }

            [DllImport(PinvokeDllNames.FindCloseDllName)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool FindClose(IntPtr handle);
        }

        // SetLastError is false as the use of this API doesn't not require GetLastError() to be called
        [DllImport(PinvokeDllNames.FindFirstFileDllName, EntryPoint = "FindFirstFileExW", SetLastError = false, CharSet = CharSet.Unicode)]
        private static extern SafeFindHandle FindFirstFileEx(string lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, ref WIN32_FIND_DATA lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, IntPtr lpSearchFilter, int dwAdditionalFlags);

        internal enum FINDEX_INFO_LEVELS : uint
        {
            FindExInfoStandard = 0x0u,
            FindExInfoBasic = 0x1u,
            FindExInfoMaxInfoLevel = 0x2u,
        }

        internal enum FINDEX_SEARCH_OPS : uint
        {
            FindExSearchNameMatch = 0x0u,
            FindExSearchLimitToDirectories = 0x1u,
            FindExSearchLimitToDevices = 0x2u,
            FindExSearchMaxSearchOp = 0x3u,
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal unsafe struct WIN32_FIND_DATA
        {
            internal uint dwFileAttributes;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
            internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
            internal uint nFileSizeHigh;
            internal uint nFileSizeLow;
            internal uint dwReserved0;
            internal uint dwReserved1;
            internal fixed char cFileName[MAX_PATH];
            internal fixed char cAlternateFileName[14];
        }

        /// <summary>
        /// Gets the target of the specified reparse point.
        /// </summary>
        /// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
        /// <returns>The target of the reparse point.</returns>
        public static string GetTarget(PSObject instance)
        {
            if (instance.BaseObject is FileSystemInfo fileSysInfo)
            {
#if !UNIX
                return WinInternalGetTarget(fileSysInfo.FullName);
#else
               return UnixInternalGetTarget(fileSysInfo.FullName);
#endif
            }

            return null;
        }

        /// <summary>
        /// Gets the link type of the specified reparse point.
        /// </summary>
        /// <param name="instance">The object of FileInfo or DirectoryInfo type.</param>
        /// <returns>The link type of the reparse point. SymbolicLink for symbolic links.</returns>
        public static string GetLinkType(PSObject instance)
        {
            FileSystemInfo fileSysInfo = instance.BaseObject as FileSystemInfo;

            if (fileSysInfo != null)
            {
                return InternalGetLinkType(fileSysInfo);
            }

            return null;
        }

#if UNIX
        private static string UnixInternalGetTarget(string filePath)
        {
            string link = Platform.NonWindowsInternalGetTarget(filePath);

            if (string.IsNullOrEmpty(link))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            return link;
        }
#endif

        private static string InternalGetLinkType(FileSystemInfo fileInfo)
        {
            if (Platform.IsWindows)
            {
                return WinInternalGetLinkType(fileInfo.FullName);
            }
            else
            {
                return Platform.NonWindowsInternalGetLinkType(fileInfo);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        private static string WinInternalGetLinkType(string filePath)
        {
            if (!Platform.IsWindows)
            {
                throw new PlatformNotSupportedException();
            }

            // We set accessMode parameter to zero because documentation says:
            // If this parameter is zero, the application can query certain metadata
            // such as file, directory, or device attributes without accessing
            // that file or device, even if GENERIC_READ access would have been denied.
            using (SafeFileHandle handle = OpenReparsePoint(filePath, FileDesiredAccess.GenericZero))
            {
                int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();

                IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
                bool success = false;

                try
                {
                    int bytesReturned;
                    string linkType = null;

                    // OACR warning 62001 about using DeviceIOControl has been disabled.
                    // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
                    handle.DangerousAddRef(ref success);

                    // Get Buffer size
                    IntPtr dangerousHandle = handle.DangerousGetHandle();

                    bool result = DeviceIoControl(dangerousHandle, FSCTL_GET_REPARSE_POINT,
                        IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);

                    if (!result)
                    {
                        int lastError = Marshal.GetLastWin32Error();
                        if (lastError == ERROR_NOT_A_REPARSE_POINT)
                            linkType = null;
                        else
                            throw new Win32Exception(lastError);
                    }

                    REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer);

                    switch (reparseDataBuffer.ReparseTag)
                    {
                        case IO_REPARSE_TAG_SYMLINK:
                            linkType = "SymbolicLink";
                            break;

                        case IO_REPARSE_TAG_MOUNT_POINT:
                            linkType = "Junction";
                            break;

                        case IO_REPARSE_TAG_APPEXECLINK:
                            linkType = "AppExeCLink";
                            break;

                        default:
                            linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null;
                            break;
                    }

                    return linkType;
                }
                finally
                {
                    if (success)
                    {
                        handle.DangerousRelease();
                    }

                    Marshal.FreeHGlobal(outBuffer);
                }
            }
        }

        internal static bool IsHardLink(FileSystemInfo fileInfo)
        {
#if UNIX
            return Platform.NonWindowsIsHardLink(fileInfo);
#else
            return WinIsHardLink(fileInfo);
#endif
        }

        internal static bool IsReparsePoint(FileSystemInfo fileInfo)
        {
            return fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint);
        }

        internal static bool IsReparsePointWithTarget(FileSystemInfo fileInfo)
        {
            if (!IsReparsePoint(fileInfo))
            {
                return false;
            }
#if !UNIX
            // It is a reparse point and we should check some reparse point tags.
            var data = new WIN32_FIND_DATA();
            using (var handle = FindFirstFileEx(fileInfo.FullName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0))
            {
                // The name surrogate bit 0x20000000 is defined in https://docs.microsoft.com/windows/win32/fileio/reparse-point-tags
                // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem
                // (like symlinks and mount points).
                // In the case of OneDrive, they are not name surrogates and would be safe to recurse into.
                if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 != IO_REPARSE_TAG_APPEXECLINK))
                {
                    return false;
                }
            }
#endif
            return true;
        }

        internal static bool WinIsHardLink(FileSystemInfo fileInfo)
        {
            bool isHardLink = false;

            // only check for hard link if the item is not directory
            if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory)
            {
                IntPtr nativeHandle = InternalSymbolicLinkLinkCodeMethods.CreateFile(
                    fileInfo.FullName,
                    InternalSymbolicLinkLinkCodeMethods.FileDesiredAccess.GenericRead,
                    InternalSymbolicLinkLinkCodeMethods.FileShareMode.Read,
                    IntPtr.Zero,
                    InternalSymbolicLinkLinkCodeMethods.FileCreationDisposition.OpenExisting,
                    InternalSymbolicLinkLinkCodeMethods.FileAttributes.Normal,
                    IntPtr.Zero);

                using (SafeFileHandle handle = new SafeFileHandle(nativeHandle, true))
                {
                    bool success = false;

                    try
                    {
                        handle.DangerousAddRef(ref success);
                        IntPtr dangerousHandle = handle.DangerousGetHandle();
                        isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(ref dangerousHandle);
                    }
                    finally
                    {
                        if (success)
                            handle.DangerousRelease();
                    }
                }
            }

            return isHardLink;
        }

        internal static bool IsSameFileSystemItem(string pathOne, string pathTwo)
        {
#if UNIX
            return Platform.NonWindowsIsSameFileSystemItem(pathOne, pathTwo);
#else
            return WinIsSameFileSystemItem(pathOne, pathTwo);
#endif
        }

#if !UNIX
        private static bool WinIsSameFileSystemItem(string pathOne, string pathTwo)
        {
            var access = FileAccess.Read;
            var share = FileShare.Read;
            var creation = FileMode.Open;
            var attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics;

            using (var sfOne = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathOne, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
            using (var sfTwo = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathTwo, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
            {
                if (!sfOne.IsInvalid && !sfTwo.IsInvalid)
                {
                    BY_HANDLE_FILE_INFORMATION infoOne;
                    BY_HANDLE_FILE_INFORMATION infoTwo;
                    if (GetFileInformationByHandle(sfOne.DangerousGetHandle(), out infoOne)
                        && GetFileInformationByHandle(sfTwo.DangerousGetHandle(), out infoTwo))
                    {
                        return infoOne.VolumeSerialNumber == infoTwo.VolumeSerialNumber
                               && infoOne.FileIndexHigh == infoTwo.FileIndexHigh
                               && infoOne.FileIndexLow == infoTwo.FileIndexLow;
                    }
                }
            }

            return false;
        }
#endif

        internal static bool GetInodeData(string path, out System.ValueTuple<UInt64, UInt64> inodeData)
        {
#if UNIX
            bool rv = Platform.NonWindowsGetInodeData(path, out inodeData);
#else
            bool rv = WinGetInodeData(path, out inodeData);
#endif
            return rv;
        }

#if !UNIX
        private static bool WinGetInodeData(string path, out System.ValueTuple<UInt64, UInt64> inodeData)
        {
            var access = FileAccess.Read;
            var share = FileShare.Read;
            var creation = FileMode.Open;
            var attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics;

            using (var sf = AlternateDataStreamUtilities.NativeMethods.CreateFile(path, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero))
            {
                if (!sf.IsInvalid)
                {
                    BY_HANDLE_FILE_INFORMATION info;

                    if (GetFileInformationByHandle(sf.DangerousGetHandle(), out info))
                    {
                        UInt64 tmp = info.FileIndexHigh;
                        tmp = (tmp << 32) | info.FileIndexLow;

                        inodeData = (info.VolumeSerialNumber, tmp);

                        return true;
                    }
                }
            }

            inodeData = (0, 0);
            return false;
        }
#endif
        internal static bool IsHardLink(ref IntPtr handle)
        {
#if UNIX
            return Platform.NonWindowsIsHardLink(ref handle);
#else
            return WinIsHardLink(ref handle);
#endif
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        internal static bool WinIsHardLink(ref IntPtr handle)
        {
            BY_HANDLE_FILE_INFORMATION handleInfo;
            bool succeeded = InternalSymbolicLinkLinkCodeMethods.GetFileInformationByHandle(handle, out handleInfo);
            return succeeded && (handleInfo.NumberOfLinks > 1);
        }

#if !UNIX
        internal static string WinInternalGetTarget(string path)
        {
            // We set accessMode parameter to zero because documentation says:
            // If this parameter is zero, the application can query certain metadata
            // such as file, directory, or device attributes without accessing
            // that file or device, even if GENERIC_READ access would have been denied.
            using (SafeFileHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericZero))
            {
                return WinInternalGetTarget(handle);
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        private static string WinInternalGetTarget(SafeFileHandle handle)
        {
            int outBufferSize = Marshal.SizeOf<REPARSE_DATA_BUFFER_SYMBOLICLINK>();

            IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);
            bool success = false;

            try
            {
                int bytesReturned;

                // OACR warning 62001 about using DeviceIOControl has been disabled.
                // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
                handle.DangerousAddRef(ref success);

                bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT,
                    IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);

                if (!result)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    if (lastError == ERROR_NOT_A_REPARSE_POINT)
                        return null;

                    throw new Win32Exception(lastError);
                }

                string targetDir = null;

                REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer);

                switch (reparseDataBuffer.ReparseTag)
                {
                    case IO_REPARSE_TAG_SYMLINK:
                        targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength);
                        break;

                    case IO_REPARSE_TAG_MOUNT_POINT:
                        REPARSE_DATA_BUFFER_MOUNTPOINT reparseMountPointDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_MOUNTPOINT>(outBuffer);
                        targetDir = Encoding.Unicode.GetString(reparseMountPointDataBuffer.PathBuffer, reparseMountPointDataBuffer.SubstituteNameOffset, reparseMountPointDataBuffer.SubstituteNameLength);
                        break;

                    case IO_REPARSE_TAG_APPEXECLINK:
                        REPARSE_DATA_BUFFER_APPEXECLINK reparseAppExeDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_APPEXECLINK>(outBuffer);
                        // The target file is at index 2
                        if (reparseAppExeDataBuffer.StringCount >= 3)
                        {
                            string temp = Encoding.Unicode.GetString(reparseAppExeDataBuffer.StringList);
                            targetDir = temp.Split('\0')[2];
                        }
                        break;

                    default:
                        return null;
                }

                if (targetDir != null && targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase))
                {
                    targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length);
                }

                return targetDir;
            }
            finally
            {
                if (success)
                {
                    handle.DangerousRelease();
                }

                Marshal.FreeHGlobal(outBuffer);
            }
        }
#endif

        internal static bool CreateJunction(string path, string target)
        {
            // this is a purely Windows specific feature, no feature flag
            // used for that reason
            if (Platform.IsWindows)
            {
                return WinCreateJunction(path, target);
            }
            else
            {
                return false;
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
        private static bool WinCreateJunction(string path, string target)
        {
            if (!string.IsNullOrEmpty(path))
            {
                if (!string.IsNullOrEmpty(target))
                {
                    using (SafeHandle handle = OpenReparsePoint(path, FileDesiredAccess.GenericWrite))
                    {
                        byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target));

                        REPARSE_DATA_BUFFER_MOUNTPOINT mountPoint = new REPARSE_DATA_BUFFER_MOUNTPOINT();
                        mountPoint.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
                        mountPoint.ReparseDataLength = (ushort)(mountPointBytes.Length + 12); // Added space for the header and null endo
                        mountPoint.SubstituteNameOffset = 0;
                        mountPoint.SubstituteNameLength = (ushort)mountPointBytes.Length;
                        mountPoint.PrintNameOffset = (ushort)(mountPointBytes.Length + 2); // 2 as unicode null take 2 bytes.
                        mountPoint.PrintNameLength = 0;
                        mountPoint.PathBuffer = new byte[0x3FF0]; // Buffer for max size.
                        Array.Copy(mountPointBytes, mountPoint.PathBuffer, mountPointBytes.Length);

                        int nativeBufferSize = Marshal.SizeOf(mountPoint);
                        IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize);
                        bool success = false;

                        try
                        {
                            Marshal.StructureToPtr(mountPoint, nativeBuffer, false);

                            int bytesReturned = 0;

                            // OACR warning 62001 about using DeviceIOControl has been disabled.
                            // According to MSDN guidance DangerousAddRef() and DangerousRelease() have been used.
                            handle.DangerousAddRef(ref success);

                            bool result = DeviceIoControl(handle.DangerousGetHandle(), FSCTL_SET_REPARSE_POINT, nativeBuffer, mountPointBytes.Length + 20, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero);

                            if (!result)
                            {
                                throw new Win32Exception(Marshal.GetLastWin32Error());
                            }

                            return result;
                        }
                        finally
                        {
                            Marshal.FreeHGlobal(nativeBuffer);

                            if (success)
                            {
                                handle.DangerousRelease();
                            }
                        }
                    }
                }
                else
                {
                    throw new ArgumentNullException(nameof(target));
                }
            }
            else
            {
                throw new ArgumentNullException(nameof(path));
            }
        }

        private static SafeFileHandle OpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
        {
#if UNIX
            throw new PlatformNotSupportedException();
#else
            return WinOpenReparsePoint(reparsePoint, accessMode);
#endif
        }

        private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode)
        {
            IntPtr nativeHandle = CreateFile(reparsePoint, accessMode,
                FileShareMode.Read | FileShareMode.Write | FileShareMode.Delete,
                IntPtr.Zero, FileCreationDisposition.OpenExisting,
                FileAttributes.BackupSemantics | FileAttributes.OpenReparsePoint,
                IntPtr.Zero);

            int lastError = Marshal.GetLastWin32Error();

            if (lastError != 0)
                throw new Win32Exception(lastError);

            SafeFileHandle reparsePointHandle = new SafeFileHandle(nativeHandle, true);

            return reparsePointHandle;
        }
    }

    #endregion
}

namespace System.Management.Automation.Internal
{
#if !UNIX
    #region AlternateDataStreamUtilities

    /// <summary>
    /// Represents alternate stream data retrieved from a file.
    /// </summary>
    public class AlternateStreamData
    {
        /// <summary>
        /// The name of the file that holds this stream.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// The name of this stream.
        /// </summary>
        public string Stream { get; set; }

        /// <summary>
        /// The length of this stream.
        /// </summary>
        public long Length { get; set; }
    }

    /// <summary>
    /// Provides access to alternate data streams on a file.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes",
        Justification = "Needed by both the FileSystem provider and Unblock-File cmdlet.")]
    public static class AlternateDataStreamUtilities
    {
        /// <summary>
        /// List all of the streams on a file.
        /// </summary>
        /// <param name="path">The fully-qualified path to the file.</param>
        /// <returns>The list of streams (and their size) in the file.</returns>
        internal static List<AlternateStreamData> GetStreams(string path)
        {
            if (path == null) throw new ArgumentNullException(nameof(path));

            List<AlternateStreamData> alternateStreams = new List<AlternateStreamData>();

            AlternateStreamNativeData findStreamData = new AlternateStreamNativeData();

            SafeFindHandle handle = NativeMethods.FindFirstStreamW(
                path, NativeMethods.StreamInfoLevels.FindStreamInfoStandard,
                findStreamData, 0);
            if (handle.IsInvalid) throw new Win32Exception();

            try
            {
                do
                {
                    // Remove the leading ':'
                    findStreamData.Name = findStreamData.Name.Substring(1);

                    // And trailing :$DATA (as long as it's not the default data stream)
                    string dataStream = ":$DATA";
                    if (!string.Equals(findStreamData.Name, dataStream, StringComparison.OrdinalIgnoreCase))
                    {
                        findStreamData.Name = findStreamData.Name.Replace(dataStream, string.Empty);
                    }

                    AlternateStreamData data = new AlternateStreamData();
                    data.Stream = findStreamData.Name;
                    data.Length = findStreamData.Length;
                    data.FileName = path.Replace(data.Stream, string.Empty);
                    data.FileName = data.FileName.Trim(Utils.Separators.Colon);

                    alternateStreams.Add(data);
                    findStreamData = new AlternateStreamNativeData();
                }
                while (NativeMethods.FindNextStreamW(handle, findStreamData));

                int lastError = Marshal.GetLastWin32Error();
                if (lastError != NativeMethods.ERROR_HANDLE_EOF)
                    throw new Win32Exception(lastError);
            }
            finally { handle.Dispose(); }

            return alternateStreams;
        }

        /// <summary>
        /// Creates a file stream on a file.
        /// </summary>
        /// <param name="path">The fully-qualified path to the file.</param>
        /// <param name="streamName">The name of the alternate data stream to open.</param>
        /// <param name="mode">The FileMode of the file.</param>
        /// <param name="access">The FileAccess of the file.</param>
        /// <param name="share">The FileShare of the file.</param>
        /// <returns>A FileStream that can be used to interact with the file.</returns>
        internal static FileStream CreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share)
        {
            if (!TryCreateFileStream(path, streamName, mode, access, share, out var stream))
            {
                string errorMessage = StringUtil.Format(
                    FileSystemProviderStrings.AlternateDataStreamNotFound, streamName, path);
                throw new FileNotFoundException(errorMessage, $"{path}:{streamName}");
            }

            return stream;
        }

        /// <summary>
        /// Tries to create a file stream on a file.
        /// </summary>
        /// <param name="path">The fully-qualified path to the file.</param>
        /// <param name="streamName">The name of the alternate data stream to open.</param>
        /// <param name="mode">The FileMode of the file.</param>
        /// <param name="access">The FileAccess of the file.</param>
        /// <param name="share">The FileShare of the file.</param>
        /// <param name="stream">A FileStream that can be used to interact with the file.</param>
        /// <returns>True if the stream was successfully created, otherwise false.</returns>
        internal static bool TryCreateFileStream(string path, string streamName, FileMode mode, FileAccess access, FileShare share, out FileStream stream)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (streamName == null)
            {
                throw new ArgumentNullException(nameof(streamName));
            }

            if (mode == FileMode.Append)
            {
                mode = FileMode.OpenOrCreate;
            }

            var resultPath = $"{path}:{streamName}";
            SafeFileHandle handle = NativeMethods.CreateFile(resultPath, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero);

            if (handle.IsInvalid)
            {
                stream = null;
                return false;
            }

            stream = new FileStream(handle, access);
            return true;
        }

        /// <summary>
        /// Removes an alternate data stream.
        /// </summary>
        /// <param name="path">The path to the file.</param>
        /// <param name="streamName">The name of the alternate data stream to delete.</param>
        internal static void DeleteFileStream(string path, string streamName)
        {
            if (path == null) throw new ArgumentNullException(nameof(path));
            if (streamName == null) throw new ArgumentNullException(nameof(streamName));

            string adjustedStreamName = streamName.Trim();
            if (adjustedStreamName.IndexOf(':') != 0)
            {
                adjustedStreamName = ":" + adjustedStreamName;
            }

            string resultPath = path + adjustedStreamName;

            File.Delete(resultPath);
        }

        internal static void SetZoneOfOrigin(string path, SecurityZone securityZone)
        {
            using (FileStream fileStream = CreateFileStream(path, "Zone.Identifier", FileMode.Create, FileAccess.Write, FileShare.None))
            using (TextWriter textWriter = new StreamWriter(fileStream, Encoding.Unicode))
            {
                textWriter.WriteLine("[ZoneTransfer]");
                textWriter.WriteLine("ZoneId={0}", (int)securityZone);
            }

            // an alternative is to use IAttachmentExecute interface as described here:
            // http://joco.name/2010/12/22/windows-antivirus-api-in-net-and-a-com-interop-crash-course/
            // the code above seems cleaner and more robust than the IAttachmentExecute approach
        }

        internal static class NativeMethods
        {
            internal const int ERROR_HANDLE_EOF = 38;

            internal enum StreamInfoLevels { FindStreamInfoStandard = 0 }

            [DllImport(PinvokeDllNames.CreateFileDllName, CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern SafeFileHandle CreateFile(string lpFileName,
                FileAccess dwDesiredAccess, FileShare dwShareMode,
                IntPtr lpSecurityAttributes, FileMode dwCreationDisposition,
                int dwFlagsAndAttributes, IntPtr hTemplateFile);

            [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
            [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")]
            internal static extern SafeFindHandle FindFirstStreamW(
                string lpFileName, StreamInfoLevels InfoLevel,
                [In, Out, MarshalAs(UnmanagedType.LPStruct)]
                AlternateStreamNativeData lpFindStreamData, uint dwFlags);

            [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            [SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "AlternateStreamNativeData.Name")]
            internal static extern bool FindNextStreamW(
                SafeFindHandle hndFindFile,
                [In, Out, MarshalAs(UnmanagedType.LPStruct)]
                AlternateStreamNativeData lpFindStreamData);
        }

        internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            private SafeFindHandle() : base(true) { }

            protected override bool ReleaseHandle()
            {
                return FindClose(this.handle);
            }

            [DllImport(PinvokeDllNames.FindCloseDllName)]
            [return: MarshalAs(UnmanagedType.Bool)]
            private static extern bool FindClose(IntPtr handle);
        }

        /// <summary>
        /// Represents alternate stream data retrieved from a file.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal class AlternateStreamNativeData
        {
            /// <summary>
            /// The length of this stream.
            /// </summary>
            public long Length;

            /// <summary>
            /// The name of this stream.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)]
            public string Name;
        }
    }

    #endregion
#endif

    #region CopyFileFromRemoteUtils

    internal static class CopyFileRemoteUtils
    {
        private const string functionToken = "function ";
        private const string nameToken = "Name";
        private const string definitionToken = "Definition";

        #region PSCopyToSessionHelper

        internal const string PSCopyToSessionHelperName = @"PSCopyToSessionHelper";

        private static string s_driveMaxSizeErrorFormatString = FileSystemProviderStrings.DriveMaxSizeError;
        private static string s_PSCopyToSessionHelperDefinition = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]", s_driveMaxSizeErrorFormatString);
        private static string s_PSCopyToSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateUserDrive()]", s_driveMaxSizeErrorFormatString);

        private const string PSCopyToSessionHelperDefinitionFormat = @"
        param (
            [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
            [Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
            {0}

            [string] $copyToFilePath,

            [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"", Mandatory=$false)]
            [Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"")]
            [ValidateNotNullOrEmpty()]
            [string] $b64Fragment,

            [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
            [switch] $createFile = $false,

            [Parameter(ParameterSetName=""PSCopyFileToRemoteSession"")]
            [switch] $emptyFile = $false,

            [Parameter(ParameterSetName=""PSCopyAlternateStreamToRemoteSession"", Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            [string] $streamName,

            [Parameter(ParameterSetName=""PSTargetSupportsAlternateStreams"", Mandatory=$true)]
            {0}

            [string] $supportAltStreamPath,

            [Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
            {0}

            [string] $metaDataFilePath,

            [Parameter(ParameterSetName=""PSSetFileMetadata"", Mandatory=$true)]
            [ValidateNotNull()]
            [hashtable] $metaDataToSet,

            [Parameter(ParameterSetName=""PSRemoteDestinationPathIsFile"", Mandatory=$true)]
            {0}

            [string] $isFilePath,

            [Parameter(ParameterSetName=""PSGetRemotePathInfo"", Mandatory=$true)]
            {0}

            [string] $remotePath,

            [Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"", Mandatory=$true)]
            {0}

            [string] $createDirectoryPath,

            [Parameter(ParameterSetName=""PSCreateDirectoryOnRemoteSession"")]
            [switch] $force
        )

        # Checks if path drive specifies max size and if max size is exceeded
        #
        function CheckPSDriveSize
        {{
            param (
                [System.Management.Automation.PathInfo] $resolvedPath,
                [int] $fragmentLength
            )

            if (($null -ne $resolvedPath.Drive) -and ($null -ne $resolvedPath.Drive.MaximumSize))
            {{
                $maxUserSize = $resolvedPath.Drive.MaximumSize
                $dirSize = 0
                Microsoft.PowerShell.Management\Get-ChildItem -LiteralPath ($resolvedPath.Drive.Name + "":"") -Recurse | ForEach-Object {{
                    Microsoft.PowerShell.Management\Get-Item -LiteralPath $_.FullName -Stream * | ForEach-Object {{ $dirSize += $_.Length }}
                }}

                if (($dirSize + $fragmentLength) -gt $maxUserSize)
                {{
                    $msg = ""{1}"" -f $maxUserSize
                    throw $msg
                }}
            }}
        }}

        # Return a hashtable with the following members:
        #    BytesWritten - the number of bytes written to a file
        #
        function PSCopyFileToRemoteSession
        {{
            param(
                [string] $copyToFilePath,
                [string] $b64Fragment,
                [switch] $createFile = $false,
                [switch] $emptyFile = $false
            )

            $op = @{{
                BytesWritten = $null
            }}

            $wstream = $null

            try
            {{
                $filePathExists = Microsoft.PowerShell.Management\Test-Path -Path $copyToFilePath

                if ($createFile -or (! $filePathExists))
                {{
                    # If the file already exists, try to delete it.
                    if ($filePathExists)
                    {{
                        Microsoft.PowerShell.Management\Remove-Item -Path $copyToFilePath -Force -ea SilentlyContinue
                    }}

                    # Create the new file.
                    $fileInfo = Microsoft.PowerShell.Management\New-Item -Path $copyToFilePath -Type File -Force

                    if ($emptyFile)
                    {{
                        # Handle the empty file scenario.
                        $op['BytesWritten'] = 0
                        return $op
                    }}
                }}

                # Resolve path in case it is a PSDrive
                $resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $copyToFilePath

                # Decode
                $fragment = [System.Convert]::FromBase64String($b64Fragment)

                # Check if drive specifies max size and if max size is exceeded
                CheckPSDriveSize $resolvedPath $fragment.Length

                # Write fragment
                $wstream = Microsoft.PowerShell.Utility\New-Object -TypeName IO.FileStream -ArgumentList ($resolvedPath.ProviderPath), ([System.IO.FileMode]::Append)
                $wstream.Write($fragment, 0, $fragment.Length)

                $op['BytesWritten'] = $fragment.Length
            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}
            finally
            {{
                if ($null -ne $wstream)
                {{
                    $wstream.Dispose()
                }}
            }}

            return $op
        }}

        # Returns a hashtable with the following members:
        #    BytesWritten - number of bytes written to an alternate file stream
        #
        function PSCopyFileAlternateStreamToRemoteSession
        {{
            param (
                [string] $copyToFilePath,
                [string] $b64Fragment,
                [string] $streamName
            )

            $op = @{{
                BytesWritten = $null
            }}

            try
            {{
                # Resolve path in case it is a PSDrive
                $resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $copyToFilePath

                # Decode
                $fragment = [System.Convert]::FromBase64String($b64Fragment)

                # Check if drive specifies max size and if max size is exceeded
                CheckPSDriveSize $resolvedPath $fragment.Length

                # Write the stream
                Microsoft.PowerShell.Management\Add-Content -Path ($resolvedPath.ProviderPath) -Value $fragment -Encoding Byte -Stream $streamName -ErrorAction Stop
                $op['BytesWritten'] = $fragment.Length
            }}
            catch
            {{
                Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
            }}

            return $op
        }}

        # Returns a hashtable with the following member:
        #    TargetSupportsAlternateStreams - boolean to keep track of whether the target supports Alternate data streams.
        #
        function PSTargetSupportsAlternateStreams
        {{
            param (
                [string] $supportAltStreamPath
            )

            $result = @{{
                TargetSupportsAlternateStreams = $false
            }}

            # Resolve path in case it is a PSDrive
            $resolvedPath = Microsoft.PowerShell.Management\Resolve-Path -literal $supportAltStreamPath

            $targetDrive = [IO.Path]::GetPathRoot($resolvedPath.ProviderPath)
            if (-not $targetDrive)
            {{
                return $result
            }}

            # Check if the target drive is NTFS
            $driveFormat = 'NTFS'
            foreach ($drive in [System.IO.DriveInfo]::GetDrives())
            {{
                if (($drive.Name -eq $targetDrive) -and ($drive.DriveFormat -eq $driveFormat))
                {{
                    # Now, check if the target supports Add-Command -Stream. This functionality was introduced in version 3.0.
                    $addContentCmdlet = Microsoft.PowerShell.Core\Get-Command Microsoft.PowerShell.Management\Add-Content -ErrorAction SilentlyContinue
                    if ($addContentCmdlet.Parameters.Keys -contains 'Stream')
                    {{
                        $result['TargetSupportsAlternateStreams'] = $true
                        break
                    }}
                }}
            }}

            return $result
        }}

        # Sets the metadata for the given file.
        #
        function PSSetFileMetadata
        {{
            param (
                [string] $metaDataFilePath,
                [hashtable] $metaDataToSet
            )

            $item = Microsoft.PowerShell.Management\get-item $metaDataFilePath -ea SilentlyContinue -Force

            if ($item)
            {{
                # LastWriteTime
                if ($metaDataToSet['LastWriteTimeUtc'])
                {{
                    $item.LastWriteTimeUtc = $metaDataToSet['LastWriteTimeUtc']
                }}

                if ($metaDataToSet['LastWriteTime'])
                {{
                    $item.LastWriteTime = $metaDataToSet['LastWriteTime']
                }}

                # Attributes
                if ($metaDataToSet['Attributes'])
                {{
                    $item.Attributes = $metaDataToSet['Attributes']
                }}
            }}
        }}

        # Returns a hashtable with the following member:
        #    IsFileInfo - boolean to keep track of whether the given path is a remote file.
        #    IsDirectoryInfo - boolean to keep track of whether the given path is a remote directory.
        #    ParentIsDirectoryInfo - boolean to keep track of whether the given parent path is a remote directory.
        #
        function PSGetRemotePathInfo
        {{
            param (
                [string] $remotePath
            )

            try
            {{

                try
                {{
                    $parentPath = Microsoft.PowerShell.Management\Split-Path $remotePath
                }}
                # catch everything and ignore the error.
                catch {{}}

                $result = @{{
                    IsFileInfo = (Microsoft.PowerShell.Management\Test-Path $remotePath -PathType Leaf)
                    IsDirectoryInfo = (Microsoft.PowerShell.Management\Test-Path $remotePath -PathType Container)
                }}

                if ($parentPath)
                {{
                    $result['ParentIsDirectoryInfo'] = (Microsoft.PowerShell.Management\Test-Path $parentPath -PathType Container)
                }}

            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}

            return $result
        }}

        # Returns a hashtable with the following information:
        #  - IsFileInfotrue bool to keep track if the given destination is a FileInfo type.
        function PSRemoteDestinationPathIsFile
        {{
            param (
                [string] $isFilePath
            )

            $op = @{{
                IsFileInfo = $null
            }}

            try
            {{
                $op['IsFileInfo'] = (Microsoft.PowerShell.Management\Test-Path $isFilePath -PathType Leaf)
            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}

            return $op
        }}

        # Return a hash table in the following format:
        #      DirectoryPath is the directory to be created.
        #      PathExists is a bool to to keep track of whether the directory already exist.
        #
        # 1) If DirectoryPath already exists:
        #     a) If -Force is specified, force create the directory. Set DirectoryPath to the created directory path.
        #     b) If not -Force is specified, then set PathExists to $true.
        # 2) If DirectoryPath does not exist, create it. Set DirectoryPath to the created directory path.
        function PSCreateDirectoryOnRemoteSession
        {{
            param (
                [string] $createDirectoryPath,
                [switch] $force = $false
            )

            $op = @{{
                DirectoryPath = $null
                PathExists = $false
            }}

            try
            {{
                if (Microsoft.PowerShell.Management\Test-Path $createDirectoryPath)
                {{
                    # -Force is specified, then force create the directory.
                    if ($force)
                    {{
                        Microsoft.PowerShell.Management\New-Item $createDirectoryPath -ItemType Directory -Force | Out-Null
                        $op['DirectoryPath'] = $createDirectoryPath
                    }}
                    else
                    {{
                        $op['PathExists'] = $true
                    }}
                }}
                else
                {{
                    Microsoft.PowerShell.Management\New-Item $createDirectoryPath -ItemType Directory | Out-Null
                    $op['DirectoryPath'] = $createDirectoryPath
                }}
            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}

            return $op
        }}

        #
        # Call helper function based on bound parameter set
        #
        $params = $PSCmdlet.MyInvocation.BoundParameters
        switch ($PSCmdlet.ParameterSetName)
        {{
            ""PSCopyFileToRemoteSession""
            {{
                return PSCopyFileToRemoteSession @params
            }}

            ""PSCopyAlternateStreamToRemoteSession""
            {{
                return PSCopyFileAlternateStreamToRemoteSession @params
            }}

            ""PSTargetSupportsAlternateStreams""
            {{
                return PSTargetSupportsAlternateStreams @params
            }}

            ""PSSetFileMetadata""
            {{
                return PSSetFileMetadata @params
            }}

            ""PSRemoteDestinationPathIsFile""
            {{
                return PSRemoteDestinationPathIsFile @params
            }}

            ""PSGetRemotePathInfo""
            {{
                return PSGetRemotePathInfo @params
            }}

            ""PSCreateDirectoryOnRemoteSession""
            {{
                return PSCreateDirectoryOnRemoteSession @params
            }}
        }}
        ";

        private static string s_PSCopyToSessionHelper = functionToken + PSCopyToSessionHelperName + @"
        {
        " + s_PSCopyToSessionHelperDefinition + @"
        }
        ";

        private static Hashtable s_PSCopyToSessionHelperFunction = new Hashtable() {
            {nameToken, PSCopyToSessionHelperName},
            {definitionToken, s_PSCopyToSessionHelperDefinitionRestricted}
        };

        #endregion

        #region PSCopyFromSessionHelper

        internal const string PSCopyFromSessionHelperName = @"PSCopyFromSessionHelper";

        private static string s_PSCopyFromSessionHelperDefinition = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]");
        private static string s_PSCopyFromSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateUserDrive()]");

        private const string PSCopyFromSessionHelperDefinitionFormat = @"
        param (
            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
            {0}

            [string] $copyFromFilePath,

            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
            [ValidateRange(0, [long]::MaxValue)]
            [long] $copyFromStart,

            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"", Mandatory=$true)]
            [ValidateRange(0, [long]::MaxValue)]
            [long] $copyFromNumBytes,

            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
            [switch] $force,

            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
            [switch] $isAlternateStream,

            [Parameter(ParameterSetName=""PSCopyFileFromRemoteSession"")]
            [ValidateNotNullOrEmpty()]
            [string] $streamName,

            [Parameter(ParameterSetName=""PSSourceSupportsAlternateStreams"", Mandatory=$true)]
            {0}

            [string] $supportAltStreamPath,

            [Parameter(ParameterSetName=""PSGetFileMetadata"", Mandatory=$true)]
            {0}

            [string] $getMetaFilePath,

            [Parameter(ParameterSetName=""PSGetPathItems"", Mandatory=$true)]
            {0}

            [string] $getPathItems,

            [Parameter(ParameterSetName=""PSGetPathDirAndFiles"", Mandatory=$true)]
            {0}

            [string] $getPathDir
        )

        # A hash table with the following members is returned:
        #   - moreAvailable bool to keep track of whether there is more data available
        #   - b64Fragment to track of the number of bytes.
        #   - ExceptionThrown bool to keep track if an exception was thrown
        function PSCopyFileFromRemoteSession
        {{
            param(
                [string] $copyFromFilePath,
                [long] $copyFromStart,
                [long] $copyFromNumbytes,
                [switch] $force = $false,
                [switch] $isAlternateStream = $false,
                [string] $streamName
            )

            $finalResult = @{{
                b64Fragment = $null
                moreAvailable = $null
                ExceptionThrown = $false
            }}

            function PerformCopyFileFromRemoteSession
            {{
                param(
                    [string] $filePath,
                    [long] $start,
                    [long] $numBytes,
                    [switch] $isAlternateStream,
                    [string] $streamName
                )

                $op = @{{
                    b64Fragment = $null
                    moreAvailable = $false
                }}

                # Ensure bytes read is less than Max allowed
                $maxBytes = 10 * 1024 * 1024
                $numBytes = [Math]::Min($numBytes, $maxBytes)

                $rstream = $null
                try
                {{
                    if ($isAlternateStream)
                    {{
                        $content = Microsoft.PowerShell.Management\Get-Content $filePath -stream $streamName -Encoding Byte -Raw
                        $rstream = [System.IO.MemoryStream]::new($content)
                    }}
                    else
                    {{
                        $rstream = [System.IO.File]::OpenRead($filePath)
                    }}

                    # Create a new array to hold the file content
                    if ($start -lt $rstream.Length)
                    {{
                        $o = $rstream.Seek($start, 0)
                        $toRead = [Math]::Min($numBytes, $rstream.Length - $start)
                        $fragment = Microsoft.PowerShell.Utility\New-Object byte[] $toRead
                        $readsoFar = 0
                        while ($readsoFar -lt $toRead)
                        {{
                            $read = $rstream.Read($fragment, $readSoFar, $toRead - $readsoFar)
                            $readsoFar += $read
                        }}

                        $op['b64Fragment'] = [System.Convert]::ToBase64String($fragment)
                        if (($start + $readsoFar) -lt $rstream.Length)
                        {{
                            $op['moreAvailable'] = $true
                        }}
                    }}

                    $op
                }}
                finally
                {{
                    if ($null -ne $rstream)
                    {{
                        $rstream.Dispose()
                    }}
                }}
            }}

            function WriteException
            {{
                param ($ex)

                if ($ex.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $ex.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $ex.Exception
                }}

                $finalResult.ExceptionThrown = $true
            }}

            # Resolve path in case it is a PSDrive
            $resolvedFilePath = (Microsoft.PowerShell.Management\Resolve-Path -literal $copyFromFilePath).ProviderPath

            $unAuthorizedAccessException = $null
            $result = $null

            $isReadOnly = $false
            $isHidden = $false
            try
            {{
                $result = PerformCopyFileFromRemoteSession -filePath $resolvedFilePath -start $copyFromStart -numBytes $copyFromNumBytes -isAlternateStream:$isAlternateStream -streamName $streamName
                $finalResult.b64Fragment =  $result.b64Fragment
                $finalResult.moreAvailable =  $result.moreAvailable
            }}
            catch [System.UnauthorizedAccessException]
            {{
                $unAuthorizedAccessException = $_
                if ($force)
                {{
                    $exception = $null
                    try
                    {{
                        # Disable the readonly and hidden attributes and try again
                        $item = Microsoft.PowerShell.Management\Get-Item $resolvedFilePath

                        if ($item.Attributes.HasFlag([System.IO.FileAttributes]::Hidden))
                        {{
                            $isHidden = $true
                            $item.Attributes = $item.Attributes -band (-bnot ([System.IO.FileAttributes]::Hidden))
                        }}

                        if ($item.Attributes.HasFlag([System.IO.FileAttributes]::ReadOnly))
                        {{
                            $isReadOnly = $true
                            $item.Attributes = $item.Attributes -band (-bnot ([System.IO.FileAttributes]::ReadOnly))
                        }}

                        $result = PerformCopyFileFromRemoteSession -filePath $resolvedFilePath -start $copyFromStart -numBytes $copyFromNumBytes -isAlternateStream:$isAlternateStream
                        $finalResult.b64Fragment =  $result.b64Fragment
                        $finalResult.moreAvailable =  $result.moreAvailable
                    }}
                    catch
                    {{
                        $e = $_
                        if (($e.Exception.InnerException -is [System.IO.FileNotFoundException]) -or
                            ($e.Exception.InnerException -is [System.IO.DirectoryNotFoundException]) -or
                            ($e.Exception.InnerException -is [System.Security.SecurityException] ) -or
                            ($e.Exception.InnerException -is [System.ArgumentException]) -or
                            ($e.Exception.InnerException -is [System.IO.IOException]))
                        {{
                            # Write out the original error since we failed to force the copy
                            WriteException $unAuthorizedAccessException
                        }}
                        else
                        {{
                            WriteException $e
                        }}

                        $finalResult.ExceptionThrown = $true
                    }}
                }}
                else
                {{
                    $finalResult.ExceptionThrown = $true
                    WriteException $unAuthorizedAccessException
                }}
            }}
            catch
            {{
                WriteException $_
            }}
            finally
            {{
                if ($isReadOnly)
                {{
                    $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::ReadOnly
                }}

                if ($isHidden)
                {{
                    $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
                }}
            }}

            return $finalResult
        }}

        # Returns a hashtable with the following members:
        #    SourceSupportsAlternateStreams - boolean to keep track of whether the source supports Alternate data streams.
        #    Streams - the list of alternate streams
        #
        function PSSourceSupportsAlternateStreams
        {{
            param ([string]$supportAltStreamPath)

            $result = @{{
                SourceSupportsAlternateStreams = $false
                Streams = @()
            }}

            # Check if the source supports 'Get-Content -Stream'. This functionality was introduced in version 3.0.
            $getContentCmdlet = Microsoft.PowerShell.Core\Get-Command Microsoft.PowerShell.Management\Get-Content -ErrorAction SilentlyContinue
            if ($getContentCmdlet.Parameters.Keys -notcontains 'Stream')
            {{
                return $result
            }}

            $result['SourceSupportsAlternateStreams'] = $true

            # Check if the file has any alternate data streams.
            $item = Microsoft.PowerShell.Management\Get-Item -Path $supportAltStreamPath -Stream * -ea SilentlyContinue
            if (-not $item)
            {{
                return $result
            }}

            foreach ($streamName in $item.Stream)
            {{
                if ($streamName -ne ':$DATA')
                {{
                    $result['Streams'] += $streamName
                }}
            }}

            return $result
        }}

        # Returns a hash table with metadata info about the file for the given path.
        #
        function PSGetFileMetadata
        {{
            param ($getMetaFilePath)

            if (-not (Microsoft.PowerShell.Management\Test-Path $getMetaFilePath))
            {{
                return
            }}

            $item = Microsoft.PowerShell.Management\Get-Item $getMetaFilePath -Force -ea SilentlyContinue
            if ($item)
            {{
                $metadata = @{{}}

                # Attributes
                $attributes = @($item.Attributes.ToString().Split(',').Trim())
                if ($attributes.Count -gt 0)
                {{
                    $metadata.Add('Attributes', $attributes)
                }}

                # LastWriteTime
                $metadata.Add('LastWriteTime', $item.LastWriteTime)
                $metadata.Add('LastWriteTimeUtc', $item.LastWriteTimeUtc)

                return $metadata
            }}
        }}

        # Converts file system path to PSDrive path
        #    Returns converted path or original path if not conversion is needed.
        function ConvertToPSDrivePath
        {{
            param (
                [System.Management.Automation.PSDriveInfo] $driveInfo,
                [string] $pathToConvert
            )

            if (!($driveInfo) -or !($driveInfo.Name) -or !($driveInfo.Root))
            {{
                return $pathToConvert
            }}

            if (! ($driveInfo.Root.StartsWith($driveInfo.Name)))
            {{
                return $pathToConvert.ToUpper().Replace($driveInfo.Root.ToUpper(), (($driveInfo.Name) + "":""))
            }}

            return $pathToConvert
        }}

        ## A hashtable is returned in the following format:
        ##  Exists - Boolean to keep track if the given path exists.
        ##  Items  - The items that Get-Item -Path $path resolves to.
        function PSGetPathItems
        {{
            param (
                [string] $getPathItems
            )

            $op = @{{
                Exists = $null
                Items = $null
            }}

            try
            {{
                if (-not (Microsoft.PowerShell.Management\Test-Path $getPathItems))
                {{
                    $op['Exists'] = $false
                    return $op
                }}

                $items = @(Microsoft.PowerShell.Management\Get-Item -Path $getPathItems | Microsoft.PowerShell.Core\ForEach-Object {{
                    @{{
                        FullName = ConvertToPSDrivePath $_.PSDrive $_.FullName;
                        Name = $_.Name;
                        FileSize = $_.Length; IsDirectory = $_ -is [System.IO.DirectoryInfo]
                     }}
                }})
                $op['Exists'] = $true
                $op['Items'] = $items

                return $op
            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}

            return $op
        }}

        # Return a hashtable with the following members:
        # Files - Array with file fullnames, and their sizes
        # Directories - Array of child directory fullnames
        function PSGetPathDirAndFiles
        {{
            param (
                [string] $getPathDir
            )

            $result = @()

            $op = @{{
                Files = $null
                Directories = $null
            }}

            try
            {{
                $item = Microsoft.PowerShell.Management\Get-Item $getPathDir

                if ($item -isnot [System.IO.DirectoryInfo])
                {{
                    return $op
                }}

                $files = @(Microsoft.PowerShell.Management\Get-ChildItem -Path $getPathDir -File | Microsoft.PowerShell.Core\ForEach-Object {{
                    @{{ FileName = $_.Name;
                        FilePath = (ConvertToPSDrivePath $_.PSDrive $_.FullName);
                        FileSize = $_.Length
                     }}
                }})

                $directories = @(Microsoft.PowerShell.Management\Get-ChildItem -Path $getPathDir -Directory | Microsoft.PowerShell.Core\ForEach-Object {{
                    @{{ Name = $_.Name;
                        FullName = (ConvertToPSDrivePath $_.PSDrive $_.FullName)
                     }}
                }})

                if ($files.count -gt 0)
                {{
                    $op['Files'] = $files
                }}

                if ($directories.count -gt 0)
                {{
                    $op['Directories'] = $directories
                }}
            }}
            catch
            {{
                if ($_.Exception.InnerException)
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception.InnerException
                }}
                else
                {{
                    Microsoft.PowerShell.Utility\Write-Error -Exception $_.Exception
                }}
            }}

            return $op
        }}

        #
        # Call helper function based on bound parameter set
        #
        $params = $PSCmdlet.MyInvocation.BoundParameters
        switch ($PSCmdlet.ParameterSetName)
        {{
            ""PSCopyFileFromRemoteSession""
            {{
                return PSCopyFileFromRemoteSession @params
            }}

            ""PSSourceSupportsAlternateStreams""
            {{
                PSSourceSupportsAlternateStreams @params
            }}

            ""PSGetFileMetadata""
            {{
                PSGetFileMetadata @params
            }}

            ""PSGetPathItems""
            {{
                PSGetPathItems @params
            }}

            ""PSGetPathDirAndFiles""
            {{
                PSGetPathDirAndFiles @params
            }}
        }}
        ";

        internal static readonly string PSCopyFromSessionHelper = functionToken + PSCopyFromSessionHelperName + @"
        {
        " + s_PSCopyFromSessionHelperDefinition + @"
        }
        ";

        private static Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() {
            {nameToken, PSCopyFromSessionHelperName},
            {definitionToken, s_PSCopyFromSessionHelperDefinitionRestricted}
        };

        #endregion

        #region PSCopyRemoteUtils

        internal const string PSCopyRemoteUtilsName = @"PSCopyRemoteUtils";

        internal static readonly string PSCopyRemoteUtilsDefinition = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateNotNullOrEmpty()]", PSValidatePathFunction);
        private static string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction);

        private const string PSCopyRemoteUtilsDefinitionFormat = @"
        param (
            [Parameter(ParameterSetName=""PSRemoteDirectoryExist"", Mandatory=$true)]
            {0}

            [string] $dirPathExists,

            [Parameter(ParameterSetName=""PSValidatePath"", Mandatory=$true)]
            {0}

            [string] $pathToValidate,

            [Parameter(ParameterSetName=""PSValidatePath"")]
            [switch] $sourceIsRemote
        )

        # Returns a hashtable with the following member:
        #    Exists - boolean to keep track of whether the given path exists for a remote directory.
        #
        function PSRemoteDirectoryExist
        {{
            param (
                [string] $dirPathExists
            )

            $result = @{{ Exists = (Microsoft.PowerShell.Management\Test-Path $dirPathExists -PathType Container) }}

            return $result
        }}

        {1}

        #
        # Call helper function based on bound parameter set
        #
        $params = $PSCmdlet.MyInvocation.BoundParameters
        switch ($PSCmdlet.ParameterSetName)
        {{
            ""PSRemoteDirectoryExist""
            {{
                return PSRemoteDirectoryExist @params
            }}

            ""PSValidatePath""
            {{
                return PSValidatePath @params
            }}
        }}
        ";

        private const string PSValidatePathFunction = functionToken + "PSValidatePath" + @"
        {
        " + PSValidatePathDefinition + @"
        }
        ";

        internal const string PSValidatePathDefinition = @"
        # Return hashtable in the following format:
        #   Exists - boolean to keep track if the given path exists
        #   Root - the root for the given path. If wildcards are used, it returns the first drive root.
        #   IsAbsolute - boolean to keep track of whether the given path is absolute
        param (
            [string] $pathToValidate,
            [switch] $sourceIsRemote
        )

        function SafeGetDriveRoot
        {
            param (
                [System.Management.Automation.PSDriveInfo] $driveInfo
            )

            if (! ($driveInfo.Root.StartsWith($driveInfo.Name)))
            {
                return (($driveInfo.Name) + "":"")
            }
            else
            {
                $driveInfo.Root
            }
        }

        $result = @{
            Exists = $null
            Root = $null
            IsAbsolute = $null
        }

        # Validate if the path is absolute
        $result['IsAbsolute'] = (Microsoft.PowerShell.Management\Split-Path $pathToValidate -IsAbsolute)
        if (-not $result['IsAbsolute'])
        {
            return $result
        }

        # Check if the given path exists.
        $result['Exists'] = (Microsoft.PowerShell.Management\Test-Path $pathToValidate)

        # If $pathToValidate is a remote source, and it does not exist, return.
        if ($sourceIsRemote -and (-not $result['Exists']))
        {
            return $result
        }

        # If the path does not exist, check if we can find its root.
        if (-not (Microsoft.PowerShell.Management\Test-Path $pathToValidate))
        {
            $possibleRoot = $null

            try
            {
                $possibleRoot = [System.IO.Path]::GetPathRoot($pathToValidate)
            }
            # Catch everything and ignore the error.
            catch {}

            if (-not $possibleRoot)
            {
                return $result
            }

            # Now use this path to find its root.
            $pathToValidate = $possibleRoot
        }

        # Get the root path using Get-Item
        $item = Microsoft.PowerShell.Management\Get-Item $pathToValidate -ea SilentlyContinue
        if (($null -ne $item) -and ($item[0].PSProvider.Name -eq 'FileSystem'))
        {
            $result['Root'] = SafeGetDriveRoot $item[0].PSDrive
            return $result
        }

        # If this fails, try to get them via Get-PSDrive
        $fileSystemDrives = @(Microsoft.PowerShell.Management\Get-PSDrive -PSProvider FileSystem -ea SilentlyContinue)

        # If this fails, try to get them via Get-PSProvider
        if ($fileSystemDrives.Count -eq 0)
        {
            $fileSystemDrives = @((Microsoft.PowerShell.Management\Get-PSProvider -PSProvider FileSystem -ea SilentlyContinue).Drives)
        }

        foreach ($drive in  $fileSystemDrives)
        {
            if ($pathToValidate.StartsWith($drive.Root))
            {
                $result['Root'] = SafeGetDriveRoot $drive
                break
            }
        }

        return $result
        ";

        internal static readonly string PSCopyRemoteUtils = functionToken + PSCopyRemoteUtilsName + @"
        {
        " + PSCopyRemoteUtilsDefinition + @"
        }
        ";

        internal static readonly Hashtable PSCopyRemoteUtilsFunction = new Hashtable() {
            {nameToken, PSCopyRemoteUtilsName},
            {definitionToken, s_PSCopyRemoteUtilsDefinitionRestricted}
        };

        #endregion

        internal static readonly string AllCopyToRemoteScripts = s_PSCopyToSessionHelper + PSCopyRemoteUtils;

        internal static IEnumerable<Hashtable> GetAllCopyToRemoteScriptFunctions()
        {
            yield return s_PSCopyToSessionHelperFunction;
            yield return PSCopyRemoteUtilsFunction;
        }

        internal static readonly string AllCopyFromRemoteScripts = PSCopyFromSessionHelper + PSCopyRemoteUtils;

        internal static IEnumerable<Hashtable> GetAllCopyFromRemoteScriptFunctions()
        {
            yield return s_PSCopyFromSessionHelperFunction;
            yield return PSCopyRemoteUtilsFunction;
        }
    }

    #endregion
}
