Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Redirect msh.exe in/out

195 views
Skip to first unread message

William Stacey [MVP]

unread,
Mar 31, 2006, 6:27:14 PM3/31/06
to
When I do something like to redirect console in/out in a c# richtextbox:
ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";

//psi.Arguments = "";

psi.UseShellExecute = false; // required for stream redirection.

psi.RedirectStandardError = true;

psi.RedirectStandardOutput = true;

psi.RedirectStandardInput = true;

psi.CreateNoWindow = false;

return psi;

With cmd.exe it works fine. I get input and output redirected. When I
change cmd.exe to msh.exe, I get the MSH Banner put nothing after that and
no prompt. I figure msh.exe does something with the streams. Any ideas?

--
William Stacey [MVP]

Marcel Ortiz [MSFT]

unread,
Mar 31, 2006, 7:35:09 PM3/31/06
to
MSH detects that the input pipe is redirected and prints no prompt. You
should still be able to issue commands to it. Is it not working if you send
commands through the input pipeline? Or is there a particular reason you
want the prompt visible?

We do have a bug about this (feature?) prompt not being printed but its
relatively low priority right now as we haven't heard from many people about
it. If it affects you in some way we'd really like to hear about it, or
better yet, have a bug on it and have people vote on it.


Thanks
Marcel

--
Marcel Ortiz [MSFT]
Monad: Microsoft Command Shell
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

"William Stacey [MVP]" <william...@gmail.com> wrote in message
news:ufnStqR...@TK2MSFTNGP12.phx.gbl...

William Stacey [MVP]

unread,
Mar 31, 2006, 8:55:37 PM3/31/06
to
Thanks Marcel. I get no output at all after the Banner. Code is same, only
changed "cmd.exe" to "msh.exe". It is like stdout is not being redirected
after the banner.

Here is all I get:
Microsoft Command Shell
Copyright (C) 2005 Microsoft Corporation. All rights reserved.

--
William Stacey [MVP]

"Marcel Ortiz [MSFT]" <mos...@microsoft.com> wrote in message
news:eJ28iQSV...@TK2MSFTNGP15.phx.gbl...

Tobias Weltner

unread,
Apr 3, 2006, 3:34:22 AM4/3/06
to
I would like to expand on this topic. To embrace MSH and build tools and
editors for it, it is absolutely necessary to create a "wrapper" around it.

I started with console redirection myself which just doesn't do the job.
Next, with hints from Jeffrey and his team, I created a .NET wrapper using
the MSH host directly so I can access all its internal objects. This does
work *sort of*:
Whenever I have MSHHost execute something, it *will* return the results via
its internal "WriteLine" method.
I *am* able to output this to a console no problem.
However, the moment I try to grab this output to process it myself, I run
into problems.
For some reason, MSH seems to be blocked until all output is received. So
when I try to output the results into a textbox control, I don't get any
output until the entire output is created, and I also get a stack overflow
indicating that the incoming data could not be processed in time.

Please please please we are all full of ideas what we could do with MSH.
However, currently it is simply not possible without internal knowledge to
create even a simple wrapper class. Could the MSH team please put such a
thing on the prio list? We all here outside of MS are eager to step in and
support MSH. Please enable us!

Dr. Tobias Weltner
MVP Windows Server / Admin Frameworks


"William Stacey [MVP]" <william...@gmail.com> schrieb im Newsbeitrag
news:OWTtU$SVGHA...@TK2MSFTNGP15.phx.gbl...

William Stacey [MVP]

unread,
Apr 3, 2006, 2:23:18 PM4/3/06
to
Thanks for that update. I was going to give up on redirect and go with
hosting, but it seems that may not work totally either. Seems like with a
pipe output, you could dequeue any new output. Would seem potentially
dangerous to block all reads until cmd complete, as that could be a *lot of
output in some cases, not to mention the UI responsiveness issue. Anyone
from MS care to clear up the output stream, pipeline, deal? TIA

--
William Stacey [MVP]

"Tobias Weltner" <tobweltnerAThotmail.com> wrote in message
news:O7rSNEvV...@TK2MSFTNGP15.phx.gbl...


|I would like to expand on this topic. To embrace MSH and build tools and
| editors for it, it is absolutely necessary to create a "wrapper" around
it.
|
| I started with console redirection myself which just doesn't do the job.
| Next, with hints from Jeffrey and his team, I created a .NET wrapper using
| the MSH host directly so I can access all its internal objects. This does
| work *sort of*:
| Whenever I have MSHHost execute something, it *will* return the results
via
| its internal "WriteLine" method.
| I *am* able to output this to a console no problem.
| However, the moment I try to grab this output to process it myself, I run
| into problems.
| For some reason, MSH seems to be blocked until all output is received. So
| when I try to output the results into a textbox control, I don't get any
| output until the entire output is created, and I also get a stack overflow
| indicating that the incoming data could not be processed in time.

...


Marcel Ortiz [MSFT]

unread,
Apr 3, 2006, 4:07:35 PM4/3/06
to
I can't tell much without looking at your code, however, the following works
for me. Note the arguments "-command -" to signal to msh that it should
read the commands from standard input.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "msh.exe";

psi.Arguments = "-command -";

psi.UseShellExecute = false;

psi.RedirectStandardError = true;

psi.RedirectStandardOutput = true;

psi.RedirectStandardInput = true;

psi.CreateNoWindow = false;

Process p = Process.Start(psi);

p.StandardInput.WriteLine("dir");

p.StandardInput.Close();

string output = p.StandardOutput.ReadToEnd();

p.WaitForExit();

Console.WriteLine("Printing characters...");

Console.WriteLine(output);


Marcel Ortiz [MSFT]
Monad: Microsoft Command Shell
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm

"William Stacey [MVP]" <william...@gmail.com> wrote in message

news:OWTtU$SVGHA...@TK2MSFTNGP15.phx.gbl...

Marcel Ortiz [MSFT]

unread,
Apr 3, 2006, 4:53:42 PM4/3/06
to
If you are hosting MSH in your process you should be able to avoid MSH
blocking until all output is ready by using the InvokeAsync method rather
than Invoke. The following snippet will loop forever, grabbing output as it
becomes available.

Runspace r = RunspaceFactory.CreateRunspace();

r.Open();

Pipeline p = r.CreatePipeline("while(1) { 1..10 }");

p.Input.Close();

p.InvokeAsync();

do

{

Collection<MshObject> output = p.Output.NonBlockingRead();

foreach (MshObject obj in output)

{

Console.WriteLine(obj.ToString());

}

}

while (p.Output.IsOpen);


--
Marcel Ortiz [MSFT]
Monad: Microsoft Command Shell
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm

"Tobias Weltner" <tobweltnerAThotmail.com> wrote in message
news:O7rSNEvV...@TK2MSFTNGP15.phx.gbl...

William Stacey [MVP]

unread,
Apr 3, 2006, 5:32:55 PM4/3/06
to
Thanks Marcel! That did it.

Here is a couple more questions if you don't mind related to this:
1) How to send up/down arrows to get history back on std out?
2) How to signal cntrl-c to msh.exe via std-in. p.Kill() is not very nice.
3) As the prompt is not sent back on std-out, how to get the curr dir from
msh.
4) How to know when cmd is actually completed via code.

TIA

--
William Stacey [MVP]

"Marcel Ortiz [MSFT]" <mos...@microsoft.com> wrote in message

news:%23X9SCp1...@TK2MSFTNGP09.phx.gbl...

William Stacey [MVP]

unread,
Apr 3, 2006, 5:40:43 PM4/3/06
to
Also, when you run into something like "help get*" and get the more prompt:
<SPACE> next page; <CR> next line; Q quit

A CR or Q sent down std-in does not seem to work. Any ideas here?

--
William Stacey [MVP]

"William Stacey [MVP]" <william...@gmail.com> wrote in message

news:unMTCZ2V...@TK2MSFTNGP14.phx.gbl...

Marcel Ortiz [MSFT]

unread,
Apr 3, 2006, 7:00:53 PM4/3/06
to
No prob. However, I'm curious, is there a particular reason why you prefer
screen scraping vs using our runspace APIs to do all of this?

Rest Inline.

"William Stacey [MVP]" <william...@gmail.com> wrote in message

news:unMTCZ2V...@TK2MSFTNGP14.phx.gbl...


> Thanks Marcel! That did it.
>
> Here is a couple more questions if you don't mind related to this:
> 1) How to send up/down arrows to get history back on std out?
> 2) How to signal cntrl-c to msh.exe via std-in. p.Kill() is not very
> nice.

You can do that using the Native APIs keybd_event and SendMessage. If you
search for keybd_event you'll find that the first hits are libraries for
simulating keyboard input.

> 3) As the prompt is not sent back on std-out, how to get the curr dir from
> msh.

You can execute (get-location).Path
MSH>(get-location).Path
C:\monad

> 4) How to know when cmd is actually completed via code.

Process.WaitForExit() ?

William Stacey [MVP]

unread,
Apr 3, 2006, 7:15:07 PM4/3/06
to
Just that it is ~easy and you can change between msh.exe and cmd.exe in one
or two lines of code. However, if I can't get passed things like cntrl-c
and hitting Space bar to continue on a More prompt, then runspace may be the
only way to go. Is there a best link to start with the runspace examples?
Chees.

--
William Stacey [MVP]

"Marcel Ortiz [MSFT]" <mos...@microsoft.com> wrote in message

news:OpFX3J3V...@TK2MSFTNGP11.phx.gbl...

Tobias Weltner [MVP]

unread,
Apr 4, 2006, 2:04:12 AM4/4/06
to
Thanks, I am going to try that out right away.
However, how would I ask MSH to cancel a request much similar to CTRL+C in
the console?
Also, I ran into some other problems:
when I launch an external app like CMD.EXE or WMIC.EXE via MSH, the MSH
expects input to come from the console window. There seems to be no way to
send input via API or at least I did not find a way.
So maybe it is necessary to combine console redirection with API calls.

In case you are wondering what we all are trying to do: we want to wrap MSH
to provide tools and editors for it.
While MSH really rocks, it is *very* console-centric, and that's a sad
thing. As *the* new scripting platform, it should be independent of any
platform or infrastructure and rather accept input, process it and return
output.

So this is what we are trying to create.


Thanks & best
tob

"Marcel Ortiz [MSFT]" <mos...@microsoft.com> schrieb im Newsbeitrag
news:%23U4G0C2...@TK2MSFTNGP15.phx.gbl...

Tobias Weltner [MVP]

unread,
Apr 4, 2006, 2:13:49 AM4/4/06
to
I tried and was running into this:
"Cannot create RunspaceConfiguration object because assembly attribute of
type RunspaceConfigurationType is not defined in assembly
WindowsApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null."

I'll try it with my other sample where I do a customrunspace-declaration...


"Marcel Ortiz [MSFT]" <mos...@microsoft.com> schrieb im Newsbeitrag
news:%23U4G0C2...@TK2MSFTNGP15.phx.gbl...

William Stacey [MVP]

unread,
Apr 4, 2006, 3:48:27 AM4/4/06
to
Hi Marcel. Question on SetBufferContents (gets called with CLS):

public override void SetBufferContents(Rectangle rectangle,
BufferCell fill)
{
//throw new NotImplementedException("The SetBufferContents()
method is not implemented by MyRawUserInterface.");
}

How should you implement SetBufferContents from the c# side using Console?

Tobias Weltner [MVP]

unread,
Apr 4, 2006, 5:29:27 AM4/4/06
to
I got it to run now.
but... what good will it do to read forever? How do I know once the current
command is completed so I can go on and either process the output or issue
another command? Also, there is no way to interrupt MSH other than CTRL+C,
and a lot of other special key events also seem to be tied to the console
rather than providing MSH methods to take care of those.

I have been working on this now for months on and off but never got
anywhere. Yes it is possible to get some info out of MSH. However, all
approaches had so many severe limitations that none so far was even remotely
suitable for controlling the MSH/being a starting point for
tools/editors/etc..

One point may be that the MSH object model is extremely complex and not
really documented. If it *is* easy or even possible at all, why can't the
MSH team post one simple example on how to send multiple subsequent commands
to MSH and have the results returned including ways to provide the "special
key events" like CTRL+C without using a console window - saving all of us a
great deal of frustration ;-)

Another point may be that the console has been integrated into the MSH
design so tightly that it really can't be left out. Which in my opinion
would be a real design problem since MSH is supposed to be the
next-generation script language and not just the next generation console. In
this case, I'd suggest using a hidden console window at the heart of MSH and
provide programmatic support for sending and receiving console data
*including* special key strokes such as CTRL+C...

Anyway, any help and suggestions greatly appreciated! I keep searching and
trying and bashing my head on my desk...

tob

Tobias Weltner [MVP]

unread,
Apr 4, 2006, 6:08:53 AM4/4/06
to
sorry, this was meant to be the response to the .NET code sample...


TonyDeSweet

unread,
Apr 4, 2006, 11:33:38 AM4/4/06
to
To add some experience of myself:

I have tried to write a remote client for MSH.exe
(http://MSHForFun.blogspot.com/) and I have been through all the
troubles to access monad engine using different UI. If you really want
a comlicated UI, implementation of your own Hosting API is the way to
go. It is not possible to just redirect Stdin and Stdout.

But even after you have your own MshHost, MshHostUserInterface and
MshHostRawUserInterface, you can still running into problems. for
example,

1. Legacy program (like ping) instead of using UI and RawUI, write its
output directly to Console.Out. So you still have to redirect Stdin
and Stdout in you MshHost. Things wents even worse when Legacy program
try to read from stdin.

2. The Console.Readkey didnot compatible with Readkey() definition in
MshHostRawUserInterface. So it is very difficult to write a 100%
compatible Readkey().

3. GetBufferContent and SetBufferContent is another pain

So I would like to see improvement of MshHost, MshHostUserInterface
and MshHostRawUserInterface. they should be more generic but not
"Console-centric".

Hope that will help.


Tony
http://MSHForFun.blogspot.com/

William Stacey [MVP]

unread,
Apr 4, 2006, 11:36:22 AM4/4/06
to
I think there is a solution for both those issues (send multiple and
cntrl-c) when you are the host. Here is a console host I am playing with
that calls prompt function to get the prompt before doing readline. Most of
code is just from the sdk docs with some mods. Cntl-c is only special to
the actual Console as it looks for it and then sends a signal to your
console app. In this host, it posts a delegate for console cntrl-c event
and just calls pipeline.Close() in the callback. Your windows control might
capture ctrl-c (or ctrl-z, etc) and call pipeline.Close(). After seeing how
this works with Console, you will see how you might move it to a richtextbox
or something and handle your own input/output. hth

using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Management.Automation.Runspaces;
using System.Globalization;
using System.Collections.ObjectModel;

namespace Microsoft.Samples.Msh.Host
{
class MshListenerConsoleSample
{
public bool ShouldExit
{
get
{
return shouldExit;
}
set
{
shouldExit = value;
}
}
private bool shouldExit;

public int ExitCode
{
get
{
return exitCode;
}
set
{
exitCode = value;
}
}
private int exitCode;

private MyHost myHost;
private Runspace myRunSpace;
private Pipeline currentPipeline;

/// <summary>
/// Used to serialize access to instance data...
/// </summary>
private object instanceLock = new object();

MshListenerConsoleSample()
{
myHost = new MyHost(this);
myRunSpace = RunspaceFactory.CreateRunspace(myHost);
myRunSpace.Open();
}

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 currentPipeline instance
// variable

lock (instanceLock)
{
currentPipeline = myRunSpace.CreatePipeline();
}

// Create a pipeline for this execution - place the result in
the currentPipeline
// instance variable so it is available to be stopped.
try
{
currentPipeline.Commands.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 MshHost
// and MshHostUserInterface classes instead of returning
objects to the hosting
// application.

currentPipeline.Commands.Add("out-default");
currentPipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error,
PipelineResultTypes.Output);

// If there was any input specified, pass it in, otherwise
just
// execute the pipeline...

if (input != null)
{
currentPipeline.Invoke(new object[] { input });
}
else
{
currentPipeline.Invoke();
}
}
finally
{
// Dispose of the pipeline line and set it to null, locked
because currentPipeline
// may be accessed by the ctrl-C handler...
lock (instanceLock)
{
currentPipeline.Dispose();
currentPipeline = null;
}
}
}

void Execute(string cmd)
{
try
{
// execute the command with no input...
executeHelper(cmd, null);
}
catch (RuntimeException rte)
{
// 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-default

executeHelper("$input | out-default", rte.ErrorRecord);
}
}

void HandleControlC(object sender, ConsoleCancelEventArgs e)
{
try
{
lock (instanceLock)
{
if (currentPipeline != null &&
currentPipeline.PipelineStateInfo.State == PipelineState.Running)
currentPipeline.Stop();
}
e.Cancel = true;
}
catch (Exception exception)
{
this.myHost.UI.WriteErrorLine(exception.ToString());
}
}

private string ExecCmd(string cmd)
{
Pipeline p = this.myRunSpace.CreatePipeline(cmd);
p.Input.Close();
StringBuilder sb = new StringBuilder();
Collection<MshObject> output = p.Invoke();


foreach (MshObject obj in output)
{

sb.Append(obj.ToString());
//Console.WriteLine(obj.ToString());
}
p.Output.Close();
return sb.ToString();

//p.InvokeAsync();
//do
//{
// Collection<MshObject> output = p.Output.NonBlockingRead();
// foreach (MshObject obj in output)
// {
// sb.AppendLine(obj.ToString());
// //Console.WriteLine(obj.ToString());
// }
//}
//while (p.Output.IsOpen);
}

private void Run()
{
// Set up the control-C handler.
Console.CancelKeyPress += new
ConsoleCancelEventHandler(HandleControlC);
Console.TreatControlCAsInput = false;

// loop reading commands to execute until ShouldExit is set by
// the user calling "exit".
string pwd = "";
string prompt = "";
while (!ShouldExit)
{
//pwd =
this.myRunSpace.SessionStateProxy.GetVariable("pwd").ToString();

//prompt = string.Format("\nMSH {0}> ",pwd);
prompt = ExecCmd("prompt");
myHost.UI.Write(ConsoleColor.White, ConsoleColor.Black,
prompt);
string cmd = Console.ReadLine();
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(ExitCode);
}

static void Main(string[] args)
{
// Display the welcome message...

Console.Title = "Monad Console Host Sample Application";
ConsoleColor oldFg = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(" Monad Console Host Interactive Sample");
Console.WriteLine(" =====================================");
Console.WriteLine("");
Console.WriteLine("This is an example of a simple interactive
console host using the Monad");
Console.WriteLine("engine to interpret commands. Type 'exit' to
exit.");
Console.WriteLine("");
Console.ForegroundColor = oldFg;

// Create the listener and run it - this never returns...

MshListenerConsoleSample listener = new
MshListenerConsoleSample();
listener.Run();
}
}
}

namespace Microsoft.Samples.Msh.Host
{
class MyHost : MshHost
{
public MyHost(MshListenerConsoleSample program)
{
this.program = program;
}
private MshListenerConsoleSample program;

public override CultureInfo CurrentCulture
{
get { return originalCultureInfo; }
}
private CultureInfo originalCultureInfo =
System.Threading.Thread.CurrentThread.CurrentCulture;

public override CultureInfo CurrentUICulture
{
get { return originalUICultureInfo; }
}
private CultureInfo originalUICultureInfo =
System.Threading.Thread.CurrentThread.CurrentUICulture;

public override void EnterNestedPrompt()
{
throw new NotImplementedException("Cannot suspend the shell,
EnterNestedPrompt() method is not implemented by MyHost.");
}

public override void ExitNestedPrompt()
{
throw new NotImplementedException("The ExitNestedPrompt() method
is not implemented by MyHost.");
}

private static Guid instanceId = Guid.NewGuid();
public override Guid InstanceId
{
get { return instanceId; }
}

public override string Name
{
get { return "MySampleConsoleHostImplementation"; }
}

public override void NotifyBeginApplication()
{
return; // Do nothing...
}

public override void NotifyEndApplication()
{
Console.WriteLine("NotifyEndApplication");
return; // Do nothing...
}

public override void SetShouldExit(int exitCode)
{
program.ShouldExit = true;
program.ExitCode = exitCode;
}

public override MshHostUserInterface UI
{
get { return myHostUserInterface; }
}
private MyHostUserInterface myHostUserInterface = new
MyHostUserInterface();

public override Version Version
{
get {return new Version(1,0,0,0); }
}
}
}

namespace Microsoft.Samples.Msh.Host
{
class MyHostUserInterface : MshHostUserInterface
{
public override Dictionary<string, MshObject> Prompt(string caption,
string message,
System.Collections.ObjectModel.Collection<FieldDescription>
descriptions)
{
Write(ConsoleColor.Blue, ConsoleColor.Black, caption + "\n" +
message + " ");
Dictionary<string, MshObject> results = new Dictionary<string,
MshObject>();
foreach (FieldDescription fd in descriptions)
{
string[] label = GetHotkeyAndLabel(fd.Label);
WriteLine(label[1]);
string userData = Console.ReadLine();
if (userData == null)
return null;
results[fd.Name] = MshObject.AsMshObject(userData);
}
return results;
}

public override int PromptForChoice(string caption, string message,
System.Collections.ObjectModel.Collection<ChoiceDescription>
choices, int defaultChoice)
{
// Write the caption and message strings in Blue.
WriteLine(ConsoleColor.Blue, ConsoleColor.Black, caption + "\n"
+ message + "\n");

// Convert the choice collection into something that's a little
easier to work with
// See the BuildHotkeysAndPlainLabels method for details.
Dictionary<string, MshObject> results = new Dictionary<string,
MshObject>();
string[,] promptData = BuildHotkeysAndPlainLabels(choices);

// Format the overall choice prompt string to display...
StringBuilder sb = new StringBuilder();
for (int element=0; element < choices.Count; element++)
{
sb.Append(String.Format("|{0}> {1} ", promptData[0,element],
promptData[1, element]));
}
sb.Append(String.Format("[Default is ({0}]",
promptData[0,defaultChoice]));

// loop reading prompts until a match is made, the default is
chosen or
// the loop is interrupted with ctrl-C.
while (true)
{
WriteLine(ConsoleColor.Cyan, ConsoleColor.Black,
sb.ToString());
string data =
Console.ReadLine().Trim().ToUpper(CultureInfo.CurrentCulture);

// if the choice string was empty, use the default selection
if (data.Length == 0)
return defaultChoice;

// see if the selection matched and return the corresponding
index if it did...
for (int i = 0; i < choices.Count; i++)
{
if (promptData[0, i] == data)
return i;
}
WriteErrorLine("Invalid choice: " + data);
}
}

/// <summary>
/// Parse a string contining a hotkey character.
///
/// Take a string of the form "Yes to &all" and return a
two-dimensional
/// array split out as "A", "Yes to all".
/// </summary>
/// <param name="input">The string to process</param>
/// <returns>A two dimensional array containing the parsed
componenets.</returns>
private static string[] GetHotkeyAndLabel(string input)
{
string[] result = new string[] {String.Empty, String.Empty };
string[] fragments = input.Split('&');
if (fragments.Length == 2)
{
if (fragments[1].Length > 0)
result[0] =
fragments[1][0].ToString().ToUpper(CultureInfo.CurrentCulture);
result[1] = (fragments[0] + fragments[1]).Trim();
}
else
{
result[1] = input;
}
return result;
}

/// <summary>
/// This is a private worker function splits out the accelerator
keys from the menu
/// and builds a two dimentional array with the first access
containing the accelerator
/// and the second containing the label string with the & removed.
/// </summary>
/// <param name="choices">The choice collection to process</param>
/// <returns>A two dimensional array containing the accelerator
characters and the cleaned-up labels</returns>
private static string[,]
BuildHotkeysAndPlainLabels(Collection<ChoiceDescription> choices)
{
// we will allocate the result array
string[,] hotkeysAndPlainLabels = new string[2, choices.Count];

for (int i = 0; i < choices.Count; ++i)
{
string[] hotkeyAndLabel =
GetHotkeyAndLabel(choices[i].Label);
hotkeysAndPlainLabels[0,i] = hotkeyAndLabel[0];
hotkeysAndPlainLabels[1,i] = hotkeyAndLabel[1];
}
return hotkeysAndPlainLabels;
}

public override MshCredential PromptForCredential(string caption,
string message, string userName, string targetName)
{
throw new NotImplementedException("The method
PromptForCredential() is not implemented by MyHost.");
}

public override MshCredential PromptForCredential(string caption,
string message, string userName, string targetName, MshCredentialTypes
allowedCredentialTypes, MshCredentialUIOptions options)
{
throw new NotImplementedException("The method
PromptForCredential() is not implemented by MyHost.");
}

private MyRawUserInterface myRawUi = new MyRawUserInterface();
public override MshHostRawUserInterface RawUI
{
get { return myRawUi; }
}

public override string ReadLine()
{
return Console.ReadLine();
}

public override System.Security.SecureString
ReadLineAsSecureString()
{
throw new NotImplementedException("The method
ReadLineAsSecureString() is not implemented by MyHost.");
}

public override void Write(string value)
{
Console.Write(value); ;
}

public override void Write(ConsoleColor foregroundColor,
ConsoleColor backgroundColor, string value)
{
ConsoleColor oldFg = Console.ForegroundColor;
ConsoleColor oldBg = Console.BackgroundColor;
Console.ForegroundColor = foregroundColor;
Console.BackgroundColor = backgroundColor;
Console.Write(value);
Console.ForegroundColor = oldFg;
Console.BackgroundColor = oldBg;
}

public override void WriteLine(ConsoleColor foregroundColor,
ConsoleColor backgroundColor, string value)
{
ConsoleColor oldFg = Console.ForegroundColor;
ConsoleColor oldBg = Console.BackgroundColor;
Console.ForegroundColor = foregroundColor;
Console.BackgroundColor = backgroundColor;
Console.WriteLine(value);
Console.ForegroundColor = oldFg;
Console.BackgroundColor = oldBg;
}

public override void WriteDebugLine(string message)
{
this.WriteLine(ConsoleColor.DarkYellow, ConsoleColor.Black,
String.Format("DEBUG: {0}", message));
}

public override void WriteErrorLine(string value)
{
this.WriteLine(ConsoleColor.Red, ConsoleColor.Black, value);
}

public override void WriteLine()
{
Console.WriteLine();
}

public override void WriteLine(string value)
{
Console.WriteLine(value);
}

public override void WriteVerboseLine(string message)
{
this.WriteLine(ConsoleColor.Green, ConsoleColor.Black,
String.Format("VERBOSE: {0}", message));
}

public override void WriteWarningLine(string message)
{
this.WriteLine(ConsoleColor.Yellow, ConsoleColor.Black,
String.Format("WARNING: {0}", message));
}

/// <summary>
/// Progress is not implemented by this class. Since it's not
required for the cmdlet
/// to work, it is better to nothing instead of throwing an
exception.
/// </summary>
/// <param name="sourceId">See base class</param>
/// <param name="record">See base class</param>
public override void WriteProgress(long sourceId, ProgressRecord
record)
{
; // Do nothing...
}
}
}

namespace Microsoft.Samples.Msh.Host
{
class MyRawUserInterface : MshHostRawUserInterface
{
public override ConsoleColor BackgroundColor
{
get { return Console.BackgroundColor; }
set { Console.BackgroundColor = value; }
}

public override Size BufferSize
{
get { return new Size(Console.BufferWidth,
Console.BufferHeight); }
set { Console.SetBufferSize(value.Width, value.Height); }
}

public override Coordinates CursorPosition
{
get
{
//throw new NotImplementedException("The CursorPosition
property is not implemented by MyRawUserInterface.");
return new Coordinates(Console.CursorLeft,
Console.CursorTop);
}
set { Console.SetCursorPosition(value.X, value.Y); }
}

public override int CursorSize
{
get { return Console.CursorSize; }
set { Console.CursorSize = value; }
}

public override void FlushInputBuffer()
{
; //Do nothing...
}

public override ConsoleColor ForegroundColor
{
get { return Console.ForegroundColor; }
set { Console.ForegroundColor = value; }
}

public override BufferCell[,] GetBufferContents(Rectangle rectangle)
{
throw new NotImplementedException("The GetBufferContents method

is not implemented by MyRawUserInterface.");
}

public override bool KeyAvailable
{
get { return Console.KeyAvailable; }
}

public override Size MaxPhysicalWindowSize
{
get { return new Size(Console.LargestWindowWidth,
Console.LargestWindowHeight); }
}

public override Size MaxWindowSize
{
get { return new Size(Console.LargestWindowWidth,
Console.LargestWindowHeight); }
}

public override KeyInfo ReadKey(ReadKeyOptions options)
{
ConsoleKeyInfo cki = Console.ReadKey(false);
KeyInfo ki = new KeyInfo();
ki.Character = cki.KeyChar;
return ki;
//throw new NotImplementedException("The ReadKey() method is not
implemented by MyRawUserInterface.");
}

public override void ScrollBufferContents(Rectangle source,
Coordinates destination, Rectangle clip, BufferCell fill)
{
throw new NotImplementedException("The ScrollBufferContents()

method is not implemented by MyRawUserInterface.");
}

public override void SetBufferContents(Coordinates origin,
BufferCell[,] contents)
{


throw new NotImplementedException("The SetBufferContents()
method is not implemented by MyRawUserInterface.");
}

public override void SetBufferContents(Rectangle rectangle,
BufferCell fill)
{
if (rectangle.Top == -1)
{
Console.Clear();
return;
}

Console.ForegroundColor = fill.ForegroundColor;
Console.BackgroundColor = fill.BackgroundColor;
for(int r = rectangle.Top; r <= rectangle.Bottom; r++)
{
for(int c = rectangle.Left; c <= rectangle.Right; c++)
{
// Console.SetCursorPosition(c, r);
// Console.Write(fill.Character);
}
}
//Console.Clear();


//throw new NotImplementedException("The SetBufferContents()
method is not implemented by MyRawUserInterface.");
}

public override Coordinates WindowPosition
{
get { return new Coordinates(Console.WindowLeft,
Console.WindowTop); }
set { Console.SetWindowPosition(value.X, value.Y); }
}

public override Size WindowSize
{
get { return new Size(Console.WindowWidth,
Console.WindowHeight); }
set { Console.SetWindowSize(value.Width, value.Height); }
}

public override string WindowTitle
{
get { return Console.Title; }
set { Console.Title = value; }
}
}
}

--
William Stacey [MVP]


William Stacey [MVP]

unread,
Apr 4, 2006, 2:29:29 PM4/4/06
to
| 1. Legacy program (like ping) instead of using UI and RawUI, write its
| output directly to Console.Out. So you still have to redirect Stdin
| and Stdout in you MshHost. Things wents even worse when Legacy program
| try to read from stdin.

I wonder how MSH is handling that case. Is there a switch or something in
the hosting apis, as I don't see support for that. Any ideas?


/\/\o\/\/

unread,
Apr 4, 2006, 2:40:51 PM4/4/06
to
I spoke J Truher on IRC (#monad freenode) some time ago,
I do not have the original text,
but it had something to do with being the last command on the pipeline.
if this is a native(legacy) command it gets the hook to the console, to
make "interactive" applications possible.

greetings /\/\o\/\/

William Stacey [MVP]

unread,
Apr 4, 2006, 4:11:49 PM4/4/06
to
Any ideas? Surely, there has to be a way. Here is what I am using now:

private void ExecuteHelper(string cmd, object input)

--
William Stacey [MVP]

"/\/\o\/\/" <n...@spam.mow> wrote in message
news:OEqUrdBW...@TK2MSFTNGP14.phx.gbl...

TonyDeSweet

unread,
Apr 5, 2006, 3:29:51 PM4/5/06
to
I have tried that. It is NOT Working.
Legacy program (like ping) still writes directly to Console.Out

William Stacey [MVP]

unread,
Apr 5, 2006, 4:27:49 PM4/5/06
to
TMK, if we had Console.Out redirected, it would work. However, we don't
even have a console attached because we have a win program. So Ping starts
a new console and writes to that AFAICT. So do we have to start a hidden
console just so that we have a console attached to the process and also read
from it in addition to the pipeline?? This would seem all very messy.
MS...what say you?

--
William Stacey [MVP]

"TonyDeSweet" <shenzh...@gmail.com> wrote in message
news:1144265391.1...@u72g2000cwu.googlegroups.com...

William Stacey [MVP]

unread,
Apr 5, 2006, 4:39:40 PM4/5/06
to
Random thoughts... Msh.exe, on the other hand, did not have to deal with
this issue, because it has a console. If you make your own console host,
ping works as expected also, but still operates outside of the pipeline.
Seems we have a catch 22. If you use console scaping, you have issues with
cntrl-c and arrow keys and key input, etc. If you host monad, you have
issues with ping and others. Wonder if msh could somehow inject a console
wedge or something? Not sure.

--
William Stacey [MVP]

"William Stacey [MVP]" <william...@gmail.com> wrote in message

news:OQ1jw9OW...@TK2MSFTNGP05.phx.gbl...

Marcel Ortiz [MSFT]

unread,
Apr 5, 2006, 7:48:05 PM4/5/06
to
Unfortunately the pipeline APIs don't provide a way of saying, "run this and
always redirect native executables". At this point its pretty much a
heuristic that determines whether or not the pipeline gets redirected.
Like, /\/\o\/\/ mentioned, the pipelines aren't always redirected because we
need interactive applications to work correctly. You'll notice that if a
native application is the only command in a pipeline then the output isn't
redirected. If it's assigned to something on the other hand (like $a = ping
localhost) then the output is redirected. Also, if the output of the
command is redirected into another command, then the output is also
redirected. For example, run "ping localhost | foreach { write-host "foo
$_" }". In any case, it sounds like a switch on the pipeline APIs that
always forces redirection of native executables would be useful. Its been
discussed, though I don't think it has too high a priority at this point.
You might want to open a bug. In the meantime, let me offer you a
workaround:

string command = "ping localhost";

command = string.Format("$results = . {{ {0} }}; $results", command);

Pipeline p = r.CreatePipeline(command);

This will force redirection because the results are being assigned to a
variable. It does have one large downside and its that because the results
are assigned to a variable and then the results of the variable are output,
you won't get any results until all the execution is complete.

--
Marcel Ortiz [MSFT]
Monad: Microsoft Command Shell
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

"William Stacey [MVP]" <william...@gmail.com> wrote in message
news:OQ1jw9OW...@TK2MSFTNGP05.phx.gbl...

William Stacey [MVP]

unread,
Apr 5, 2006, 9:43:02 PM4/5/06
to
Thanks for the info. This is good to know. However, afaict, this will not
work in a general sense as you would have to "know" all native commands
before hand to make a generic console UI in Form. Seems there is more edge
cases here then at first glance. Why things like edit.com still don't work
under msh? Seems like interactive programs could work in all cases if:
1) All output was redirected to pipeline.
2) Have two input streams. A raw keyboard input stream (for things like More
prompt and special keys) and a std input data stream.

Or maybe 1 input Q and 1 output Q and everything is a Message and handled
based on Message type.

Console2...Console2...Console2. I guess I have to punt. Cheers Marcel.

--
William Stacey [MVP]

"Marcel Ortiz [MSFT]" <mos...@microsoft.com> wrote in message

news:%23tPWftQ...@TK2MSFTNGP02.phx.gbl...

TonyDeSweet

unread,
Apr 7, 2006, 10:37:02 AM4/7/06
to
Thanks for Marcel Ortiz's work-around

But, It is still a problem. As William Stacey mentioned, How do you
know in advance
that next user input is a "native executables".

For a non-interactive "native executables" like ping.exe, it is OK if
we just lost some output. What if an interactive "native executables"
want's to read from Console.In and MshHost did not redirect
Console.In, Host would wait "native executables" to exit and freeze for
a long time( until timeout). This will cause big problem.

Tobias Weltner [MVP]

unread,
Apr 10, 2006, 11:13:06 AM4/10/06
to
Good job, William!
I am going to explore your code tonight.
I am moving away from console windows altogether.
However, this raises another problem: when you launch an app from MSH that
requires a console such as NETSH or WMIC, it will break.


Tobias Weltner [MVP]

unread,
Apr 10, 2006, 11:23:03 AM4/10/06
to
To further complicate matters, there are external console apps that require
access to stdin. So even though ping does redirect using the tricks
mentioned before, commands like "netsh" and "wmic" fail.
They only work if I AllocConsole a console window and type the desired input
"blindly" into the blank console window. The result again is redirected
correctly.

I did not find a way to provide a redirected stdin so far that would work
with such commands.

I tend to agree that MSH development is a bit too console-centric. After
all, we are all more or less trying to escape the console. I'd love to see
some general "console wrapper".

Does anyone know when there will be a more comprehensive SDK for MSH? I
found some bits but without the meat I was looking for... ;-)

Thanx!

"TonyDeSweet" <shenzh...@gmail.com> schrieb im Newsbeitrag
news:1144420622.7...@u72g2000cwu.googlegroups.com...

William Stacey [MVP]

unread,
Apr 10, 2006, 11:53:40 AM4/10/06
to
Unfortunately yes. I don't know the solution here. I guess you could also
start a hidden console and capture that, but that seems hacky, plus not sure
even that will handle all cases. I can't seem to find the light on these
issues.

--
William Stacey [MVP]

"Tobias Weltner [MVP]" <tobweltnerAThotmail.com> wrote in message
news:uLRPPFLX...@TK2MSFTNGP05.phx.gbl...

William Stacey [MVP]

unread,
Apr 10, 2006, 11:59:18 AM4/10/06
to
I think the general issue is the two win32 api modes of the console -
high-level and low-level. One needs to address both to get all apps working
or you can loose output or hang waiting for input that never comes. Question
is how to do this cleanly?

--
William Stacey [MVP]

"Tobias Weltner [MVP]" <tobweltnerAThotmail.com> wrote in message

news:%230UyvKL...@TK2MSFTNGP04.phx.gbl...

0 new messages