Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
NavigationProviderBase.cs1092 linesDownload Raw Back to namespaces
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Management.Automation.Internal;6 7namespace System.Management.Automation.Provider8{9    #region NavigationCmdletProvider10 11    /// <summary>12    /// The base class for a Cmdlet provider that expose a hierarchy of items and containers.13    /// </summary>14    /// <remarks>15    /// The NavigationCmdletProvider class is a base class that provider can derive from16    /// to implement a set of methods that allow17    /// the use of a set of core commands against the data store that the provider18    /// gives access to. By implementing this interface users can take advantage19    /// the recursive commands, nested containers, and relative paths.20    /// </remarks>21    public abstract class NavigationCmdletProvider : ContainerCmdletProvider22    {23        #region Internal methods24 25        /// <summary>26        /// Internal wrapper for the MakePath protected method. It is called instead27        /// of the protected method that is overridden by derived classes so that the28        /// context of the command can be set.29        /// </summary>30        /// <param name="parent">31        /// The parent segment of a path to be joined with the child.32        /// </param>33        /// <param name="child">34        /// The child segment of a path to be joined with the parent.35        /// </param>36        /// <param name="context">37        /// The context under which this method is being called.38        /// </param>39        /// <returns>40        /// A string that represents the parent and child segments of the path41        /// joined by a path separator.42        /// </returns>43        /// <remarks>44        /// This method should use lexical joining of two path segments with a path45        /// separator character. It should not validate the path as a legal fully46        /// qualified path in the provider namespace as each parameter could be only47        /// partial segments of a path and joined they may not generate a fully48        /// qualified path.49        /// Example: the file system provider may get "windows\system32" as the parent50        /// parameter and "foo.dll" as the child parameter. The method should join these51        /// with the "\" separator and return "windows\system32\foo.dll". Note that52        /// the returned path is not a fully qualified file system path.53        ///54        /// Also beware that the path segments may contain characters that are illegal55        /// in the provider namespace. These characters are most likely being used56        /// for globbing and should not be removed by the implementation of this method.57        /// </remarks>58        internal string MakePath(59            string parent,60            string child,61            CmdletProviderContext context)62        {63            Context = context;64 65            // Call virtual method66 67            return MakePath(parent, child);68        }69 70        /// <summary>71        /// Internal wrapper for the GetParentPath protected method. It is called instead72        /// of the protected method that is overridden by derived classes so that the73        /// context of the command can be set.74        /// </summary>75        /// <param name="path">76        /// A fully qualified provider specific path to an item. The item may or77        /// may not exist.78        /// </param>79        /// <param name="root">80        /// The fully qualified path to the root of a drive. This parameter may be null81        /// or empty if a mounted drive is not in use for this operation. If this parameter82        /// is not null or empty the result of the method should not be a path to a container83        /// that is a parent or in a different tree than the root.84        /// </param>85        /// <param name="context">86        /// The context under which this method is being called.87        /// </param>88        /// <returns>89        /// The path of the parent of the path parameter.90        /// </returns>91        /// <remarks>92        /// This should be a lexical splitting of the path on the path separator character93        /// for the provider namespace. For example, the file system provider should look94        /// for the last "\" and return everything to the left of the "\".95        /// </remarks>96        internal string GetParentPath(97            string path,98            string root,99            CmdletProviderContext context)100        {101            Context = context;102 103            // Call virtual method104 105            return GetParentPath(path, root);106        }107 108        /// <summary>109        /// Internal wrapper for the NormalizeRelativePath method. It is called instead110        /// of the protected method that is overridden by derived classes so that the111        /// context of the command can be set.112        /// </summary>113        /// <param name="path">114        /// A fully qualified provider specific path to an item. The item should exist115        /// or the provider should write out an error.116        /// </param>117        /// <param name="basePath">118        /// The path that the return value should be relative to.119        /// </param>120        /// <param name="context">121        /// The context under which this method is being called.122        /// </param>123        /// <returns>124        /// A normalized path that is relative to the basePath that was passed. The125        /// provider should parse the path parameter, normalize the path, and then126        /// return the normalized path relative to the basePath.127        /// </returns>128        /// <remarks>129        /// This method does not have to be purely syntactical parsing of the path. It130        /// is encouraged that the provider actually use the path to lookup in its store131        /// and create a relative path that matches the casing, and standardized path syntax.132        /// </remarks>133        internal string NormalizeRelativePath(134            string path,135            string basePath,136            CmdletProviderContext context)137        {138            Context = context;139 140            // Call virtual method141 142            return NormalizeRelativePath(path, basePath);143        }144 145        /// <summary>146        /// Internal wrapper for the GetChildName protected method. It is called instead147        /// of the protected method that is overridden by derived classes so that the148        /// context of the command can be set.149        /// </summary>150        /// <param name="path">151        /// The fully qualified path to the item152        /// </param>153        /// <returns>154        /// The leaf element in the path.155        /// </returns>156        /// <param name="context">157        /// The context under which this method is being called.158        /// </param>159        /// <remarks>160        /// This should be implemented as a split on the path separator. The characters161        /// in the fullPath may not be legal characters in the namespace but may be162        /// used in globing or regular expression matching. The provider should not error163        /// unless there are no path separators in the fully qualified path.164        /// </remarks>165        internal string GetChildName(166            string path,167            CmdletProviderContext context)168        {169            Context = context;170 171            // Call virtual method172 173            return GetChildName(path);174        }175 176        /// <summary>177        /// Internal wrapper for the IsItemContainer protected method. It is called instead178        /// of the protected method that is overridden by derived classes so that the179        /// context of the command can be set.180        /// </summary>181        /// <param name="path">182        /// The path to the item to determine if it is a container.183        /// </param>184        /// <param name="context">185        /// The context under which this method is being called.186        /// </param>187        /// <returns>188        /// true if the item specified by path is a container, false otherwise.189        /// </returns>190        internal bool IsItemContainer(191            string path,192            CmdletProviderContext context)193        {194            Context = context;195 196            // Call virtual method197 198            return IsItemContainer(path);199        }200 201        /// <summary>202        /// Internal wrapper for the MoveItem protected method. It is called instead203        /// of the protected method that is overridden by derived classes so that the204        /// context of the command can be set.205        /// </summary>206        /// <param name="path">207        /// The path to the item to be moved.208        /// </param>209        /// <param name="destination">210        /// The path of the destination container.211        /// </param>212        /// <param name="context">213        /// The context under which this method is being called.214        /// </param>215        /// <returns>216        /// Nothing. All objects that are moved should be written to the WriteObject method.217        /// </returns>218        internal void MoveItem(219            string path,220            string destination,221            CmdletProviderContext context)222        {223            Context = context;224 225            // Call virtual method226 227            MoveItem(path, destination);228        }229 230        /// <summary>231        /// Gives the provider to attach additional parameters to232        /// the move-item cmdlet.233        /// </summary>234        /// <param name="path">235        /// If the path was specified on the command line, this is the path236        /// to the item to get the dynamic parameters for.237        /// </param>238        /// <param name="destination">239        /// The path of the destination container.240        /// </param>241        /// <param name="context">242        /// The context under which this method is being called.243        /// </param>244        /// <returns>245        /// An object that has properties and fields decorated with246        /// parsing attributes similar to a cmdlet class.247        /// </returns>248        internal object MoveItemDynamicParameters(249            string path,250            string destination,251            CmdletProviderContext context)252        {253            Context = context;254            return MoveItemDynamicParameters(path, destination);255        }256 257        #endregion Internal methods258 259        #region protected methods260 261        /// <summary>262        /// Joins two strings with a path a provider specific path separator.263        /// </summary>264        /// <param name="parent">265        /// The parent segment of a path to be joined with the child.266        /// </param>267        /// <param name="child">268        /// The child segment of a path to be joined with the parent.269        /// </param>270        /// <returns>271        /// A string that represents the parent and child segments of the path272        /// joined by a path separator.273        /// </returns>274        /// <remarks>275        /// This method should use lexical joining of two path segments with a path276        /// separator character. It should not validate the path as a legal fully277        /// qualified path in the provider namespace as each parameter could be only278        /// partial segments of a path and joined they may not generate a fully279        /// qualified path.280        /// Example: the file system provider may get "windows\system32" as the parent281        /// parameter and "foo.dll" as the child parameter. The method should join these282        /// with the "\" separator and return "windows\system32\foo.dll". Note that283        /// the returned path is not a fully qualified file system path.284        ///285        /// Also beware that the path segments may contain characters that are illegal286        /// in the provider namespace. These characters are most likely being used287        /// for globbing and should not be removed by the implementation of this method.288        /// </remarks>289        protected virtual string MakePath(string parent, string child)290        {291            return MakePath(parent, child, childIsLeaf: false);292        }293 294        /// <summary>295        /// Joins two strings with a path a provider specific path separator.296        /// </summary>297        /// <param name="parent">298        /// The parent segment of a path to be joined with the child.299        /// </param>300        /// <param name="child">301        /// The child segment of a path to be joined with the parent.302        /// </param>303        /// <param name="childIsLeaf">304        /// Indicate that the <paramref name="child"/> is the name of a child item that's guaranteed to exist305        /// </param>306        /// <remarks>307        /// If the <paramref name="childIsLeaf"/> is True, then we don't normalize the child path, and would do308        /// some checks to decide whether to normalize the parent path.309        /// </remarks>310        /// <returns>New path string.</returns>311        protected string MakePath(string parent, string child, bool childIsLeaf)312        {313            using (PSTransactionManager.GetEngineProtectionScope())314            {315                string result = null;316 317                if (parent == null &&318                    child == null)319                {320                    throw PSTraceSource.NewArgumentException(nameof(parent));321                }322 323                if (string.IsNullOrEmpty(parent) &&324                    string.IsNullOrEmpty(child))325                {326                    result = string.Empty;327                }328                else if (string.IsNullOrEmpty(parent) &&329                         !string.IsNullOrEmpty(child))330                {331                    result = NormalizePath(child);332                }333                else if (!string.IsNullOrEmpty(parent) &&334                         (string.IsNullOrEmpty(child) ||335                          child.Equals(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) ||336                          child.Equals(StringLiterals.AlternatePathSeparatorString, StringComparison.Ordinal)))337                {338                    if (parent.EndsWith(StringLiterals.DefaultPathSeparator))339                    {340                        result = parent;341                    }342                    else343                    {344                        result = parent + StringLiterals.DefaultPathSeparator;345                    }346                }347                else348                {349                    // Both parts are not empty so join them350                    // 'childIsLeaf == true' indicates that 'child' is actually the name of a child item and351                    // guaranteed to exist. In this case, we don't normalize the child path.352                    if (childIsLeaf)353                    {354                        parent = NormalizePath(parent);355                    }356                    else357                    {358                        // Normalize the path so that only the default path separator is used as a359                        // separator even if the user types the alternate slash.360                        parent = NormalizePath(parent);361                        child = NormalizePath(child);362                    }363 364                    ReadOnlySpan<char> appendChild = child.AsSpan();365                    if (child.StartsWith(StringLiterals.DefaultPathSeparator))366                    {367                        appendChild = appendChild.Slice(1);368                    }369 370                    result = IO.Path.Join(parent.AsSpan(), appendChild);371                }372 373                return result;374            }375        }376 377        /// <summary>378        /// Removes the child segment of a path and returns the remaining parent379        /// portion.380        /// </summary>381        /// <param name="path">382        /// A fully qualified provider specific path to an item. The item may or383        /// may not exist.384        /// </param>385        /// <param name="root">386        /// The fully qualified path to the root of a drive. This parameter may be null387        /// or empty if a mounted drive is not in use for this operation. If this parameter388        /// is not null or empty the result of the method should not be a path to a container389        /// that is a parent or in a different tree than the root.390        /// </param>391        /// <returns>392        /// The path of the parent of the path parameter.393        /// </returns>394        /// <remarks>395        /// This should be a lexical splitting of the path on the path separator character396        /// for the provider namespace. For example, the file system provider should look397        /// for the last "\" and return everything to the left of the "\".398        /// </remarks>399        protected virtual string GetParentPath(string path, string root)400        {401            using (PSTransactionManager.GetEngineProtectionScope())402            {403                string parentPath = null;404 405                // Verify the parameters406 407                if (string.IsNullOrEmpty(path))408                {409                    throw PSTraceSource.NewArgumentException(nameof(path));410                }411 412                if (root == null)413                {414                    if (PSDriveInfo != null)415                    {416                        root = PSDriveInfo.Root;417                    }418                }419 420                // Normalize the path421 422                path = NormalizePath(path);423                path = path.TrimEnd(StringLiterals.DefaultPathSeparator);424                string rootPath = string.Empty;425 426                if (root != null)427                {428                    rootPath = NormalizePath(root);429                }430 431                // Check to see if the path is equal to the root432                // of the virtual drive433 434                if (string.Equals(435                    path,436                    rootPath,437                    StringComparison.OrdinalIgnoreCase))438                {439                    parentPath = string.Empty;440                }441                else442                {443                    int lastIndex = path.LastIndexOf(StringLiterals.DefaultPathSeparator);444 445                    if (lastIndex != -1)446                    {447                        if (lastIndex == 0)448                        {449                            ++lastIndex;450                        }451                        // Get the parent directory452 453                        parentPath = path.Substring(0, lastIndex);454                    }455                    else456                    {457                        parentPath = string.Empty;458                    }459                }460 461                return parentPath;462            }463        }464 465        /// <summary>466        /// Normalizes the path that was passed in and returns the normalized path467        /// as a relative path to the basePath that was passed.468        /// </summary>469        /// <param name="path">470        /// A fully qualified provider specific path to an item. The item should exist471        /// or the provider should write out an error.472        /// </param>473        /// <param name="basePath">474        /// The path that the return value should be relative to.475        /// </param>476        /// <returns>477        /// A normalized path that is relative to the basePath that was passed. The478        /// provider should parse the path parameter, normalize the path, and then479        /// return the normalized path relative to the basePath.480        /// </returns>481        /// <remarks>482        /// This method does not have to be purely syntactical parsing of the path. It483        /// is encouraged that the provider actually use the path to lookup in its store484        /// and create a relative path that matches the casing, and standardized path syntax.485        ///486        /// Note, the base class implementation uses GetParentPath, GetChildName, and MakePath487        /// to normalize the path and then make it relative to basePath. All string comparisons488        /// are done using StringComparison.InvariantCultureIgnoreCase.489        /// </remarks>490        protected virtual string NormalizeRelativePath(491            string path,492            string basePath)493        {494            using (PSTransactionManager.GetEngineProtectionScope())495            {496                return ContractRelativePath(path, basePath, false, Context);497            }498        }499 500        internal string ContractRelativePath(501            string path,502            string basePath,503            bool allowNonExistingPaths,504            CmdletProviderContext context)505        {506            Context = context;507 508            if (path == null)509            {510                throw PSTraceSource.NewArgumentNullException(nameof(path));511            }512 513            if (path.Length == 0)514            {515                return string.Empty;516            }517 518            basePath ??= string.Empty;519 520            providerBaseTracer.WriteLine("basePath = {0}", basePath);521 522            string result = path;523            bool originalPathHadTrailingSlash = false;524 525            string normalizedPath = path;526            string normalizedBasePath = basePath;527 528            // NTRAID#Windows 7-697922-2009/06/29-leeholm529            // WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND530            //531            // This path normalization got moved here from the MakePath override in V2 to prevent532            // over-normalization of paths. This was a net-improvement for providers that use the default533            // implementations, but now incorrectly replaces forward slashes with back slashes during the call to534            // GetParentPath and GetChildName. This breaks providers that are sensitive to slash direction, the only535            // one we are aware of being the Active Directory provider. This change prevents this over-normalization536            // from being done on AD paths.537            //538            // For more information, see Win7:695292. Do not change this code without closely working with the539            // Active Directory team.540            //541            // WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND WORKAROUND542            if (!string.Equals(context.ProviderInstance.ProviderInfo.FullName,543                @"Microsoft.ActiveDirectory.Management\ActiveDirectory", StringComparison.OrdinalIgnoreCase))544            {545                normalizedPath = NormalizePath(path);546                normalizedBasePath = NormalizePath(basePath);547            }548 549            do // false loop550            {551                // Convert to the correct path separators and trim trailing separators552                string originalPath = path;553                Stack<string> tokenizedPathStack = null;554 555                if (path.EndsWith(StringLiterals.DefaultPathSeparator))556                {557                    path = path.TrimEnd(StringLiterals.DefaultPathSeparator);558                    originalPathHadTrailingSlash = true;559                }560 561                basePath = basePath.TrimEnd(StringLiterals.DefaultPathSeparator);562 563                // See if the base and the path are already the same. We resolve this to564                // ..\Leaf, since resolving "." to "." doesn't offer much information.565                if (string.Equals(normalizedPath, normalizedBasePath, StringComparison.OrdinalIgnoreCase) &&566                    (!originalPath.EndsWith(StringLiterals.DefaultPathSeparator)))567                {568                    string childName = GetChildName(path);569                    result = MakePath("..", childName);570                    break;571                }572 573                // If the base path isn't really a base, then we resolve to a parent574                // path (such as ../../foo)575                if (!normalizedPath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase) &&576                    (basePath.Length > 0))577                {578                    result = string.Empty;579                    string commonBase = GetCommonBase(normalizedPath, normalizedBasePath);580 581                    Stack<string> parentNavigationStack = TokenizePathToStack(normalizedBasePath, commonBase);582                    int parentPopCount = parentNavigationStack.Count;583 584                    if (string.IsNullOrEmpty(commonBase))585                    {586                        parentPopCount--;587                    }588 589                    for (int leafCounter = 0; leafCounter < parentPopCount; leafCounter++)590                    {591                        result = MakePath("..", result);592                    }593 594                    // This is true if we get passed a base path like:595                    //    c:\directory1\directory2596                    // and an actual path of597                    //    c:\directory1598                    // Which happens when the user is in c:\directory1\directory2599                    // and wants to resolve something like:600                    // ..\..\dir*601                    // In that case (as above,) we keep the ..\..\directory1602                    // instead of ".." as would usually be returned603                    if (!string.IsNullOrEmpty(commonBase))604                    {605                        if (string.Equals(normalizedPath, commonBase, StringComparison.OrdinalIgnoreCase) &&606                            (!normalizedPath.EndsWith(StringLiterals.DefaultPathSeparator)))607                        {608                            string childName = GetChildName(path);609                            result = MakePath("..", result);610                            result = MakePath(result, childName);611                        }612                        else613                        {614                            string[] childNavigationItems = TokenizePathToStack(normalizedPath, commonBase).ToArray();615 616                            for (int leafCounter = 0; leafCounter < childNavigationItems.Length; leafCounter++)617                            {618                                result = MakePath(result, childNavigationItems[leafCounter]);619                            }620                        }621                    }622                }623                // Otherwise, we resolve to a child path (such as foo/bar)624                else625                {626                    tokenizedPathStack = TokenizePathToStack(path, basePath);627 628                    // Now we have to normalize the path629                    // by processing each token on the stack630                    Stack<string> normalizedPathStack;631 632                    try633                    {634                        normalizedPathStack = NormalizeThePath(tokenizedPathStack, path, basePath, allowNonExistingPaths);635                    }636                    catch (ArgumentException argumentException)637                    {638                        WriteError(new ErrorRecord(argumentException, argumentException.GetType().FullName, ErrorCategory.InvalidArgument, null));639                        result = null;640                        break;641                    }642 643                    // Now that the path has been normalized, create the relative path644                    result = CreateNormalizedRelativePathFromStack(normalizedPathStack);645                }646            } while (false);647 648            if (originalPathHadTrailingSlash)649            {650                result += StringLiterals.DefaultPathSeparator;651            }652 653            return result;654        }655 656        /// <summary>657        /// Get the common base path of two paths.658        /// </summary>659        /// <param name="path1">One path.</param>660        /// <param name="path2">Another path.</param>661        private string GetCommonBase(string path1, string path2)662        {663            // Always see if the shorter path is a substring of the664            // longer path. If it is not, take the child off of the longer665            // path and compare again.666 667            while (!string.Equals(path1, path2, StringComparison.OrdinalIgnoreCase))668            {669                if (path2.Length > path1.Length)670                {671                    path2 = GetParentPath(path2, null);672                }673                else674                {675                    path1 = GetParentPath(path1, null);676                }677            }678 679            return path1;680        }681 682        /// <summary>683        /// Gets the name of the leaf element in the specified path.684        /// </summary>685        /// <param name="path">686        /// The fully qualified path to the item687        /// </param>688        /// <returns>689        /// The leaf element in the path.690        /// </returns>691        /// <remarks>692        /// This should be implemented as a split on the path separator. The characters693        /// in the fullPath may not be legal characters in the namespace but may be694        /// used in globing or regular expression matching. The provider should not error695        /// unless there are no path separators in the fully qualified path.696        /// </remarks>697        protected virtual string GetChildName(string path)698        {699            using (PSTransactionManager.GetEngineProtectionScope())700            {701                // Verify the parameters702 703                if (string.IsNullOrEmpty(path))704                {705                    throw PSTraceSource.NewArgumentException(nameof(path));706                }707 708                // Normalize the path709                path = NormalizePath(path);710                // Trim trailing back slashes711                path = path.TrimEnd(StringLiterals.DefaultPathSeparator);712                string result = null;713 714                int separatorIndex = path.LastIndexOf(StringLiterals.DefaultPathSeparator);715 716                // Since there was no path separator return the entire path717                if (separatorIndex == -1)718                {719                    result = path;720                }721                // If the full path existed, we must semantically evaluate the parent path722                else if (ItemExists(path, Context))723                {724                    string parentPath = GetParentPath(path, null);725 726                    // No parent, return the entire path727                    if (string.IsNullOrEmpty(parentPath))728                        result = path;729                    // If the parent path ends with the path separator, we can't split730                    // the path based on that731                    else if (parentPath.IndexOf(StringLiterals.DefaultPathSeparator) == (parentPath.Length - 1))732                    {733                        separatorIndex = path.IndexOf(parentPath, StringComparison.OrdinalIgnoreCase) + parentPath.Length;734                        result = path.Substring(separatorIndex);735                    }736                    else737                    {738                        separatorIndex = path.IndexOf(parentPath, StringComparison.OrdinalIgnoreCase) + parentPath.Length;739                        result = path.Substring(separatorIndex + 1);740                    }741                }742                // Otherwise, use lexical parsing743                else744                {745                    result = path.Substring(separatorIndex + 1);746                }747 748                return result;749            }750        }751 752        /// <summary>753        /// Determines if the item specified by the path is a container.754        /// </summary>755        /// <param name="path">756        /// The path to the item to determine if it is a container.757        /// </param>758        /// <returns>759        /// true if the item specified by path is a container, false otherwise.760        /// </returns>761        /// <remarks>762        /// Providers override this method to give the user the ability to check763        /// to see if a provider object is a container using the test-path -container cmdlet.764        ///765        /// Providers that declare <see cref="System.Management.Automation.Provider.ProviderCapabilities"/>766        /// of ExpandWildcards, Filter, Include, or Exclude should ensure that the path passed meets those767        /// requirements by accessing the appropriate property from the base class.768        ///769        /// The default implementation of this method throws an <see cref="System.Management.Automation.PSNotSupportedException"/>.770        /// </remarks>771        protected virtual bool IsItemContainer(string path)772        {773            using (PSTransactionManager.GetEngineProtectionScope())774            {775                throw776                    PSTraceSource.NewNotSupportedException(777                        SessionStateStrings.CmdletProvider_NotSupported);778            }779        }780 781        /// <summary>782        /// Moves the item specified by path to the specified destination.783        /// </summary>784        /// <param name="path">785        /// The path to the item to be moved.786        /// </param>787        /// <param name="destination">788        /// The path of the destination container.789        /// </param>790        /// <returns>791        /// Nothing is returned, but all the objects that were moved should be written to the WriteItemObject method.792        /// </returns>793        /// <remarks>794        /// Providers override this method to give the user the ability to move provider objects using795        /// the move-item cmdlet.796        ///797        /// Providers that declare <see cref="System.Management.Automation.Provider.ProviderCapabilities"/>798        /// of ExpandWildcards, Filter, Include, or Exclude should ensure that the path and items being moved799        /// meets those requirements by accessing the appropriate property from the base class.800        ///801        /// By default overrides of this method should not move objects over existing items unless the Force802        /// property is set to true. For instance, the FileSystem provider should not move c:\temp\foo.txt over803        /// c:\bar.txt if c:\bar.txt already exists unless the Force parameter is true.804        ///805        /// If <paramref name="destination"/> exists and is a container then Force isn't required and <paramref name="path"/>806        /// should be moved into the <paramref name="destination"/> container as a child.807        ///808        /// The default implementation of this method throws an <see cref="System.Management.Automation.PSNotSupportedException"/>.809        /// </remarks>810        protected virtual void MoveItem(811            string path,812            string destination)813        {814            using (PSTransactionManager.GetEngineProtectionScope())815            {816                throw817                    PSTraceSource.NewNotSupportedException(818                        SessionStateStrings.CmdletProvider_NotSupported);819            }820        }821 822        /// <summary>823        /// Gives the provider an opportunity to attach additional parameters to824        /// the move-item cmdlet.825        /// </summary>826        /// <param name="path">827        /// If the path was specified on the command line, this is the path828        /// to the item to get the dynamic parameters for.829        /// </param>830        /// <param name="destination">831        /// The path of the destination container.832        /// </param>833        /// <returns>834        /// Overrides of this method should return an object that has properties and fields decorated with835        /// parsing attributes similar to a cmdlet class or a836        /// <see cref="System.Management.Automation.RuntimeDefinedParameterDictionary"/>.837        ///838        /// The default implementation returns null. (no additional parameters)839        /// </returns>840        protected virtual object MoveItemDynamicParameters(841            string path,842            string destination)843        {844            using (PSTransactionManager.GetEngineProtectionScope())845            {846                return null;847            }848        }849 850        #endregion Protected methods851 852        #region private members853 854        /// <summary>855        /// When a path contains both forward slash and backslash, we may introduce some errors by856        /// normalizing the path. This method does some smart checks to reduce the chances of making857        /// those errors.858        /// </summary>859        /// <param name="path">860        /// The path to normalize.861        /// </param>862        /// <returns>863        /// Normalized path or the original path.864        /// </returns>865        private string NormalizePath(string path)866        {867            // If we have a mix of slashes, then we may introduce an error by normalizing the path.868            // For example: path HKCU:\Test\/ is pointing to a subkey '/' of 'HKCU:\Test', if we869            // normalize it, then we will get a wrong path.870            //871            // Fast return if nothing to normalize.872            if (!path.Contains(StringLiterals.AlternatePathSeparator))873            {874                return path;875            }876 877            bool pathHasBackSlash = path.Contains(StringLiterals.DefaultPathSeparator);878            string normalizedPath;879 880            // There is a mix of slashes & the path is rooted & the path exists without normalization.881            // In this case, we might want to skip the normalization to the path.882            if (pathHasBackSlash && IsAbsolutePath(path) && ItemExists(path))883            {884                // 1. The path exists and ends with a forward slash, in this case, it's very possible the ending forward slash885                //    make sense to the underlying provider, so we skip normalization886                // 2. The path exists, but not anymore after normalization, then we skip normalization887                if (path.EndsWith(StringLiterals.AlternatePathSeparator))888                {889                    return path;890                }891 892                normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);893 894                if (!ItemExists(normalizedPath))895                {896                    return path;897                }898                else899                {900                    return normalizedPath;901                }902            }903 904            normalizedPath = path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);905 906            return normalizedPath;907        }908 909        /// <summary>910        /// Test if the path is an absolute path.911        /// </summary>912        /// <param name="path"></param>913        /// <returns></returns>914        private bool IsAbsolutePath(string path)915        {916            bool result = false;917 918            if (LocationGlobber.IsAbsolutePath(path))919            {920                result = true;921            }922            else if (this.PSDriveInfo != null && !string.IsNullOrEmpty(this.PSDriveInfo.Root) &&923                     path.StartsWith(this.PSDriveInfo.Root, StringComparison.OrdinalIgnoreCase))924            {925                result = true;926            }927 928            return result;929        }930 931        /// <summary>932        /// Tokenizes the specified path onto a stack.933        /// </summary>934        /// <param name="path">935        /// The path to tokenize.936        /// </param>937        /// <param name="basePath">938        /// The base part of the path that should not be tokenized.939        /// </param>940        /// <returns>941        /// A stack containing the tokenized path with leaf elements on the bottom942        /// of the stack and the most ancestral parent at the top.943        /// </returns>944        private Stack<string> TokenizePathToStack(string path, string basePath)945        {946            Stack<string> tokenizedPathStack = new Stack<string>();947            string tempPath = path;948            string previousParent = path;949 950            while (tempPath.Length > basePath.Length)951            {952                // Get the child name and push it onto the stack953                // if its valid954 955                string childName = GetChildName(tempPath);956                if (string.IsNullOrEmpty(childName))957                {958                    // Push the parent on and then stop959                    tokenizedPathStack.Push(tempPath);960                    break;961                }962 963                providerBaseTracer.WriteLine("tokenizedPathStack.Push({0})", childName);964                tokenizedPathStack.Push(childName);965 966                // Get the parent path and verify if we have to continue967                // tokenizing968 969                tempPath = GetParentPath(tempPath, basePath);970                if (tempPath.Length >= previousParent.Length)971                {972                    break;973                }974 975                previousParent = tempPath;976            }977 978            return tokenizedPathStack;979        }980 981        /// <summary>982        /// Given the tokenized path, the relative path elements are removed.983        /// </summary>984        /// <param name="tokenizedPathStack">985        /// A stack containing path elements where the leaf most element is at986        /// the bottom of the stack and the most ancestral parent is on the top.987        /// Generally this stack comes from TokenizePathToStack().988        /// </param>989        /// <param name="path">990        /// The path being normalized. Just used for error reporting.991        /// </param>992        /// <param name="basePath">993        /// The base path to make the path relative to. Just used for error reporting.994        /// </param>995        /// <param name="allowNonExistingPaths">996        /// Determines whether to throw an exception on non-existing paths.997        /// </param>998        /// <returns>999        /// A stack in reverse order with the path elements normalized and all relative1000        /// path tokens removed.1001        /// </returns>1002        private static Stack<string> NormalizeThePath(1003            Stack<string> tokenizedPathStack, string path,1004            string basePath, bool allowNonExistingPaths)1005        {1006            Stack<string> normalizedPathStack = new Stack<string>();1007 1008            while (tokenizedPathStack.Count > 0)1009            {1010                string childName = tokenizedPathStack.Pop();1011 1012                providerBaseTracer.WriteLine("childName = {0}", childName);1013 1014                // Ignore the current directory token1015                if (childName.Equals(".", StringComparison.OrdinalIgnoreCase))1016                {1017                    // Just ignore it and move on.1018                    continue;1019                }1020 1021                // Make sure we don't have1022                if (childName.Equals("..", StringComparison.OrdinalIgnoreCase))1023                {1024                    if (normalizedPathStack.Count > 0)1025                    {1026                        // Pop the result and continue processing1027                        string poppedName = normalizedPathStack.Pop();1028                        providerBaseTracer.WriteLine("normalizedPathStack.Pop() : {0}", poppedName);1029                        continue;1030                    }1031                    else1032                    {1033                        if (!allowNonExistingPaths)1034                        {1035                            PSArgumentException e =1036                                (PSArgumentException)PSTraceSource.NewArgumentException(1037                                    nameof(path),1038                                    SessionStateStrings.NormalizeRelativePathOutsideBase,1039                                    path,1040                                    basePath);1041                            throw e;1042                        }1043                    }1044                }1045 1046                providerBaseTracer.WriteLine("normalizedPathStack.Push({0})", childName);1047                normalizedPathStack.Push(childName);1048            }1049 1050            return normalizedPathStack;1051        }1052 1053        /// <summary>1054        /// Pops each leaf element of the stack and uses MakePath to generate the relative path.1055        /// </summary>1056        /// <param name="normalizedPathStack">1057        /// The stack containing the leaf elements of the path.1058        /// </param>1059        /// <returns>1060        /// A path that is made up of the leaf elements on the given stack.1061        /// </returns>1062        /// <remarks>1063        /// The elements on the stack start from the leaf element followed by its parent1064        /// followed by its parent, etc. Each following element on the stack is the parent1065        /// of the one before it.1066        /// </remarks>1067        private string CreateNormalizedRelativePathFromStack(Stack<string> normalizedPathStack)1068        {1069            string leafElement = string.Empty;1070 1071            while (normalizedPathStack.Count > 0)1072            {1073                if (string.IsNullOrEmpty(leafElement))1074                {1075                    leafElement = normalizedPathStack.Pop();1076                }1077                else1078                {1079                    string parentElement = normalizedPathStack.Pop();1080                    leafElement = MakePath(parentElement, leafElement);1081                }1082            }1083 1084            return leafElement;1085        }1086 1087        #endregion private members1088    }1089 1090    #endregion NavigationCmdletProvider1091}1092