2

我已经实现了一个简单的PowerShell NavigationCmdletProviderPowerShell提供程序相对路径制表符完成问题

对于那些不知道的人来说,这意味着我可以使用cmdlet创建一个管理单元,该管理单元实际上是一个虚拟文件系统驱动器;这个驱动器可以像任何普通文件夹一样从PowerShell安装和导航。针对驱动器的每个操作(例如,检查路径指向有效项目,获取文件夹中子项目的名称列表等)被映射到继承自NavigationCmdletProvider类的.NET类的方法。

我正面临tab完成问题,并希望找到解决方案。我发现使用相对路径时,制表符填充会导致错误的结果。对于绝对路径,它工作正常。

对于那些不知道的人,NavigationCmdletProvider的标签填写通过调用GetChildNames方法的PowerShell工作,该方法被NavigationCmdletProvider类覆盖。

的issue--

--Demonstration假设我有一个供应商, 'TEST',具有下列文件夹层次:

TEST::child1 
TEST::child1\child1a 
TEST::child1\child1b 
TEST::child2 
TEST::child2\child2a 
TEST::child2\child2b 
TEST::child3 
TEST::child3\child3a 
TEST::child3\child3b 

绝对路径:

如果我输入“dir TEST::child1\”并按标签几次,它给我的预计业绩:

> dir TEST::child1\child1a 
> dir TEST::child1\child1b 

相对路径:

首先,我浏览到 “TEST :: child1”:

> cd TEST::child1 

然后,如果我输入“dirspace”并按标签几次,它给我不正确的结果:

> dir .\child1\child1a 
> dir .\child1\child1b 

我希望看到这些来代替:

> dir .\child1a 
> dir .\child1b 

这是在PowerShell中的错误,还是我做错了什么?

这里是为提供完整的,自包含的代码:

[CmdletProvider("TEST", ProviderCapabilities.None)] 
public class MyTestProvider : NavigationCmdletProvider 
{ 
    private Node m_Root; 
    private void ConstructTestHierarchy() 
    { 
     // 
     // Create the nodes 
     // 
     Node root = new Node(""); 
      Node child1 = new Node("child1"); 
       Node child1a = new Node("child1a"); 
       Node child1b = new Node("child1b"); 
      Node child2 = new Node("child2"); 
       Node child2a = new Node("child2a"); 
       Node child2b = new Node("child2b"); 
      Node child3 = new Node("child3"); 
       Node child3a = new Node("child3a"); 
       Node child3b = new Node("child3b"); 

     // 
     // Construct node hierarchy 
     // 
     m_Root = root; 
      root.AddChild(child1); 
       child1.AddChild(child1a); 
       child1.AddChild(child1b); 
      root.AddChild(child2); 
       child2.AddChild(child2a); 
       child2.AddChild(child2b); 
      root.AddChild(child3); 
       child3.AddChild(child3a); 
       child3.AddChild(child3b); 
    } 

    public MyTestProvider() 
    { 
     ConstructTestHierarchy(); 
    } 

    protected override bool IsValidPath(string path) 
    { 
     return m_Root.ItemExistsAtPath(path); 
    } 

    protected override bool ItemExists(string path) 
    { 
     return m_Root.ItemExistsAtPath(path); 
    } 

    protected override void GetChildNames(string path, ReturnContainers returnContainers) 
    { 
     var children = m_Root.GetItemAtPath(path).Children; 
     foreach (var child in children) 
     { 
      WriteItemObject(child.Name, child.Name, true); 
     } 
    } 
    protected override bool IsItemContainer(string path) 
    { 
     return true; 
    } 
    protected override void GetChildItems(string path, bool recurse) 
    { 
     var children = m_Root.GetItemAtPath(path).Children; 
     foreach (var child in children) 
     { 
      WriteItemObject(child.Name, child.Name, true); 
     } 
    } 
} 

/// <summary> 
/// This is a node used to represent a folder inside a PowerShell provider 
/// </summary> 
public class Node 
{ 
    private string m_Name; 
    private List<Node> m_Children; 

    public string Name { get { return m_Name; } } 
    public ICollection<Node> Children { get { return m_Children; } } 

    public Node(string name) 
    { 
     m_Name = name; 
     m_Children = new List<Node>(); 
    } 

    /// <summary> 
    /// Adds a node to this node's list of children 
    /// </summary> 
    public void AddChild(Node node) 
    { 
     m_Children.Add(node); 
    } 
    /// <summary> 
    /// Test whether a string matches a wildcard string ('*' must be at end of wildcardstring) 
    /// </summary> 
    private bool WildcardMatch(string basestring, string wildcardstring) 
    { 
     // 
     // If wildcardstring has no *, just do a string comparison 
     // 
     if (!wildcardstring.Contains('*')) 
     { 
      return String.Equals(basestring, wildcardstring); 
     } 
     else 
     { 
      // 
      // If wildcardstring is really just '*', then any name works 
      // 
      if (String.Equals(wildcardstring, "*")) 
       return true; 

      // 
      // Given the wildcardstring "abc*", we just need to test if basestring starts with "abc" 
      // 
      string leftOfAsterisk = wildcardstring.Split(new char[] { '*' })[0]; 
      return basestring.StartsWith(leftOfAsterisk); 

     } 
    } 

    /// <summary> 
    /// Recursively check if "child1\child2\child3" exists 
    /// </summary> 
    public bool ItemExistsAtPath(string path) 
    { 
     // 
     // If path is self, return self 
     // 
     if (String.Equals(path, "")) return true; 

     // 
     // If path has no slashes, test if it matches the child name 
     // 
     if(!path.Contains(@"\")) 
     { 
      // 
      // See if any children have this name 
      // 
      foreach (var child in m_Children) 
      { 
       if (WildcardMatch(child.Name, path)) 
        return true; 
      } 
      return false; 
     } 
     else 
     { 
      // 
      // Split the path 
      // 
      string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); 

      // 
      // Take out the first chunk; this is the child we're going to search 
      // 
      string nextChild = pathChunks[0]; 

      // 
      // Combine the rest of the path; this is the path we're going to provide to the child 
      // 
      string nextPath = String.Join(@"\", pathChunks.Skip(1).ToArray()); 

      // 
      // Recurse into child 
      // 
      foreach (var child in m_Children) 
      { 
       if (String.Equals(child.Name, nextChild)) 
        return child.ItemExistsAtPath(nextPath); 
      } 
      return false; 
     } 
    } 

    /// <summary> 
    /// Recursively fetch "child1\child2\child3" 
    /// </summary> 
    public Node GetItemAtPath(string path) 
    { 
     // 
     // If path is self, return self 
     // 
     if (String.Equals(path, "")) return this; 

     // 
     // If path has no slashes, test if it matches the child name 
     // 
     if (!path.Contains(@"\")) 
     { 
      // 
      // See if any children have this name 
      // 
      foreach (var child in m_Children) 
      { 
       if (WildcardMatch(child.Name, path)) 
        return child; 
      } 
      throw new ApplicationException("Child doesn't exist!"); 
     } 
     else 
     { 
      // 
      // Split the path 
      // 
      string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); 

      // 
      // Take out the first chunk; this is the child we're going to search 
      // 
      string nextChild = pathChunks[0]; 

      // 
      // Combine the rest of the path; this is the path we're going to provide to the child 
      // 
      string nextPath = String.Join(@"\", pathChunks.Skip(1).ToArray()); 

      // 
      // Recurse into child 
      // 
      foreach (var child in m_Children) 
      { 
       if (String.Equals(child.Name, nextChild)) 
        return child.GetItemAtPath(nextPath); 
      } 
      throw new ApplicationException("Child doesn't exist!"); 
     } 
    } 
} 
+0

你能指出在dir例子中返回name的函数吗?你检查了什么是“正常”行为? (例如,如果您从“相对路径开始”,某些unix shell将执行相同的操作)。 – 2012-03-30 09:03:29

+0

GetChildNames方法正在返回名称。在我的相对路径示例中,它返回“child1a”和“child1b”。我试过返回各种各样的组合,总是得到相同的结果。亲自尝试一下。 – 2012-03-30 14:37:34

+0

我想任何人都会同意这不应该是“正常”的行为,如果是的话,这是一个错误。例如,当您在PowerShell中遍历C驱动器时,它从来不会这样做。我也不知道你的意思是“从何而来”。 – 2012-03-30 14:39:32

回答

0

我列出这个作为微软连接PowerShell提供错误:Issue with relative path tab-completion (via Get-ChildNames) for NavigationCmdletProvider

如果任何人都可以重现此,请访问链接并说出来,因为如果只有一个人报告它,微软可能不会考虑这一点。

它看起来像是在PowerShell 3.0中修复的。我不知道为什么微软不想在旧版本中解决这个问题,它不是任何代码可能依赖的东西。

1

不知道这是一个错误,I found this workaround似乎“做这份工作”。 (小更新,原来我原来的代码将“跑路”自己的方式工作时,下多个级别。

''' <summary> 
''' Joins two strings with a provider specific path separator. 
''' </summary> 
''' <param name="parent">The parent segment of a path to be joined with the child.</param> 
''' <param name="child">The child segment of a path to be joined with the parent.</param> 
''' <returns>A string that contains the parent and child segments of the path joined by a path separator.</returns> 
''' <remarks></remarks> 
Protected Overrides Function MakePath(parent As String, child As String) As String 
    Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ")") 
    Dim res As String = MyBase.MakePath(parent, child) 
    Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ") " & res) 
    If parent = "." Then 
     'res = ".\" & child.Split("\").Last 
     If String.IsNullOrEmpty(Me.SessionState.Path.CurrentLocation.ProviderPath) Then 
     res = parent & PATH_SEPARATOR & child 
     Else 
     res = parent & PATH_SEPARATOR & child.Substring(Me.SessionState.Path.CurrentLocation.ProviderPath.Length + 1) 
     'res = parent & PATH_SEPARATOR & child.Replace(Me.SessionState.Path.CurrentLocation.ProviderPath & PATH_SEPARATOR, String.Empty) 
     End If 
     Trace.WriteLine("::**** TRANSFORM: " & res) 
    End If 
    Return res 
End Function 
+0

+1首先,伟大的工作。但我仍然认为这是一个错误(特别是如果它需要这样的解决方法)。你是说这是一个功能吗? – 2012-11-14 04:01:29

1

您可以解决这一点,如果你设计你提供程序,以便它预计进入非空根当你创建一个新的驱动器。我注意到,标签完成错误提示完整的子路径代替只是孩子的名字如果PSDriveInfo的根属性尚未设置。

可以限制对于某些提供者总是需要一个非空的根。上面的解决方法wor如果您不希望在创建新驱动器时让用户始终输入一些Root,那么很好。