Monday, August 13, 2007

Someone on the PowerShell NG asked recently if there was some kind of equivalent to c#'s "using namespace;" or VB.NET's "Imports namespace" statements. Well, the short answer to that is no, not really. However, you can simulate it by creating functions for each static method on the class:

  1. param([type]$type = $(throw "need a type!"))
  2.  
  3. $type | gm -static | ? {$_.membertype -eq "method" } | % {
  4.         $func = "function:$($_.name)"
  5.         if (test-path $func) { remove-item $func }
  6.         $flags = 'Public,Static,InvokeMethod,DeclaredOnly'
  7.         new-item $func -value "[$($type.fullname)].InvokeMember('$($_.name)', ${flags}, `$null, `$null, `$args[0])"
  8. }

Save this as "import.ps1" for example and use like so:

PS > . .\import.ps1 ([Math])
...

PS > Sin 1
0.841470984807897

Methods that take multiple args must have them passed as a single array:

PS > Max @(1,2)
2

Have fun!

Update: there are some interesting extensions appearing based around my initial post on microsoft.public.windows.powershell.

 



posted on Monday, August 13, 2007 3:27:05 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2] Trackback
 Tuesday, August 07, 2007

I noticed that my last script could not handle more complex webservices like the MSDN ContentService (unlike WSDL.EXE) for example, so I rewrote it from the ground up. This time, it supports Basic Profile 1.1 and can generate proxy instances for simple ASMX services and more complicated multi-schema services like the MSDN/TechNet Publishing System (MTPS) Content Service.

What's New:

  • No need for the .NET 2.0 SDK, just PowerShell
  • Auto discovery of all referenced schema
  • No need to point it at page.asmx?wsdl any more, just the asmx
  • Validation against WS-I Basic Profile 1.1

get-webservice2.ps1.txt (4.28 KB)  (updated 2007/12/19: errant comma in write-progress fixed)

  1. #
  2. # Get-WebService.ps1 (v2.0 Aug 6, 2007)
  3. #
  4. # Oisin Grehan <oising@gmail.com> (x0n)
  5. #
  6. # Usage:
  7. #   $proxy = .\get-webservice2.ps1 [-Url] http://site/service.asmx [-Anonymous] [[-SoapProtocol] <Soap | Soap12>]
  8. #
  9. # to see available webmethods:
  10. # $proxy | gm
  11. #
  12.  
  13. # $url = "http://services.msdn.microsoft.com/contentservices/contentservice.asmx?wsdl"
  14.  
  15. param($url = $(throw "need `$url"), [switch]$Anonymous, [string]$protocol = "Soap")
  16.  
  17. [void][system.Reflection.Assembly]::LoadWithPartialName("system.web.services")
  18.  
  19. trap {
  20.         "Error:`n`n $error";
  21.         break;
  22. }
  23.  
  24. #$request = [System.Net.WebRequest]::Create($url);
  25. $dcp = new-object system.web.services.discovery.discoveryclientprotocol
  26.  
  27. if (! $Anonymous) {
  28.     Write-Progress "Network Credentials" "Awaiting input..."
  29.     $dcp.Credentials = (Get-Credential).GetNetworkCredential()
  30. }
  31.  
  32. Write-Progress "Discovery" "Searching..."
  33. $dcp.AllowAutoRedirect = $true
  34. [void]$dcp.DiscoverAny($url)
  35. $dcp.ResolveAll()
  36.  
  37. # get service name
  38. foreach ($entry in $dcp.Documents.GetEnumerator()) { # needed for Dictionary
  39.     if ($entry.Value -is [System.Web.Services.Description.ServiceDescription]) {
  40.         $script:serviceName = $entry.Value.Services[0].Name
  41.         Write-Verbose "Service: $serviceName"
  42.     }
  43. }
  44.  
  45. Write-Progress "WS-I Basic Profile 1.1" "Validating..."
  46. $ns = new-Object System.CodeDom.CodeNamespace # "WebServices"
  47.  
  48. $wref = new-object System.Web.Services.Description.WebReference $dcp.Documents, $ns
  49. $wrefs = new-object system.web.services.description.webreferencecollection
  50. [void]$wrefs.Add($wref)
  51.  
  52. $ccUnit = new-object System.CodeDom.CodeCompileUnit
  53. [void]$ccUnit.Namespaces.Add($ns)
  54.  
  55. $violations = new-object system.web.Services.Description.BasicProfileViolationCollection
  56. $wsi11 = [system.web.services.WsiProfiles]::BasicProfile1_1
  57.  
  58. if ([system.web.Services.Description.WebServicesInteroperability]::CheckConformance($wsi11, $wref, $violations)) {
  59.     Write-Progress "Proxy Generation" "Compiling..."
  60.    
  61.     $webRefOpts = new-object System.Web.Services.Description.WebReferenceOptions
  62.         $webRefOpts.CodeGenerationOptions = "GenerateNewAsync","GenerateProperties" #,"GenerateOldAsync"
  63.  
  64.         #StringCollection strings = ServiceDescriptionImporter.GenerateWebReferences(
  65.         #       webReferences, codeProvider, codeCompileUnit, parameters.GetWebReferenceOptions());
  66.  
  67.     $csprovider = new-object Microsoft.CSharp.CSharpCodeProvider
  68.         $warnings = [System.Web.Services.Description.ServiceDescriptionImporter]::GenerateWebReferences(
  69.                 $wrefs, $csprovider, $ccunit, $webRefOpts)
  70.        
  71.     if ($warnings.Count -eq 0) {
  72.         $param = new-object system.CodeDom.Compiler.CompilerParameters
  73.         [void]$param.ReferencedAssemblies.Add("System.Xml.dll")
  74.         [void]$param.ReferencedAssemblies.Add("System.Web.Services.dll")       
  75.         $param.GenerateInMemory = $true;
  76.         #$param.TempFiles = (new-object System.CodeDom.Compiler.TempFileCollection "c:\temp", $true)
  77.         $param.GenerateExecutable = $false;
  78.         #$param.OutputAssembly = "$($ns.Name)_$($sdname).dll"
  79.         $param.TreatWarningsAsErrors = $false;
  80.         $param.WarningLevel = 4;
  81.        
  82.         # do it
  83.         $compileResults = $csprovider.CompileAssemblyFromDom($param, $ccUnit);
  84.  
  85.         if ($compileResults.Errors.Count -gt 0) {
  86.             Write-Progress "Proxy Generation" "Failed."
  87.             foreach ($output in $compileResults.Output) { write-host $output }
  88.             foreach ($err in $compileResults.Errors) { write-warning $err }           
  89.         } else {           
  90.             $assembly = $compileResults.CompiledAssembly
  91.  
  92.             if ($assembly) {
  93.                 $serviceType = $assembly.GetType($serviceName)               
  94.                 $assembly.GetTypes() | % { Write-Verbose $_.FullName }
  95.             } else {
  96.                 Write-Warning "Failed: `$assembly is null"
  97.                                 return
  98.             }
  99.            
  100.             # return proxy instance
  101.             $proxy = new-object $serviceType.FullName
  102.             if (! $Anonymous) {
  103.                 $proxy.Credentials = $dcp.Credentials
  104.             }
  105.             $proxy # dump instance to pipeline
  106.         }
  107.     } else {
  108.         Write-Progress "Proxy Generation" "Failed."       
  109.         Write-Warning $warnings
  110.     }
  111.     #Write-Progress -Completed
  112. }
posted on Tuesday, August 07, 2007 11:44:03 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Thursday, August 02, 2007

For me, this opens up a whole new world of PowerShell interaction with remote servers. No more constructing soap packets - just point it at a WSDL url, and you get back a first-class object with methods that you can call corresponding to remote webmethods. PowerShell's [xml] accelerator automatically wraps the responses so you can easily manipulate the results.

Have fun!

UPDATE: newer more robust version 2.0 available!

posted on Thursday, August 02, 2007 12:56:44 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Tuesday, July 17, 2007

PSEventing 1.0 Released

Trap and respond to synchronous & asynchronous .NET events within your powershell scripts with this easy to use suite of cmdlets.

What's new?


  • A PSEvent object's Source property now contains the PSVariable wrapping the original object which generated the event. This resolves scoping issues with 0.5 whereby it wasn't always possible to reach the original sender by variable name (using get-variable).
  • New-Event cmdlet added for inserting user-generated "events" into the queue. This cmdlet allows attaching an abitrary object payload which is available in the PSEvent's Args.Data property.
  • All event handlers are now AUTOMATICALLY unhooked if the original PSVariable goes out of scope! No more phantom events continually generated if you forget to use the explicit "Disconnect-EventListener" and lose your reference. Simply null out a variable or let it disappear into the scopeless void!
  • and finally, some bugfixes, as listed on the release page.

Pick it up from CodePlex, includes source: http://www.codeplex.com/PSEventing/

 

posted on Tuesday, July 17, 2007 8:41:47 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Tuesday, June 12, 2007

I like to see myself as a competent SharePoint developer having worked with it since beta 1 on various large scale projects, but it always bothered me that there were large swathes of it on which I was no expert, primarily due to one thing: CAML. It disgusts me - in fact, my brain downright rejects it. Every time I have to create some markup for a simple SPQuery, I have to look pull out the SDK reference material. CAML for me symbolizes everything bad that came out of the 1998-2001 "XML revolution," where absolutely everything had to be xml/xslt driven, where all wisdom concerning the separation of presentation and data disappeared in a puff of <smoke />.

It's not often that you see a new technology and you just "get it." No thinking required; instant grokkage. I thought I "got" LINQ, but it wasn't until today that I truely got it. So, I'm not sure if this qualifies as a "straw" as per the metaphor used in the title, perhaps a full-on hulking haybale would be more apt. Enter Bart de Smet's savegely impressive LINQ to SharePoint project on CodePlex. No more of the nausea-inducing verbosity that is CAML; just plain old C# 3.0.

posted on Tuesday, June 12, 2007 9:36:10 AM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
 Wednesday, May 16, 2007

I've included some handy wrapper functions to make working with events and scriptblocks a bit easier - download the example script called "event handling wrapper functions" from the Releases page.

  • Add-EventHandler : Automatically run a scriptblock when the provided event occurs on a given variable (when inside Do-Events loop)
    • Add-EventHandler [-Variable] <PSVariable> [-EventName] <String> [-Script] <ScriptBlock>
  • Remove-EventHandler : Remove all bindings for the specific event from the given variable.
    • Remove-EventHandler [-Variable] <PSVariable> [-EventName] <String>
  • Do-Events : Much like VB's DoEvents command, this function puts PowerShell into a waiting state and will call your ScriptBlocks when your configured events occur. Use Ctrl+C to exit.
    • Do-Events [-ExitImmediately] <Boolean>


NOTE: your ScriptBlocks will only be called if you're inside a Do-Events loop.

1# Add-PSSnapin PSEventing
 
2# $fsw = new-object system.io.filesystemwatcher
3# $fsw.Path = "c:\temp"
4# $fsw.EnableRaisingEvents = $true
 
5# Add-EventHandler (get-variable fsw) deleted {
            param([System.Management.Automation.PSVariable]$variable, [EventArgs]$args)
     ... do stuff ... }
 
6# Do-Events $false

an example how to wire up an event using the wrapper scripts

posted on Wednesday, May 16, 2007 6:02:10 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
 Sunday, May 13, 2007

Nrgghg,  I feel another PowerShell project coming on ...  enter PowerShell Eventing 0.5 Beta! With the magic of lightweight codegen, aka LCG, a smidgeon of reflection (well, quite a bit) and some inspiration, I managed to cough up this latest project.

While you cannot directly bind scriptblocks as eventhandlers, you can automatically route events in realtime to a background queue and deal with them in your time with a special Get-Event cmdlet. There is a sample walkthrough on the home page of the Wiki, and you can download a Sql backup script which shows progress reporting, all in script!

PS 1# Add-PSSnapin PSEventing                                                                      
PS 2# $wc = new-object system.net.webclient                                                        
PS 3# get-eventbinding -IncludeUnboundEvents | ft -auto                                            
                                                                                                                        
VariableName   EventName               TypeName  Listening                                                              
------------   ---------               --------  ---------                                                              
wc             Disposed                WebClient     False                                                              
wc             DownloadDataCompleted   WebClient     False                                                              
wc             DownloadFileCompleted   WebClient     False                                                              
wc             DownloadProgressChanged WebClient     False                                                              
wc             DownloadStringCompleted WebClient     False                                                              
wc             OpenReadCompleted       WebClient     False                                                              
wc             OpenWriteCompleted      WebClient     False                                                              
wc             UploadDataCompleted     WebClient     False                                                              
wc             UploadFileCompleted     WebClient     False                                                              
wc             UploadProgressChanged   WebClient     False                                                              
wc             UploadStringCompleted   WebClient     False                                                              
wc             UploadValuesCompleted   WebClient     False                                                              
                                                                                                                                                                                                                                        
PS 4# Connect-EventListener wc disposed -verbose                                                   
VERBOSE: Target is a WebClient                                                                                          
VERBOSE: Now listening for 'disposed' events from $wc                                                                   
PS 5# $wc.Dispose()                                                                                
PS 6# get-event | ft -auto                                                                         
                                                                                                                        
Occurred             Source      Name     Args                                                                          
--------             ------      ----     ----                                                                          
5/13/2007 8:04:20 PM variable:wc Disposed System.EventArgs                                                              
                                                                                                                                                 

Have fun!

posted on Sunday, May 13, 2007 7:09:16 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1] Trackback
 Tuesday, April 17, 2007

I had an interesting problem today, I wanted to temporarily set a powershell variable to "readonly." So, I tried the obvious, and expected it to fail (it did):

PS E:\projects\powershell> $p = 7                                                                                       
PS E:\projects\powershell> (gi variable:p).Options = "readonly"                                                         
PS E:\projects\powershell> (gi variable:p).Options = "none"                                                             
Exception setting "Options": "Cannot overwrite variable p because it is read-only or constant."                         
At line:1 char:17                                                                                                       
+ (gi variable:p).O <<<< ptions = "none"                                                                                

Of course it seems pretty obvious in hindsight that this would fail, but why do they provide both "const" AND "readonly" choices for variables? well, after some spelunking with the excellent Reflector (my first choice these days, I find it easier than navigating around the msdn behemoth), I found the answer:

internal void SetOptions(ScopedItemOptions newOptions, bool force)
{
    using (IDisposable disposable = tracer.TraceMethod(newOptions))
    {
        if (this.IsConstant || (!force && this.IsReadOnly))
        {
            SessionStateUnauthorizedAccessException exceptionRecord = new SessionStateUnauthorizedAccessException(this.name, SessionStateCategory.Variable, "VariableNotWritable");
            tracer.TraceException(exceptionRecord);
            throw exceptionRecord;
        }

E.g, if the variable is not a constant, you can force the change:

PS E:\projects\powershell> set-item variable:p $p -force                                                                
PS E:\projects\powershell> (gi variable:p).Options                                                                      
None                                                                                                                    

This resets the "readonly" bit.

posted on Tuesday, April 17, 2007 8:07:39 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback