2

単純なPowerShellを実装しましたNavigationCmdletProvider

知らない人にとっては、これは、事実上仮想ファイルシステムドライブであるコマンドレットを使用してスナップインを作成できることを意味します。このドライブは、通常のフォルダーと同じように、PowerShellからマウントしてナビゲートできます。ドライブに対する各アクション(たとえば、パスが有効なアイテムを指しているかどうかの確認、フォルダー内の子アイテムの名前のリストの取得など)は、クラスから継承された.NETクラスのメソッドにマップされますNavigationCmdletProvider

タブ補完の問題に直面しているので、解決策を見つけたいと思います。相対パスを使用すると、タブ補完によって誤った結果が得られることがわかりました。絶対パスの場合、正常に機能します。

知らない人のために、タブ補完は、クラス からオーバーライドされるメソッドをNavigationCmdletProvider呼び出すPowerShellを介して機能します。GetChildNamesNavigationCmdletProvider

-問題のデモンストレーション--

次のフォルダー階層を持つプロバイダー「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

これがバグかどうかはわかりませんが、「仕事をする」ように見えるこの回避策を見つけました。(小さな更新ですが、複数のレベルを下るときに、元のコードが「バグアウト」することがわかりました。

''' <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 に答える
1

新しいドライブを作成するときに空でないルートを入力することを期待するようにプロバイダーを設計する場合は、これを回避できます。PSDriveInfoのRootプロパティが設定されていない場合、タブ補完によって子名だけでなく完全な子パスが誤って提案されることに気付きました。

一部のプロバイダーが常に空でないルートを要求することは、制限となる可能性があります。上記の回避策は、新しいドライブを作成するときにユーザーが常にルートを入力するようにしたくない場合にうまく機能します。

于 2012-11-18T07:09:55.193 に答える
0

これをMicrosoftConnectのPowerShellプロバイダーのバグとしてリストしました:NavigationCmdletProviderの相対パスタブ補完(Get-ChildNames経由)の問題

誰かがこれを再現できる場合は、リンクにアクセスしてそのように言ってください。報告しているのが1人だけの場合、Microsoftはおそらくこれを調べないからです。

これはPowerShell3.0で修正されているようです。マイクロソフトが古いバージョンでこれを修正したくない理由はわかりません。コードが依存する可能性のあるものではありません。

于 2012-06-06T20:45:37.113 に答える
0

string[] ExpandPath(string path)機能を上書きして設定することで動作させることができましたProviderCapabilities.ExpandWildcards

于 2018-09-27T14:42:05.123 に答える