Powershell自定义cmd在标准方法实现之外调用WriteVerbose

本文关键字:调用 WriteVerbose 实现 方法 自定义 cmd 标准 Powershell | 更新日期: 2023-09-27 18:02:06

Msdn文档说明对于WriteVerbose方法:

"这个方法只能从BeginProcessing, ProcessRecord和EndProcessing方法的实现中调用,并且只能从该线程中调用。如果这个调用是从这些实现外部或从另一个线程进行的,则抛出InvalidOperationException异常。"

但是在我的测试代码中,我能够调用三个标准方法之外的方法。但是,当从另一个线程调用该方法时,我得到一个异常。

我相信这里的大多数人在实现自定义cmdlet时需要多线程支持。

我使用后台工作来完成简单的多线程。我在ProcessRecord函数中等待doWork事件完成,然后再继续。现在我想使用writeverbose从doWork向用户报告进度,但由于它是一个不同的线程,我不能这样做。

我已经尝试使用一个AutoResetEvent并发队列来实现这一点,但是有一个更好的方法来解决这个更简单的方式吗?

Powershell自定义cmd在标准方法实现之外调用WriteVerbose

如果您使用的是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);