MegaBites-AI/Windows-powershell
0372
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 