команды powershell exchange на c # — ограничения рабочего пространства

#c# #powershell #exchange-server

#c# #powershell #exchange-сервер

Вопрос:

в последнее время я создавал некоторый модуль в .net project, который отправляет команды удаленным powershell на сервер exchange. У меня много проблем с runespace — иногда его состояние нарушается, иногда я превысил максимально допустимое количество подключений (3)

Я не знаю, как это сделать. Приведенный ниже код не совсем хорош, но он работает лучше, чем предыдущий код, поэтому, пожалуйста, не смотрите на качество в данный момент

Первый класс отвечает за возврат пространства выполнения (и соединения с powershell) — я зарегистрировал этот класс как одноэлементный (это проект 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();
    }
}
  

Второй класс — это powersellcomand, который отвечает за выполнение команд

 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);
  

Этот класс является производным от определенных классов, которые переопределили команду (например, Get-Group, Get-User и т. Д.) С параметрами

Это работает, но часто возникают некоторые ошибки с powershell remote: -превышен лимит рабочего пространства (я думаю, что создаю только один — если он сломан, я удаляю его и создаю новый) -истекает время ожидания — мне приходится ждать X секунд после вызова последней команды… -плохой xml — это самая странная вещь — удаленный powershell отвечает мне, что я отправляю неправильные xml-данные — это происходит редко и полностью рандомизировано

Я знаю, что код немного запутан, но когда я попробовал самый простой подход, который я нашел в Интернете, было действительно гораздо больше ошибок, связанных с ограничениями времени и рабочего пространства

Команды будут выполняться часто — например, возможно, что 5 пользователей могут выполнить какую-то команду одновременно.

использование удаленного powershell — это единственный возможный способ работы с включенными почтовыми группами с поддержкой безопасности в Exchange, поэтому я не могу использовать graph api…

Ответ №1:

я решил проблему, объединив блокировки и одноэлементные 😉