#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:
я решил проблему, объединив блокировки и одноэлементные 😉