// // Copyright (c) 2009 Microsoft Corporation. All rights reserved. // // DISCLAIMER OF WARRANTY: The software is licensed “as-is.” You // bear the risk of using it. Microsoft gives no express warranties, // guarantees or conditions. You may have additional consumer rights // under your local laws which this agreement cannot change. To the extent // permitted under your local laws, Microsoft excludes the implied warranties // of merchantability, fitness for a particular purpose and non-infringement. namespace Microsoft.Samples.PowerShell.Host { using System; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using PowerShell = System.Management.Automation.PowerShell; /// /// Simple PowerShell interactive console host listener implementation. This class /// implements a basic read-evaluate-print loop or 'listener' allowing you to /// interactively work with the PowerShell engine. /// internal class PSListenerConsoleSample { /// /// Indicator to tell the host application that it should exit. /// private bool shouldExit; /// /// The exit code that the host application will use to exit. /// private int exitCode; /// /// Holds a reference to the PSHost object for this interpreter. /// private MyHost myHost; /// /// Holds a reference to the runspace for this interpeter. /// private Runspace myRunSpace; /// /// Holds a reference to the currently executing pipeline so that /// it can be stopped by the control-C handler. /// private PowerShell currentPowerShell; /// /// Used to serialize access to instance data. /// private object instanceLock = new object(); /// /// Gets or sets a value indicating whether the host applcation /// should exit. /// public bool ShouldExit { get { return this.shouldExit; } set { this.shouldExit = value; } } /// /// Gets or sets the exit code that the host application will use /// when exiting. /// public int ExitCode { get { return this.exitCode; } set { this.exitCode = value; } } /// /// Creates and initiates the listener. /// /// The parameter is not used private static void Main(string[] args) { // Display the welcome message. Console.Title = "Windows PowerShell Console Host Application Sample"; ConsoleColor oldFg = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine(" Windows PowerShell Console Host Interactive Sample"); Console.WriteLine(" =================================================="); Console.WriteLine(string.Empty); Console.WriteLine("This is an example of a simple interactive console host that uses "); Console.WriteLine("the Windows PowerShell engine to interpret commands."); Console.WriteLine("Type 'exit' to exit."); Console.WriteLine(string.Empty); Console.ForegroundColor = oldFg; // Create the listener and run it - this never returns... PSListenerConsoleSample listener = new PSListenerConsoleSample(); listener.Run(); } /// /// Create this instance of the console listener. /// private PSListenerConsoleSample() { // Create the host and runspace instances for this interpreter. Note that // this application doesn't support console files so only the default snapins // will be available. this.myHost = new MyHost(this); this.myRunSpace = RunspaceFactory.CreateRunspace(this.myHost); this.myRunSpace.Open(); // Create a PowerShell object that will be used to execute the commands // to create $profile and load the profiles. lock (this.instanceLock) { this.currentPowerShell = PowerShell.Create(); } try { this.currentPowerShell.Runspace = this.myRunSpace; PSCommand[] profileCommands = Microsoft.Samples.PowerShell.Host.HostUtilities.GetProfileCommands("SampleHost04"); foreach (PSCommand command in profileCommands) { this.currentPowerShell.Commands = command; this.currentPowerShell.Invoke(); } } finally { // Dispose of the pipeline line and set it to null, locked because currentPowerShell // may be accessed by the ctrl-C handler... lock (this.instanceLock) { this.currentPowerShell.Dispose(); this.currentPowerShell = null; } } } /// /// A helper class that builds and executes a pipeline that writes to the /// default output path. Any exceptions that are thrown are just passed to /// the caller. Since all output goes to the default outter, this method() /// won't return anything. /// /// The script to run /// Any input arguments to pass to the script. If null /// then nothing is passed in. private void executeHelper(string cmd, object input) { // Just ignore empty command lines. if (String.IsNullOrEmpty(cmd)) { return; } // Create the pipeline object and make it available // to the ctrl-C handle through the currentPowerShell instance // variable lock (this.instanceLock) { this.currentPowerShell = PowerShell.Create(); } this.currentPowerShell.Runspace = this.myRunSpace; // Create a pipeline for this execution - place the result in the currentPowerShell // instance variable so it is available to be stopped. try { this.currentPowerShell.AddScript(cmd); // Now add the default outputter to the end of the pipe and indicate // that it should handle both output and errors from the previous // commands. This will result in the output being written using the PSHost // and PSHostUserInterface classes instead of returning objects to the hosting // application. this.currentPowerShell.AddCommand("out-default"); this.currentPowerShell.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output); // If there was any input specified, pass it in, otherwise just // execute the pipeline... if (input != null) { this.currentPowerShell.Invoke(new object[] { input }); } else { this.currentPowerShell.Invoke(); } } finally { // Dispose of the pipeline line and set it to null, locked because currentPowerShell // may be accessed by the ctrl-C handler... lock (this.instanceLock) { this.currentPowerShell.Dispose(); this.currentPowerShell = null; } } } /// /// An exception occurred that we want to display /// using the display formatter. To do this we run /// a second pipeline passing in the error record. /// The runtime will bind this to the $input variable /// which is why $input is being piped to out-string. /// We then call WriteErrorLine to make sure the error /// gets displayed in the correct error color. /// /// The exception to display private void ReportException(Exception e) { if (e != null) { object error; IContainsErrorRecord icer = e as IContainsErrorRecord; if (icer != null) { error = icer.ErrorRecord; } else { error = (object)new ErrorRecord(e, "Host.ReportException", ErrorCategory.NotSpecified, null); } lock (this.instanceLock) { this.currentPowerShell = PowerShell.Create(); } this.currentPowerShell.Runspace = this.myRunSpace; try { this.currentPowerShell.AddScript("$input").AddCommand("out-string"); // Don't merge errors, this function will swallow errors. Collection result; PSDataCollection inputCollection = new PSDataCollection(); inputCollection.Add(error); inputCollection.Complete(); result = this.currentPowerShell.Invoke(inputCollection); if (result.Count > 0) { string str = result[0].BaseObject as string; if (!string.IsNullOrEmpty(str)) { // out-string adds \r\n, we remove it this.myHost.UI.WriteErrorLine(str.Substring(0, str.Length - 2)); } } } finally { // Dispose of the pipeline line and set it to null, locked because currentPowerShell // may be accessed by the ctrl-C handler... lock (this.instanceLock) { this.currentPowerShell.Dispose(); this.currentPowerShell = null; } } } } /// /// Basic script execution routine - any runtime exceptions are /// caught and passed back into the engine to display. /// /// The parameter is not used. private void Execute(string cmd) { try { // execute the command with no input. this.executeHelper(cmd, null); } catch (RuntimeException rte) { this.ReportException(rte); } } /// /// Method used to handle control-C's from the user. It calls the /// pipeline Stop() method to stop execution. If any exceptions occur /// they are printed to the console but otherwise ignored. /// /// See sender property of ConsoleCancelEventHandler documentation. /// See e property of ConsoleCancelEventHandler documentation private void HandleControlC(object sender, ConsoleCancelEventArgs e) { try { lock (this.instanceLock) { if (this.currentPowerShell != null && this.currentPowerShell.InvocationStateInfo.State == PSInvocationState.Running) { this.currentPowerShell.Stop(); } } e.Cancel = true; } catch (Exception exception) { this.myHost.UI.WriteErrorLine(exception.ToString()); } } /// /// Implements the basic listener loop. It sets up the ctrl-C handler, then /// reads a command from the user, executes it and repeats until the ShouldExit /// flag is set. /// private void Run() { // Set up the control-C handler. Console.CancelKeyPress += new ConsoleCancelEventHandler(this.HandleControlC); Console.TreatControlCAsInput = false; // loop reading commands to execute until ShouldExit is set by // the user calling "exit". while (!this.ShouldExit) { this.myHost.UI.Write(ConsoleColor.Cyan, ConsoleColor.Black, "\nPSConsoleSample: "); string cmd = Console.ReadLine(); this.Execute(cmd); } // and exit with the desired exit code that was set by exit command. // This is set in the host by the MyHost.SetShouldExit() implementation. Environment.Exit(this.ExitCode); } } }