2

我已经实现了一个简单的 PowerShellNavigationCmdletProvider

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

我面临制表符完成的问题,并希望找到解决方案。我发现使用相对路径时制表符补全会给出不正确的结果。对于绝对路径,它工作正常。

对于那些不知道的人,NavigationCmdletProvider通过 PowerShell 调用该GetChildNames方法的选项卡完成工作,该方法被NavigationCmdletProvider类覆盖。

--问题论证--

假设我有一个提供程序“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\”并按tab几次,它会给我预期的结果:

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

相对路径:

首先,我导航到“TEST::child1”:

> cd TEST::child1

然后,如果我键入“ dirspace”并按tab几次,它会给我不正确的结果:

> 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!");
        }
    }
}
4

4 回答 4

1

如果您设计您的提供程序,以便在您创建新驱动器时输入非空根,您可以解决此问题。我注意到如果未设置 PSDriveInfo 的 Root 属性,则选项卡完成错误地建议完整的子路径,而不仅仅是子名称。

某些提供者始终需要非空根可能会受到限制。如果您不想让用户在创建新驱动器时总是输入一些 Root,上述解决方法很有效。

于 2012-11-18T07:09:55.193 回答
1

不确定这是一个错误,我发现这种解决方法似乎可以“完成工作”。(小更新,事实证明我的原始代码在多个级别工作时会“出错”。

''' <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
于 2012-11-09T14:54:45.933 回答
0

我已将此列为 Microsoft Connect 中的 PowerShell 提供程序错误:NavigationCmdletProvider 的相对路径选项卡完成问题(通过 Get-ChildNames)

如果有人可以复制此内容,请访问链接并说出来,因为如果只有一个人报告,Microsoft 可能不会对此进行调查。

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

于 2012-06-06T20:45:37.113 回答
0

我能够让它与覆盖string[] ExpandPath(string path)和设置ProviderCapabilities.ExpandWildcards功能一起工作。

于 2018-09-27T14:42:05.123 回答