Emulating Bash / GNU Readline with PowerShell 3.0

Introducing PSReadline 1.0 (Beta)

This is a module that takes advantage of a new hook added to PowerShell 3.0 that allows you to completely take over the readline API. To do so, you must define a function like:

function PSConsoleHostReadline {
    [Console]::Readline()
}

The is an example of a very simple implementation. You may think it's enough, until you realise that there is absolutely zero line editing: The cursor keys don't work, no home/end, no tab completion, nothing! There's a lot more to do than just grabbing a line of text.

This module tries to emulate the Unix Bash/GNU Readline experience. Tab completion works by dumping out a long line of space-separated matches, and will only complete the current line up to the maximum amount of shared leading letters for all matches based on the current token.

Thankfully, you will also get Bash style tab completion for types, cmdlets, parameters and their values as the PSReadline module uses PowerShell 3.0's powerful and fast tab completion APIs.

This is a beta release, so expect a glitch or two. Currently all of the bindings are based on EMACS. The next release will let you define your own bindings. The EMACS bindings are documented below.

Have fun!
readline
Installation
=============

- Download ZIP file, unblock with unblock-file cmdlet.
- Extract to ~\documents\windowspowershell\modules\
  -- This should result in a PSReadline folder 

PS> Import-Module PSReadline

Known issues:
=============

- <esc> does not clear the current line
- does not use powershell history (so get-history returns nothing)
- doesn't support fancy prompt functions with newlines and/or those
  that use write-host; single line prompt function only

Credits
==============
- Miguel de Icaza (getline.cs)
  
  Thanks to his unending masochism & for donating 
  the guts of the Readline emulation, which was torn kicking &
  screaming from an old Mono REPL C# shell. Much massaging and 
  poking was needed for NT & PowerShell, but it works.

Common Bindings
=========================================
Home          Cursor Home
LeftArrow     Cursor Left
RightArrow    Cursor Right
UpArrow       History - Previous
DownArrow     History - Next
Enter         Done
Backspace     Backspace
Delete        Delete Character
Tab           Tab / Tab Complete

EMACS Bindings
=========================================
Ctrl+A        Home
Ctrl+E        End
Ctrl+B        Left
Ctrl+F        Right
Ctrl+P        History - Previous
Ctrl+N        History - Next
Ctrl+K        Kill to EOL
Ctrl+Y        Yank
Ctrl+D        Delete Character
Ctrl+L        Refresh
Ctrl+R        Reverse Search History

Alt+B         Word - Backwards
Alt+F         Word - Forwards
Alt+D         Word - Delete
Alt+BkSpc     Word - Delete Backwards

=========================================
Ctrl+Q        Quote
=========================================

PowerShell 3.0 - Scripting callbacks and Delegates in Managed APIs

A question came up on an MVP mailing list about passing delegates of .NET methods to managed APIs in PowerShell. I covered callbacks to script blocks in a previous post, but I've never covered anything on passing regular .NET methods to APIs so here's a function I wrote to easily create Action, Action<> or Func<> delegates for any given static or instance method. The syntax is pipeline friendly and very flexible. As methods may be overloaded, you must provide a means to select an overload for the delegate. This is done by either providing a specific delegate type you wish to create, or by passing an array of types that should be used to find a compatible overload. The latter technique is more flexible because you only need to provide compatible parameters; the explicit delegate technique needs an exact match for the overload. Here are some examples of the syntax:

# Gets a delegate for a matching overload with string,string parameters.
# It will actually return func<string,object,string> which is the correct 
# signature for invoking string.format with string,string.
$delegate = [string]::format | Get-Delegate string,string

# Gets a delegate for a matching overload with no parameters.
$delegate = [console]::beep | Get-Delegate @()

# Gets a delegate for a matching overload with @(int,int) parameters.
$delegate = [console]::beep | get-delegate int,int

# Gets a delegate for an explicit func[].
$delegate = [string]::format | Get-Delegate -Delegate 'func[string,object,string]'

# Gets a delegate for an explicit action[].
$delegate = [console]::writeline | Get-Delegate -Delegate 'action[int]'

# For a method with no overloads, we will choose the default method and 
# create a corresponding action, action[] or func[].
$delegate = [string]::isnullorempty | get-delegate 

# Gets a delegate to an instance method of a stringbuilder: Append(string)
$sb = new-object text.stringbuilder
$delegate = $sb.append | get-delegate string

Here is the function definition itself. It requires PowerShell 3.0 (in public beta right now) due to some new language features but in theory it could be modified to support PowerShell 2.0. It's also available on PoshCode.

#requires -version 3

function Get-Delegate {
<#
.SYNOPSIS
Create an action[] or func[] delegate for a psmethod reference.
.DESCRIPTION
Create an action[] or func[] delegate for a psmethod reference.
.PARAMETER Method
A PSMethod reference to create a delegate for. This parameter accepts pipeline input.
.PARAMETER ParameterType
An array of types to use for method overload resolution. If there are no overloaded methods
then this array will be ignored but a warning will be omitted if the desired parameters were
not compatible.
.PARAMETER DelegateType
The delegate to create for the corresponding method. Example: [string]::format | get-delegate -delegatetype func[int,string]
.INPUTS System.Management.Automation.PSMethod, System.Type[]
.EXAMPLE
$delegate = [string]::format | Get-Delegate string,string

Gets a delegate for a matching overload with string,string parameters.
It will actually return func which is the correct 
signature for invoking string.format with string,string.
.EXAMPLE
$delegate = [console]::beep | Get-Delegate @()

Gets a delegate for a matching overload with no parameters.
.EXAMPLE
$delegate = [console]::beep | get-delegate int,int

Gets a delegate for a matching overload with @(int,int) parameters.
.EXAMPLE
$delegate = [string]::format | Get-Delegate -Delegate 'func[string,object,string]'

Gets a delegate for an explicit func[].
.EXAMPLE
$delegate = [console]::writeline | Get-Delegate -Delegate 'action[int]'

Gets a delegate for an explicit action[].
.EXAMPLE
$delegate = [string]::isnullorempty | get-delegate 

For a method with no overloads, we will choose the default method and create a corresponding action/action[] or func[].
#>
    [CmdletBinding(DefaultParameterSetName="FromParameterType")]
    [outputtype('System.Action','System.Action[]','System.Func[]')]
    param(
        [parameter(mandatory=$true, valuefrompipeline=$true)]
        [system.management.automation.psmethod]$Method,

        [parameter(position=0, valuefromremainingarguments=$true, parametersetname="FromParameterType")]
        [validatenotnull()]
        [allowemptycollection()]
        [Alias("types")]
        [type[]]$ParameterType = @(),

        [parameter(mandatory=$true, parametersetname="FromDelegate")]
        [validatenotnull()]
        [validatescript({ ([delegate].isassignablefrom($_)) })]
        [type]$DelegateType
    )

    $base = $method.GetType().GetField("baseObject","nonpublic,instance").GetValue($method)    
    
    if ($base -is [type]) {
        [type]$baseType = $base
        [reflection.bindingflags]$flags = "Public,Static"
    } else {
        [type]$baseType = $base.GetType()
        [reflection.bindingflags]$flags = "Public,Instance"
    }

    if ($pscmdlet.ParameterSetName -eq "FromDelegate") {
        write-verbose "Inferring from delegate."

        if ($DelegateType -eq [action]) {
            # void action        
            $ParameterType = [type[]]@()
        
        } elseif ($DelegateType.IsGenericType) {
            # get type name
            $name = $DelegateType.Name

            # is it [action[]] ?
            if ($name.StartsWith("Action``")) {
    
                $ParameterType = @($DelegateType.GetGenericArguments())    
            
            } elseif ($name.StartsWith("Func``")) {
    
                # it's a [func[]]
                $ParameterType = @($DelegateType.GetGenericArguments())
                $ParameterType = $ParameterType[0..$($ParameterType.length - 2)] # trim last element (TReturn)
            } else {
                throw "Unsupported delegate type: Use Action<> or Func<>."
            }
        }
    }

    [reflection.methodinfo]$methodInfo = $null

    if ($Method.OverloadDefinitions.Count -gt 1) {
        # find best match overload
        write-verbose "$($method.name) has multiple overloads; finding best match."

        $finder = [type].getmethod("GetMethodImpl", [reflection.bindingflags]"NonPublic,Instance")

        write-verbose "base is $($base.gettype())"

        $methodInfo = $finder.invoke(
            $baseType,
             @(
                  $method.Name,
                  $flags,
                  $null,
                  $null,
                  [type[]]$ParameterType,
                  $null
             )
        ) # end invoke
    } else {
        # method not overloaded
        Write-Verbose "$($method.name) is not overloaded."
        if ($base -is [type]) {
            $methodInfo = $base.getmethod($method.name, $flags)
        } else {
            $methodInfo = $base.gettype().GetMethod($method.name, $flags)
        }

        # if parametertype is $null, fill it out; if it's not $null,
        # override it to correct it if needed, and warn user.
        if ($pscmdlet.ParameterSetName -eq "FromParameterType") {           
            if ($ParameterType -and ((compare-object $parametertype $methodinfo.GetParameters().parametertype))) {
                write-warning "Method not overloaded: Ignoring provided parameter type(s)."
            }
            $ParameterType = $methodInfo.GetParameters().parametertype
            write-verbose ("Set default parameters to: {0}" -f ($ParameterType -join ","))
        }
    }

    if (-not $methodInfo) {
        write-warning "Could not find matching signature for $($method.Name) with $($parametertype.count) parameter(s)."
    } else {
        
        write-verbose "MethodInfo: $methodInfo"

        # it's important here to use the actual MethodInfo's parameter types,
        # not the desired types ($parametertype) because they may not match,
        # e.g. asked for method(int) but match is method(object).

        if ($pscmdlet.ParameterSetName -eq "FromParameterType") {            
            
            if ($methodInfo.GetParameters().count -gt 0) {
                $ParameterType = $methodInfo.GetParameters().ParameterType
            }
            
            # need to create corresponding [action[]] or [func[]]
            if ($methodInfo.ReturnType -eq [void]) {
                if ($ParameterType.Length -eq 0) {
                    $DelegateType = [action]
                } else {
                    # action<...>
                    
                    # replace desired with matching overload parameter types
                    #$ParameterType = $methodInfo.GetParameters().ParameterType
                    $DelegateType = ("action[{0}]" -f ($ParameterType -join ",")) -as [type]
                }
            } else {
                # func<...>

                # replace desired with matching overload parameter types
                #$ParameterType = $methodInfo.GetParameters().ParameterType
                $DelegateType = ("func[{0}]" -f (($ParameterType + $methodInfo.ReturnType) -join ",")) -as [type]
            }                        
        }
        Write-Verbose $DelegateType

        if ($flags -band [reflection.bindingflags]::Instance) {
            $methodInfo.createdelegate($DelegateType, $base)
        } else {
            $methodInfo.createdelegate($DelegateType)
        }
    }
}

Here are some tests to demonstrate the various cases covered. They were pretty much essential while I was tweaking the script.

# general test function
function Assert-True {
    param(
        [parameter(position=0, mandatory=$true)]
        [validatenotnull()]
        [scriptblock]$Script,

        [parameter(position=1)]
        [validatenotnullorempty()]
        [string]$Name = "Assert-True"
    )    
    $eap = $ErrorActionPreference
    Write-Host -NoNewline "Assert-True [ $Name ] "
    try {
        $erroractionpreference = "stop"
        if ((& $script) -eq $true) {
            write-host -ForegroundColor Green "[PASS]"
            return
        }
        $reason = "Assert failed."
    }
    catch {
        $reason = "Error: $_"
    }
    finally {
        $ErrorActionPreference = $eap
    }
    write-host -ForegroundColor Red "[FAIL] " -NoNewline
    write-host "Reason: '$reason'"
}

#
# static methods
#

assert-true {
    $delegate = [string]::format | Get-Delegate -Delegate 'func[string,object,string]'
    $delegate.invoke("hello, {0}", "world") -eq "hello, world"
} -name "[string]::format | get-delegate -delegate 'func[string,object,string]'"

assert-true {
    $delegate = [console]::writeline | Get-Delegate -Delegate 'action[int]'
    $delegate -is [action[int]]
} -name "[console]::writeline | get-delegate -delegate 'action[int]'"

assert-true {
    $delegate = [string]::format | Get-Delegate string,string
    $delegate.invoke("hello, {0}", "world") -eq "hello, world"
} -name "[string]::format | get-delegate string,string"

assert-true {
    $delegate = [console]::beep | Get-Delegate @()
    $delegate -is [action]
} -name "[console]::beep | get-delegate @()"

assert-true {
    $delegate = [console]::beep | Get-Delegate -DelegateType action
    $delegate -is [action]
} -name "[console]::beep | Get-Delegate -DelegateType action"

assert-true {
    $delegate = [string]::IsNullOrEmpty | get-delegate
    $delegate -is [func[string,bool]]
} -name "[string]::IsNullOrEmpty | get-delegate # single overload"

assert-true {
    $delegate = [string]::IsNullOrEmpty | get-delegate string
    $delegate -is [func[string,bool]]
} -name "[string]::IsNullOrEmpty | get-delegate string # single overload"

#
# instance methods
#

assert-true {
    $sb = new-object text.stringbuilder
    $delegate = $sb.Append | get-delegate string
    $delegate -is [System.Func[string,System.Text.StringBuilder]]
} -name "`$sb.Append | get-delegate string"

assert-true {
    $sb = new-object text.stringbuilder
    $delegate = $sb.AppendFormat | get-delegate string, int, int
    $delegate -is [System.Func[string,object,object,System.Text.StringBuilder]]
} -name "`$sb.AppendFormat | get-delegate string, int, int"

PowerShell 3.0–Now with Property Unrolling!

There are many new improvements to the language and parser in v3 (some of which I hope to cover over the next few posts) but one of my favourites is what Microsoft are calling singleton/array enumeration (or something equally obtuse.) I am hereby christening it “Property Unrolling” as it works similarly to how PowerShell does automatic collection unrolling when piping an enumerable (list, collection, array.)

This powershell 3.0 technique is where you can take a variable that contains an array (or collection, list or anything else that is enumerable) like $myarray and if you want to access a property on each element in that array, you no longer need to use foreach-object with $_.propertyName to access it. Instead, you can simply type $myarray.propertyName and powershell will return that property from each element in the array, but only if the array itself does not have that property. For example if you had an array of strings, asking for $arr.length would return the length of the array, and not the length of each string. The best way to show this is with some examples:

Array of files

# the older way (still works)
dir | foreach-object { $_.lastwritetime } | sort

# now, here's the shortcut way for v3
(dir).lastwritetime | sort

XML

Here's an example on how working with XML just got ten times easier. Here's some XML:

    
                     
             42
        
                     
             43
        
                     
             44
        
    

Here's a script that dumps the prop value in each element:

# the older way
$xml = [xml]" ... "
$xml.root.element | foreach-object { $_.prop }

# the v3 way ;)
$xml.root.element.prop

This is such a time saver. Thank you Microsoft!

Bypassing Restricted Execution Policy in Code or in Script

Many businesses are averse to moving away from a restricted execution policy because they don't really understand it. As Microsoft will tell you, It's not a security boundary - it's just an extra hoop to jump through so you don't shoot yourself in the foot by running something you shouldn’t. If you want to run ps1 scripts in your own application, simply host your own Runspace and use the base authorization manager which pays no heed to system execution policy, even if it’s controlled by group policy and immune to powershell –bypass and set-executiopolicy:

Bypassing in Code

InitialSessionState initial = InitialSessionState.CreateDefault(); 
 
// Replace PSAuthorizationManager with a null manager
// which ignores execution policy 
initial.AuthorizationManager = new 
      System.Management.Automation.AuthorizationManager("MyShellId"); 
 
// Extract psm1 from resource, save locally 
// ... 
 
// load my extracted module with my commands 
initial.ImportPSModule(new[] { <path_to_psm1> }); 
 
// open runspace 
Runspace runspace = RunspaceFactory.CreateRunspace(initial); 
runspace.Open(); 
 
RunspaceInvoke invoker = new RunspaceInvoke(runspace); 
 
// execute a command from my module 
Collection<PSObject> results = invoker.Invoke("my-command"); 
 
// or run a ps1 script     
Collection<PSObject> results = invoker.Invoke(@"c:\program files\myapp\my.ps1");

By using this null authorization manager, execution policy is completed ignored. Remember - this is not some "hack" because execution policy is something for protecting users against themselves. It's not for protecting against malicious third parties, like some kind of script firewall. Whatever could be put in a script, could be run by hand in a dozen different ways using invoke-expression, and even file based scripts can be executed this way: invoke-expression (get-content .\foo.ps1).

Bypassing in Script

Now this is a little more hackish because it involves manipulating powershell.exe internals at runtime. This is a useful one-liner (if you can memorise it) when you find yourself in one of those clients who has GPO controlled execution policy. It’s pushing it for a one-liner, I know, but hey:

function Disable-ExecutionPolicy {
    ($ctx = $executioncontext.gettype().getfield(
        "_context","nonpublic,instance").getvalue(
            $executioncontext)).gettype().getfield(
                "_authorizationManager","nonpublic,instance").setvalue(
        $ctx, (new-object System.Management.Automation.AuthorizationManager
                  "Microsoft.PowerShell"))
}

This function will swap out the powershell host’s AuthorizationManager implementation (PSAuthorizationManager) with the null, policy-ignoring version. Execution policy will be effectively unrestricted, regardless of enterprise, machine or user level attempts to set it to restricted. This is an in-memory bypass only – when powershell.exe is closed and restarted, it’s back to business (or lack thereof.)

Have fun!

PowerShell – Module Installation Best Practices

I’m seeing a few errant companies have their installers throw their modules into ${env:systemroot}\WindowsPowerShell\1.0\Modules but this is not the right place. The only things that should go there are core operating system modules from Microsoft. So, where should you install them?

How to: Install a module for all users

  1. Create the folder ${env:programfiles}\YourProduct\PowerShell\Modules\
  2. Place your module (or modules) under this folder
  3. Add the folder from step 1 to the system scoped environment variable PSModulePath; consider embedding %ProgramFiles% to keep the environment string as short as possible
  4. Profit.

How to: Install a module for the current user

  1. Test for, and create if necessary the folder which is the result of this call (or equivalent in managed code): join-path ([environment]::GetFolderPath("MyDocuments")) WindowsPowerShell\Modules
  2. Copy your module or (modules) to folder at above
  3. Profit.

It’s as easy as that.

PowerShell 3.0–Now with a legible registry provider!

I don’t need to say anything to accompany these pictures. A screenshot or three is worth a thousand words:

PS C:\> gi hklm:\software\microsoft\windows\currentversion

    Hive: Registry::HKEY_LOCAL_MACHINE\software\microsoft\windows

Name                           Property
----                           --------
currentversion                 SM_GamesName             : Games
                               SM_ConfigureProgramsName : Set Program Access and Defaults
                               CommonFilesDir           : C:\Program Files\Common Files
                               CommonFilesDir (x86)     : C:\Program Files (x86)\Common Files
                               CommonW6432Dir           : C:\Program Files\Common Files
                               DevicePath               : C:\windows\inf
                               MediaPathUnexpanded      : C:\windows\Media
                               ProgramFilesDir          : C:\Program Files
                               ProgramFilesDir (x86)    : C:\Program Files (x86)
                               ProgramFilesPath         : C:\Program Files
                               ProgramW6432Dir          : C:\Program Files
                               SM_AccessoriesName       : Accessories
                               PF_AccessoriesName       : Accessories

Now here’s the corresponding view in the Registry Editor:

image

Awesomesauce!

PowerShell 2011 Scripting Games

2011 Scripting Games

The 2011 Scripting Games begin on April 4, 2011 and run through April 15, 2011. What is the Scripting Games, you may ask? Well simply put, the Scripting Games are the premier learning event of the year for IT Pro’s and others who wish to master Windows PowerShell. Comprising 10 events, a registered contestant has seven days to create a solution to a scenario driven problem and post their code to a MVP maintained script repository for evaluation by a panel of internationally recognized judges. Daily leaderboards and prize drawings help to maintain the suspense throughout the two-week international event.

During the 2011 Scripting Games hundreds of contestants will submit thousands of scripts that will be reviewed by dozens of judges. Hundreds of thousands of spectators from all around the world will view the games themselves. Last year, participants from more than 100 countries participated in the Scripting Games. With an emphasis on real world scripting scenarios and problems, the scripts will be of immediate value to both the participants and to the spectators.

Preparation for the 2011 Scripting Games is already underway, with a learning guide, step-by-step tutorials, videos and other resources being generated on a daily basis. The 2011 Scripting Games all in one page is the portal for the games themselves. The 2010 Scripting Games all in one page is still available, as are the events from the 2009 Scripting Games.

Making Windows PowerShell ISE “Good Enough.”

I don’t generally write a lot of big scripts, but when I do, I usually fire up the PowerShell 2.0 graphical IDE. However, every time I use it to develop a module I always get irritated by the lack of any session management. When I hack away on a module, I usually have a couple of files open. I rarely finish a module in one sitting either, so I’ll come back to it in a day or two. I don’t always leave ISE open though so when I fire it up again I have to navigate to the location (or locations) where my files are and re-open them. Another annoying situation is playing with assemblies either by loading some external ones, or by using Add-Type to create your own in-memory types/classes. Debugging these things can get annoying as once loaded or created, you have to quit ISE to “clean” memory of all your dabblings. Well, I lie when I say you have to quit ISE. Strictly speaking, you can create a new tab with CTRL+T, close the old one and load all of your scripts into the new tab. Tabs are isolated from one another and assemblies loaded in one are not available elsewhere. So, taking advantage of this trick, let me introduce:

ISE Session Tools Module v1.0

I have more features planned for v1.1 to give ISE a proper “project” based feel to it, but in the interest of shipping something, v1.0 has the following features:

  • AutoSaving of current session (files open in current tab.)
    • This can be disabled and manually controlled if desired.
  • Prompt to reload last session on ISE open
    • A hint is shown to you reminding you of some of the files you had open.
    • Press <enter> to accept the default of “Yes, reload my last session.”
  • Restarting of the current tab
    • Essentially cleaning memory and keeping your files open in the editor.
    • You get prompted for this action. Press <enter> to accept default of “Yes, restart this tab.”
  • All commands available under “Add-ons” menu for the mouse-fixated.

image

The commands exported by the module are:

  • Restart-PowerShellTab  (CTRL+ALT+R)
  • Export-PowerShellTab (CTRL+ALT+S)
  • Import-PowerShellTab (CTRL+ALT+L)
  • Enable-AutoSaveSession (CTRL+ALT+A)
  • Disable-AutoSaveSession (CTRL+ALT+X)

As you can see, they have hotkeys. This is possible because these commands are also bound to the “Add-ons” menu. I was careful to choose keys that do not interfere with IsePack if you like to use that module too.

image

Installing IseSessionTools

Extract the files  into a folder named “IseSessionTools” under <my documents>\WindowsPowerShell\Modules\. In my ISE $profile, I have the following lines at the end of the file:

Import-Module ISESessionTools
Enable-AutoSaveSession

If you want to clear your session, disable autosaving and delete the session file at ~\psise.session.clixml.

Download ISE Session Tools Module v1.0

Have fun!

PowerShell 2.0–Implementing a Matrix-Style Console Screen Saver

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.

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!

Tips for working with the SharePoint Object Model in PowerShell

One of the models used by SharePoint 2010's cmdlets is a kind of "deferred disposal" which means that SPWeb instances are not disposed until the pipeline in which they are involved in completes. This works like this:

function Get-SPWeb { 
     param([uri]$Url) 
 
     begin { 
         # get SPSite that owns the passed Url 
         $site = new-object microsoft.sharepoint.spsite $url 
         # return specific SPWeb instance 
         $site.OpenWeb()  
     } 
     end { 
         # this disposes owning spsite AND the returned web 
         $site.Dispose() 
     } 
}

Now here is how this works in practice (this is a single line):

ps> get-spweb "http://localhost/sites/test" | foreach-object { 
    $_.Title = "New Name"; $_.update() 
}

The first portion will obtain a single SPWeb instance and pass it to the ForEach-Object portion. Only when the foreach completes (and finishes changing the web's title) will the corresponding End block be called in get-spweb, which disposes the site and web. What's important is that entire pipeline is a single block of code that is executed in a single call.

This won't work interactively like this:

ps> $w = get-spweb "http://localhost/sites/test" # calls begin AND end 
ps> $w.title = "new name" 
ps> $w.update() # boom! web is already disposed

So in this latter example you'd have to use a different implementation of get-spweb (one that omits the end block, or suppresses it with a switch parameter) and then you'd have to dispose the site yourself.

Another important detail is that working interactively in powershell with sharepoint objects will cause you to leak memory if you don't take certain precautions. By default, powershell runs in MTA (multi-threaded apartment) and will use a pool of threads to execute your commands. Each line entered will use a different thread. Each time you access a COM object with a different thread, you will leak some memory from the unmanaged heap as a new heap is allocated for the context switch (without the old one being freed.) This can be alleviated by starting powershell.exe with the -STA switch. This will ensure that all commands and pipelines are executed with the same thread, avoiding the memory leak. That said, simply closing the powershell console will regain all of that memory again but long running scripts might starve your servers of memory if you're not careful, bringing down SharePoint (anything else that doesn't like to get starved of working set.) This is why the single-line approach works so well in the former example: the object is allocated and disposed in the same pipeline, and by extension, the same thread. No leak.

(from an answer I gave on Stack Overflow)

About the author

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

Month List

Page List