::::::::: PowerShell :::::::::
Wednesday, February 22, 2006
  [MSH] How to a[void] returning multiple values from a function
I have succesfully made one of C# application I was working on at work to call MSH script on the fly during runtime. Everything seemed to have been working fine until I have encounted this problem that MSH functions can return mutiple values I have passed a variable named $arr of type System.Collections.ArrayList to the Msh Script through
Runspace rs = RunspaceFactory.CreateRunspace();
rs.SessionStateProxy.SetVariable("arr", new ArrayList());
/*** snip ***/
string cmd = ". .\\parse-file.msh";
Pipeline pl = rs.CreatePipeline(cmd);
From here on, a variable named $arr is visible in "parse-file.msh".

Now in "parse-file.msh" script, I tried something like(NOTE: In actual "parse-file.msh", there is no initialization code for $arr to ArrayList, in the following function, $arr are initialized as a demonstrative purpose only, /\/\o\/\/, I have cleared it up here :))

MSH>function bar {
>> $arr = new-object System.Collections.ArrayList
>> $arr.Add(1)
>> $arr.Add(2)
>> $arr.Add(3)
>>
>> return $arr
>> }
>>
MSH>bar
0
1
2
1
2
3


And the function returns 4 values, 3 int32 values and 1 3-element ArrayList object, instead of just the contents of $arr.
We can confirm that through the following snippet

MSH>function multiple-returns {
>> $arr = new-object System.Collections.ArrayList
>> $arr.Add(5)
>> $arr.Add(6)
>> $arr.Add(7)
>> return $arr}
>>
MSH>multiple-returns
0
1
2
5
6
7
MSH>$a, $b, $c, $d, $e = multiple-returns
MSH>$a
0
MSH>$b
1
MSH>$c
2
MSH>$d
5
6
7
MSH>$e
MSH>$d.getType().Name
ArrayList
MSH>$e.getType().Name
You cannot call a method on a null-valued expression.
At line:1 char:11
+ $e.getType( <<<< ).Name


First 3 return values(0, 1, 2) were caused by the fact that ArrayList returns "index at which the value has been added".
Well, But then, what if you don't actually want to return indexes at which value has been added?

There are two choices(and as far as I understand, they work about the same although the background principle is different)
*EDIT*: there are 3 choices( Thanks /\/\o\/\/ for another solution :), Check out "comments" section )

  1. [void]

  2. out-null

  3. > $null



[void] in front of a method indicates that, the method does not return a value.
For example, try out the following(yes, do try them out!)
MSH>[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

GAC Version Location
--- ------- --------
True v2.0.50727 C:\WINDOWS\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e08...
MSH>[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
MSH>[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | out-null
MSH>[System.Reflection.Assembly]::LoadWithPartialName("system.windows.forms") >$null
MSH>

Let's see what just happened

Now Let's revise the function "bar".

MSH>function bar {
>> $arr = new-object System.Collections.ArrayList
>> [void]$arr.Add(1)
>> [void]$arr.Add(2)
>> [void]$arr.Add(3)
>>
>> return $arr
>> }
>>
MSH>bar
1
2
3

MSH>function bar {
>> $arr = new-object System.Collections.ArrayList
>> $arr.Add(1) | out-null
>> $arr.Add(2) | out-null
>> $arr.Add(3) | out-null
>>
>> return $arr
>> }
>>
MSH>bar
1
2
3



Now those functions did not return added index positions during "ArrayList.Add" operations.
I tend to go with [void] since it sounds technically right to mark(or cast, not sure about how to call it) a method as not returning any value


Tags :
 
Comments:
Way 3 :

$arr.Add(1) > $null

and you should remove this line,
$arr = new-object System.Collections.ArrayList

the arrayList is allready passed in by the SessionStateProxy
and I noticed you can not "re-dim" it, it does nothing.

gr /\/\o\/\/
 
Ah, in "parse-file.msh", I didn't declare "$arr = new-object System.Collection.ArrayList". I just initialized $arr to ArrayList in "bar" and "multiple-returns" functions as demonstration :)

Thanks for pointing that out for me.

Oh yeah, I am using C# so I only have a vague of an idea on what "re-dim" does :) (*DBMwS goes over his old VB6 apps*)
 
try this,

$a = (1,2,3)
$arr = $a

where $arr is the Session Variable.

you will notice that $arr is still empty.(this is what I did mean by Re-dim

so you can not assign another variable to the passed one, you can only use the Object itself,
I guess this is a scoping issue.

gr /\/\o\/\/
 
Oh, now that's interesting.
So by re-dim'ming, you meant assigning passed variable(in my case, $arr) cannot be assigned to a local or script-level variables

I am definiately trying that out when I get home. I have never tried to assigned the Session Variable to a script level variable :)

thanks for the tips
 
/\/\o\/\/, I have tried the following.
I have passed "specification" of type ArrayList to "parse-file.msh" and in the script,
##############################
# here $specification is available everywhere in the script.
*** snip ***

[void] $specification.Add($fqn)
*** snip ***
[void] $specification.Add($($private:n.substring(0, 1)))

$arr = @(1,2,3)
$specification = $arr

# return $specification
$specification
##############################

And the in the debugger after "objPipeLine.Invoke()",

In the "Immediate Window",
------------------------------
NOTE: rs is an instance of Runspace
rs.SessionStateProxy.GetVariable("arr")
{Dimensions:[3]}
[0]: 1
[1]: 2
[2]: 3

rs.SessionStateProxy.GetVariable("specification")
{Dimensions:[3]}
[0]: 1
[1]: 2
[2]: 3
------------------------------

"specification" IS replaced to the value of $arr. Would you try to check that out how you passed your variable to the script in your application?

BTW, as a reference, let me give you what I am supposed to get from $specification

------------------------------
rs.SessionStateProxy.GetVariable("specification")
Count = 12
[0]: null
[1]: "B"
[2]: "12"
[3]: "23"
[4]: "05"
[5]: "43"
[6]: "011"
[7]: "B"
[8]: "001"
[9]: "2006"
[10]: ".TIF"
[11]: "A"
------------------------------
 
Ahh,

I think I get it, I do just use the variable back in the application.

e.g.
DataTable dtOut = new DataTable();
runspace.SessionStateProxy.SetVariable("DT", dtOut );
Pipeline pipeline = runspace.CreatePipeline(rtbMshCommand.Text );
results = pipeline.Invoke();
dgvOutput.DataSource = dtOut;

that's it, so I do not reassign it again when finished with the pipeline, I think that is the difference.

gr /\/\o\/\/
 
OK, here is the part where i have used MSH to parse file names according to some specification.

protected override void Parse()
{
Runspace rs = RunspaceFactory.CreateRunspace();

try
{
this.specification.Clear();
int cnt = System.Enum.GetValues(typeof(FileNameSpecIndices)).Length;
this.specification = new ArrayList(cnt);

rs.Open();

rs.SessionStateProxy.SetVariable("specification", this.specification);
rs.SessionStateProxy.SetVariable("fileName", this.FullName);

string parserScriptPath = ". .\\parse-file.msh";
Pipeline pl = rs.CreatePipeline(parserScriptPath);
pl.Invoke(); // after this, specification is filled with parsed values
}
catch (Exception ex)
{
this.logger.Error("Error occurred:\n" + ex.StackTrace);
throw new Exception("Parse Error for " + this.fullName);
}
finally
{
if (rs.RunspaceStateInfo.State == RunspaceState.Opened)
{
rs.Close();
}
}
}

I didn't even use the returned value(of type ICollection<MshObject>) since "specification"(ArrayList) was modified inside the script already.

Yeah, I guss that's the difference that I am simply letting the Msh script to fill up the arraylist( although I DO return it in MSH script i am not utilizing it in this version, so i am going to modify the script not to return any value :) )
 
I just put the results to out-string into a richtextbox, and use the dataset filled you like you.
the results collection will contain script errors also (as my script is in another richtextbox) I can see them and change the script.

I'm also still a bit puzzling but I thinks its very powerfull

gr /\/\o\/\/
 
Ah, dataset. It seems like a good idea to use a DataSet instance and bind it to Gridview...
Why didn't I think of that? :)

Well, It's really *puzzling* to me, as well so I am reading MSDN doc on http://windowssdk.msdn.microsoft.com/library/default.asp?url=/library/en-us/monad_prog/html/db8223c9-ec89-4ef0-92d3-fffe5fecf343.asp and currently reading about "How MSH works" :)
 
What a great post my friend, I was looking for this for almost a week, thanks for sharing.
 
Post a Comment

Links to this post:

Create a Link



<< Home
Let's get lazy with PowerShell!

Name:
Location: Flushing, NY, United States

Experimenting with a different format of blogs...

Links
ARCHIVES
10/01/2005 - 11/01/2005 / 11/01/2005 - 12/01/2005 / 12/01/2005 - 01/01/2006 / 01/01/2006 - 02/01/2006 / 02/01/2006 - 03/01/2006 / 03/01/2006 - 04/01/2006 / 04/01/2006 - 05/01/2006 / 05/01/2006 - 06/01/2006 / 06/01/2006 - 07/01/2006 / 07/01/2006 - 08/01/2006 / 08/01/2006 - 09/01/2006 / 10/01/2006 - 11/01/2006 / 11/01/2006 - 12/01/2006 /


Powered by Blogger