CSharp/WPFJob.cs

namespace ShowUI
{
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;
using System.Windows.Threading;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections;
using System.Collections.ObjectModel;
 
 
    public class WPFJob : Job, INotifyPropertyChanged
    {
        Runspace runspace;
        InitialSessionState initialSessionState;
 
        PowerShell powerShellCommand;
        Dispatcher JobDispatcher;
        public Window JobWindow;
        Thread jobThread;
 
        Hashtable namedControls;
        Runspace interopRunspace;
 
        Runspace GetWPFCurrentThreadRunspace(InitialSessionState sessionState)
        {
            InitialSessionState clone = sessionState.Clone();
            clone.ThreadOptions = PSThreadOptions.UseCurrentThread;
            SessionStateVariableEntry window = new SessionStateVariableEntry("Window", JobWindow, "");
            SessionStateVariableEntry namedControls = new SessionStateVariableEntry("NamedControls", this.namedControls, "");
            clone.Variables.Add(window);
            clone.Variables.Add(namedControls);
            return RunspaceFactory.CreateRunspace(clone);
        }
 
 
        delegate Collection<PSObject> RunScriptCallback(string script);
        delegate Collection<PSObject> RunScriptWithParameters(string script, Object parameters);
 
        public PSObject[] InvokeScriptInJob(string script, object parameters, bool async)
        {
            if (this.JobStateInfo.State == JobState.Running)
            {
                for (int i = 0; i < 10; i++)
                {
                    if (JobWindow != null) { break; }
                    Thread.Sleep(50);
                }
 
                if (JobWindow == null)
                {
                    return null;
                }
                return (PSObject[])RunOnUIThread(
                    new DispatcherOperationCallback(
                    delegate
                    {
                        PowerShell psCmd = PowerShell.Create();
                        if (interopRunspace == null)
                        {
                            interopRunspace = GetWPFCurrentThreadRunspace(this.initialSessionState);
                            interopRunspace.Open();
                        }
                        psCmd.Runspace = interopRunspace;
                        psCmd.AddScript(script);
                        if (parameters is IDictionary)
                        {
                            psCmd.AddParameters(parameters as IDictionary);
                        }
                        else
                        {
                            if (parameters is IList)
                            {
                                psCmd.AddParameters(parameters as IList);
                            }
                        }
                        Collection<PSObject> results = psCmd.Invoke();
                        if (psCmd.InvocationStateInfo.Reason != null)
                        {
                            throw psCmd.InvocationStateInfo.Reason;
                        }
                        PSObject[] resultArray = new PSObject[results.Count + psCmd.Streams.Error.Count];
                        int count = 0;
                        if (psCmd.Streams.Error.Count > 0)
                        {
                            foreach (ErrorRecord err in psCmd.Streams.Error)
                            {
                                resultArray[count++] = new PSObject(err);
                            }
                        }
                        foreach (PSObject r in results)
                        {
                            resultArray[count++] = r;
                        }
                        return resultArray;
                    }),
                    async);
            }
            else
            {
                return null;
            }
        }
 
        object RunOnUIThread(DispatcherOperationCallback dispatcherMethod, bool async)
        {
            if (Application.Current != null)
            {
                if (Application.Current.Dispatcher.Thread == Thread.CurrentThread)
                {
                    // This avoids dispatching to the UI thread if we are already in the UI thread.
                    // Without this runing a command like 1/0 was throwing due to nested dispatches.
                    return dispatcherMethod.Invoke(null);
                }
            }
 
            Exception e = null;
            object returnValue = null;
            SynchronizationContext sync = new DispatcherSynchronizationContext(JobWindow.Dispatcher);
            if (sync == null) { return null; }
            if (async) {
                sync.Post(
                    new SendOrPostCallback(delegate(object obj)
                    {
                        try
                        {
                            returnValue = dispatcherMethod.Invoke(obj);
                        }
                        catch (Exception uiException)
                        {
                            e = uiException;
                        }
                    }),
                    null);
 
            } else {
                sync.Send(
                    new SendOrPostCallback(delegate(object obj)
                    {
                        try
                        {
                            returnValue = dispatcherMethod.Invoke(obj);
                        }
                        catch (Exception uiException)
                        {
                            e = uiException;
                        }
                    }),
                    null);
 
            }
 
            if (e != null)
            {
                throw new System.Reflection.TargetInvocationException(e.Message, e);
            }
            return returnValue;
        }
 
 
        public static InitialSessionState GetSessionStateForCommands(CommandInfo[] commands)
        {
            InitialSessionState iss = InitialSessionState.CreateDefault();
            Dictionary<string, SessionStateCommandEntry> commandCache = new Dictionary<string, SessionStateCommandEntry>();
            foreach (SessionStateCommandEntry ssce in iss.Commands)
            {
                commandCache[ssce.Name] = ssce;
            }
            iss.ApartmentState = ApartmentState.STA;
            iss.ThreadOptions = PSThreadOptions.ReuseThread;
            if (commands.Length == 0)
            {
                return iss;
            }
            foreach (CommandInfo cmd in commands)
            {
                if (cmd.Module != null)
                {
                        string manifestPath = cmd.Module.Path.Replace(".psm1",".psd1").Replace(".dll", ".psd1");
                        if (System.IO.File.Exists(manifestPath)) {
                            iss.ImportPSModule(new string[] { manifestPath });
                        } else {
                            iss.ImportPSModule(new string[] { cmd.Module.Path });
                        }
                         
                        continue;
                }
                if (cmd is AliasInfo)
                {
                    CommandInfo loopCommand = cmd;
                    while (loopCommand is AliasInfo)
                    {
                        SessionStateAliasEntry alias = new SessionStateAliasEntry(loopCommand.Name, loopCommand.Definition);
                        iss.Commands.Add(alias);
                        loopCommand = (loopCommand as AliasInfo).ReferencedCommand;
                    }
                    if (loopCommand is FunctionInfo)
                    {
                        SessionStateFunctionEntry func = new SessionStateFunctionEntry(loopCommand.Name, loopCommand.Definition);
                        iss.Commands.Add(func);
                    }
                    if (loopCommand is CmdletInfo)
                    {
                        CmdletInfo cmdletData = loopCommand as CmdletInfo;
                        SessionStateCmdletEntry cmdlet = new SessionStateCmdletEntry(cmd.Name,
                                cmdletData.ImplementingType,
                                cmdletData.HelpFile);
                        iss.Commands.Add(cmdlet);
                    }
                }
                if (cmd is FunctionInfo)
                {
                    SessionStateFunctionEntry func = new SessionStateFunctionEntry(cmd.Name, cmd.Definition);
                    iss.Commands.Add(func);
                }
                if (cmd is CmdletInfo)
                {
                    CmdletInfo cmdletData = cmd as CmdletInfo;
                    SessionStateCmdletEntry cmdlet = new SessionStateCmdletEntry(cmd.Name,
                            cmdletData.ImplementingType,
                            cmdletData.HelpFile);
                    iss.Commands.Add(cmdlet);
                }
            }
            return iss;
        }
 
        public WPFJob(string name, string command, ScriptBlock scriptBlock)
            : base(command, name)
        {
            this.initialSessionState = InitialSessionState.CreateDefault();
            Start(scriptBlock, new Hashtable());
        }
 
        private WPFJob(ScriptBlock scriptBlock)
        {
            Start(scriptBlock, new Hashtable());
        }
         
        public WPFJob(string name, string command, ScriptBlock scriptBlock, InitialSessionState initalSessionState)
            : base(command, name)
        {
            this.initialSessionState = initalSessionState;
            Start(scriptBlock, new Hashtable());
        }
         
        public WPFJob(string name, string command, ScriptBlock scriptBlock, InitialSessionState initalSessionState, Hashtable parameters)
            : base(command, name)
        {
            this.initialSessionState = initalSessionState;
            Start(scriptBlock, parameters);
        }
 
        private WPFJob(string name, string command, ScriptBlock scriptBlock, InitialSessionState initalSessionState, Hashtable parameters, bool isChildJob)
            : base(command, name)
        {
            this.initialSessionState = initalSessionState;
            if (isChildJob)
            {
                Start(scriptBlock, parameters);
            }
            else
            {
                WPFJob childJob = new WPFJob(name, command, scriptBlock, initalSessionState, parameters, true);
                childJob.StateChanged += new EventHandler<JobStateEventArgs>(childJob_StateChanged);
                this.ChildJobs.Add(childJob);
            }
        }
 
 
        void childJob_StateChanged(object sender, JobStateEventArgs e)
        {
            this.SetJobState(e.JobStateInfo.State);
        }
 
        /// <summary>
        /// Synchronizes Job State with Background Runspace
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void powerShellCommand_InvocationStateChanged(object sender, PSInvocationStateChangedEventArgs e)
        {
            try
            {
                if (e.InvocationStateInfo.State == PSInvocationState.Completed)
                {
                    runspace.Close();
                }
                if (e.InvocationStateInfo.State == PSInvocationState.Failed)
                {
                    ErrorRecord err = new ErrorRecord(e.InvocationStateInfo.Reason, "JobFailed", ErrorCategory.OperationStopped, this);
                    Error.Add(err);
                    runspace.Close();
                }
                JobState js = (JobState)Enum.Parse(typeof(JobState), e.InvocationStateInfo.State.ToString(), true);
                this.SetJobState(js);
            }
            catch
            {
            }
        }
 
 
        void Start(ScriptBlock scriptBlock, Hashtable parameters)
        {
            SessionStateAssemblyEntry windowsBase = new SessionStateAssemblyEntry(typeof(Dispatcher).Assembly.ToString());
            SessionStateAssemblyEntry presentationCore = new SessionStateAssemblyEntry(typeof(UIElement).Assembly.ToString());
            SessionStateAssemblyEntry presentationFramework = new SessionStateAssemblyEntry(typeof(Control).Assembly.ToString());
            initialSessionState.Assemblies.Add(windowsBase);
            initialSessionState.Assemblies.Add(presentationCore);
            initialSessionState.Assemblies.Add(presentationFramework);
            initialSessionState.Assemblies.Add(presentationFramework);
            runspace = RunspaceFactory.CreateRunspace(this.initialSessionState);
            runspace.ThreadOptions = PSThreadOptions.ReuseThread;
            runspace.ApartmentState = ApartmentState.STA;
            runspace.Open();
            powerShellCommand = PowerShell.Create();
            powerShellCommand.Runspace = runspace;
            jobThread = powerShellCommand.AddScript("[Threading.Thread]::CurrentThread").Invoke<Thread>()[0];
 
            powerShellCommand.Streams.Error = this.Error;
            this.Error.DataAdded += new EventHandler<DataAddedEventArgs>(Error_DataAdded);
            powerShellCommand.Streams.Warning = this.Warning;
            this.Warning.DataAdded += new EventHandler<DataAddedEventArgs>(Warning_DataAdded);
            powerShellCommand.Streams.Verbose = this.Verbose;
            this.Verbose.DataAdded += new EventHandler<DataAddedEventArgs>(Verbose_DataAdded);
            powerShellCommand.Streams.Debug = this.Debug;
            this.Debug.DataAdded += new EventHandler<DataAddedEventArgs>(Debug_DataAdded);
            powerShellCommand.Streams.Progress = this.Progress;
            this.Progress.DataAdded += new EventHandler<DataAddedEventArgs>(Progress_DataAdded);
            this.Output.DataAdded += new EventHandler<DataAddedEventArgs>(Output_DataAdded);
            powerShellCommand.Commands.Clear();
            powerShellCommand.Commands.AddScript(scriptBlock.ToString(), false);
            if (parameters.Count > 0)
            {
                powerShellCommand.AddParameters(parameters);
            }
            Collection<Visual> output = powerShellCommand.Invoke<Visual>();
            if (output.Count == 0)
            {
                return;
            }
            powerShellCommand.Commands.Clear();
            powerShellCommand.Commands.AddCommand("Show-Window").AddArgument(output[0]).AddParameter("OutputWindowFirst");
            Object var = powerShellCommand.Runspace.SessionStateProxy.GetVariable("NamedControls");
            if (var != null && ((var as Hashtable) != null))
            {
                namedControls = var as Hashtable;
            }
            JobDispatcher = Dispatcher.FromThread(jobThread);
            JobDispatcher.UnhandledException += new DispatcherUnhandledExceptionEventHandler(jobDispatcher_UnhandledException);
            powerShellCommand.InvocationStateChanged += new EventHandler<PSInvocationStateChangedEventArgs>(powerShellCommand_InvocationStateChanged);
            powerShellCommand.BeginInvoke<Object, PSObject>(null, this.Output);
            DateTime startTime = DateTime.Now;
            if (output[0] is FrameworkElement)
            {
 
                while (JobWindow == null)
                {
                    if ((DateTime.Now - startTime) > TimeSpan.FromSeconds(30))
                    {
                        this.SetJobState(JobState.Failed);
                        return;
                    }
                    System.Threading.Thread.Sleep(25);
                }
            }
 
 
        }
 
        void jobDispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            ErrorRecord err = new ErrorRecord(e.Exception, "UnhandledException", ErrorCategory.OperationStopped, this);
            this.Error.Add(err);
            StopJob();
        }
 
        void Output_DataAdded(object sender, DataAddedEventArgs e)
        {
            PSDataCollection<PSObject> output = sender as PSDataCollection<PSObject>;
            if (output == null)
            {
                return;
            }
            if (output[e.Index].BaseObject is Window)
            {
                JobWindow = output[e.Index].BaseObject as Window;
            }
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Output"));
            }
        }
 
        void Progress_DataAdded(object sender, DataAddedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Progress"));
            }
        }
 
        void Debug_DataAdded(object sender, DataAddedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Debug"));
            }
        }
 
        void Verbose_DataAdded(object sender, DataAddedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Verbose"));
            }
        }
 
        void Warning_DataAdded(object sender, DataAddedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Warning"));
            }
        }
 
        void Error_DataAdded(object sender, DataAddedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Error"));
            }
        }
 
        /// <summary>
        /// If the comamnd is running, the job indicates it has more data
        /// </summary>
        public override bool HasMoreData
        {
            get
            {
                if (powerShellCommand.InvocationStateInfo.State == PSInvocationState.Running)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
 
        public override string Location
        {
            get
            {
                if (this.JobStateInfo.State == JobState.Running && (JobWindow != null))
                {
 
                    return (string)RunOnUIThread(
                        new DispatcherOperationCallback(
                        delegate
                        {
                            return "Left: " + JobWindow.Left +
                                " Top: " + JobWindow.Top +
                                " Width: " + JobWindow.ActualWidth +
                                " Height: " + JobWindow.ActualHeight;
                        }),
                        false);
                }
                else
                {
                    return " ";
                }
            }
        }
 
 
        public override string StatusMessage
        {
            get { return string.Empty; }
        }
 
        public override void StopJob()
        {
            Dispatcher dispatch = Dispatcher.FromThread(jobThread);
            if (dispatch != null)
            {
                if (!dispatch.HasShutdownStarted)
                {
                    dispatch.InvokeShutdown();
                }
            }
            powerShellCommand.Stop();
            runspace.Close();
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                powerShellCommand.Dispose();
                runspace.Close();
                runspace.Dispose();
            }
            base.Dispose(disposing);
        }
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        #endregion
    }
}