Thread.ApartmentState and PowerShell Execution Thread

Thread.ApartmentState and PowerShell Execution Thread

Rate This
  • Comments 14

Recently someone, in internal discussion groups, asked if it is possible to set “ApartmentState” on the powershell’s execution thread. I thought this info is useful to some of you and hence this blog.  To touch basics on ApartmentState see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingapartmentstateclasstopic.asp

Majority of PowerShell’s execution happens through Pipeline class. PowerShell’s  ConsoleHost  constructs a pipeline with user requested command +  input and invokes the pipeline. This is true for any host built on top of PowerShell. Pipeline’s Invoke method creates a child thread and delegates execution to this child thread. Unfortunately in v1.0 release, there is no way to set the ApartmentState of this child thread. By default CLR initializes new threads as “MTA” and there is no way to change the ApartmentState once the thread is started. As a result of this a cmdlet always runs in MTA thread when invoked using Pipeline.

There is no easy solution to this problem. If you want to execute your cmdlet in “STA” apartment, your cmdlet should create a new thread + change apartment state to “STA” -> then execute the task in this child thread. One way of achieving this is creating a new cmdlet with the same functionality as invoke-expression cmdlet to take “Apartment” as the parameter (Invoke-Expression is a sealed class, so we cannot extend from this class).  I am calling this cmdlet “Invoke-Apartment”. Here is how it works:

PS C:\temp> get-command invoke-apartment | select definition | fl *

Definition: Invoke-Apartment [-Apartment] <ApartmentState> [-Expression] <String>

The cmdlet takes an “ApartmentState” and executes “Expression” in that apartment.  It creates a new Thread with the user requested ApartmentState -> constructs a ScriptBlock with the expression supplied by the user -> executes the ScriptBlock in this child thread.

The behavior is as follows:

PS C:\temp> invoke-apartment "MTA" “[System.Threading.Thread]::CurrentThread.GetApartmentState()"

MTA

PS C:\temp> invoke-apartment "STA" "[System.Threading.Thread]::CurrentThread.GetApartmentState()"

STA

To achieve this, I did the following:

1.       A ScriptBlock is constructed using “Expression” parameter.

2.       A new thread is created and ApartmentState is set to the one requested by the user (if current apartment state is not what user requested).

3.       ScriptBlock is invoked in the new thread while the parent thread waits for task completion.

4.       ScriptBlock.InvokeReturnAsIs() needs a runspace to execute the script and it looks for runspace in the Thread-Local storage (TLS)

5.       Since we are executing in a different thread, we need to set Runspace.DefaultRunspace on this thread as well. [Runspace.DefaultRunspace = somerunspace actually updates the TLS]


Note: It is not recommended to execute cmdlets out of the pipeline context as described here as Runspace cannot handle multiple execuitons at the same time like this. Also there is a potential risk of compromising global variables state.

Thanks 

Krishna Vutukuri

Microsoft PowerShell Development 

This posting is provided "AS IS" with no rights and warranties.

 

/* 

 The code here is provided AS IS with no rights or warranties of any kind. Use it at your own discretion.

 */

 

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

using System.ComponentModel;

using System.Configuration;

using System.Collections.ObjectModel;

 

using System.Management.Automation;

using System.Management.Automation.Runspaces;

 

namespace Powershell.Blogs

{

    /// <summary>

    /// Class implemeting Invoke-Apartment

    /// </summary>

    [Cmdlet("Invoke", "Apartment")]

    public sealed

    class

    InvokeApartmentCommand : PSCmdlet

    {

        internal class ExecutionResult

        {

            private object output;

            private Exception error;

 

            public Object Output

            {

                get { return output; }

                set { output = value; }

            }

 

            public Exception Error

            {

                get { return error; }

                set { error = value; }

            }

        }

 

        #region Private Data

 

        private ManualResetEvent waitHandle;

 

        private Runspace runspace;

        private Runspace Runspace

        {

            get

            {               

                return runspace;

            }

 

            set

            {

                runspace = value;

            }

        }

 

        #endregion

 

        #region parameters

 

        private string command;

        private ApartmentState apartment = ApartmentState.MTA;

       

        /// <summary>

        /// Apartment to run the cmdlet int

        /// </summary>

        [Parameter(Position = 0, Mandatory = true)]

        public ApartmentState Apartment

        {

            get { return apartment; }

            set { apartment = value; }

        }

 

        /// <summary>

        /// Command to execute.

        /// </summary>

        [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true)]

        public string Expression

        {

            get {  return command; }

            set { command = value; }

        }

 

        #endregion parameters

 

        protected override void BeginProcessing()

        {

            // Set the runspace

            Runspace = Runspace.DefaultRunspace;

        }

 

        /// <summary>

        /// For each record, execute it, and push the results into the

        /// success stream.

        /// </summary>

        protected override void ProcessRecord()

        {

            ExecutionResult result = new ExecutionResult();

 

            if (Thread.CurrentThread.GetApartmentState() == apartment)

            {

                // Since the current apartment state is same as the one requested

                // do the work in same thread.

                DoWork(result);

            }

            else

            {

                // the apartment state is different..perform the task in

                // a differnt thread.

                Thread executionThread = new Thread(new ParameterizedThreadStart(PerformExecution));

                executionThread.SetApartmentState(apartment);

 

                // Create a handle to wait for completion

                waitHandle = new ManualResetEvent(false);

                executionThread.Start(result);

 

                waitHandle.WaitOne();

            }

                       

            if (null != result.Error)

            {

                throw result.Error;

            }

 

            if (null != result.Output)

            {

                WriteObject(result.Output);

            }

        }

 

        private void PerformExecution(object outputToWriteTo)

        {

            ExecutionResult result = (ExecutionResult)outputToWriteTo;

 

            // Use the runspace to execute the script

            Runspace.DefaultRunspace = Runspace;

 

            DoWork(result);

           

            if (null != waitHandle)

            {

                waitHandle.Set();

            }

        }

 

        private void DoWork(ExecutionResult result)

        {

            try

            {

                ScriptBlock myScriptBlock = InvokeCommand.NewScriptBlock(Expression);

                result.Output = myScriptBlock.InvokeReturnAsIs(null);

            }

            catch (Exception e)

            {

                result.Error = e;

            }

        }

       

    }

 

    /// <summary>

    /// Create this sample as a PowerShell snap-in

    /// </summary>

    [RunInstaller(true)]

    public class InvokeApartmentPSSnapIn : PSSnapIn

    {

        /// <summary>

        /// Create an instance of the InvokeApartmentPSSnapIn

        /// </summary>

        public InvokeApartmentPSSnapIn()

            : base()

        {

        }

 

        /// <summary>

        /// Get a name for this PowerShell snap-in. This name will be used in registering

        /// this PowerShell snap-in.

        /// </summary>

        public override string Name

        {

            get

            {

                return "InvokeApartmentSample";

            }

        }

 

        /// <summary>

        /// Vendor information for this PowerShell snap-in.

        /// </summary>

        public override string Vendor

        {

            get

            {

                return "PowershellBlog";

            }

        }

 

        /// <summary>

        /// Description of this PowerShell snap-in.

        /// </summary>

        public override string Description

        {

            get

            {

                return "This is a PowerShell snap-in that includes the invoke-apartment cmdlet.";

            }

        }

    }

}

Leave a Comment
  • Please add 4 and 1 and type the answer here:
  • Post
  • Interesting post, it's good to know a little about the inner workings of powershell, please keep posting this kind of "Programming with PowerShell" info..

  • i had thought of this, but it would only work for doing C# stuff , but it seems you can invoke a scriptblock like this, outside the context of the runspace thread.. Is it possible to invoke a scriptblock outside the context of a runspace? or being inside a cmdlet, is it still running in the context of the runspace?

  • ScriptBlock.Invoke needs a runspace. It looks for runspace in Thread Local Storage (TLS). The TLS for runspace can be set using Runspace.DefaultRunspace. So you can invoke a ScriptBlock outside of the context of Runspace Thread. Runspaces are not thread safe, so dont use the same runspace in 2 different threads. So to invoke a scriptblock outside of the context of Runspace thread, you need to create a new runspace which defeats the purpose as custom variables / functions / aliases and other state information may not be available directly in this new Runspace.

    -Krishna[MSFT]

    This posting is provided "AS IS" with no rights or warranties

  • Bless you Krishna Vutukuri!  You are the MAN!

    I pulled my hair out for a couple of days fighting an interaction between PowerShell and some older InstallShield installers.  Specifically I have mostly seen the interaction with installers that use _ISDEL.EXE, though I have had one installer hang intermittently that doesn't use this binary.  Hopefully this interaction is limited to installers, so the general community isn't being frustrated by this issue.

    Some older applications freeze when run from PowerShell because of a negative interaction with PowerShell's MTA ApartmentState.  Running the same applications through Invoke-Apartment 'STA' avoids this problem allows them to run without freezing.

  • Thanks Krishna, for the useful technique.  But how about addressing more interactive shell scenarios?  I would like to use PS to test my WPF-based controls.  I could wrap entire test scripts in Invoke-Apartment, but that does not allow me to do ad hoc interactive testing.  The problem is that multiple Invoke-Apartment invocations result in distinct threads, and I'm back to MTA/STA exceptions again. What I think I need is some kind of PSObject wrapper for my WPF types, which can proxy all method/prop accesses through a persistent Invoke-Apartment instance (same thread).  Anyone out there done something like that?

  • i'm eric. joining a couple boards and looking

    forward to participating. hehe unless i get

    too distracted!

    eric

  • Hey,

    I'm Gerry.

    Just saying hey - I'm new.

  • The Windows Live ID Client SDK documentation shows you quite well how to sign in silently or with the

  • Hello,

    I'm newer here and stopping in to say hi.

    I hope everyone has a good day.

    Jaeric

  • I've recently joined and wanted to introduce myself :)

  • This has been extremely useful to me. I did have a little bit of a problem with the expression parameter being a string. I just changed it to take a script block and all was good.

    I blogged about this a while ago, but thought someone else might be interested as well.

    http://get-powershell.com/2008/11/06/single-threaded-apartment-in-powershell-v1/

    Andy

  • Hi,

    I'm not familiar with MTA but when I tried your example the threads didn't run parallel. They run sequential.

    I tried the following to run them asynchronous:

    ...

    protected override void BeginProcessing()

    {

    this.m_Runspace = RunspaceFactory.CreateRunspace();            

    }

    protected override void ProcessRecord()

    {

    //Start a new thread in a multithreaded apartment Thread t = new Thread(new ThreadStart(DoWork));

               t.SetApartmentState(ApartmentState.MTA);

    t.Start();

    }

    public void DoWork()

    {

    //Open the runspace

    this.m_Runspace.Open();

    //Invoke the Scriptblock        this.m_myScriptBlock.Invoke(this.m_Array);

    //Close the Runspace

    this.m_Runspace.Close();

    }

    ...

    But that doesn't work.

    Can you give me hint how I can solve this problem?

  • Another way to do the threading type is to use another commandlet. The sample one form the PowerShell

  • Hi.

    Our community hours ago established a forum site same as this [url=http://www.berigtigelse-skoede.com/bolighandel/salg-af-ejendom-Farum.html]skoede sommerhus[/url] but I have difficulty getting  it  running.

    is this forum site Build on UltraBB ?

Page 1 of 1 (14 items)