- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
本篇文章基于 Windows-classic-samples 仓库中的 ActivityControllerExtensibility 样例,系统讲解如何通过自定义PSActivityHostController扩展 Windows PowerShell Workflow 的活动控制器(Activity Controller)。该样例以 RunspacePool 队列化(queuing)的方式,将工作流中的 PowerShell 活动调度到本地 WinRM 自定义工作流端点执行,并完整附带端点搭建脚本。阅读本文后,你将掌握活动控制器的扩展点(CreatePSActivityHostController、StartResumablePSCommand、ResumeBookmark等)、自定义工作流端点的注册方法,以及该机制在真实代码中的完整调用链。
样例概览:它要解决什么问题
Windows PowerShell Workflow(Windows 8.1 / Windows Server 2012 R2 引入)允许把 PowerShell 命令包装为工作流中的活动(Activity)。默认情况下,活动可以"进程内"直接执行;但对于某些活动,宿主希望把执行调度出去——例如放到另一台机器、放到某个专用的 WinRM 端点,或自行控制并发排队策略。此时就需要扩展活动控制器。
本样例(源码位于 Samples/PowerShellWorkflow/ActivityControllerExtensibility/cs)演示了两件事:
- 如何扩展 Windows PowerShell Workflow 的活动控制器:通过继承
PSResumableActivityHostController提供自定义执行逻辑,并把活动命令放入ConcurrentDictionary队列,由线程池线程在自定义 WinRM 工作流端点上执行; - 如何创建自定义工作流端点:通过随附的
CustomWorkflowEndpointSetup.ps1脚本,用Register-PSSessionConfiguration -SessionType Workflow注册一个本地 WinRM 工作流端点。
整体架构与执行链路
从源码结构看,整个样例由 5 个文件构成一个闭环:
| 文件 | 作用 |
|---|---|
| SampleConfigurationProvider.cs | 配置宿主运行时,注入自定义活动控制器并声明"进程外"活动清单 |
| SampleActivityController.cs | 核心扩展实现:队列化请求、在线程池执行命令、恢复 Bookmark |
| Program.cs | 宿主程序:加载 XAML 工作流、创建PSWorkflowJob、订阅状态事件并等待完成 |
| SampleWorkflow.xaml | XAML 工作流定义,含一个GetCommand活动 |
| CustomWorkflowEndpointSetup.ps1 | 注册自定义 WinRM 工作流端点的 PowerShell 脚本 |
执行链路可以概括为:
Program.cs 加载 XAML └─> new PSWorkflowRuntime(new SampleConfigurationProvider()) └─> CreatePSActivityHostController() 返回 SampleActivityController └─> JobManager.CreateJob(...) 创建 PSWorkflowJob 并 StartJob └─> 工作流运行到 GetCommand 活动 └─> StartResumablePSCommand() 将命令入队 └─> ThreadPool 线程 ServiceRequests → PerformWork └─> 在自定义端点 RunspacePool 上 Invoke └─> job.ResumeBookmark(...) 恢复工作流 └─> Program 收到 Completed/Failed 事件下面依次剖析每一环的源码实现。
注入自定义控制器:SampleConfigurationProvider
SampleConfigurationProvider.cs 继承自Microsoft.PowerShell.Workflow.PSWorkflowConfigurationProvider,负责定义整个PSWorkflowRuntime的配置。它重写了两个关键成员:
public override Microsoft.PowerShell.Activities.PSActivityHostController CreatePSActivityHostController() { return new SampleActivityController(Runtime); }CreatePSActivityHostController()是扩展活动控制器的唯一入口:运行时在此处决定使用默认控制器还是自定义控制器,返回SampleActivityController实例(构造函数接收PSWorkflowRuntime以便后续恢复 Job)。
public override IEnumerable<string> OutOfProcessActivity { get { yield return "GetProcess"; } }OutOfProcessActivity属性声明了哪些活动属于"进程外"活动。源码注释明确说明:只有进程外活动才会触发活动控制器调用,进程内活动不需要活动控制器。这里将"GetProcess"列入进程外清单——这意味着任何名为GetProcess的活动都会经由自定义控制器调度执行(本例中 XAML 工作流实际使用GetCommand活动,但其输出流与执行机制同理)。若要扩展更多活动,只需继续yield return其他活动名称即可。
核心扩展实现:SampleActivityController
SampleActivityController.cs 继承PSResumableActivityHostController,是整个样例的精华所在。它实现了"先入队、后执行、再恢复"的异步调度模型。
支持断开式流(SupportDisconnectedPSStreams)
public override bool SupportDisconnectedPSStreams { get { return true; } }该属性通知工作流运行时关于流的使用方式:如果不需要保持 Job 的实时流,则应返回true;当支持断开式流时,活动甚至可以在另一台计算机上执行,Job 完成后数据再回传到实时流。
请求入队:StartResumablePSCommand
public override void StartResumablePSCommand(Guid jobInstanceId, Bookmark bookmark, PowerShell command, PowerShellStreams<PSObject, PSObject> streams, PSActivityEnvironment environment, PSActivity activityInstance)这是活动执行时被回调的入口方法。注释给出了重要的设计约束:
强烈建议不要在函数内阻塞式执行活动;应把全部信息记入队列后立即返回,让工作流并行做其他事;由独立线程(或另一台机器)从队列取出数据执行活动;活动完成后恢复 Bookmark。
实现方式是把调用参数打包成内部类ActivityActionData,以jobInstanceId为键放入ConcurrentDictionary<Guid, ActivityActionData> ActivityActionsQueue(该类使用并发字典,注释指出"字典嵌套字典可用于处理多个并行 Job"),随后用ThreadPool.QueueUserWorkItem(ServiceRequests, jobInstanceId)安排一个线程池线程消费:
ActivityActionData data = new ActivityActionData(); data.jobInstanceId = jobInstanceId; data.bookmark = bookmark; data.command = command; data.streams = streams; data.environment = environment; ActivityActionsQueue.TryAdd(jobInstanceId, data); ThreadPool.QueueUserWorkItem(ServiceRequests, jobInstanceId);队列消费与真实执行:ServiceRequests / PerformWork
线程池线程执行ServiceRequests,从队列中TryRemove出ActivityActionData,然后调用PerformWork真正干活:
private void PerformWork(ActivityActionData data) { // 1) 把 PowerShell 命令的输出流绑定到工作流提供的流 data.command.Streams.Debug = data.streams.DebugStream; data.command.Streams.Error = data.streams.ErrorStream; data.command.Streams.Progress = data.streams.ProgressStream; data.command.Streams.Verbose = data.streams.VerboseStream; data.command.Streams.Warning = data.streams.WarningStream; // 2) 连接自定义 WinRM 工作流端点 WSManConnectionInfo connectionInfo = new WSManConnectionInfo(); connectionInfo.ShellUri = "http://schemas.microsoft.com/powershell/CustomeWorkflowEndpoint"; // 3) 在自定义端点上创建 RunspacePool 并执行 using (RunspacePool r = RunspaceFactory.CreateRunspacePool(1, 1, connectionInfo)) { r.Open(); data.command.RunspacePool = r; data.command.Invoke(data.streams.InputStream, data.streams.OutputStream, new PSInvocationSettings()); r.Close(); r.Dispose(); } }几个值得注意的细节:
- 流绑定:
Debug/Error/Progress/Verbose/Warning五类流被显式重定向到工作流提供的流对象,保证活动输出能回流到 Job 的实时流中。 - 端点寻址:
ShellUri必须与CustomWorkflowEndpointSetup.ps1注册的端点名完全一致(包括其中CustomeWorkflowEndpoint的拼写——这是脚本与代码约定好的端点名,不可更改,否则连接会失败)。 - RunspacePool 参数:
CreateRunspacePool(1, 1, connectionInfo)表示最小/最大运行空间数均为 1,即单实例串行执行;这也是"RunspacePool-based queuing"的体现——队列在此排队等待唯一可用的运行空间。
恢复执行:ResumeBookmark
活动执行完成后必须恢复工作流,否则工作流会一直挂起。PerformWork的收尾逻辑是:
PSWorkflowJob job = _runtime.JobManager.GetJob(data.jobInstanceId); if (failed) job.ResumeBookmark(data.bookmark, this.SupportDisconnectedPSStreams, data.streams, exception); else job.ResumeBookmark(data.bookmark, this.SupportDisconnectedPSStreams, data.streams);由于执行发生在后台线程,任何异常都必须被捕获并随ResumeBookmark传入,让工作流以失败状态恢复,而不是让异常逃逸出线程池。成功路径则把bookmark、SupportDisconnectedPSStreams和streams回传给运行时,工作流从 Bookmark 处继续执行。
停止命令:StopAllResumablePSCommands
public override void StopAllResumablePSCommands(Guid jobInstanceId) { this.StopExecution(jobInstanceId); }该方法是 Job 停止时清理执行中命令的扩展点。样例中的StopExecution仅为骨架(注释说明可通过持有ActivityActionData引用并调用 command 对象的Stop()来实现),实际产品化时可在此补充强制终止逻辑。
宿主程序:Program.cs 的运行编排
Program.cs 演示了宿主如何使用自定义配置运行工作流 Job,流程如下:
// 1) 读取 XAML 工作流定义 string xaml = File.ReadAllText("SampleWorkflow.xaml"); // 2) 以自定义配置提供器创建运行时 PSWorkflowRuntime runtime = new PSWorkflowRuntime(new SampleConfigurationProvider()); // 3) 创建 Job(工作流参数可放入字典) Dictionary<string, object> parameters = new Dictionary<string, object>(); PSWorkflowJob job = runtime.JobManager.CreateJob( Guid.NewGuid(), xaml, "Get-CurrentProcess", "SampleWorkflow", parameters); // 4) 启动前订阅状态事件 AutoResetEvent wfEvent = new AutoResetEvent(false); job.StateChanged += delegate(object sender, JobStateEventArgs e) { switch (e.JobStateInfo.State) { case JobState.Failed: case JobState.Completed: wfEvent.Set(); break; } }; // 5) 启动并等待 job.StartJob(); wfEvent.WaitOne(); if (job.JobStateInfo.State == JobState.Completed) { Console.WriteLine("The job has completed successfully."); Console.WriteLine("Total Commands found: " + job.PSWorkflowInstance.Streams.OutputStream.Count); } else Console.WriteLine("The job has Failed.");要点说明:
CreateJob的参数依次为 Job 实例 ID、XAML 定义、Job 名称("Get-CurrentProcess")、工作流名称("SampleWorkflow")和参数字典。源码注释指出:由于这里使用默认的基于文件的存储(file-based store),因此必须显式提供 XAML 工作流定义。StateChanged事件只关心Failed与Completed两个终态,用AutoResetEvent做同步等待,避免主线程忙等。- Job 完成后通过
job.PSWorkflowInstance.Streams.OutputStream.Count读取输出流中累积的对象数量(即工作流输出的命令条目数),这就是活动流被正确回传的直接证据。
工作流定义:SampleWorkflow.xaml
SampleWorkflow.xaml 是一个最小的 XAML 工作流:Sequence中包含一个来自Microsoft.PowerShell.Core.Activities的GetCommand活动,并为其Module参数指定了'PSDiagnostics':
<Sequence ...> <ns3:GetCommand DisplayName="Get-Command" ...> <ns3:GetCommand.Module> <ns2:InArgument x:TypeArguments="s:String[]"> <mpa:PowerShellValue x:TypeArguments="s:String[]" Expression="'PSDiagnostics'"/> </ns2:InArgument> </ns3:GetCommand.Module> </ns3:GetCommand> </Sequence>在项目文件 ActivityControllerExtensibilitySample.csproj 中,该 XAML 被声明为<XamlAppDef>且CopyToOutputDirectory=Always,因此编译后会随 exe 一起出现在输出目录,运行时File.ReadAllText("SampleWorkflow.xaml")才能正确加载。
搭建自定义工作流端点:CustomWorkflowEndpointSetup.ps1
这是整个方案的"基础设施"部分,脚本完整内容(位于 CustomWorkflowEndpointSetup.ps1)只有 5 步,却环环相扣:
# 1) 确保 WinRM 已配置并启动 winrm quickconfig -quiet restart-service winrm # 2) 创建工作流会话选项,限制最大并发工作流数为 35 $wfExecuteOption = New-PSWorkflowExecutionOption -MaxRunningWorkflows 35 # 3) 注册自定义工作流端点 Register-PSSessionConfiguration -Name CustomeWorkflowEndpoint ` -SessionType Workflow -SessionTypeOption $wfExecuteOption -Force # 4) 验证端点:创建会话并运行一个测试工作流 $wfEP = New-PSSession -ConfigurationName CustomeWorkflowEndpoint icm $wfEP {workflow TestWorkflow {get-process}} icm $wfEP {TestWorkflow} Remove-PSSession $wfEP对每一步的说明:
| 命令 | 作用与参数说明 |
|---|---|
winrm quickconfig -quiet | 快速完成 WinRM 服务与监听器配置;-quiet表示不交互询问 |
restart-service winrm | 重启 WinRM 服务使配置生效 |
New-PSWorkflowExecutionOption -MaxRunningWorkflows 35 | 创建工作流会话类型选项;-MaxRunningWorkflows控制该端点上最多并发运行的工作流数量,样例设为 35,可按实际负载调整 |
Register-PSSessionConfiguration -Name CustomeWorkflowEndpoint -SessionType Workflow -SessionTypeOption $wfExecuteOption -Force | 注册名为CustomeWorkflowEndpoint的会话配置;-SessionType Workflow指明这是工作流类型端点(非默认的DefaultRemoteShell),-Force覆盖可能存在的同名配置。注意端点名拼写:代码中WSManConnectionInfo.ShellUri指向http://schemas.microsoft.com/powershell/CustomeWorkflowEndpoint,二者必须严格一致 |
New-PSSession -ConfigurationName CustomeWorkflowEndpoint | 用刚注册的端点创建 PSSession,用于连通性验证 |
icm $wfEP {workflow TestWorkflow {get-process}} | 在会话内定义并运行测试工作流,确认端点可以承载工作流执行 |
该脚本必须以管理员权限运行(注册会话配置需要提升权限),且需在运行样例 exe之前完成,否则RunspacePool.Open()将因找不到端点而失败。
构建与运行
系统要求
| 类别 | 要求 |
|---|---|
| 客户端 | Windows 8.1 |
| 服务器 | Windows Server 2012 R2 |
| 运行时 | .NET Framework 4.0(项目TargetFrameworkVersion为 v4.0)、PowerShell 3.0 及以上(工作流功能自 PowerShell 3.0 引入) |
项目引用了Microsoft.PowerShell.Activities、Microsoft.PowerShell.Core.Activities、Microsoft.PowerShell.Management.Activities、Microsoft.PowerShell.Workflow.ServiceCore和System.Management.Automation(版本均为 3.0.0.0)等程序集,这些组件随 Windows 8.1 / Server 2012 R2 自带,无需额外安装。
构建步骤
- 启动 Visual Studio,选择File > Open > Project/Solution;
- 进入 Samples/PowerShellWorkflow/ActivityControllerExtensibility/cs 目录,双击
ActivityControllerExtensibilitySample.sln打开解决方案(解决方案支持 Debug/Release 两种配置,平台为 Any CPU); - 按F7或选择Build > Build Solution编译。
构建成功后,ActivityControllerExtensibilitySample.exe(以及随输出的SampleWorkflow.xaml)会生成在bin\Debug目录下。
运行步骤
- 以管理员身份启动 Windows PowerShell;
- 切换到 CustomWorkflowEndpointSetup.ps1 所在目录;
- 执行该脚本,注册
CustomeWorkflowEndpoint自定义工作流端点(脚本末尾的测试命令会输出get-process结果,可据此确认端点可用); - 切换到样例二进制所在目录(
bin\Debug); - 执行
ActivityControllerExtensibilitySample.exe。
程序运行时会加载SampleWorkflow.xaml,创建 Job 并启动,工作流内的GetCommand活动请求经SampleActivityController排队后调度到自定义端点的 RunspacePool 上执行;完成后控制台输出:
The job has completed successfully. Total Commands found: <数量> Press <Enter> to continue...Total Commands found即工作流输出流中累积的对象数,验证了"活动在自定义端点上执行、结果流回宿主"的完整闭环。
扩展设计的工程要点
从本样例的源码注释中可以提炼出扩展活动控制器的几条关键设计原则:
- 回调函数绝不阻塞:
StartResumablePSCommand应只做入队等轻量操作后立即返回,让工作流引擎继续并行推进其他活动;真实执行放到独立线程或独立机器。 - 队列驱动解耦:用并发队列(样例为
ConcurrentDictionary<Guid, ActivityActionData>)在"活动请求"与"执行者"之间解耦;键用jobInstanceId天然支持多 Job 并发,注释提示"字典嵌套字典"可扩展到同 Job 多活动并行。 - Bookmark 是恢复锚点:活动完成后必须调用
ResumeBookmark恢复工作流挂起点;异常必须随ResumeBookmark上报,使 Job 进入失败态而非悬挂。 - 流必须显式接线:
Debug/Error/Progress/Verbose/Warning流的接线决定活动输出能否回传;SupportDisconnectedPSStreams返回true意味着 Job 可断开、活动可异地执行。 - 端点与代码强约定:自定义端点名出现在脚本注册命令与
WSManConnectionInfo.ShellUri两处,任何一处改名都会导致运行失败——这是把执行调度到 WinRM 端点时必须牢记的一致性约束。
小结
ActivityControllerExtensibility 样例用约 200 行 C# 代码完整展示了 Windows PowerShell Workflow 活动控制器扩展的完整范式:配置提供器注入自定义控制器、OutOfProcessActivity声明进程外活动、控制器内"入队 → 线程池执行 → ResumeBookmark 恢复"的三段式调度,以及用Register-PSSessionConfiguration -SessionType Workflow搭建自定义工作流端点。这套机制是实现工作流活动异地执行、并发限流与自定义调度策略的参考蓝本,适合需要深度定制 PowerShell 工作流执行引擎的开发者研读。若需了解仓库内更多工作流相关样例,可继续查阅 Samples/PowerShellWorkflow 目录下的其他子项目。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
使用 Windows PowerShell Activity Generation API 生成 Workflow 活动源码并编译为 DLL(ActivityGenerator 示例深度解析)
使用 Windows PowerShell Activity Generation API 生成 Workflow 活动源码并编译为 DLL(ActivityG
示例工程Windows-classic-samples 实战:用 PowerShell Workflow 自定义活动(Activity)接收脚本块参数——ContainerActivity 示例深度解析
Windows classic samples 实战:用 PowerShell Workflow 自定义活动(Activity)接收脚本块参数——Contain
示例工程Windows PowerShell Debugger API 实战:基于 Windows-classic-samples 构建 Workflow 调试器示例
Windows PowerShell Debugger API 实战:基于 Windows classic samples 构建 Workflow 调试器示例
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考