c# 中的 powershell 交换命令 - 运行空间限制

powershell exchange commands in c# - runspace limits

最近我一直在 .net 项目中创建一些模块,它通过远程 powershell 发送命令到交换服务器。 我在使用 runespace 时遇到很多问题 - 有时它的状态被破坏,有时我超过了允许的最大连接数(3)

我不知道该怎么做。 下面的代码不是很好,但它比以前的代码工作得更好 - 所以现在请不要看质量

第一个 class 负责返回 runespace(和 powershell 连接)- 我已将此 class 注册为单例(它是 webapi 项目)

public class PowershellCommandEnvironment : IPowershellCommandEnvironment, IDisposable
{
    readonly (string user, string password) powerShellAuth;
    private static Runspace _runspace = null;
    WSManConnectionInfo _connectionInfo;
    

    public PowershellCommandEnvironment()
    {
        powerShellAuth.user = CloudConfigurationManager.GetSetting("ExchangePowerShellUser");
        powerShellAuth.password = CloudConfigurationManager.GetSetting("ExchangePowerShellPassword");

        SecureString secureStrin = new NetworkCredential("", powerShellAuth.password).SecurePassword;
        var creds = new PSCredential(powerShellAuth.user, secureStrin);
        _connectionInfo = new WSManConnectionInfo(new Uri("https://outlook.office365.com/powershell-liveid/"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", creds);
        _connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
        _connectionInfo.MaximumConnectionRedirectionCount = 2;
        _runspace = RunspaceFactory.CreateRunspace(_connectionInfo);
        _runspace.StateChanged += _runspace_StateChanged;


    }

    private void _runspace_StateChanged(object sender, RunspaceStateEventArgs e)
    {
        var state = _runspace.RunspaceStateInfo.State;
        switch (state)
        {
            case RunspaceState.Broken:
                _runspace.Close();
                _runspace.Dispose();
                _runspace = RunspaceFactory.CreateRunspace(_connectionInfo);
                break;

            case RunspaceState.Opening:
                Thread.Sleep(500);
                break;

            case RunspaceState.BeforeOpen:
                _runspace.Open();
                break;
        }
    }

    public Runspace GetRunspace()
    {
      
            while (_runspace.RunspaceStateInfo.State != RunspaceState.Opened)
            {

                OpenRunSpaceTimeExceededAttempt(0);
                Thread.Sleep(100);
            }
            return _runspace;
        
      
    }

    private void OpenRunSpaceTimeExceededAttempt(int attempt)
    {
        if (attempt > 2)
            return;

  
        try
        {
            var state = _runspace?.RunspaceStateInfo.State;
            if (_runspace == null || state == RunspaceState.Closed)
            {
                _runspace = RunspaceFactory.CreateRunspace(_connectionInfo);
                _runspace.Open();
            }


            if (state == RunspaceState.BeforeOpen)
                _runspace.Open();


   
            if (!(state == RunspaceState.Opened))
            {
                OpenRunSpaceTimeExceededAttempt(attempt+1);
            }
        }
        catch (Exception ex)
        {
            if (ex.Message.Contains("Please wait for"))
            {
                System.Threading.Thread.Sleep(10000);
            }
            OpenRunSpaceTimeExceededAttempt(attempt + 1);
        }
    }

 

    public void Dispose()
    {
        _runspace.Dispose();
    }
}

第二个class是PowershellComand,负责执行命令

protected abstract Dictionary<string, object> Parameters { get; set; }
        protected abstract string Command { get; }

        private static Runspace _runspace = null;
        public PowershellCommand(IPowershellCommandEnvironment powershellCommandEnvironment)
        {
            _runspace = powershellCommandEnvironment.GetRunspace();
            Parameters = new Dictionary<string, object>();
            
        }
      
        public T Execute(int attemp=0)
        {
            if (attemp > 2)
                return null;

            try
            {
                using (var powershell = System.Management.Automation.PowerShell.Create())
                {
                    powershell.Runspace = _runspace;
                        
                    powershell.AddCommand(Command);
                    foreach (var param in Parameters)
                    {
                        powershell.AddParameter(param.Key, param.Value);
                    }
                    Collection<PSObject> result = powershell.Invoke();
                    powershell.Runspace.Dispose();
                    return Map(result);
                }
            }
            catch(Exception ex)
            {
                string logMessage = $"Command ${Command} not suceeded.{Environment.NewLine} {ex.Message} {ex.InnerException?.Message}";
                _logger.Log(LogLevel.Error, logMessage);
                int sleep = 5000;
                if (ex.Message.Contains("Please wait for"))
                {
                    sleep = 10000;
                    _logger.Log(LogLevel.Error, "waiting 10000 seconds (powershell command time exceeded");
                 
                }
                Thread.Sleep(sleep);
                return Execute(attemp+1);
            }
        }

        protected abstract T Map(IEnumerable<PSObject> psobj);

这个 class 是由特定的 classes 派生的,这些es已经用参数

覆盖了命令(如 Get-Group、Get-User 等)

它可以工作,但 powershell remote 经常会出现一些错误: - 超出了 runespace 的限制(我想我只创建了一个 - 如果它坏了我会处理它并创建一个新的) -exeeding time litims - 我必须在最新命令调用后等待 X 秒... -坏 xml - 这是最奇怪的事情 - 远程 powershell 回答我说我发送了错误的 xml 数据 - 它很少发生并且是完全随机的

我知道代码有点混乱,但是当我尝试我在互联网上找到的最简单的方法时,确实有更多与时间限制和运行空间限制相关的错误

命令会经常执行 - 可能有 5 个用户可以同时执行某个命令。

使用远程 powershell 是在 exchange 上对启用了安全邮件的组进行操作的唯一可能方法,所以我不能使用图表 api...

我通过组合锁和单例解决了这个问题;)