C# 线程中的 powershell/runspace
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1013331/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
powershell / runspace in a thread
提问by Vincent
I'm running the following code :
我正在运行以下代码:
RunspaceConfiguration config = RunspaceConfiguration.Create();
PSSnapInException warning;
config.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out warning);
if (warning != null) throw warning;
Runspace thisRunspace = RunspaceFactory.CreateRunspace(config);
thisRunspace.Open();
string alias = usr.AD.CN.Replace(' ', '.');
string letter = usr.AD.CN.Substring(0, 1);
string email = alias + "@" + (!usr.Mdph ? Constantes.AD_DOMAIN : Constantes.MDPH_DOMAIN) + "." + Constantes.AD_LANG;
string db = "CN=IS-" + letter + ",CN=SG-" + letter + ",CN=InformationStore,CN=" + ((char)letter.ToCharArray()[0] < 'K' ? Constantes.EXC_SRVC : Constantes.EXC_SRVD) + Constantes.EXC_DBMEL;
string cmd = "Enable-Mailbox -Identity \"" + usr.AD.CN + "\" -Alias " + alias + " -PrimarySmtpAddress " + email + " -DisplayName \"" + usr.AD.CN + "\" -Database \"" + db + "\"";
Pipeline thisPipeline = thisRunspace.CreatePipeline(cmd);
thisPipeline.Invoke();
The code is running in a thread created that way :
代码在以这种方式创建的线程中运行:
t.WorkThread = new Thread(cu.CreerUser);
t.WorkThread.Start();
If I run the code directly (not through a thread), it's working.
如果我直接运行代码(而不是通过线程),它就可以工作。
When in a thread it throws the following exception : ObjectDisposedException "The safe handle has been closed." (Translated from french)
在线程中时,它会引发以下异常: ObjectDisposedException “安全句柄已关闭。” (翻译自法语)
I then replaced "Open" wirh "OpenAsync" which helped not getting the previous exception. But when on Invoke I get the following exception : InvalidRunspaceStateException "Unable to call the pipeline because its state of execution is not Opened. Its current state is Opening." (Also translated from french)
然后我替换了“Open”wirh“OpenAsync”,这有助于没有得到以前的异常。但是在调用时,我收到以下异常: InvalidRunspaceStateException “无法调用管道,因为其执行状态未打开。其当前状态为打开。” (也翻译自法语)
I'm clueless...
我无知...
Any help welcome !!! Thanks !!!
欢迎任何帮助!!!谢谢 !!!
With Open:
打开:
à Microsoft.Win32.Win32Native.GetTokenInformation(SafeTokenHandle TokenHandle, UInt32 TokenInformationClass, SafeLocalAllocHandle TokenInformation, UInt32 TokenInformationLength, UInt32& ReturnLength)
à System.Security.Principal.WindowsIdentity.GetTokenInformation(SafeTokenHandle tokenHandle, TokenInformationClass tokenInformationClass, UInt32& dwLength)
à System.Security.Principal.WindowsIdentity.get_User()
à System.Security.Principal.WindowsIdentity.GetName()
à System.Security.Principal.WindowsIdentity.get_Name()
à System.Management.Automation.MshLog.GetLogContext(ExecutionContext executionContext, InvocationInfo invocationInfo, Severity severity)
à System.Management.Automation.MshLog.GetLogContext(ExecutionContext executionContext, InvocationInfo invocationInfo)
à System.Management.Automation.MshLog.LogEngineLifecycleEvent(ExecutionContext executionContext, EngineState engineState, InvocationInfo invocationInfo)
à System.Management.Automation.MshLog.LogEngineLifecycleEvent(ExecutionContext executionContext, EngineState engineState)
à System.Management.Automation.Runspaces.LocalRunspace.OpenHelper()
à System.Management.Automation.Runspaces.RunspaceBase.CoreOpen(Boolean syncCall)
à System.Management.Automation.Runspaces.RunspaceBase.Open()
à Cg62.ComposantsCommuns.ActiveDirectory.Exchange.BoitesAuxLettres.CreationBAL(User usr, IList`1 log) dans D:\Applications\Commun\Sources .Net\COMIAD\COMIAD\Exchange.cs:ligne 141
à Cg62.ComposantsCommuns.ActiveDirectory.ComptesUtilisateurs.CreationUser.CreerUser() dans D:\Applications\Commun\Sources .Net\COMIAD\COMIAD\ComptesUtilisateurs.cs:ligne 199
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.runTryCode(Object userData)
à System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
à System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()
With OpenAsync:
使用 OpenAsync:
à System.Management.Automation.Runspaces.RunspaceBase.AddToRunningPipelineList(PipelineBase pipeline)
à System.Management.Automation.Runspaces.RunspaceBase.DoConcurrentCheckAndAddToRunningPipelines(PipelineBase pipeline, Boolean syncCall)
à System.Management.Automation.Runspaces.PipelineBase.CoreInvoke(IEnumerable input, Boolean syncCall)
à System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
à System.Management.Automation.Runspaces.Pipeline.Invoke()
à Cg62.ComposantsCommuns.ActiveDirectory.Exchange.BoitesAuxLettres.CreationBAL(User usr, IList`1 log) dans D:\Applications\Commun\Sources .Net\COMIAD\COMIAD\Exchange.cs:ligne 149
à Cg62.ComposantsCommuns.ActiveDirectory.ComptesUtilisateurs.CreationUser.CreerUser() dans D:\Applications\Commun\Sources .Net\COMIAD\COMIAD\ComptesUtilisateurs.cs:ligne 199
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.runTryCode(Object userData)
à System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
à System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()
Sorry for the late reply... I had a lot going on
抱歉回复晚了...我有很多事情要做
Upgraded to Powershell 2.0 and I got past the Open error but now I have the following on the Invoke. I changed my command to :
升级到 Powershell 2.0,我克服了 Open 错误,但现在我在 Invoke 上有以下内容。我将命令更改为:
Enable-Mailbox -Identity "Aagtest Abe" -Alias Aagtest.Abe -PrimarySmtpAddress [email protected] -DisplayName "Aagtest Abe" -Database "myDb" -DomainController adc.domain.int
The command works fine from powershell. I get the following exception CmdletInvocationException : "Une exception a été levée par l'initialiseur de type pour '<Module>'." No idea on how to translate that...
该命令在 powershell 中工作正常。我收到以下异常 CmdletInvocationException :“Une exception a été levée par l'initialiseur de type pour '<Module>'。” 不知道怎么翻译。。。
StackTrace :
堆栈跟踪 :
à Microsoft.Exchange.Data.Directory.DSAccessTopologyProvider..ctor(String machineName)
à Microsoft.Exchange.Data.Directory.DSAccessTopologyProvider..ctor()
à Microsoft.Exchange.Data.Directory.DirectoryServicesTopologyProvider.DiscoverConfigDC()
à Microsoft.Exchange.Data.Directory.DirectoryServicesTopologyProvider..ctor()
à Microsoft.Exchange.Data.Directory.TopologyProvider.InitializeInstance()
à Microsoft.Exchange.Data.Directory.TopologyProvider.GetInstance()
à Microsoft.Exchange.Data.Directory.ADSession.GetConnection(String preferredServer, Boolean isWriteOperation, Boolean isNotifyOperation, ADObjectId& rootId)
à Microsoft.Exchange.Data.Directory.ADSession.GetReadConnection(String preferredServer, ADObjectId& rootId)
à Microsoft.Exchange.Data.Directory.ADSession.IsReadConnectionAvailable()
à Microsoft.Exchange.Configuration.Tasks.RecipientObjectActionTask`2.InternalBeginProcessing()
à Microsoft.Exchange.Management.RecipientTasks.EnableMailbox.InternalBeginProcessing()
à Microsoft.Exchange.Configuration.Tasks.Task.BeginProcessing()
à System.Management.Automation.Cmdlet.DoBeginProcessing()
à System.Management.Automation.CommandProcessorBase.DoBegin()
采纳答案by Vincent
So the final answer to that problem is you can't execute remote Exchange powershell commands if you impersonate through advapi32 LogonUser.
因此,该问题的最终答案是,如果您通过 advapi32 LogonUser 模拟,则无法执行远程 Exchange powershell 命令。
Wether the commands are executed in a thread or not don't matter at all as far as I tested.
就我测试而言,命令是在线程中执行还是无关紧要。
What seem to be the correct way - well the one that works for me - is to authenticate right after you connect to the Runspace.
似乎正确的方法 - 对我有用的方法 - 是在您连接到 Runspace 后立即进行身份验证。
As to why this happens... feel free to tell me !!
至于为什么会发生这种情况……请随时告诉我!!
This code must notbe executed while impersonating.
模拟时不得执行此代码。
IContexteRemotePowerShell crp:
crp.ConfigurationName = "Microsoft.Exchange";
crp.RemoteUri = "http://exhangeserver/powershell";
crp.User = "account who has rights to do stuff on the exchange server";
crp.Password = "its password";
crp.Domaine = "Domain";
private static void Connect(IContexteRemotePowerShell contexte)
{
try
{
Espace = RunspaceFactory.CreateRunspace();
Espace.Open();
}
catch (InvalidRunspaceStateException ex)
{
throw new TechniqueException(MethodBase.GetCurrentMethod(), "Error while creating runspace.", ex);
}
// Create secure password
SecureString password = new SecureString();
foreach (char c in contexte.Password)
{
password.AppendChar(c);
}
// Create credential
PSCredential psc = new PSCredential(contexte.User, password);
PSCommand command = new PSCommand();
command.AddCommand("New-PSSession");
command.AddParameter("Credential", psc);
if (!String.IsNullOrEmpty(contexte.Serveur))
command.AddParameter("computername", contexte.Serveur);
if (!String.IsNullOrEmpty(contexte.RemoteUri))
command.AddParameter("ConnectionUri", new Uri(contexte.RemoteUri));
if (!string.IsNullOrEmpty(contexte.ConfigurationName))
command.AddParameter("ConfigurationName", contexte.ConfigurationName);
//// Create the session
PowerShell powershell = PowerShell.Create();
powershell.Commands = command;
powershell.Runspace = Espace;
Collection<PSObject> result = ExecuterCommande(command);
if (result.Count != 1)
throw new TechniqueException(MethodBase.GetCurrentMethod(),
"Error while connecting.");
// Create session variable
command = new PSCommand();
command.AddCommand("Set-Variable");
command.AddParameter("Name", "ra");
command.AddParameter("Value", result[0]);
ExecuterCommande(command);
}
private const string InvokeCommand = "Invoke-Command -ScriptBlock {{ {0} }} -Session $ra";
private static string ExecuteRemoteScript(string cmd)
{
PSCommand command = new PSCommand();
command.AddScript(string.Format(InvokeCommand, cmd));
Collection<PSObject> result = ExecuterCommande(command);
StringBuilder sb = new StringBuilder();
foreach (PSObject obj in result)
{
sb.AppendLine(obj.ToString());
}
return sb.ToString().Trim();
}
回答by Vincent
Did you try this?
你试过这个吗?
//set the default runspace for this thread
System.Management.Automation.Runspaces.Runspace.DefaultRunspace = runspace;
回答by Ronald Wildenberg
What goes wrong when you call Runspace.Open
from another thread is a symptom of the same problem described here. I had the exact same problem in a web project where I started a new thread from the thread that served the request. The new thread eventually called the Enable-Mailbox
cmdlet.
Runspace.Open
从另一个线程调用时出现的错误是此处描述的相同问题的症状。我在一个 web 项目中遇到了完全相同的问题,我从服务请求的线程启动了一个新线程。新线程最终调用了Enable-Mailbox
cmdlet。
The problem in my case was that the web request itself had long finished before the call to Runspace.Open
occurred. The web request is associated with a WindowsIdentity
that is stored on the thread. The identity is passed to the thread that calls Runspace.Open
. However, since the thread that handles the web request finishes before this call, the identity is disposed before the runspace is opened. Somewhere during the Runspace.Open
call, an attempt is made to get the security token of the now disposed identity (see the first line in your stacktrace: Microsoft.Win32.Win32Native.GetTokenInformation
). This fails with an ObjectDisposedException
.
在我的情况下,问题是 Web 请求本身在调用Runspace.Open
发生之前就已经完成了。Web 请求与WindowsIdentity
存储在线程上的相关联。身份被传递给调用 的线程Runspace.Open
。但是,由于处理 Web 请求的线程在此调用之前完成,因此在打开运行空间之前处理标识。在Runspace.Open
调用过程中的某个地方,会尝试获取现在已处置身份的安全令牌(请参阅堆栈跟踪中的第一行:)Microsoft.Win32.Win32Native.GetTokenInformation
。这失败了ObjectDisposedException
。
There are two ways to fix this:
有两种方法可以解决这个问题:
- Do not call
Runspace.Open
from another thread. At least not when the calling thread finishes beforeRunspace.Open
is called. Make sure there is another identity on the thread before you call
Runspace.Open
. For example, you can do the following:Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("PSTest"), null)
What you fill in for the name of the
GenericIdentity
doesn't really matter.
- 不要
Runspace.Open
从另一个线程调用。至少在调用线程完成之前Runspace.Open
不会被调用。 在调用之前确保线程上有另一个标识
Runspace.Open
。例如,您可以执行以下操作:Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("PSTest"), null)
您为 的名称填写的内容
GenericIdentity
并不重要。
回答by Ritchie annand
Thank you, Ronald. Even if your answer did not help the OP, it helped me.
谢谢你,罗纳德。即使您的回答对 OP 没有帮助,它也对我有所帮助。
I was running into an issue just trying to launch something in an AppDomain on a web server. Right on the line:
我在尝试在 Web 服务器上的 AppDomain 中启动某些内容时遇到了问题。就行了:
ObjectHandle handle = domain.CreateInstance(_assemblyName, _className);
...it exploded with an ObjectDisposedException. I could not figure it out for the life of me - nothing in the AppDomain was being disposed, and this seemed to be working on other servers.
...它爆炸了一个 ObjectDisposedException。我一生都无法弄清楚 - AppDomain 中的任何内容都没有被处理,这似乎在其他服务器上运行。
I stepped into the disassembly, and saw it trying to serialize the thread execution context, the logical call context contained in that, and the principal contained in that, and the principal is what it exploded on.
我进入反汇编,看到它试图序列化线程执行上下文,其中包含的逻辑调用上下文,以及其中包含的主体,主体就是它爆炸的东西。
It turned out that the thread on which this was running was created when a user was logged on, and IIS was in 'impersonate' mode, so the thread's current principal was the expired principal of that original user.
结果表明,运行该线程的线程是在用户登录时创建的,并且 IIS 处于“模拟”模式,因此该线程的当前主体是该原始用户的已过期主体。
I put this around the code:
我把它放在代码周围:
IPrincipal oldPrincipal = Thread.CurrentPrincipal;
try
{
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("NetPlugins"), null);
ObjectHandle handle = domain.CreateInstance(_assemblyName, _className);
// unwrapping and calling code omitted
}
finally
{
Thread.CurrentPrincipal = oldPrincipal;
}
I probably need not put the old principal back, and could also probably solve this by changing the current principal before creating the thread in question, but thank you, Ronald: this works like a top!
我可能不需要把旧的主体放回去,也可以通过在创建有问题的线程之前更改当前主体来解决这个问题,但是谢谢你,罗纳德:这就像一个顶部!