Monday, September 24, 2007

Howto: Invoking cmdlets within a cmdlet using C# (part II: ...using csc.exe)

(NOTE: The formatting is a bit off.  I really believe this is due to the Live Writer code snippet plugin.  I will try to edit the post to fix it up at a later time.)

In the introduction to this howto series, I mentioned part II would show how one invokes cmdlets within a cmdlet using csc.exe. In this particular example, I'm going to show an example of when the cmdlet we are going to invoke derives from the cmdlet class.

Oisin Grehan provided me with a cool one-liner to determine whether a cmdlet derives from the cmdlet or pscmdlet class:

   1: get-command invoke-webservice |`
   2:   foreach-object {
   3:     $_.name +`
   4:     " is pscmdlet: " +`
   5:     [management.automation.pscmdlet].`
   6:     IsAssignableFrom($_.implementingtype)
   7: }

If the output from the above is false, our cmdlet derives from the cmdlet class.


In our case, our example is from PowerGadgets where I will use their invoke-webservice cmdlet (in later posts, I will do the same with their invoke-sql cmdlet). This cmdlet derives from the cmdlet class.


We're going to try to use the invoke-webservice in a new invoke-webservice2 cmdlet. I was inspired by Keith Hill's post on calling a web service from PowerShell.


Here's the non-commented "guts" of the C# code (I've stripped the regular stuff like the pssnapin stuff), but the full C# code is downloadable here:



   1: InvokeWebServiceCommand iws = new InvokeWebServiceCommand();
   2: iws.WSDL = "http://www.webservicex.net/WeatherForecast.asmx?WSDL";
   3: iws.Method = "GetWeatherByZipCode";
   4: iws.Parameters = new object[] { "80526" };
   5: foreach (object o in iws.Invoke<object>())
   6: {
   7:     WriteObject(o);
   8: }

 


Compiling from the command-line using the .NET Framework's C# compiler (csc.exe) is pretty easy and is packaged with the .NET 2.0 (and a new one with the .NET 3.5 framework), so any machine with PowerShell installed has the compiler.


Let's setup the environment:



   1: PSH> $framework=$([System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())
   2: PSH> set-alias csc "$($framework)csc.exe"
   3: PSH> set-alias installutil "$($framework)installutil.exe"
   4: PSH> $ref=[psobject].assembly.location
   5: PSH> $pg="c:\program files\powergadgets\"
   6: PSH> $pg1="$($pg)powergadgets.commands.dll"
   7: PSH> $pg2="$($pg)powergadgets.data.dll"



(NOTE: This is to get you going quickly, most do not recommend you use DLLs from the GAC as I do above with how I create the "$ref" variable. Problem is, you need to go and download a huge SDK otherwise, which you should do if you do regular PowerShell development.)


Now, we are going to compile the cmdlet, load it, then run it. I provide the link for the full C# source code above which you will need to go and get:




PSH> csc /t:library /r:$ref /r:$pg1 /r:$pg2 invokewebservice.cs
PSH> installutil invokewebservice.dll
PSH> Add-PSSnapin InvokeWebService2
PSH> invoke-webservice2
Latitude         : 40.54729
Longitude        : 105.1076
AllocationFactor : 0.008857
FipsCode         : 08
PlaceName        : FORT COLLINS
StateCode        : CO
Status           :
Details          : {WeatherData, WeatherData, WeatherData, WeatherData...}



(NOTE: Again, this is just quick and dirty, *before* you run the above commands, you should create a permanent directory somewhere in C:\Program Files, maybe something like "My_Cmdlets", for example, where you will place the .cs file, and create the .dll from there. Otherwise, you risk creating the DLLs in a temporary directory, then deleting them by mistake down the road.)


Success!


I've just shown one way to invoke a cmdlet from within another cmdlet. As I briefly mentioned, you can only use this method when the cmdlet you are invoking derives from the cmdlet class. In one of my next postings, I'll show you how to invoke a cmdlet when it does not derive from the cmdlet class.


Enjoy!