【发布时间】:2021-07-21 05:52:28
【问题描述】:
我正在编写一个 C# 应用程序,该应用程序将涉及使用 PowerShell V2 module 从 Exchange Online 收集数据。在客户执行管理员同意后,我将使用在 Windows 虚拟机上运行的多线程 C# 应用程序与他们的环境建立 PowerShell 连接。我正在使用 Net 5.0 和 PowerShell 7.x。我需要使用多个线程,因为从单个租户收集数据可能是一个漫长的过程。
问题是,虽然应用程序运行良好,但如果我尝试使用多个线程同时为两个租户运行应用程序,则会发生冲突。该模块似乎不是线程安全的。
我已经构建了一个通过 .Net DI 作为瞬态注入的服务。此服务创建一个 HostedRunspace 类,用于执行 PowerShell 的状态管理。
public class HostedRunspace : IDisposable
{
private Runspace runspace;
public void Initialize(string[] modulesToLoad = null)
{
InitialSessionState defaultSessionState = InitialSessionState.CreateDefault();
defaultSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned;
if (modulesToLoad != null)
{
foreach (string moduleName in modulesToLoad)
{
defaultSessionState.ImportPSModule(moduleName);
}
}
runspace = RunspaceFactory.CreateRunspace(defaultSessionState);
runspace.ThreadOptions = PSThreadOptions.UseNewThread;
runspace.ApartmentState = ApartmentState.STA;
runspace.Open();
}
public async Task<List<PSObject>> RunScript(string scriptContents, Dictionary<string, object> scriptParameters = null)
{
if (runspace == null)
{
throw new ApplicationException("Runspace must be initialized before calling RunScript().");
}
PSDataCollection<PSObject> pipelineObjects;
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(runspace))
{
ps.AddScript(scriptContents);
if (scriptParameters != null)
{
ps.AddParameters(scriptParameters);
}
ps.Streams.Error.DataAdded += Error_DataAdded;
ps.Streams.Warning.DataAdded += Warning_DataAdded;
ps.Streams.Information.DataAdded += Information_DataAdded;
// execute the script and await the result.
pipelineObjects = await ps.InvokeAsync().ConfigureAwait(false);
// print the resulting pipeline objects to the console.
Console.WriteLine("----- Pipeline Output below this point -----");
foreach (PSObject item in pipelineObjects)
{
Console.WriteLine(item.BaseObject.ToString());
}
}
List<PSObject> psObjects = new List<PSObject>();
foreach (PSObject pipelineObject in pipelineObjects)
{
psObjects.Add(pipelineObject);
}
return psObjects;
}
当需要收集租户的 PowerShell 数据时,会创建一个新线程,如下所示:
IOnlineDataTaskRunner taskRunner = serviceProvider.GetRequiredService<IOnlineDataTaskRunner>();
Thread thread = new Thread(() => taskRunner.RunAsync(dataTask));
thread.Start();
在这里,我得到了我的 PowerShell 服务的临时版本,它本身将新建一个 HostedRunspace。我创建了一个新线程,为其提供一些配置并启动该线程。
当线程运行时,我首先必须使用证书连接到 Exchange Online。
string command = $"Connect-ExchangeOnline -CertificateThumbprint \"{Thumbprint}\" -AppId \"{ClientId}\" -ShowBanner:$false -Organization {tenant}";
await runspace.RunScript(command);
然后,在此之后,我使用 PowerShell 模块执行各种其他数据检索任务,包括检索邮箱信息、存储大小等。这些也是通过执行的
await runspace.RunScript(command);
如上所述,如果我一次运行一个线程,则没有问题。但是,如果我将线程 1 连接到租户 A 并将线程 2 连接到租户 B,则初始 Connect-ExchangeOnline 将毫无问题地进行。
但是,如果您检索邮箱信息,例如,两个线程都会为最后连接的租户提取数据。这表明模块或我的实现可能存在线程问题。
【问题讨论】:
-
您从 C# 运行 powershell 脚本是否有原因?为什么不直接用C#实现powershell,那你就没有问题了?
-
@Neil 我不确定我是否理解您的问题。 “在 C# 中实现 PowerShell”是什么意思?我引用了 Microsoft.PowerShell.SDK nuget 并使用它来运行 PowerShell Core,我相信这是在 .net 核心应用程序中运行 PowerShell 的方式。没有其他 Exchange Online API 可供我用作替代方案。
-
有必要使用C#吗?您可以使用运行空间以及 ThreadJob 模块进行多线程处理,并且仅使用 PowerShell 不会出现此问题
-
我可能错了,但不是 PS 库,只是 REST 调用的包装器吗?在这种情况下,只需从 C# 进行 REST 调用
-
@Neil 没有记录 REST 调用。我希望他们是,因为我讨厌 PowerShell,很明显。
标签: c# powershell office365 exchange-server powershell-core