PowerShell Remoting Project Home

Wednesday, April 26, 2006

PowerShell, Change ... is Good ... but Painful

"Monad" has evolved into "Windows PowerShell”. Don’t tell me your feeling about this name. That’s one of the problems working on a pre-release product. Just to make you fell better, it means we are really close to final release.

There is a bunch of improvement for console user:

1.       Tab completion gets better now.

Did anyone know where is the API mentioned in release note: We added support for parameters and variables tab completion. This was done by having the host call a PowerShell function TabExpansion that takes two parameters – the line being entered and the last token on that line.

2. Some cmdlet Parameters alias changed

For example: *-Process Changed -ProcessName to -Name alias -ProcessName, add alias ProcessId to –Id

This makes me feel much better when working with *-process cmdlet.

Here comes the pain:

1.       Change your old script file extention: *.msh - > *.ps1

2.       Check into individual script (especially you profile.msh) see if you use any old cmdlet names: for example, combine-path -> join-path

3.       Change you %my documents%\msh\profile.msh to %my documents%\PSconfiguration\profile.ps1

If you previously wrote some cmdlets or hosting application, you are going to deal with some mess here:

1. Be careful about name changes:







Some hidden changes:





If you are using my “Monad Remoting”

Uninstall “Monad Remoting” before upgrade to “PowerShell”
You’ve been warned!

To use “PowerShell Remoting”, you have to change you client script.
To build your own client, be careful about name change.


Monday, April 24, 2006

Build Your Own Client for Monad Remoting

New version of Monad Remoting with a client class library suitable for building you own client. Available at MSH For Fun Workspace at gotdotnet.

Uninstall your old Server before upgrade!

My DELL laptop was down again. This time, it was hard drive. I lost two weeks' work. (Ouch! If anyone want to donate a hard drive to me for backup, please contact me. Hehe. ) But I am not giving up. After one month's hard work, I was able to present you the new version of Monad Remoting.

We have a better server: more compatible with ConsoleHost and many bug fixed. But most dramatic changes were made at client side. Now you can build your own cleint application using client class library :

1. To build your own client, you need to initialize an instance of ClientMshHost (Add reference MonadRemoting.ClientMshHost.dll, namespace MonadRemoting), which served as proxy between Monad Remoting server user interface (remote UI) and Local userinterface (Local UI). Although with similar public properties, this "Host" is not derived from MshHost thus can not be used to open a runspace.

2. ClientMshHost has only two public method Start() (to connect server and run) and SetShouldExit(int) (to force client to quit) . Let ClientMshHost do all the hard work: connect to server, authenticate & encrypt, open runspace and run script. Whenever a user interface API was invoked at Server, parameters were sent to ClientMshHost. ClientMshHost will invoke same user interface API at Local UI: display information or get user input, return data back to server. ClientMshHost tries not to throw any exceptions, instead it use Local UI to display all server error and local error.

3. The constructor of ClientMshHost requires a IPEndPoint (to connect Remote UI) and a MshHostUserinterface (to connect LocalUI).
public ClientMshHost(IPEndPoint host, MshHostUserInterface UI){}
MshHostRawUserinterface within MshHostUserinterface is NOT mandatory but is supported by ClientMshHost. Minimum requirement is to implement a MshHostUserinterface which overridden
public abstract string ReadLine();
public abstract SecureString ReadLineAsSecureString();
public abstract void Write(string value);
public abstract void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value);
public abstract void WriteDebugLine(string message);
public abstract void WriteErrorLine(string value);
public virtual void WriteLine();
public abstract void WriteLine(string value);
public virtual void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value);
public abstract void WriteVerboseLine(string message);
public abstract void WriteWarningLine(string message);
Note: To simplify server and client implementation, following API in local UI were ignored (instead normal Read/Write API will be invoked):
public abstract Dictionary<string, MshObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions);
public abstract int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice);
public abstract MshCredential PromptForCredential(string caption, string message, string userName, string targetName);
public abstract MshCredential PromptForCredential(string caption, string message, string userName, string targetName, MshCredentialTypes allowedCredentialTypes, MshCredentialUIOptions options);
There are great advantages of this design:
1. There is no need to implement my own console client. Don't invent wheels! Msh.exe already exposed a great MshHostUserinterface (ConsoleHostUserInterface) as $host.UI variable. So just run a simple script, we are able to "steal" ConsoleHostUserInterface for remoting (Or I should say,unleash the power of $host.UI). I already add following script into my profile.msh.
function start-remotehost {
Param ([string] $IPAddress = "", [int] $Port = 8080)

# Make sure you change path to where you saved MonadRemoting.ClientMshHost.dll

 $RemoteIP = [System.Net.IPAddress]::Parse($IPAddress)
 $RemoteEP = new-object  System.Net.IPEndPoint ($RemoteIP, $Port)
 $RemotingClient = new-object
 $RemotingClient = $null
2. We enjoy all the benefits of ConsoleHostUserInterface and whenever Monad was upgraded, our Monad Remoting local UI was automatically upgraded. Actually, because it use 100% naive msh.exe UI, it is difficult to find out you are in local shell or remote shell. You can always invoke
If you saw ConsoleHost, you were in local shell
Name             : ConsoleHost
Version          : 1.0.7487.0
InstanceId       : ae801745-7671-4a46-9501-6f120bae8a81
UI               : System.Management.Automation.Internal.Host.InternalHostUserI
If you saw ServerMshHost, you were in remote shell.
Name             : ServerMshHost
Version          :
InstanceId       : 84574e63-2b39-4438-b8cc-bb13bd7af932
UI               : System.Management.Automation.Internal.Host.InternalHostUserI

3. You will never leave you msh.exe console window and start another program for remote access. You can call start-remoteHost to gain remote access within msh.exe. When you are done, type
and return to your local shell. You want connect to Remote host again? Just call start-remoteHost again. Actually, I am now using this client as a in-process su command. I was thinking of building this client as a MshSnapin someday.

4. The ClientMshHost dose not care what kind of outter application it is in. It works for console application like msh.exe, also works for Winform base GUI application like Karl Prosser's Msh Analyzer. Hopefully, soon I can use Msh analyzer as my Monad remoting client (Its Rich UI is really cool)

Have Fun

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]


Sunday, April 09, 2006

MshObject, the Magic Therein

From Arul Kumaravel's WebLog: "Monad is the only scripting platform that provides access to .Net, WMI, COM in a well-integrated manner."

How? It is MshObject which does all the tricks. MshObject serve as an adapter to .Net, WMI and COM objects. For more details, read MSH Object Concepts.

Here are members of System.Management.Automation.MshObject:
public class MshObject : IFormattable
      // Methods
      static MshObject();
      public MshObject();
      public MshObject(object obj);
      public static MshObject AsMshObject(object obj);
      public virtual MshObject Copy();
      public override string ToString();
      public string ToString(string format, IFormatProvider formatProvider);

      // Properties
      public object BaseObject { get; }
      public object ImmediateBaseObject { get; }
      public MshMemberInfoCollection Members { get; }
      public MshMemberInfoCollection Methods { get; }
      public MshMemberInfoCollection Properties { get; }
      public Collection<string> TypeNames { get; }
MshObject is hidden under the surface of Msh. You might not notice its exsitance unless you write a cmdlet which deal with MshObject directly. I  prevously working on Serialization of MshObject in my Monad Remoting project. I wrote a small cmdlet to track Mshobject within MSH.exe
using System;
using System.Collections.Generic;
using System.Text;
using System.Management.Automation;

namespace MshCx
    [Cmdlet("get", "mshobject")]
    public class GetMshobject: Cmdlet

        private MshObject[] inputObjects = null;
        [Parameter(Mandatory = false,ValueFromPipeline = true)]
        public MshObject[] InputObjects
            get { return inputObjects; }
            set { inputObjects = value; }

        protected override void ProcessRecord()
            if (inputObjects != null)
                StringBuilder sb = new StringBuilder();
                foreach (MshObject Item in inputObjects)
                    sb.AppendLine("MshObject: ");
                    sb.AppendLine("\t" + Item.ToString());
                    sb.AppendLine("BaseObject: ");
                    sb.AppendLine("\t"+ Item.BaseObject.ToString() + " (" + Item.BaseObject.GetType().FullName + ")");
                    sb.AppendLine("ImmediateBaseObject: ");
                    sb.AppendLine("\t" + Item.ImmediateBaseObject.ToString() + " (" + Item.ImmediateBaseObject.GetType().FullName + ")");
                    sb.AppendLine("TypeNames: ");
                    foreach (string Type in Item.TypeNames)
                        sb.AppendLine("\t"+ Type);
It is really intersting to see how export-clixml and import-clixml works:
> $file= (get-childitem)[0]
> $file

    Directory: Microsoft.Management.Automation.Core\FileSystem::D:\msh

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          2006-4-3     22:27        350 1.txt

> $file | get-member

   TypeName: System.IO.FileInfo

Name                      MemberType     Definition
----                      ----------     ----------
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(String de...
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef Crea...
CreateText                Method         System.IO.StreamWriter CreateText()
Decrypt                   Method         System.Void Decrypt()
Delete                    Method         System.Void Delete()
Encrypt                   Method         System.Void Encrypt()
Equals                    Method         System.Boolean Equals(Object obj)
get_Attributes            Method         System.IO.FileAttributes get_Attrib...
get_CreationTime          Method         System.DateTime get_CreationTime()
get_CreationTimeUtc       Method         System.DateTime get_CreationTimeUtc()
get_Directory             Method         System.IO.DirectoryInfo get_Directo...
get_DirectoryName         Method         System.String get_DirectoryName()
get_Exists                Method         System.Boolean get_Exists()
get_Extension             Method         System.String get_Extension()
get_FullName              Method         System.String get_FullName()
get_IsReadOnly            Method         System.Boolean get_IsReadOnly()
get_LastAccessTime        Method         System.DateTime get_LastAccessTime()
get_LastAccessTimeUtc     Method         System.DateTime get_LastAccessTimeU...
get_LastWriteTime         Method         System.DateTime get_LastWriteTime()
get_LastWriteTimeUtc      Method         System.DateTime get_LastWriteTimeUtc()
get_Length                Method         System.Int64 get_Length()
get_Name                  Method         System.String get_Name()
GetAccessControl          Method         System.Security.AccessControl.FileS...
GetHashCode               Method         System.Int32 GetHashCode()
GetLifetimeService        Method         System.Object GetLifetimeService()
GetObjectData             Method         System.Void GetObjectData(Serializa...
GetType                   Method         System.Type GetType()
InitializeLifetimeService Method         System.Object InitializeLifetimeSer...
MoveTo                    Method         System.Void MoveTo(String destFileN...
Open                      Method         System.IO.FileStream Open(FileMode ...
OpenRead                  Method         System.IO.FileStream OpenRead()
OpenText                  Method         System.IO.StreamReader OpenText()
OpenWrite                 Method         System.IO.FileStream OpenWrite()
Refresh                   Method         System.Void Refresh()
Replace                   Method         System.IO.FileInfo Replace(String d...
set_Attributes            Method         System.Void set_Attributes(FileAttr...
set_CreationTime          Method         System.Void set_CreationTime(DateTi...
set_CreationTimeUtc       Method         System.Void set_CreationTimeUtc(Dat...
set_IsReadOnly            Method         System.Void set_IsReadOnly(Boolean ...
set_LastAccessTime        Method         System.Void set_LastAccessTime(Date...
set_LastAccessTimeUtc     Method         System.Void set_LastAccessTimeUtc(D...
set_LastWriteTime         Method         System.Void set_LastWriteTime(DateT...
set_LastWriteTimeUtc      Method         System.Void set_LastWriteTimeUtc(Da...
SetAccessControl          Method         System.Void SetAccessControl(FileSe...
ToString                  Method         System.String ToString()
MshChildName              NoteProperty   System.String MshChildName=1.txt
MshDrive                  NoteProperty   System.Management.Automation.DriveI...
MshIsContainer            NoteProperty   System.Boolean MshIsContainer=False
MshParentPath             NoteProperty   System.String MshParentPath=Microso...
MshPath                   NoteProperty   System.String MshPath=Microsoft.Man...
MshProvider               NoteProperty   System.Management.Automation.Provid...
Attributes                Property       System.IO.FileAttributes Attributes...
CreationTime              Property       System.DateTime CreationTime {get;s...
CreationTimeUtc           Property       System.DateTime CreationTimeUtc {ge...
Directory                 Property       System.IO.DirectoryInfo Directory {...
DirectoryName             Property       System.String DirectoryName {get;}
Exists                    Property       System.Boolean Exists {get;}
Extension                 Property       System.String Extension {get;}
FullName                  Property       System.String FullName {get;}
IsReadOnly                Property       System.Boolean IsReadOnly {get;set;}
LastAccessTime            Property       System.DateTime LastAccessTime {get...
LastAccessTimeUtc         Property       System.DateTime LastAccessTimeUtc {...
LastWriteTime             Property       System.DateTime LastWriteTime {get;...
LastWriteTimeUtc          Property       System.DateTime LastWriteTimeUtc {g...
Length                    Property       System.Int64 Length {get;}
Name                      Property       System.String Name {get;}
Mode                      ScriptProperty System.Object Mode {get=$catr = "";
> $file | get-mshobject

        1.txt (System.IO.FileInfo)
        1.txt (System.IO.FileInfo)
> $file |export-clixml abc.xml

> $file = import-clixml abc.xml
> $file

    Directory: Microsoft.Management.Automation.Core\FileSystem::D:\msh

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
               2006-4-3     22:27        350 1.txt

> $file | get-member

   TypeName: Deserialized.System.IO.FileInfo

Name              MemberType   Definition
----              ----------   ----------
MshChildName      NoteProperty System.String MshChildName=1.txt
MshDrive          NoteProperty System.Management.Automation.MshObject MshDri...
MshIsContainer    NoteProperty System.Boolean MshIsContainer=False
MshParentPath     NoteProperty System.String MshParentPath=Microsoft.Managem...
MshPath           NoteProperty System.String MshPath=Microsoft.Management.Au...
MshProvider       NoteProperty System.Management.Automation.MshObject MshPro...
Attributes        Property     System.String {get;set;}
CreationTime      Property     System.DateTime {get;set;}
CreationTimeUtc   Property     System.DateTime {get;set;}
Directory         Property     System.String {get;set;}
DirectoryName     Property     System.String {get;set;}
Exists            Property     System.Boolean {get;set;}
Extension         Property     System.String {get;set;}
FullName          Property     System.String {get;set;}
IsReadOnly        Property     System.Boolean {get;set;}
LastAccessTime    Property     System.DateTime {get;set;}
LastAccessTimeUtc Property     System.DateTime {get;set;}
LastWriteTime     Property     System.DateTime {get;set;}
LastWriteTimeUtc  Property     System.DateTime {get;set;}
Length            Property     System.Int64 {get;set;}
Name              Property     System.String {get;set;}

> $file | get-mshobject

rentPath=Microsoft.Management.Automation.Core\FileSystem::D:\msh; MshChildName=
1.txt; MshDrive=; MshProvider=; MshIsContainer=False; Name=1.txt; Length=350; D
irectoryName=D:\msh; Directory=D:\msh; IsReadOnly=False; Exists=True; FullName=
D:\msh\1.txt; Extension=.txt; CreationTime=2006-4-3 22:27:16; CreationTimeUtc=2
006-4-4 2:27:16; LastAccessTime=2006-4-3 22:27:16; LastAccessTimeUtc=2006-4-4 2
:27:16; LastWriteTime=2006-4-3 22:27:16; LastWriteTimeUtc=2006-4-4 2:27:16; Att

If you look carefully, you will notice:
1. export-clixml write MshObject.Properties into a xml file and import-clixml reconstruct a MshObject.
2. Although they looks the same, something changed  after serializtion and deserializtion:
Before: A FileInfo object is wrapped within MshObject
After:    A MshCustomObject is wrapped within MshObject. All Methods are lost.
3. Deserialized MshCustomObject is a Hash table.

Have Fun.
[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]