使用 CommandMetadata 与 ProxyCommand 构建受限 Runspace:Windows PowerShell Runspace11 示例深度解析 示例工程【免费下载链接】Windows-classic-samplesThis repo contains samples that demonstrate the API used in Windows classic desktop applications.项目地址https://gitcode.com/gh_mirrors/wi/Windows-classic-samples点击查看免费下载本文围绕 Windows-classic-samples 仓库中的 Runspace11 示例完整讲解如何通过CommandMetadata描述既有 cmdlet 的元数据、移除其中部分参数再用ProxyCommand.Create生成代理函数proxy function最终放入InitialSessionState构建一个受限 runspaceconstrained runspace。读完本文你将掌握让用户只能通过受控代理间接调用 cmdlet、且仅能使用允许的参数子集这一经典约束模式并能直接对照源码复现运行。示例要解决什么问题在托管 Windows PowerShell 引擎的宿主应用中默认InitialSessionState.CreateDefault()创建的 runspace 会暴露引擎提供的全部命令与语言能力。对于面向普通用户或安全敏感场景的宿主需要将命令集与参数集收窄。Runspace11 正是这一场景的最小可运行示范定义一个名为Get-Proc的自定义 cmdlet实现取自 GetProcessSample02通过SessionStateCmdletEntry把它加入初始会话状态并将可见性设为Private基于该 cmdlet 的类型构造CommandMetadata从元数据中移除Name参数调用ProxyCommand.Create生成一段代理函数脚本代理内部仍调用原始 cmdlet但对外只暴露允许的参数把代理函数以新名称get-procProxy加入同一初始会话状态创建受限 runspace并依次验证私有 cmdlet 直接调用失败、代理函数带被移除参数调用失败、代理函数正常调用成功。整个过程对应原 README 列出的六个目标创建CommandMetadata、修改元数据删除参数、把 cmdlet 加入会话状态并设为私有、生成受限参数的代理函数、注册代理函数、通过调用演示受限效果。关键概念CommandMetadata、代理命令与受限 RunspaceCommandMetadataCommandMetadata位于System.Management.Automation命名空间用于描述一个命令的元数据包括命令名、参数集以及各参数的声明信息。示例中直接以 cmdlet 类型构造CommandMetadata cmdletMetadata new CommandMetadata(typeof(GetProcCommand));此后cmdletMetadata.Parameters集合即代表该 cmdlet 对外可见的参数表。示例通过Parameters.Remove(Name)精确摘除某个参数从而让后续生成的代理函数看不到该参数。ProxyCommand 与代理函数ProxyCommand.Create(cmdletMetadata)根据元数据生成一段 PowerShell 函数体脚本字符串。这段函数在内部以Microsoft.PowerShell.Core\...方式调用底层 cmdlet同时按元数据重新声明对外参数。它相当于给原始命令包了一层门卫函数名可与原 cmdlet 相同但示例刻意使用不同名称get-procProxy以便清楚展示原始 cmdlet 已不可访问函数只暴露元数据中保留的参数被移除的参数在函数签名中不存在调用时必然触发参数绑定错误。InitialSessionState 与受限 RunspaceInitialSessionState决定创建 runspace 时的初始命令、变量、模块与会话配置。示例的核心约束手段有三处InitialSessionState iss InitialSessionState.CreateDefault(); SessionStateCmdletEntry cmdletEntry new SessionStateCmdletEntry(get-proc, typeof(GetProcCommand), null); iss.Commands.Add(cmdletEntry); cmdletEntry.Visibility SessionStateEntryVisibility.Private; iss.LanguageMode PSLanguageMode.NoLanguage;CreateDefault()构建包含引擎全部默认元素的初始会话状态Visibility SessionStateEntryVisibility.Private把get-proc标记为私有命令用户会话中无法直接解析该命令名PSLanguageMode.NoLanguage禁用脚本语言特性只允许调用公开命令进一步收紧宿主能力。随后RunspaceFactory.CreateRunspace(iss)以该会话状态创建受限 runspace所有命令都在这套约束之下运行。源码级剖析受约束的 Get-Proc cmdlet示例的 cmdlet 定义位于 Runspace11.cs 的GetProcCommand类源码注释说明其原样取自 GetProcessSample02.cs[Cmdlet(VerbsCommon.Get, Proc)] public class GetProcCommand : Cmdlet { private string[] processNames; [Parameter(Position 0)] [ValidateNotNullOrEmpty] public string[] Name { get { return this.processNames; } set { this.processNames value; } } protected override void ProcessRecord() { if (this.processNames null) { WriteObject(Process.GetProcesses(), true); } else { foreach (string name in this.processNames) { WriteObject(Process.GetProcessesByName(name), true); } } } }要点[Cmdlet(VerbsCommon.Get, Proc)]声明命令名Get-Proc注册到会话状态时使用小写连字符形式get-procName参数带[Parameter(Position 0)]即位置参数后续移除它即可让代理函数不再接受任何按位置或按名称传入的进程名无参数时输出全部进程带参数时按名称过滤输出行为简单直观便于验证约束前后差异。源码级剖析三次调用验证受限效果受限 runspace 创建并Open()之后Main方法依次执行三次调用见 Runspace11.cs。第一次直接调用私有 cmdlet —— 预期失败using (PowerShell powershell PowerShell.Create()) { powershell.Runspace myRunspace; powershell.AddCommand(get-proc).AddParameter(Name, *explore*); powershell.Invoke(); }由于get-proc的可见性为Private命令名在受限会话中不可解析Invoke()抛出CommandNotFoundException。示例捕获后打印Invoking get-proc failed as expected: System.Management.Automation.CommandNotFoundException: ...这一步证明了用户不能直接调用原始 cmdlet。第二次代理函数带被移除参数调用 —— 预期失败powershell.AddCommand(get-procProxy).AddParameter(Name, idle);代理函数的参数签名中已没有Name因此抛出ParameterBindingExceptionInvoking get-procProxy -Name idle failed as expected: System.Management.Automation.ParameterBindingException: ...这一步证明了代理函数只暴露被允许的参数子集。第三次代理函数无参数调用 —— 预期成功using (PowerShell powershell PowerShell.Create()) { powershell.Runspace myRunspace; powershell.AddCommand(get-procProxy); ListProcess processes new ListProcess(powershell.InvokeProcess()); System.Console.WriteLine( \nInvoking the get-procProxy function called into the get-proc cmdlet and returned {0} processes, processes.Count); }代理函数正常执行并真正调用到底层私有 cmdlet返回全部进程列表。这一步同时证明了两点代理函数与底层 cmdlet 之间的内部调用链是通的以及受限 runspace 下用户仍可通过受控入口获得命令能力。三次调用的结果组合起来恰好构成对受限 runspace行为契约的完整验证命令级不可见、参数级不可用、功能级可用。完整调用链一览为便于整体把握将示例主流程串起来关键语句来自 Runspace11.cs// 1. 默认初始会话状态 InitialSessionState iss InitialSessionState.CreateDefault(); // 2. 注册 get-proc 并设为私有 SessionStateCmdletEntry cmdletEntry new SessionStateCmdletEntry(get-proc, typeof(GetProcCommand), null); iss.Commands.Add(cmdletEntry); cmdletEntry.Visibility SessionStateEntryVisibility.Private; // 3. 禁用语言特性 iss.LanguageMode PSLanguageMode.NoLanguage; // 4. 基于 cmdlet 元数据生成代理函数 CommandMetadata cmdletMetadata new CommandMetadata(typeof(GetProcCommand)); cmdletMetadata.Parameters.Remove(Name); string bodyOfProxyFunction ProxyCommand.Create(cmdletMetadata); // 5. 注册代理函数使用不同名称以区分原始 cmdlet iss.Commands.Add(new SessionStateFunctionEntry(get-procProxy, bodyOfProxyFunction)); // 6. 用受限会话状态创建 runspace using (Runspace myRunspace RunspaceFactory.CreateRunspace(iss)) { myRunspace.Open(); // ... 三次验证调用见上文 ... myRunspace.Close(); }注意源码中的代理函数体bodyOfProxyFunction是运行时由ProxyCommand.Create生成的脚本字符串其内容在本示例中并未显式打印如果需要查看生成的函数体可在调试器中读取该字符串或在会话中执行Get-Command get-procProxy观察其签名。构建与运行工程结构示例的 C# 实现位于 Samples/PowerShell/Runspace/Runspace11/cs 目录包括Runspace11.cs全部源码cmdlet 定义 Main 流程Runspace11.csprojMSBuild 工程文件Runspace11.slnVisual Studio 解决方案Format Version 11.00即 VS2012AssemblyInfo.cs程序集元数据Readme.txt英文构建/运行说明。构建步骤对应原 README 的 Build the sample启动 Microsoft Visual Studio选择File Open Project/Solution进入 Runspace11 示例目录双击cs下的Runspace11.sln打开解决方案按F7或使用Build Build Solution编译。编译产物输出到默认目录。从 Runspace11.csproj 的配置看Debug 配置输出到.\bin\Debug\并定义DEBUG;TRACE常量Release 配置输出到.\bin\Release\定义TRACE常量目标框架为.NET Framework v4.0OutputType为EXE程序集名为Runspace11。运行步骤对应原 README 的 Run the sample打开命令提示符Command Prompt切换到包含示例可执行文件的目录如cs\bin\Debug\或cs\bin\Release\运行Runspace11.exe。程序会依次输出三次调用结果两次as expected的异常信息与一次进程计数随后显示Hit any key to exit...按任意键退出。运行环境与依赖原 README 声明的操作系统要求为客户端Windows 8.1服务器Windows Server 2012 R2。工程依赖方面Runspace11.csproj 明确引用了System.Management.Automation, Version3.0.0.0PublicKeyToken31bf3856ad364e35HintPath 指向Windows\Microsoft.NET\assembly\GAC_MSIL下的 GAC 程序集——这说明构建机器上需安装对应版本的 Windows PowerShell SDK/运行时标准引用System、System.Configuration.Install、System.Data、System.Management、System.Xml。当前仓库的 Runspace 系列 还包含 Runspace01Runspace10 等兄弟示例如独立的 cmdlet、直接调用、批量调用等主题Runspace11 的约束模式可以与它们对照阅读理解同一引擎在不同会话状态下的能力边界。设计要点小结从源码结构看Runspace11 给出了一套可复用的最小权限代理范式核心结论如下命令隐藏靠 Visibility而不是删除把原始 cmdlet 设为Private保留其存在代理函数仍可调用它同时让用户会话无法直接解析命令名是内部可用、外部不可见的关键参数裁剪靠 CommandMetadataCommandMetadata.Parameters.Remove(...)在元数据层面删参数生成的代理函数签名随之收窄用户传入被删参数必然触发ParameterBindingException入口收窄靠 LanguageModePSLanguageMode.NoLanguage从语言层封堵脚本表达式进一步降低被滥用的风险验证闭环完整三次调用分别覆盖命令不可见、参数不可用、功能可用三个断言异常类型CommandNotFoundException、ParameterBindingException精确对应各自失败原因可作为编写受限宿主自测用例的模板。该模式适合嵌入需要向不可信用户暴露有限管理命令的宿主程序例如只允许查看进程列表却禁止按名称筛选/操作进程的场景。结合 Runspace11.cs 中的完整实现与注释读者可以直接在其基础上扩展自己的 cmdlet 与参数白名单。赞分享示例工程【免费下载链接】Windows-classic-samplesThis repo contains samples that demonstrate the API used in Windows classic desktop applications.项目地址https://gitcode.com/gh_mirrors/wi/Windows-classic-samples点击查看免费下载相关推荐使用 InitialSessionState 约束 PowerShell RunspacePowerShell01 示例深度解析使用 InitialSessionState 约束 PowerShell RunspacePowerShell01 示例深度解析 导读 在 Windows 桌示例工程使用 Turborepo 构建 Svelte 5 SvelteKit monorepowith-svelte 示例深度解析使用 Turborepo 构建 Svelte 5 SvelteKit monorepowith svelte 示例深度解析 本篇技术指南以当前仓库中的 e构建工具开发工具CLI用 Agent OS KernelSpace 构建受治理的带记忆聊天 Agentchat-agent 示例深度解析用 Agent OS KernelSpace 构建受治理的带记忆聊天 Agentchat agent 示例深度解析 Agent governance tool人工智能AI AgentAI 安全治理策略引擎认证鉴权Agent 沙箱可观测性上一篇英雄联盟客户端自动化工具包终极指南如何5分钟提升你的游戏体验下一篇阿里巴巴Dragonwell17 JDK完整指南如何快速部署高性能Java运行环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考