Archives for the Month of February, 2007

Calling a Webservice from PowerShell

One question that comes up fairly frequently in our internal mailing list, the newsgroup, and the internet at large is how to call a webservice from PowerShell. In fact, several excellent PowerShellers have written about it: Keith, and Geert. In general, the guidance has been to use wsdl.exe to generate the webservice proxy, compile that proxy into a DLL, then finally load that DLL into memory.

This is a topic that I cover in my upcoming book, and initially wrote a script to automate these proxy generation steps. However, the prerequisite to running a script designed in that matter is fairly huge. Wsdl.exe doesn't come with the .NET Framework, so you need to have the .NET SDK installed. That was something that made me uncomfortable, so I instead opted for a solution that could generate the web service proxy without wsdl.exe.

The .NET Framework supports a few classes that make this possible, although the documentation for them is terrible at best 🙂

To give a glimpse into the writing process behind my upcoming "Windows PowerShell - The Definitive Guide" (O'Reilly,) I'll occasionally post entries "as the author sees it." This entry discusses calling a webservice from PowerShell.

 

Program: Connect-WebService

Although "screen scraping" (parsing the HTML of a web page) is the most common way to obtain data from the internet, web services are becoming increasingly common. Web services provide a significant advantage over HTML parsing, as they are much less likely to break when the web designer changes minor features in their design.

The only benefit to web services isn't their more stable interface, however. When working with web services, the .NET Framework allows you to generate proxies that let you interact with the web service as easily as you would work with a regular .NET object. That is because to you, the web service user, these proxies act almost exactly the same as any other .NET object. To call a method on the web service, simply call a method on the proxy.

The primary difference you will notice when working with a web service proxy (as opposed to a regular .NET object) is the speed and internet connectivity requirements. Depending on conditions, a method call on a web service proxy could easily take several seconds to complete. If your computer (or the remote computer) experiences network difficulties, the call might even return a network error message instead of the information you had hoped for.

 The following script allows you to connect to a remote webservice, if you know the location of its service description file (WSDL.) It generates the web service proxy for you, allowing you to interact with it as you would any other .NET object.

Example 9-3. Connect-WebService.ps1

##############################################################################

## Connect-WebService.ps1

##

## Connect to a given web service, and create a type that allows you to

## interact with that web service.

##

## Example:

##

##     $wsdl = "http://terraserver.microsoft.com/TerraService2.asmx?WSDL"

##     $terraServer = Connect-WebService $wsdl

##     $place = New-Object Place

##     $place.City = "Redmond"

##     $place.State = "WA"

##     $place.Country = "USA"

##     $facts = $terraserver.GetPlaceFacts($place)

##     $facts.Center

##############################################################################

param(

    [string] $wsdlLocation = $(throw "Please specify a WSDL location"),

    [string] $namespace,

    [Switch] $requiresAuthentication)

 

## Create the web service cache, if it doesn't already exist

if(-not (Test-Path Variable:\Lee.Holmes.WebServiceCache))

{

    ${GLOBAL:Lee.Holmes.WebServiceCache} = @{}

}

 

## Check if there was an instance from a previous connection to

## this web service. If so, return that instead.

$oldInstance = ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation]

if($oldInstance)

{

    $oldInstance

    return

}

 

## Load the required Web Services DLL

[void] [Reflection.Assembly]::LoadWithPartialName("System.Web.Services")

 

## Download the WSDL for the service, and create a service description from

## it.

$wc = new-object System.Net.WebClient

 

if($requiresAuthentication)

{

    $wc.UseDefaultCredentials = $true

}

 

$wsdlStream = $wc.OpenRead($wsdlLocation)

 

## Ensure that we were able to fetch the WSDL

if(-not (Test-Path Variable:\wsdlStream))

{

    return

}

 

$serviceDescription =

    [Web.Services.Description.ServiceDescription]::Read($wsdlStream)

$wsdlStream.Close()

 

## Ensure that we were able to read the WSDL into a service description

if(-not (Test-Path Variable:\serviceDescription))

{

    return

}

 

## Import the web service into a CodeDom

$serviceNamespace = New-Object System.CodeDom.CodeNamespace

if($namespace)

{

    $serviceNamespace.Name = $namespace

}

 

$codeCompileUnit = New-Object System.CodeDom.CodeCompileUnit

$serviceDescriptionImporter =

    New-Object Web.Services.Description.ServiceDescriptionImporter

$serviceDescriptionImporter.AddServiceDescription(

    $serviceDescription, $null, $null)

[void] $codeCompileUnit.Namespaces.Add($serviceNamespace)

[void] $serviceDescriptionImporter.Import(

    $serviceNamespace, $codeCompileUnit)

 

## Generate the code from that CodeDom into a string

$generatedCode = New-Object Text.StringBuilder

$stringWriter = New-Object IO.StringWriter $generatedCode

$provider = New-Object Microsoft.CSharp.CSharpCodeProvider

$provider.GenerateCodeFromCompileUnit($codeCompileUnit, $stringWriter, $null)

 

## Compile the source code.

$references = @("System.dll", "System.Web.Services.dll", "System.Xml.dll")

$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters

$compilerParameters.ReferencedAssemblies.AddRange($references)

$compilerParameters.GenerateInMemory = $true

 

$compilerResults =

    $provider.CompileAssemblyFromSource($compilerParameters, $generatedCode)

 

## Write any errors if generated.        

if($compilerResults.Errors.Count -gt 0)

{

    $errorLines = ""

    foreach($error in $compilerResults.Errors)

    {

        $errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText

    }

 

    Write-Error $errorLines

    return

}

## There were no errors.  Create the webservice object and return it.

else

{

    ## Get the assembly that we just compiled

    $assembly = $compilerResults.CompiledAssembly

 

    ## Find the type that had the WebServiceBindingAttribute.

    ## There may be other "helper types" in this file, but they will

    ## not have this attribute

    $type = $assembly.GetTypes() |

        Where-Object { $_.GetCustomAttributes(

            [System.Web.Services.WebServiceBindingAttribute], $false) }

 

    if(-not $type)

    {

        Write-Error "Could not generate web service proxy."

        return

    }

 

    ## Create an instance of the type, store it in the cache,

    ## and return it to the user.

    $instance = $assembly.CreateInstance($type)

    ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation] = $instance

    $instance

}

 

[Update: Several readers have requested that this script support web services that reqire credentials, and support web services that return the same type of object. Added parameters to allow this.]

PowerShell in Action Now Available

Allow me to be tragically late to the party in pointing out that Bruce Payette’s PowerShell in Action is now available.

As shown by its Amazon page, the reviews are immensely positive.

Unfortunately for me, I’ve sequestered myself from reading any of the PowerShell books until my own is complete. This one, however, calls longingly from co-workers’ desks as I walk down the halls 🙂 Conversations with Bruce are always interesting, and this book is sure to not disappoint.

Controlling Robots with PowerShell

Over the last while, Scott Hanselman has been blogging a lot about some pretty cool ideas – controlling an IR port through C#, monkeying around with robots, and of course, PowerShell.

This all came together today, with the publishing of the second part of his Coding4Fun series, "Microbric Viper Robot with an Iguanaworks IR Serial Port and PowerShell."

In this final part, he extends the PowerShell Logo example to make a real robot execute your PowerShell script. Check it out – it's mind blowing!

 

A Different Kind of Management Script

It turns out that the chisel nib on a thick whiteboard marker is actually exceedingly good for whiteboard calligraphy. It gives an x-height of about 20mm, though 🙂

The only problem is the guide lines – since on a whiteboard, there are none 🙂 I’ve tried penciling them in, but that doesn’t work very well. I think my solution for that will be the true geek way – lasers.

Customize Even More with AutoHotkey

One of the things people commonly ask about is support for rich keyboard macros and hotkeys in PowerShell.  For many, a pointer to the Doskey hotkey reference (which provides the history management functionality in PowerShell’s “cooked mode”) is enough: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/doskey.mspx?mfr=true.

Once you’ve become used to those features, though, you start to want more – especially if you’ve grown accustomed to some of the power available on traditional Unix shells. Typical questions include:

  • How do I use Emacs keystrokes to navigate my history?
  • How do I use the keyboard to copy and paste in PowerShell?
  • How do I clear the screen with a keystroke?
  • How do exit the shell with a keystroke?
  • How do I use the keyboard to adjust the font size in PowerShell?

Although PowerShell itself doesn’t directly offer these additional customization options, there is still an outlet – UI Automation and Keystroke macro programs.

Programs that support keystroke macros are great, as they let you automate anything that you can already do with a keyboard (and sometimes the mouse.) For example, you can use the Alt+Space combination in any console window to open up its system menu.  A console system window includes all kinds of tasty treats – copy, paste, find, and even properties.

Using these menus and a program that supports keystroke macros, we can customize most of the things we want to.

I personally use a program called AutoHotkey as my macro program. It has a lot of power, although its scripting language leaves a lot to be desired. I think it would be the coolest thing ever if somebody wrote a macro program that hosted PowerShell – giving you all the benefits of PowerShell scripting for UI automation.

AutoHotkey uses a script to let you write keystroke macros. You tell it what keys to listen for, and what it should do when it sees those keys. Let’s take some simple examples.

One thing you might wonder is how to use Emacs keystrokes to navigate your history. The console cooked mode already supports many of the concepts that you want, but just doesn’t use the keystrokes that you want. You want to push Ctrl-A, but the console uses HOME for that. You want to push Ctrl-E, but the console uses END for that. So in your macro, tell AutoHotkey to send the HOME key when you push Ctrl-A:

;;
;; Beginning of line with ^A if we're in a console window
;;
#IfWinActive, ahk_class ConsoleWindowClass
^a::SendInput {HOME}
#IfWinActive

That sequence tells AutoHotkey: if the class of the current window is ConsoleWindowClass, then run these commands when I press ^a. Those commands are to send the HOME key as input. Similar macros work for the other navigation commands you might want to remap.
These keystrokes can get even more complex, if desired. For example, you want Ctrl-Y (the Emacs keystroke for Paste) to paste into a console window. The console doesn’t directly support that with a keystroke substitution, but the glorious system menu does:

You can automate that with Alt+Space, E, P:

;;
;; Paste to the console window with Ctrl-Y if we're in one
;;
#IfWinActive, ahk_class ConsoleWindowClass
^y::SendInput ! ep
#IfWinActive

Once you move past simple keystroke automation, you can also think of things you’d like to do that automate PowerShell itself – such as clear the screen. For that, you press ESC to clear the current line, “type” CLS, and then press ENTER:

;;
;; Clear screen with ^L if we're in a console window
;;
#IfWinActive, ahk_class ConsoleWindowClass
^l::SendInput {ESC}cls{ENTER}
#IfWinActive

Or, an exceedingly useful command to wrap the current line in parentheses:

;;
;; Wrap current line in brackets
;;
#IfWinActive, ahk_class ConsoleWindowClass
^w::SendInput {HOME}({END})
#IfWinActive

While you’re at it, you might have found that it is extremely annoying to demonstrate a text-based prompt to colleagues, or during a presentation. You fumble with the properties window to pick a font and text size, but these do it all for you:

;;
;; Font larger with ^UP if we're in a console window
;;
#IfWinActive, ahk_class ConsoleWindowClass
^UP::
Sleep 300
SendInput ! p
Sleep 200
SendInput {CTRLDOWN}{TAB}{CTRLUP}
Send !s
Sleep 200
Send {DOWN}
Sleep 200
Send {CTRLDOWN}{SHIFTDOWN}{TAB}{SHIFTUP}{CTRLUP}
Sleep 200
Send {ENTER}
Sleep 200
Send {ENTER}
return
#IfWinActive

;;
;; Font smaller with ^DOWN if we're in a console window
;;
#IfWinActive, ahk_class ConsoleWindowClass
^DOWN::
Sleep 300
SendInput ! p
Sleep 200
SendInput {CTRLDOWN}{TAB}{CTRLUP}
Send !s
Sleep 200
Send {UP}
Sleep 200
Send {CTRLDOWN}{SHIFTDOWN}{TAB}{SHIFTUP}{CTRLUP}
Sleep 200
Send {ENTER}
Sleep 200
Send {ENTER}
return
#IfWinActive

All-in-all, quite a useful way to customize the console environment to your liking.