Single Threaded Apartment in PowerShell V1

I’ve been working on a Winform that I use in a PowerShell script and I needed to display HTML in the form. As I was playing with the form designer in Visual Studio, I found a Web Browser Control that I thought would do the trick. It worked great in C# but when I ported it over to PowerShell, I found I had problems with STA and MTA. The WebBrowser Control will only work in STA mode. Not so good for PS V1 which only runs in MTA.

Marco Shaw reminded me of a blog post on the PowerShell Team Blog about a cmdlet that could run a command in STA mode. This worked like a champ as soon as I downloaded the code and built the Snapin. I was able to run invoke-apartment STA ShowWinform and everything was great.

But then I tried to parameterize my function and it got a little more hairy. The Invoke-Apartment cmdlet takes two parameters, the first is the apartment you want to use (MTA or STA) and the second is called expression that is a STRING.

I wanted to pass in something like this

invoke-apartment –apartment STA –expression “ShowWinform –html <h1>Hello World</h1>”

This blew up in my face, and I was getting parsing errors and all kinds of weird stuff.

Now mind you, I am not a developer, but PowerShell has given me enough background to be willing to crack open some C# code now and then, really just enough to make me dangerous.

So I started looking at the invoke-apartment cmdlet’s parameters. I saw that both parameters were of type string. I thought, hmm, I wonder what would happen if I set them to ScriptBlock instead of String.

I poked around the code a bit more to see where these parameters were being used and gave it a shot. To my utter amazement, the thing actually compiled. Furthermore, to my utter, utter amazement, it actually did what I was hoping. It now took a ScriptBlock instead of a string and I wasn’t getting my parsing errors. The only trick was that I had to wrap my expression in curly braces so PowerShell would know that it was a ScriptBlock.

So now the following code totally worked in PowerShell.

invoke-apartment –apartment STA –expression {“ShowWinform –html <h1>Hello World</h1>”}

The point of all of this is that I love how PowerShell has enabled me to get comfortable enough with C# so that I can look at something and tweak it to make it work for what I needed to do.

I am not saying that all admin should crack open C#, but if you haven’t used a cli yet, fire up PowerShell. If you have played with the cli, but haven’t written any functions, write one. If you haven’t written any scripts, take some of your functions and put them together in a script. It’s all about the journey. PoweShell has really helped take a bunch of next steps.

Oh yeah, and if you are interested, here is the source code with my modifications. Given the code below and reading this blog post, you can build your own Invoke-Apartment cmdlet and Snapin.

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 ScriptBlock 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 ScriptBlock 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);
                ScriptBlock myScriptBlock = 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 "InvokeApartment";
 
            }
 
        }
 
 
 
        /// <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.";
 
            }
 
        }
 
    }
 
}
 

Pingbacks and trackbacks (1)+

Comments are closed