I took a session on Monad at the Bangalore .NET User Group meeting yesterday. Here is a brief write-up about the session for those of you who missed the session. People starting off on Monad may be interested in reading this.
Introduction
Unlike conventional shells that work on text streams and expect the end user to be an expert in text parsing, MSH introduces the idea of using structured object pipelines where objects instead of text flow between commands.
At the msh prompt, if we type
MSH C:/ > dir
we see a list of files [the same way the dos dir worked]. We see the same output for ls.
In Monad commands are called cmdlet [Commandlets]. Each cmdlet is a combination of a verb-noun pair. For example the cmdlet get-children or get/children gets all the children [files and directories when invoked in a FS]. dir and ls are aliases of get-children. By default the verb ‘get’ is assumed for a command given without a verb. So typing children gives the result of get-children and command gives the result of get-command. Alias is an exception. Typing alias gives the result of set-alias and typing aliases give the result of get-alias.
To see all the commands type command. To see all the aliases type aliases.
Some example Commands
MSH C:/> get-process
By using reflection, any object can list the methods and properties that it supports. To get a list of methods supported by a get-process object [which is a System.Diagnostics.Process object] try the command below.
MSH C:/> get-process | get-member -methods
MSH C:/> $a = get-process
MSH C:/> $a[0]
MSH C:/> $a[0].ToString()
Here $a is an array of Process Objects. We can invoke any function or property from the process class on object $a[0] and see the results like the ToString() example above.
MSH C:/> get-process | get-member -property
MSH C:/> $a[0].handlecount
MSH C:/> gps
MSH C:/> gps | tail
MSH C:/> gps | sort id | tail
MSH C:/> gps | where “processname –like cmd*”
MSH C:/> gps | reduce-expression {$_.handlecount -ge 500}
get-process or gps returns an array of objects. Reduce-expression works like a lambda in functional programming languages [similar to anonymous method]
For example in scheme (lambda((a b)(+ a b))) acts as a block of code.
In Monad the block of code after reduce-expression in braces is the script that will be carried for each pipeline object. $_ is set to the current pipeline object. So for each object in the object array returned by gps, $_ is filled with an object (the current pipeline object) for which the block of code is executed.
MSH C:/> gps | sort handlecount | tail | out-excel
MSH C:/> get-process | out-chart processname,handlecount -Title "Processes" -Filename Processes.html
MSH C:/> gps | sort handlecount | tail | out-grid processname,handlecount
MSH C:/> get-command out-*
MSH C:/> foreach ($p in $a | where "handlecount -ge 500" | sort handlecount) { "{0,-15} has {1,6} handles" % $p.processname,$p.handlecount }
Notice the .NET style for formatting strings above.
MSH C:/> function n{notepad}
MSH C:/> n
MSH C:/> n;n;n
MSH C:/> gps [a-c]*,[t-z]* -exclude *[p-t] | stop-process –whatif
MSH C:/> gps | where “processname –like notepad*” | stop-ps1 –confirm
For invalid process ids, an error message is displaying when trying to stop them.
MSH C:/> stop-process 123,345,100000,200000,300000
MSH C:/> stop-process 123,345,100000,200000,300000 -errorpolicy notifystop
MSH C:/> stop-process 100000,200000,300000 -errorpolicy inquire
[Error stop-process]: (No process found for given ID : : 100000) 100000"
[Error stop-process]: (No process found for given ID : : 100000) 100000"
Continue :[y/yes/ n/no t/yestoall l/notoall s/suspend] s
MSH C:/> exit
[Error stop-process]: (No process found for given ID : : 100000) 100000"
Continue :
[y/yes/ n/no t/yestoall l/notoall s/suspend] l
Stopped (Cmdlet:stop-process): User requested stop
MSH C:/>
Cmdlet
Each cmdlet is a managed class and not a separate executable. A simple cmdlet code is given below.
using System;
using System.Diagnostics;
using System.Management.Automation;
namespace SampleCmdlet
{
#region GetPs1
[CmdletDeclaration("get", "ps1")]
public class GetPs1: Cmdlet
{
public override void ProcessRecord()
{
WriteObjects(Process.GetProcesses());
}
}
#endregion
}
The class that is to be exposed as a cmdlet is annotated with a CmdletDeclaration property specifying the verb and noun that is to be used to access this cmdlet. Also your class must inherit from the Cmdlet class and override atleast one of the three functions StartProcessing, ProcessRecord and EndProcessing.
A cmdlet that receives some input parameters and does exception-handling is given below
using System;
using System.Diagnostics;
using System.Management.Automation;
namespace SampleCmdlet
{
#region StopPs1
[CmdletDeclaration("stop", "ps1")]
public class StopPs1: Cmdlet
{
[ParsingParameterMapping(0)]
[ParsingAllowPipelineInput]
[ParsingMandatoryParameter]
[ParsingPromptString( "Input the id: " )]
public int [] Id;
public override void ProcessRecord()
{
Process p = null;
foreach ( int i in Id )
{ if (ShouldProcess(i.ToString()))
{
try
{
p = Process.GetProcessById(i);
p.Kill();
}
catch (System.ComponentModel.Win32Exception e)
{ WriteErrorObject("No adequate permissions",e); }
catch (System.ArgumentException e)
{ WriteErrorObject("No such process exists",e); }
}
}
}
}
#endregion
}
The code declares the input parameter, annotates it with attributes and assumes that the input values have been filled in. It doesn’t care if the input is coming form the pipeline, file, console or any other means. All the parsing and filling up the value is taken care by the shell. The same goes for output. The code calls WriteObject() function to do any output and the shell takes care of the formatting, displaying and routing of output as per the context.
The code above is for Monad, March 2004 build. Sample code for July, 2004 build is available at
http://msdn.microsoft.com/theshow/Episode043/Transcript.html
Cmdlet Providers
Cmdlet Providers provide the user with a basic set of cmdlets like pushd, move, copy, cd, dir etc that make the provider as navigable as a file store. Two standard providers come with the msh installation, one for registry browsing and the other for active directory.
[ProviderDeclaration("REG", "Microsoft", "Windows", "6.0", "MSH", "Registry", "1.0", ProviderCapabilityFlags.None)]
public class RegistryProvider : NavigationCmdletBase
{
protected override void GetItem(string path)
{
RegistryKey key = GetRegkeyForPath(path, false);
if (key == null)
{ WriteErrorObject(path, new ArgumentException("does not exist"));
}
WriteObject(key);
}
....
}
For installing registry provider use:
MSH C:/> new-provider -Assembly "${MSHHOME}\System.Management.Automation.Core.dll" -Provider REG

<