Thread.ApartmentState and PowerShell Execution Thread

PowerShell Team

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.”;

            }

        }

    }

}

0 comments

Discussion is closed.

Feedback usabilla icon