Welcome to MSDN Blogs Sign in | Join | Help

Thread.ApartmentState and PowerShell Execution Thread

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

            }

        }

    }

}

Published Friday, March 23, 2007 8:50 PM by PowerShellTeam
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Thread.ApartmentState and PowerShell Execution Thread

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

Saturday, March 24, 2007 10:54 AM by Joris van Lier

# re: Thread.ApartmentState and PowerShell Execution Thread

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?

Monday, March 26, 2007 7:06 PM by Karl Prosser

# re: Thread.ApartmentState and PowerShell Execution Thread

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

Tuesday, March 27, 2007 2:27 PM by PowerShellTeam

# re: Thread.ApartmentState and PowerShell Execution Thread

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.

Friday, March 30, 2007 1:33 PM by DarthHack

# re: Thread.ApartmentState and PowerShell Execution Thread

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?

Tuesday, May 22, 2007 8:20 AM by Scott Jones

# just what i needed

i'm eric. joining a couple boards and looking

forward to participating. hehe unless i get

too distracted!

eric

Sunday, November 04, 2007 6:42 AM by xztheericzx

# Just saying hi quickly

Hey,

I'm Gerry.

Just saying hey - I'm new.

Friday, December 14, 2007 3:08 PM by oOgerryOo

# Powershell CmdLet for LiveContacts Get-WindowsLiveContact

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

Tuesday, March 25, 2008 1:10 PM by Peter Schneider

# Good day.

Hello,

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

I hope everyone has a good day.

Jaeric

Friday, June 20, 2008 6:26 AM by Arrarabrale

Leave a Comment

(required) 
required 
(required) 
 
Page view tracker