PowerShell 2.0 - Enabling Remoting with Virtual XP Mode on Windows 7

UPDATE: Some general information on remoting: Hey, Scripting Guy! Tell Me About Remoting in Windows PowerShell 2.0
UPDATE 2:
This also applies to machines not attached to a domain (thanks @alexandair)

If you’re running Windows 7 (and if not, why not?) you may have noticed that premium versions include a license for Virtual XP Mode. (read more at http://www.microsoft.com/windows/virtual-pc/download.aspx) Essentially it’s an integrated version of Virtual PC with a full copy of Windows XP SP2 running in it. It’s pretty nice – you can have programs from the virtualized XP instance in your Windows 7 Start Menu and they can even share your desktop.

Problem

First thing you might think as a PowerShell fan is to put PowerShell v2.0 on Virtual XP Mode so you can tinker around with PowerShell Remoting (don’t forget to install .NET Framework 3.5 SP1 first!) Unfortunately it’s not all plain sailing as a default security configuration in Virtual XP prevents the Enable-PSRemoting Cmdlet from succeeding:

PS C:\Documents and Settings\XPMUser> Enable-PSRemoting

WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.
 This includes:
    1. Starting or restarting (if already started) the WinRM service
    2. Setting the WinRM service type to auto start
    3. Creating a listener to accept requests on any IP address
    4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):
WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.

Set-WSManQuickConfig : Access is denied. 
At line:50 char:33
+             Set-WSManQuickConfig <<<<  -force
    + CategoryInfo          : InvalidOperation: (:) [Set-WSManQuickConfig], InvalidOperationException
    + FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.SetWSManQuickConfigCommand

Resolution

Thankfully, it’s a quick fix; log into VXP and perform Start > Run… “gpedit.msc” and navigate your way to:

Computer Configuration > Windows Settings > Security Settings > Local Policies > Security Options

…and change: Network access: Sharing and security model for local accounts

…to: Classic – local users authenticate as themselves.

Now run Enable-PSRemoting again and it should work. No need to reboot!

fix_gpo_security_network_access

PowerShell 2.0 goes RTM for ALL Platforms

( from: http://support.microsoft.com/default.aspx/kb/968929 – downloads at foot of page )

Windows PowerShell is a command-line shell and scripting language that is designed for system administration and Automation. Built on the Microsoft .NET Framework, Windows PowerShell enables IT professionals and developers to control and automate the administration of Windows and applications.

New features that are introduced in Windows PowerShell 2.0 include the following:

  • Remoting
    Windows PowerShell 2.0 lets you run commands on one or more remote computers from a single computer that is running Windows PowerShell. PowerShell remoting allows for multiple ways of connecting. These ways include interactive (1:1), fan-out (1:many), and fan-in (many:1 by using the IIS hosting model).
  • Integrated Scripting Environment
    PowerShell Integrated Scripting Environment (ISE) enables you to run interactive commands and edit and debug scripts in a graphical environment. The main features include color-coded syntax, selective execution, graphical debugging, Unicode support, and context-sensitive help.
  • Modules
    Modules allow for script developers and administrators to partition and organize their Windows PowerShell code in self-contained, reusable units. Code from a module executes in its own self-contained context and does not affect the state outside the module.
  • Advanced functions
    Advanced functions are functions that have the same capabilities and behavior as cmdlets. However, they are written completely in the Windows PowerShell language, instead of compiled C#.
  • Background jobs
    Windows PowerShell 2.0 allows for running a command or expression asynchronously and "in the background" without interacting with the console.
  • Eventing
    This feature adds support to the Windows PowerShell engine infrastructure for listening, forwarding, and acting on management and system events.
  • Script internationalization
    This new feature enables Windows PowerShell scripts to display messages in the spoken language that is specified by the UI culture setting on the user's computer.
  • Script debugging
    New debugging features were added to Windows PowerShell that let you set breakpoints on lines, columns, variables, and commands, and that let you specify the action that occurs when the breakpoint is hit.
  • New cmdlets
    Windows PowerShell 2.0 introduces over 100 built-in cmdlets. These cmdlets, excluding other tasks, enables you to do computer-related, event log, and performance counter management tasks.

WinRM 2.0

WinRM is the Microsoft implementation of WS-Management Protocol, a standard Simple Object Access Protocol (SOAP)–based, firewall-friendly protocol that allows for hardware and operating systems from different vendors to interoperate. The WS-Management Protocol specification provides a common way for systems to access and exchange management information across an IT infrastructure.

WinRM 2.0 includes the following new features:

  • The WinRM Client Shell API provides functionality to create and manage shells and shell operations, commands, and data streams on remote computers.
  • The WinRM Plug-in API provides functionality that enables a user to write plug-ins by implementing certain APIs for supported resources and operations.
  • WinRM 2.0 introduces a hosting framework. Two hosting models are supported. One is Internet Information Services (IIS)-based and the other is WinRM service-based.
  • Association traversal lets a user retrieve instances of Association classes by using a standard filtering mechanism.
  • WinRM 2.0 supports delegating user credentials across multiple remote computers.
  • Users of WinRM 2.0 can use Windows PowerShell cmdlets for system management.
  • WinRM has added a specific set of quotas that provide a better quality of service and allocate server resources to concurrent users. The WinRM quota set is based on the quota infrastructure that is implemented for the IIS service.

System requirements

WinRM 2.0 and PowerShell 2.0 can be installed on the following supported operating systems:

  • Windows Server 2008 with Service Pack 2
  • Windows Server 2003 with Service Pack 2
  • Windows Vista with Service Pack 2
  • Windows Vista with Service Pack 1
  • Windows XP with Service Pack 3
Windows PowerShell 2.0 requires the Microsoft .NET Framework 2.0 with Service Pack 1.

BITS 4.0

BITS 4.0 can be installed on the following supported operating systems:
  • Windows Server 2008 with Service Pack 2
  • Windows Vista with Service Pack 2
  • Windows Vista with Service Pack 1

PowerShell 2.0 - Module Initializers

You might not know it, but when you import a PowerShell module you can pass it one or more arguments by way of Import-Module's -ArgumentList (aliased to -Args) parameter. While it looks like passing parameters to a standard ps1 file, there are some limitations. Take this module for an example:

# -- begin foo.psm1 --
param()
 
. {
  param(
     [validateset("a","b","c")]
     $letter
 
     function Get-Letter {
        "You initialized the module with $letter"
     }
} @args
# -- end foo.psm1

ps> import-module foo -args b
ps> get-letter
"You initialized the module with b"

You might notice that I am dotting (dot-sourcing or dot-executing) the scriptblock. By doing this, you are ensuring that anything declared in the scriptblock is imported into the calling scope. You can also apply advanced-function style validation to the module initializer by splatting (@) the module arguments ($args). If the scriptblock was called instead (via & { ... } @args) then the nested scope created from the call (&) operator prevents the function Get-Letter from being exported from the module because it goes out of scope when the called scriptblock completes. So, why not put the param block at the top of the module where param() is now you might ask? Because that mechanism is broken and/or partially implemented in v2, and completely ignores [cmdletbinding()] directives and validators. Note also that there is no way to use switches in the traditional sense with module arguments; instead you can pass boolean literals like $true or $false which will be mapped positionally to any declared switches.

So where might you use this technique of module initializers? You could create a generalized custom module that works with your various development environments, or SQL clusters/servers. You pass the name of the environment (or server) to the module on import, and all of the functions exported then are "bound" to that cluster (or server) so you don't have to continually pass each function the cluster (or server) name as an argument.

Have fun!

PowerShell 2.0 – Getting and setting text to and from the clipboard

Updated: now use a temporary file to set text to avoid overflowing command-line buffer

The Windows Clipboard – accessible via System.Windows.Forms.Clipboard – requires an STA thread to read/write to it. By default, the console version of PowerShell 2.0 (i.e. not ISE) starts in MTA mode. This means that read/writing via this class is unreliable. Rather than always starting up console PowerShell in STA mode via the –STA flag, you can use this flag in a sneakier way to get what you want:

function Set-ClipboardText {
        param($text)

        # need to use temp file to avoid exceeding command-line length limit
        $temp = [io.path]::GetTempFileName()

        try {
            set-content -Path $temp -Value $text

            $command = {
                    add-type -an system.windows.forms
                    [System.Windows.Forms.Clipboard]::SetText((get-content $args))
            }
            
            powershell -sta -noprofile -command $command -args $temp

        } finally {
            if ((test-path $temp)) {
                remove-item $temp
            }
        }
}

function Get-ClipboardText {
        $command = {
                add-type -an system.windows.forms
                [System.Windows.Forms.Clipboard]::GetText()
        }
        powershell -sta -noprofile -command $command
}

Essentially we are running PowerShell as a child process temporarily in STA mode, skipping loading the profile and executing a scriptblock.

PowerShell 2.0 – Asynchronous Callbacks from .NET

Asynchronous callback delegates are not a friend to PowerShell. They are serviced by the .NET threadpool which means that if they point to script blocks, there will be no Runspace available to execute them. Runspaces are thread-local resources in the PowerShell threadpool. The .NET threadpool, operating independently, is not too interested in coordinating callbacks with PowerShell. So what do we do?

There is one feature of PowerShell 2.0 that is capable of running scriptblocks in a pseudo-asynchronous manner: Eventing. Any events bound to with Register-ObjectEvent, EngineEvent or WmIEvent can have associated scriptblocks that will get executed when the associated event is raised. So, if we can somehow convert an asynchronous callback to a .NET event then we can run scriptblocks in response to Async .NET Callbacks. I’ve written a simple function called New-ScriptBlockCallback that helps us do exactly that:

#requires -version 2.0

function New-ScriptBlockCallback {
    param(
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$Callback
    )
<#
    .SYNOPSIS
        Allows running ScriptBlocks via .NET async callbacks.

    .DESCRIPTION
        Allows running ScriptBlocks via .NET async callbacks. Internally this is
        managed by converting .NET async callbacks into .NET events. This enables
        PowerShell 2.0 to run ScriptBlocks indirectly through Register-ObjectEvent.         

    .PARAMETER Callback
        Specify a ScriptBlock to be executed in response to the callback.
        Because the ScriptBlock is executed by the eventing subsystem, it only has
        access to global scope. Any additional arguments to this function will be
        passed as event MessageData.
        
    .EXAMPLE
        You wish to run a scriptblock in reponse to a callback. Here is the .NET
        method signature:
        
        void Bar(AsyncCallback handler, int blah)
        
        ps> [foo]::bar((New-ScriptBlockCallback { ... }), 42)                        

    .OUTPUTS
        A System.AsyncCallback delegate.
#>
    # is this type already defined?    
    if (-not ("CallbackEventBridge" -as [type])) {
        Add-Type @"
            using System;
            
            public sealed class CallbackEventBridge
            {
                public event AsyncCallback CallbackComplete = delegate { };

                private CallbackEventBridge() {}

                private void CallbackInternal(IAsyncResult result)
                {
                    CallbackComplete(result);
                }

                public AsyncCallback Callback
                {
                    get { return new AsyncCallback(CallbackInternal); }
                }

                public static CallbackEventBridge Create()
                {
                    return new CallbackEventBridge();
                }
            }
"@
    }
    $bridge = [callbackeventbridge]::create()
    Register-ObjectEvent -input $bridge -EventName callbackcomplete -action $callback -messagedata $args > $null
    $bridge.callback
}

PowerShell – Function Parameters & .NET Attributes

ArgumentTransformationAttributes are attached to function parameters. They intercept the value coming in and optionally transform it before it is assigned to the target parameter. This is how the powershell type system is enforced:

function Foo ( [string]$str ) { ... }
(gi function:foo).parameters.str.attributes
TypeId
------
System.Management.Automation.ArgumentTypeConverterAttribute
System.Management.Automation.ParameterAttribute

This type converter derives from ArgumentTransformationAttribute. It uses the public utility type [Management.Automation.LanguagePrimitives] (which is full of cool stuff btw) to coerce incoming types to the designated type constraint, in this case [string].

As an aside, you can use the credential attribute on a function parameter too:

function do-something ([system.management.automation.credential()]$cred) { $cred }
do-something # get-credential dialog pops up if $cred is not explicitly passed

And while a little strange but definitely empowering from a future extensibility point of view, you can decorate parameters with entirely inappropriate attributes:

function Foo ( [obsolete($true)][string]$bar ) { ... }

System.ObsoleteAttribute is used in C#/VB.NET to tell the compiler that usage of the decorated item should raise a warning (false) or error (true)

PowerShell 2.0: A Configurable and Flexible Script Logger Module

One thing that has been lamented frequently about PowerShell is that it is very difficult to log, if not impossible, to log all of the various types of streams it has to a single source. The legacy Windows shell CMD, and Unix shells like Bash, Ksh etc only deal with three streams: stdin, stdout and stderr for Input, Ouput and Error respectively. PowerShell has many more: Input, Output, Verbose, Warning, Debug, Progress and Error. Finally, the APIs in v2.0 offer enough hooks to unify the logging but you got to work a bit to make it come together. Well, to be honest, you got me doing the work. The rest is easy ;)

import-module .\scriptlogger.psm1 -force

$logger = New-ScriptLogger

# override error handler
$logger.ErrorHandler = {
    param($record)
    
    $record.tostring() >> scriptlog.txt
}

# override verbose handler
$logger.VerboseHandler = {
    param($record)
    
    $record.message >> scriptlog.txt
}

# run scriptblock with logging
$logger.Invoke(
    {
        $verbosepreference='continue';
        $erroractionpreference = 'continue';
        $debugpreference = 'continue';
        write-verbose "verbose";
        write-error "an error";
        write-warning "a warning"
        Write-debug "debug string"
        "this is output"
        1,2,3
    })
And here is the module; save it as ScriptLogger.psm1. By default, all logging goes to an attached debugger, like sysinternals DbgView. You can override any of the handlers like above and do what you want. Each handler receives one argument: a ErrorRecord, WarningRecord, VerboseRecord, DebugRecord or ProgressRecord. All of these Types are native powershell types and are documented on MSDN.
<#
    Name     : Universal Script Logging Module (ScriptLogger.psm1)
    Version  : 0.1
    Author   : Oisin Grehan (MVP)
    Site     : http://www.nivot.org/
#>
function New-ScriptLogger {
    New-Module -AsCustomObject -ScriptBlock {
        
        $script:ps              = [powershell]::Create()
        $script:ar              = $null
        $script:module          = $ExecutionContext.SessionState.Module
        
        [scriptblock]
        $script:ErrorHandler    = {
            param(
                [Management.Automation.ErrorRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Error: " + $record.tostring());
        }
        [scriptblock]
        $script:WarningHandler  = {
            param(
                [Management.Automation.WarningRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Warning: " + $record.message);
        }
        [scriptblock]
        $script:VerboseHandler  = {
            param(
                [Management.Automation.VerboseRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Verbose: " + $record.message);
        }
        [scriptblock]
        $script:DebugHandler    = {
            param(
                [Management.Automation.DebugRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Debug: " + $record.message);
        }
        [scriptblock]
        $script:ProgressHandler = {
            param(
                [Management.Automation.ProgressRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Progress: " + $record);
        }
        
        $script:Handlers   = @{
            Error = Register-ObjectEvent $ps.Streams.Error DataAdded -Action {
                & $event.MessageData {& $ErrorHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Warning = Register-ObjectEvent $ps.Streams.Warning DataAdded -Action {
                & $event.MessageData {& $WarningHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Verbose = Register-ObjectEvent $ps.Streams.Verbose DataAdded -Action {
                & $event.MessageData {& $VerboseHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Debug = Register-ObjectEvent $ps.Streams.Debug DataAdded -Action {
                & $event.MessageData {& $DebugHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Progress = Register-ObjectEvent $ps.Streams.Progress DataAdded -Action {
                & $event.MessageData {& $ProgressHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
        }
        
        function Invoke {
            param(
                [validatenotnullorempty()]
                [scriptblock]$script
            )
            
            try {
            
                write-host -foreground green "Running"
                
                $ps.commands.clear()
                $command = new-object management.automation.runspaces.command $script, $true            
                $ps.commands.addcommand($command) > $null                
                $ps.invoke() # returns output                
            
            } catch {
            
                # oops-ee!
                write-host -foreground red "Unhandled terminating error: $_"
                $record = new-object management.automation.errorrecord $(
                    new-object exception $_.tostring()), "TerminatingError", "NotSpecified", $null
                & $ErrorHandler $record
            
            } finally {
            
                write-host -foreground green "Complete"
            
            }
        }
                
        Export-ModuleMember -Function Invoke -Variable ErrorHandler, WarningHandler, VerboseHandler, DebugHandler, ProgressHandler
    }
}

Have fun!

PowerShell 2.0 – Persisting Command History

update: 2009-09-27 - removed usage of closures - turns out i hadn't tested that modification properly. oops.

Here’s a quick one – in order to sway excessive BASH jealously, I knocked this up to persist the last 100 history items between PowerShell sessions. It hooks the special engine (as opposed to object) event “poweshell.exiting” and runs a script to save history to an XML file using the universally useful Export-CliXML cmdlet. Another trick in there is to use a closure to capture the value of the $historyPath variable. I need to do this because powershell event handlers use their own runspace (and will lose the values of the variables in the current runspace). I also could have passed the value via the –MessageData parameter and done it that way, but I figured I’m already in v2 territory so lets use that feature ;-)

# save last 100 history items on exit
$historyPath = Join-Path (split-path $profile) history.clixml

# hook powershell's exiting event & hide the registration with -supportevent.
Register-EngineEvent -SourceIdentifier powershell.exiting -SupportEvent -Action {
    Get-History -Count 100 | Export-Clixml (Join-Path (split-path $profile) history.clixml) }

# load previous history, if it exists
if ((Test-Path $historyPath)) {
    Import-Clixml $historyPath | ? {$count++;$true} | Add-History
    Write-Host -Fore Green "`nLoaded $count history item(s).`n"
}

Dump this into your profile and have fun!

PowerShell 2.0 Now Available for Vista and Server 2008

I’ve been reliably informed (and double checked) and I’m happy to relay to you all that PowerShell 2.0 is available as part of the Windows Management Framework RC. This includes the following components:

  • WinRM 2.0
  • Windows PowerShell 2.0
  • BITS 4.0

This is the culmination of nearly three years’ of work to bring Windows to the cutting edge of automation technology. Grab it while it’s toasty from:

https://connect.microsoft.com/windowsmanagement/Downloads

Spread the word!

PowerShell 2.0 RC: Working with .NET Callbacks – Part 1 - Synchronous

updated 2009/7/20: added link to PSEventing for v1.0 event handling
updated 2009/7/24: added link to Get-Delegate script for v1.0 callbacks

There have been some nice improvements made in the latest build of PowerShell with respect to interop with the "callback” pattern in .NET. What exactly are callbacks anyway? It’s exactly what it sounds like, pretty much. In .NET there are sometimes APIs you need to call that expect you to hand them delegates (pointers to methods) which that API will call some time in the future, usually based on certain conditions being fulfilled. If that sounds a bit like .NET events, you’d be right. An event is a much gussied-up callback - it’s just a way of permitting multiple methods to be invoked in response to some condition.

Synchronous Callbacks in PowerShell v1.0

In .NET there are two types of callbacks: Asynchronous (non-blocking) and synchronous (blocking). In PowerShell v1.0, the only callbacks that were catered for were for EventHandler Delegates. This is the method signature that most of the Windows Forms controls expect to call back to in response to button clicks etc. You may have seen code similar to:

$button = new-object system.windows.forms.button
$button.add_Click( { $form.Close() } )

This works because in PowerShell v1.0, there is specialized support for this kind of callback to methods with the EventHandler signature, that is to say, methods with parameters of (object sender, EventArgs e). PowerShell is able to run the ScriptBlock in response to the button being clicked and will even pass the two arguments to the scriptblock for you. When the form is shown from a PowerShell script, there is a single thread that is running the message loop for the application. It is this same thread that handles running the script. In the PowerShell engine, there is a pool of threads created at startup, and each of them has its own “Runspace” for running scripts. Because it is one of the PowerShell threads that is running the application, that same thread is able to run the ScriptBlocks in its Runspace when called upon to do so. Although it is in a slightly roundabout way, this is an example of a synchronous callback. This single application thread is effectively waiting (blocked) for the callback to occur in response to a button click (in reality, it’s doing other things while waiting, but it’s still waiting.)

Some rather creative folks, namely one of the primary developers of PowerShell, wrote a delegate/scriptblock binder in pure script some years ago which you can use to pass script to a .NET api to be called back to in a synchronous manner.  See: Creating arbitrary delegates to ScriptBlocks.

If you want to work with .NET events in version 1.0 of PowerShell, you’ll need an add-on, like my PSEventing Snap-In.

Synchronous Callbacks in PowerShell v2.0

In the latest and greatest version of PowerShell, v2.0 RC (which comes with the public Windows 7 RC), synchronous callbacks got a whole lot easier. PowerShell is now able to deal with pretty much ANY delegate signature, automatically. Lets test this by using the .NET 3.5 System.Func<T, TResult> delegate. This is a generic delegate which lets us pass a method to an API expecting a callback to a method which has one parameters of type T1, and will return type T2. Because it’s generic, we get to pick which parameters. Lets demo creating a ScriptBlock that will be passed a DateTime and returns a String:

add-type –assembly system.core # load .net 3.5
$callback = [system.func[datetime, string]] { param($date); "the date is $date" }
$callback.Invoke( [datetime]::now )
# returns
”the date is 2009/07/14 21:50:35”

We can even take advantage of PowerShell’s super-versatile parameter binder by passing it in a string and having it get coerced to DateTime with ne’er a Parse in sight!

$callback.Invoke(“1/1/2009”)
”the date is 2009/01/01 00:00:00”

So, what’s the point of all this? Why go to all that trouble? Why not just write a function? The point is that functions cannot be called by .NET directly. So when would you need a callback like this in a PowerShell script?

Calling Web Services with Invalid, Untrusted or Expired SSL Certificates

The title says it all – sometimes you need to do this. Usually it’s because you’re working with self-signed, expired or otherwise invalid certificates on a QA or Development system. The New-WebServiceProxy Cmdlet in v2.0 is great for calling Web Services, but it doesn’t have a switch to ignore invalid certificates. If you were writing .NET code in C# or VB.NET, the way go about this is to pass a callback method to an API that expects a RemoteCertificateValidationCallback Delegate. This delegate is designed to point to a method that is passed a handful of arguments describing the attempted connection, and is expected to return a boolean; that is to say, true or false. True means “sure, the connection looks fine – go for it.” A value of False being returned tells .NET to stop the connection before it happens.

The amount of .NET code needed to do this is not a ton, but it’s still a fair handful of lines. Check out this example here: http://blog.jameshiggs.com/2008/05/01/c-how-to-accept-an-invalid-ssl-certificate-programmatically/

Now let’s see how much PowerShell script is needed for this same task:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

Bwahahahah! Eat that, Mr. C# coder! Again, PowerShell’s binder comes into play here and automatically casts our ScriptBlock to a RemoteCertificateValidateCallback delegate (it’s not really a cast – there is no conversion – it’s a sizeable chunk of code.) From this point on, any attempts to use the New-WebServiceProxy Cmdlet with dodgy SSL certificates will succeed without so much as a warning. In fact, pretty much any other classes, like WebRequest will behave the same way. It’s important to note that this only affects the current AppDomain, that is to say, the current PowerShell process. Other processes on the system will continue to stick their nose up at dodgy SSL certs. Quit PowerShell and restart it -- or set that ServerCertificateValidationCallback property to $null -- and all is right in the world again. This works as long as you use one of PowerShell’s threads to do the work of connecting; i.e. don’t use an asynchronous request. This ensures there is a Runspace available to execute this ScriptBlock.

Converting Synchronous Callbacks into Events

This is really quite straightforward. Taking the previous example, we would generate an event inside the scriptblock using New-Event, and then bind one or more event handlers using the Register-EngineEvent Cmdlet:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {
		new-event -SourceIdentifier SslCheck -MessageData $args > $null
		$true
	}
# dump out arguments to cert validate callback
Register-EngineEvent -SourceIdentifier SslCheck -Action { write-output $args }
Next time, we'll get into some meatier stuff. Have fun!

In Part Two: Asynchronous Callbacks in PowerShell v1.0 and v2.0

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List