PowerShell 2.0–Implementing a Matrix-Style Console Screen Saver

by oising 18. December 2010 03:20

I’ve always been jealous of XTerm’s uber-geeky console mode screensaver, CMatrix. It was written shortly after the Matrix came out about ten years ago, and when I first saw it I thought it was really cool. I accidentally ran into it again recently and thought: “Hey, I could write that for PowerShell.” So I did.

PowerShell Screen Saver

The Matrix animation code uses the RawUI interface and is probably not all that interesting. It uses a simple “game loop” routine, repeatedly calling a “Step” function on multiple instances of a custom object, each representing an animating vertical column of letters. Each column is a dynamic module instantiated using the “-ascustomobject” parameter to allow encapsulation of state. I had originally intended to just prototype it using immediate writes to the console buffer, then moving onto a “frame buffer” implementation whereby the entire screen would be rendered into a BufferCell[,] array with a single SetBufferContents each cycle. It turns out though that the simpler implementation runs fast enough so there’s no need to get fancy pants. The idle detection was a little trickier. It uses PowerShell 2.0’s Eventing feature to start an instance of System.Timers.Timer running in the background. Instead of starting and stopping the timer during idle detection, which is actually quite a sluggish operation, the timer’s Elapsed handler increments a global variable every second. Each time the prompt function executes, it resets the counter back to 0. If this variable reaches the timeout value (default 3 minutes) it will invoke the screen saver from within the event handler. This allows the screen saver to activate without any user interaction. When running in an Action handler like this, you have to use CTRL+C to exit it; for some reason the $host KeyAvailable property doesn’t work correctly in this context. If you manually invoke the screen saver with Start-ScreenSaver, you can hit any key to exit. There is also some extra state being kept to temporarily disable the timer when the screen saver is active, or if the user has issued Disable-ScreenSaver. The implementation is a PowerShell 2.0 Module named “ScreenSaver.” To set it up on your console (sorry, it doesn’t work in graphical shells like ISE, PowerGUI or PoshConsole) just put in the following three lines in your $profile after you have installed the module.

import-module screensaver
set-screensavertimeout (new-timespan -minutes 5)
enable-screensaver

There are a couple of self explanatory functions exported from the module: Enable-ScreenSaver, Disable-ScreenSaver, Set-ScreenSaverTimeout <timespan | int>, Get-ScreenSaverTimeout and Start-ScreenSaver. Remove-Module ScreenSaver will do the right thing and clean up, disposing the timer. By the way, it is possible to have implemented this without the user of global variables but it would have added more complexity for little gain.

Or view the source on PoshCode. Normally I would paste the code inline, but there’s a bit more than my normal posts would have. Feel free to clone the PoshCode script and add your own screen saver patterns. Btw, you can tweak the number of columns and the animation speed by examining the Start-ScreenSaver function.

Have fun!

Tags:

.NET | Eventing | Functions | Modules | Monad | PowerShell | PowerShell 2.0

PowerShell 2.0 – PSCX Labs: Invoke-Reflector

by oising 5. May 2010 21:05

This is a lot of fun if you spend a lot of time tinkering around with APIs in PowerShell. This function (included in the upcoming PSCX 2.0, alias: refl) will let you open Lutz Roeder’s Reflector for any Type or Cmdlet. Reflector will automatically load the correct Assembly and will highlight the relevant Type, without you having to do diddley-squat. Examples and help will display with -?

function Invoke-Reflector {
<#
    .SYNOPSIS
        Quickly load Reflector, with the specified Type or Command selected.
    .DESCRIPTION
        Quickly load Reflector, with the specified Type or Command selected. The function will also
        ensure that Reflector has the Type or Command's containing Assembly loaded.
    .EXAMPLE
        # Opens System.String in Reflector. Will load its Assembly into Reflector if required.
        ps> [string] | invoke-reflector
    .EXAMPLE
        # Opens GetChildItemCommand in Reflector. Will load its Assembly into Reflector if required.
        ps> gcm ls | invoke-reflector
    .EXAMPLE
        # Opens GetChildItemCommand in Reflector. Will load its Assembly into Reflector if required.
        ps> invoke-reflector dir
    .PARAMETER CommandName
        Accepts name of command. Does not accept pipeline input.
    .PARAMETER CommandInfo
        Accepts output from Get-Command (gcm). Accepts pipeline input.
    .PARAMETER Type
        Accepts a System.Type (System.RuntimeType). Accepts pipeline input.
    .PARAMETER ReflectorPath
        Optional. Defaults to Reflector.exe's location if it is found in your $ENV:PATH. If not found, you must specify.
    .INPUTS
        [System.Type]
        [System.Management.Automation.CommandInfo]
    .OUTPUTS
        None
#>
     [cmdletbinding(defaultparametersetname="name")]
     param(
         [parameter(
            parametersetname="name",
            position=0,
            mandatory=$true
         )]
         [validatenotnullorempty()]
         [string]$CommandName,

         [parameter(
            parametersetname="command",
            position=0,
            valuefrompipeline=$true,
            mandatory=$true
         )]
         [validatenotnull()]
         [management.automation.commandinfo]$CommandInfo,

         [parameter(
            parametersetname="type",
            position=0,
            valuefrompipeline=$true,
            mandatory=$true
         )]
         [validatenotnull()]
         [type]$Type,

         [parameter(
            position=1
         )]
         [validatenotnullorempty()]
         [string]$ReflectorPath = $((gcm reflector.exe -ea 0).definition)
     )

        # no process block; i only want
        # a single reflector instance

        if ($ReflectorPath -and (test-path $reflectorpath)) {

            $typeName = $null
            $assemblyLocation = $null

            switch ($pscmdlet.parametersetname) {

                 { "name","command" -contains $_ } {

                    if ($CommandName) {
                        $CommandInfo = gcm $CommandName -ea 0
                    } else {
                        $CommandName = $CommandInfo.Name
                    }

                    if ($CommandInfo -is [management.automation.aliasinfo]) {

                        # expand aliases
                        while ($CommandInfo.CommandType -eq "Alias") {
                            $CommandInfo = gcm $CommandInfo.Definition
                        }
                    }

                    # can only reflect cmdlets, obviously.
                    if ($CommandInfo.CommandType -eq "Cmdlet") {

                        $typeName = $commandinfo.implementingtype.fullname
                        $assemblyLocation = $commandinfo.implementingtype.assembly.location

                    } elseif ($CommandInfo) {
                        write-warning "$CommandInfo is not a Cmdlet."
                    } else {
                        write-warning "Cmdlet $CommandName does not exist in current scope. Have you loaded its containing module or snap-in?"
                    }
                }

                "type" {
                    $typeName = $type.fullname
                    $assemblyLocation = $type.assembly.location
                }
            } # end switch


            if ($typeName -and $assemblyLocation) {
                & $reflectorPath /select:$typeName $assemblyLocation
            }

        } else {
            write-warning "Unable to find Reflector.exe. Please specify full path via -ReflectorPath."
        }
}

Have fun!

Tags:

.NET | Cmdlets | Developer | Functions | Monad | PowerShell | PowerShell 2.0 | PSCX

PowerShell 2.0 – Partial Application of Functions and Cmdlets

by oising 11. March 2010 01:01

This is unashamedly a post for developers, in particular those with an interest in functional languages. With the advent of PowerShell 2.0, some of you may have noticed that ScriptBlocks - which I suppose could also be called anonymous functions or lambdas - gained a new method: GetNewClosure. Closures are one of the essential tools for functional programming., something I’ve been trying to learn more about over the last few years. I don’t really have an opportunity to use it in work other than the hybrid trickery available in C# 3.0, but I have been tinkering a lot with PowerShell 2.0 to see if some of the tricks of the functional trade could be implemented. It’s just a shell language, but there are some nice features in there that enable a wide variety of funky stuff.

Partial Application

In a nutshell, partial application of a function is when you pass in only some of the parameters and get a function back that can accept the remaining parameters:

# define a simple function
function test {
    param($a, $b, $c);
    "a: $a; b: $b; c:$c"
}

# partially apply with -c parameter
$f = merge-parameter (gcm test) -c 5

# partially apply with -c and -a then execute with -b (papp is an alias)
& (papp (papp (gcm test) -c 3) -a 2) -b 7

# partially apply the get-command cmdlet with -commandtype
# and assign the result to a new function
si function:get-function (papp (gcm get-command) -commandtype function)

This is by no means a complete implementation of a partial application framework for powershell. The merge-parameter function (aliased to papp) currently only works with the default parameterset and does not mirror any of the parameteric attributes in the applied function or cmdlet. I'm not saying it couldn't do that, but this is purely a proof of concept. The module is listed below and is also available from PoshCode at http://poshcode.org/1687

# save as functional.psm1 and drop into your module path
Set-StrictMode -Version 2

$commonParameters = @("Verbose",
                      "Debug",
                      "ErrorAction",
                      "WarningAction",
                      "ErrorVariable",
                      "WarningVariable",
                      "OutVariable",
                      "OutBuffer")

<#
.SYNOPSIS
    Support function for partially-applied cmdlets and functions.
#>
function Get-ParameterDictionary {
    [outputtype([Management.Automation.RuntimeDefinedParameterDictionary])]
    [cmdletbinding()]
    param(
        [validatenotnull()]
        [management.automation.commandinfo]$CommandInfo,
        [validatenotnull()]
        [management.automation.pscmdlet]$PSCmdletContext = $PSCmdlet
    )
    
    # dictionary to hold dynamic parameters
    $rdpd = new-object Management.Automation.RuntimeDefinedParameterDictionary

    try {
        # grab parameters from function
        if ($CommandInfo.parametersets.count > 1) {
            $parameters = $CommandInfo.ParameterSets[[string]$CommandInfo.DefaultParameterSet].parameters
        } else {
            $parameters = $CommandInfo.parameters.getenumerator() | % {$CommandInfo.parameters[$_.key]}
        }        
                
        $parameters | % {
            
            write-verbose "testing $($_.name)"
                                    
            # skip common parameters        
            if ($commonParameters -like $_.Name) {                                  
                
                write-verbose "skipping common parameter $($_.name)"
                
            } else {
                
                $rdp = new-object management.automation.runtimedefinedparameter
                $rdp.Name = $_.Name
                $rdp.ParameterType = $_.ParameterType
                
                # tag new parameters to match this function's parameterset
                $pa = new-object system.management.automation.parameterattribute
                $pa.ParameterSetName = $PSCmdletContext.ParameterSetName
                $rdp.Attributes.Add($pa)
                
                $rdpd.add($_.Name, $rdp)
            }
            
        }
    } catch {
    
        Write-Warning "Error getting parameter dictionary: $_"
    }
    
    # return
    $rdpd
}

<#
.SYNOPSIS
    Function that accepts a FunctionInfo or CmdletInfo reference and one or more parameters
    and returns a FunctionInfo bound to those parameter(s) and their value(s.)
.DESCRIPTION
    Function that accepts a FunctionInfo or CmdletInfo reference and one or more parameters
    and returns a FunctionInfo bound to those parameter(s) and their value(s.)
    
    Any parameters "merged" into the function are removed from the available parameters for
    future invocations. Multiple chained merge-parameter calls are permitted.
.EXAMPLE

    First, we define a simple function:
    
    function test {
        param($a, $b, $c, $d);
        "a: $a; b: $b; c:$c; d:$d"
    }
    
    Now we merge -b parameter into functioninfo with the static value of 5, returning a new
    functioninfo:
    
    ps> $x = merge-parameter (gcm test) -b 5
    
    We execute the new functioninfo with the & (call) operator, passing in the remaining 
    arguments:
    
    ps> & $x -a 2 -c 4 -d 9
    a: 2; b: 5; c: 4; d: 9
    
    Now we merge two new parameters in, -c with the value 3 and -d with 5:
    
    ps> $y = merge-parameter $x -c 3 -d 5
    
    Again we call $y with the remaining named parameter -a:
    
    ps> & $y -a 2
    a: 2; b: 5; c: 3; d: 5
.EXAMPLE

    Cmdlets can also be subject to partial application. In this case we create a new
    function with the returned functioninfo:
    
    ps> si function:get-function (merge-parameter (gcm get-command) -commandtype function)
    ps> get-function
                
.PARAMETER _CommandInfo
    The FunctionInfo or CmdletInfo into which to merge (apply) parameter(s.)
    
    The parameter is named with a leading underscore character to prevent parameter
    collisions when exposing the targetted command's parameters and dynamic parameters.
.INPUTS
    FunctionInfo or CmdletInfo
.OUTPUTS
    FunctionInfo
#>
function Merge-Parameter {    
    [OutputType([Management.Automation.FunctionInfo])]
    [CmdletBinding()]
    param(
        [parameter(position=0, mandatory=$true)]
        [validatenotnull()]
        [validatescript({
            ($_ -is [management.automation.functioninfo]) -or `
            ($_ -is [management.automation.cmdletinfo])
        })]
        [management.automation.commandinfo]$_Command
    )
    
    dynamicparam {
        # strict mode compatible check for parameter
        if ((test-path variable:_command)) {
            # attach input functioninfo's parameters to self
            Get-ParameterDictionary $_Command $PSCmdlet
        }
    }

    begin {
        write-verbose "merge-parameter: begin"
        
        # copy our bound parameters, except common ones              
        $mergedParameters = new-object 'collections.generic.dictionary[string,object]' $PSBoundParameters
        
        # remove our parameters, leaving only target function/CommandInfo's args to curry in
        $mergedParameters.remove("_Command") > $null
        
        # remove common parameters
        $commonParameters | % {
            if ($mergedParameters.ContainsKey($_)) {
                $mergedParameters.Remove($_)  > $null
            }
        }
    }
    
    process {
        write-verbose "merge-parameter: process"
        
        # temporary function name
        $temp = [guid]::NewGuid()

        $target = $_Command

        # splat our fixed named parameter(s) and then splat remaining args
        $partial = {
            [cmdletbinding()]
            param()
            
            # begin dynamicparam
            dynamicparam
            {                
                $targetRdpd = Get-ParameterDictionary $target $PSCmdlet
        
                # remove fixed parameters
                $mergedParameters.keys | % {
                    $targetRdpd.remove($_) > $null
                }
                $targetRdpd
            }
            begin {
                write-verbose "i have $($mergedParameters.count) fixed parameter(s)."
                write-verbose "i have $($targetrdpd.count) remaining parameter(s)"
            }
            # end dynamicparam
            process {
                $boundParameters = $PSCmdlet.MyInvocation.BoundParameters
                
                # remove common parameters (verbose, whatif etc)
                $commonParameters | % {
                    if ($boundParameters.ContainsKey($_)) {
                        $boundParameters.Remove($_)  > $null
                    }
                }
                
                # invoke command with fixed parameters and passed parameters (all named)
                . $target @mergedParameters @boundParameters
                
                if ($args) {
                    write-warning "received $($args.count) arg(s) not part of function."
                }
            }
        }
        
        # emit function/CommandInfo
        new-item -Path function:$temp -Value $partial.GetNewClosure()
    }
    
    end {
        # cleanup
        rm function:$temp
    }    
}

new-alias papp Merge-Parameter -force

Export-ModuleMember -Alias papp -Function Merge-Parameter, Get-ParameterDictionary

Have fun[ctional]!

Tags:

Closures | Cmdlets | Functional Programming | Functions | Modules | Monad | PowerShell

PowerShell – The Patchwork of Paths, PSPaths and ProviderPaths

by oising 5. March 2010 06:02

Paths in PowerShell are tough to understand [at first.] PowerShell Paths - or PSPaths, not to be confused with Win32 paths - in their absolute forms, come in two distinct flavours:

  • Provider-qualified: FileSystem::c:\temp\foo.txt
  • Drive-qualified: c:\temp\foo.txt

It's very easy to get confused over provider-internal (The ProviderPath property of a resolved PathInfo – and the bold portion of the provider-qualified path above) and drive-qualified paths since they look the same if you look at the default FileSystem provider drives. That is to say, the PSDrive has the same name (C) as the native backing store, the windows filesystem (C). So, to make it easier for yourself to understand the differences, create yourself a new PSDrive:

ps c:\> new-psdrive temp filesystem c:\temp\
ps c:\> cd temp:
ps temp:\>

Now, let's look at this again:

  • Provider-qualified: FileSystem::c:\temp\foo.txt
  • Drive-qualified: temp:\foo.txt

A bit easier this time to see what’s different this time. The bold text to the right of the provider name is the ProviderPath.

So, your goals for writing a generalized provider-friendly Cmdlet (or advanced function) that accepts paths are:

  • Define a LiteralPath path parameter aliased to PSPath
  • Define a Path parameter (which will resolve wildcards / glob)
  • Assume you are receiving PSPaths, NOT native provider-paths

Point number three is especially important. Also, obviously LiteralPath and Path should belong in mutually exclusive parameter sets.

Relative Paths

A good question is: how do we deal with relative paths being passed to a Cmdlet. As you should assume all paths being given to you are PSPaths,  let’s look at what the Cmdlet below does:

ps temp:\> write-zip -literalpath foo.txt

The command should assume foo.txt is in the current drive, so this should be resolved immediately in the ProcessRecord or EndProcessing block like (using the scripting API here to demo):

$provider = $null;
$drive = $null
$providerPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("foo.txt", [ref]$provider, [ref]$drive)

Now you everything you need to recreate the two absolute forms of PSPaths, and you also have the native absolute ProviderPath. To create a provider-qualified PSPath for foo.txt, use $provider.Name + “::” + $providerPath. If $drive is not null (your current location might be provider-qualified in which case $drive will be null) then you should use $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" to get a drive-qualified PSPath.

Have fun!

Tags:

Cmdlets | Functions | Monad | PowerShell | PowerShell 2.0 | PSPath

About the author

Oisin Grehan lives in Montreal, Canada and builds all sorts of crap for all sorts of people.

Month List

Page List