在二进制PowerShell模块的参数补全器中获取当前工作目录
问题背景
基于.NET 6维护已有的二进制PowerShell模块,需为ReaderCmdlet新增带参数补全功能的ConfigurationName参数。现有Cmdlet定义:
public class ReaderCmdlet : Cmdlet { [Parameter] public string ProjectFolder { get; set; } // 其他成员 }
新增参数及补全器定义:
[Parameter] [ArgumentCompleter(typeof(ConfigurationCompleter))] public string ConfigurationName { get; set; }
public class ConfigurationCompleter : IArgumentCompleter { public IEnumerable<CompletionResult> CompleteArgument( string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) { // 补全逻辑 } }
遇到的问题:ConfigurationName的补全结果依赖ProjectFolder指定的项目路径。当用户输入绝对路径时,可通过fakeBoundParameters正常处理;但输入相对路径时,需要结合PowerShell当前工作目录解析,而IArgumentCompleter没有Cmdlet上下文,无法直接通过SessionState.Path获取当前目录。尝试通过Runspace.DefaultRunspace.SessionStateProxy.Path访问时,因补全管道运行状态抛出异常。
解决方案
方案1:改用ICmdletArgumentCompleter接口(推荐)
PowerShell 5.1及以上版本提供了ICmdletArgumentCompleter接口,它的CompleteArgument方法会直接传入当前Cmdlet实例,让你可以直接访问Cmdlet的SessionState,完美解决路径解析问题。
修改补全器实现:
using System.Collections.Generic; using System.Management.Automation; public class ConfigurationCompleter : ICmdletArgumentCompleter { public IEnumerable<CompletionResult> CompleteArgument( Cmdlet cmdlet, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) { if (fakeBoundParameters.TryGetValue("ProjectFolder", out var projectFolderObj) && projectFolderObj is string projectFolder) { // 利用Cmdlet的SessionState解析路径,自动处理绝对/相对路径 string resolvedProjectPath = cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(projectFolder); // 这里根据resolvedProjectPath获取配置名称列表,示例逻辑 var configNames = GetConfigurationNamesFromProject(resolvedProjectPath); foreach (var name in configNames) { yield return new CompletionResult(name); } } } // 示例方法:从项目路径获取配置名称 private IEnumerable<string> GetConfigurationNamesFromProject(string projectPath) { // 实际逻辑:读取项目目录下的配置文件或配置项 return new List<string> { "Debug", "Release", "Staging" }; } }
更新参数的ArgumentCompleter属性指向该补全器即可。
方案2:通过Runspace获取当前工作目录(兼容旧版本)
如果需要兼容PowerShell 5.1以下版本,可通过Runspace.DefaultRunspace获取当前会话的PWD变量(PowerShell当前工作目录),手动解析相对路径:
using System.Collections.Generic; using System.IO; using System.Management.Automation; using System.Management.Automation.Language; public class ConfigurationCompleter : IArgumentCompleter { public IEnumerable<CompletionResult> CompleteArgument( string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) { if (fakeBoundParameters.TryGetValue("ProjectFolder", out var projectFolderObj) && projectFolderObj is string projectFolder) { string resolvedProjectPath; if (Path.IsPathRooted(projectFolder)) { resolvedProjectPath = projectFolder; } else { // 获取PowerShell当前工作目录 var pwd = Runspace.DefaultRunspace.SessionStateProxy.GetVariable("PWD") as PathInfo; string currentDir = pwd?.Path ?? Directory.GetCurrentDirectory(); resolvedProjectPath = Path.Combine(currentDir, projectFolder); } // 后续逻辑同方案1 var configNames = GetConfigurationNamesFromProject(resolvedProjectPath); foreach (var name in configNames) { yield return new CompletionResult(name); } } yield break; } private IEnumerable<string> GetConfigurationNamesFromProject(string projectPath) { return new List<string> { "Debug", "Release", "Staging" }; } }
注意:这种方式要确保在补全线程中访问Runspace.DefaultRunspace的安全性,常规场景下可正常工作。
内容的提问来源于stack exchange,提问作者Hemisphera

