我实现了一个简单的 PowerShell NavigationCmdletProvider
。
对于那些不知道的人来说,这意味着我可以使用 cmdlet 创建一个管理单元,它实际上是一个虚拟文件系统驱动器;可以像任何普通文件夹一样从 PowerShell 安装并导航到该驱动器。针对驱动器的每个操作(例如,检查路径是否指向有效项目、获取文件夹中子项目的名称列表等)都映射到从 NavigationCmdletProvider 继承的 .NET 类的方法
类。
我遇到制表符补全问题,希望找到解决方案。我发现使用相对路径时,制表符补全会给出不正确的结果。对于绝对路径,它工作得很好。
对于那些不知道的人来说,NavigationCmdletProvider
的 Tab 键补全功能是通过 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
然后,如果我输入“dir
space”并按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!");
}
}
}
最佳答案
不确定这是一个错误,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
关于PowerShell 提供程序相对路径制表符完成问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9726725/