Powershell自定义cmd在标准方法实现之外调用WriteVerbose
本文关键字:调用 WriteVerbose 实现 方法 自定义 cmd 标准 Powershell | 更新日期: 2023-09-27 18:02:06
Msdn文档说明对于WriteVerbose方法:
"这个方法只能从BeginProcessing, ProcessRecord和EndProcessing方法的实现中调用,并且只能从该线程中调用。如果这个调用是从这些实现外部或从另一个线程进行的,则抛出InvalidOperationException异常。"
但是在我的测试代码中,我能够调用三个标准方法之外的方法。但是,当从另一个线程调用该方法时,我得到一个异常。
我相信这里的大多数人在实现自定义cmdlet时需要多线程支持。
我使用后台工作来完成简单的多线程。我在ProcessRecord函数中等待doWork事件完成,然后再继续。现在我想使用writeverbose从doWork向用户报告进度,但由于它是一个不同的线程,我不能这样做。
我已经尝试使用一个AutoResetEvent并发队列来实现这一点,但是有一个更好的方法来解决这个更简单的方式吗?
如果您使用的是BackgroundWorker
,请订阅它的ProgressChanged
事件。在DoWork()
内部,您可以使用ReportProgress()
来触发ProgressChanged
事件。在cmlet上,ProgressChanged处理程序可以使用进度信息执行WriteVerbose调用。然而,因为你没有调用任何形式,你将不得不手动设置SynchronizationContext
,让BackgroundWorker在你的运行空间线程上做事件回调。在你创建BackgroundWorker之前,做这个调用:
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
让WindowsFormsSynchronizationContext工作的技巧是,AFAICT,你必须泵消息。以下是我有限的测试成果:
using System.ComponentModel;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
using System.Windows.Forms;
namespace CmdletExp
{
[Cmdlet("Invoke", "LongRunning")]
public class InvokeLongRunningCommand : PSCmdlet
{
private readonly BackgroundWorker _backgroundWorker;
private readonly AutoResetEvent _autoResetEvent;
public InvokeLongRunningCommand()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
_backgroundWorker.ProgressChanged += _backgroundWorker_ProgressChanged;
_autoResetEvent = new AutoResetEvent(false);
}
protected override void EndProcessing()
{
Debug.WriteLine("EndProcessing ThreadId: " + Thread.CurrentThread.ManagedThreadId);
_backgroundWorker.RunWorkerAsync();
do
{
Application.DoEvents();
} while (!_autoResetEvent.WaitOne(250));
Application.DoEvents();
base.EndProcessing();
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Debug.WriteLine("DoWork ThreadId: " + Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 20; i++)
{
_backgroundWorker.ReportProgress(i * 5);
Thread.Sleep(1000);
}
_autoResetEvent.Set();
}
void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Debug.WriteLine("ProgressChanged ThreadId: " + Thread.CurrentThread.ManagedThreadId + " progress: " + e.ProgressPercentage);
WriteVerbose("Progress is " + e.ProgressPercentage);
}
}
}
像这样调用:
PS> Invoke-LongRunning -Verbose
VERBOSE: Progress is 0
VERBOSE: Progress is 5
VERBOSE: Progress is 10
VERBOSE: Progress is 15
VERBOSE: Progress is 20
VERBOSE: Progress is 25
...
我只是要求我使用的所有方法都在Cmdlet中取a,像这样;
public static bool bpNewLDSInstance(**Cmdlet ni**, string Computername, string InstanceName, installType InstallType, int LDAPPort, int SSLPort, string dbPath, string logPath,
string ImportLDIFFiles, string Partition, string SourceServer, int SourcePort, string ServiceAccount, SecureString ServicePassword, string Administrator, ADLDSInstance InstIn)
{
bool bInstanceValidated = false;
ni.WriteVerbose("1");
}
然后当我从Cmdlet中调用它时,我只需要将Cmdlet传入,像这样,
bool bSuccess = Program.bpNewLDSInstance(**this**, Computername, InstanceName, InstallType, LDAPPort, SSLPort, dbPath, logPath,
ImportLDIFFiles, Partition, SourceServer, SourcePort, ServiceAccount, ServicePassword, Administrator, InstIn);