December 09
First I must say I think it's awesome to get a comment from someone on the PoSH team. Thanks.
Now, about the script, I must criticize/comment on the inspiration for this script. The original inspiration is
Karl Prosser's *-backgroundpipeline snap in. That snap in, for some reason, didn't work for me when it was new, and now it's out of date. Due to the lack of function of it I tried to write a script to fill the hole in my heart. That script worked, but it sucked. So I had to re script the whole thing, but before I did that I looked for one that works and is easier to use. I did a very short search and found Jim Truher's New-Job script. The New-Job script is an attempt to copy the background job feature of bash. The New-Job script got a vague appearance of that feature, however bash allows as many background jobs as you want, the script did not.
My script is a library script with seven functions. Three are used to control the runspace objects that are necessary for multi threading in Powershell, and the other four are for controlling pipeline execution(commands). The runspaces can be referenced by a number returned when it is created, or by a name given when it is created. Wild card characters can be given to the Get-AsyncRunspace function to references to several runspaces. The Invoke-ExpressionInRunspace function is simplified function of the other pipeline functions, and executes the given command syncronusly with the current thread, making it more efficient with small commands. The Read-AsyncPipe function is optional and can be used to get the results of a command as it executes.
I hope you find it easy to use if you do use it. I'll sugjest using three letter aliases, in place of the long function names, when you're using the shell in interactive mode. Of course, each runspace keeps track of their own variables and you could pass reference objects between them, and overall do most of what you could do with any other .NET, multi threaded programming language.
Now the script.
file: Library-AsyncRunspace.ps1
# Library-AsyncRunspace
# Version: 1.1
# Include with {. Library-AsyncRunspace}
# Sugjested aliases...
##New-Alias eap End-AsyncPipe
##New-Alias gar Get-AsyncRunspace
##New-Alias r! Invoke-ExpressionInRunspace
##New-Alias iar Invoke-ExpressionInRunspace
##New-Alias nar New-AsyncRunspace
##New-Alias rap Read-AsyncPipe
##New-Alias rar Remove-AsyncRunspace
##New-Alias sap Start-AsyncPipe
# Don't destroy previous runspace catalog
if ( -not $RunspaceCatalog ) {
$RunspaceCatalog = New-Object PSObject
$RunspaceCatalog | Add-Member NoteProperty RunspaceCountPosition 0 -PassThru |
Add-Member NoteProperty RunspaceList @{}
}
function New-AsyncRunspace {
param ([String]$NewName)
# Incroment id number counter
$RunspaceCatalog.RunspaceCountPosition++
# Generate a runspace and add usefull properties
$NewRunspace = [management.automation.runspaces.runspacefactory]::CreateRunspace() |
Add-Member NoteProperty Name $NewName -PassThru |
Add-Member NoteProperty CurrentPipe $null -PassThru |
Add-Member ScriptProperty PipeState { $this.CurrentPipe.PipelineStateInfo.State } -PassThru |
Add-Member ScriptProperty PipeStateReason { $this.CurrentPipe.PipelineStateInfo.Reason } -PassThru |
Add-Member ScriptProperty CurrentCommands { "{$($this.CurrentPipe.Commands)}" } -PassThru
$NewRunspace.Open()
# Add new runspace to catalog
$RunspaceCatalog.RunspaceList[$RunspaceCatalog.RunspaceCountPosition] = $NewRunspace
# Report new runspace id number
$RunspaceCatalog.RunspaceCountPosition
}
function Remove-AsyncRunspace {
param ($RunspaceID = (read-host "Enter a valid runspace ID or name."))
if ( -not ($RunspaceID -is [Int32] -or $RunspaceID -is [String])) { throw "A runspace id must be supplied by integer or name!" }
#Find selected runspace by number
if ($RunspaceID -is [Int32]) {
$RunspaceCatalog.RunspaceList[$RunspaceID].Close()
$RunspaceCatalog.RunspaceList.Remove($RunspaceID)
}
#Or find selected runspace by name
else {
$keys = $RunspaceCatalog.RunspaceList.Keys
$keys | Foreach-Object {
if ( $RunspaceCatalog.RunspaceList[$_].Name -like $RunspaceID ) {
$RunspaceCatalog.RunspaceList[$_].Close()
$RunspaceCatalog.RunspaceList.Remove($_)
}
}
}
}
function Get-AsyncRunspace {
param ($RunspaceID)
# Return the entire list of available runspaces by default
if ( $RunspaceID -eq $null ) {
return $RunspaceCatalog.RunspaceList.Values
}
# Type check on id
if ( -not ($RunspaceID -is [Int32] -or $RunspaceID -is [String])) { throw "A runspace id must be supplied by integer or name!" }
#Find selected runspace by number
if ($RunspaceID -is [Int32]) {
$SelectedRunspace = $RunspaceCatalog.RunspaceList[$RunspaceID]
}
#Or find selected runspace by name
else {
$SelectedRunspace = $RunspaceCatalog.RunspaceList.Values | Where-Object { $_.Name -like $RunspaceID }
}
# Return result
$SelectedRunspace
}
function Invoke-ExpressionInRunspace {
param ($RunspaceID = (read-host "Enter a valid runspace ID or name."), [String]$Command = (read-host "Enter a command to be executed in the runspace."))
$SelectedRunspace = Get-AsyncRunspace $RunspaceID | Select-Object -first 1
# Throw error if no runspace was found with the given id
if ( ($SelectedRunspace | Measure-Object).Count -eq 0 ) { throw "Runspace not found!" }
# Insert the command into the runspace.
$Pipeline = $SelectedRunspace.CreatePipeline($Command)
# Insert input into the new pipe
$input | Foreach-Object { [void]$Pipeline.Input.Write($_) }
$Pipeline.Input.Close()
# Execute pipe
$Pipeline.Invoke()
# Return output from pipe
$Pipeline.Output.ReadToEnd()
}
function Start-AsyncPipe {
param (
$RunspaceID = (read-host "Enter a valid runspace ID or name."),
[String]$Command = (read-host "Enter a command to executed in the runspace.")
)
BEGIN {
$SelectedRunspace = Get-AsyncRunspace $RunspaceID | Select-Object -first 1
# Throw error if no runspace was found with the given id
if ( ($SelectedRunspace | Measure-Object).Count -eq 0 ) { throw "Runspace not found!" }
# Insert the command into the runspace.
$Pipeline = $SelectedRunspace.CreatePipeline($Command)
# Start async process
$Pipeline.InvokeAsync()
# List this pipe as current with the runspace
$SelectedRunspace.CurrentPipe = $Pipeline
}
PROCESS {
# Insert input into the new pipe
[void]$Pipeline.Input.Write($_)
}
END {
# Close input
$Pipeline.Input.Close()
}
}
function Read-AsyncPipe {
param ($RunspaceID = (read-host "Enter a valid runspace ID or name."))
$SelectedRunspace = Get-AsyncRunspace $RunspaceID | Select-Object -first 1
# Throw error if no runspace was found with the given id
if ( ($SelectedRunspace | Measure-Object).Count -eq 0 ) { throw "Runspace not found!" }
# Read pipeline result
if ($SelectedRunspace.CurrentPipe.pipelineStateInfo.state -eq [management.automation.runspaces.pipelinestate]::failed) {
throw $SelectedRunspace.CurrentPipe.PipelineStateInfo.Reason
}
$SelectedRunspace.CurrentPipe.Output.nonblockingread()
}
function End-AsyncPipe {
param ($RunspaceID = (read-host "Enter a valid runspace ID or name."))
$SelectedRunspace = Get-AsyncRunspace $RunspaceID | Select-Object -first 1
# Throw error if no runspace was found with the given id
if ( ($SelectedRunspace | Measure-Object).Count -eq 0 ) { throw "Runspace not found!" }
# Pipeline is no longer current
$Pipeline = $SelectedRunspace.CurrentPipe
$SelectedRunspace.CurrentPipe = $null
# Read pipeline result
if ($Pipeline.PipelineStateInfo.State -eq [management.automation.runspaces.pipelinestate]::failed) {
throw $Pipeline.PipelineStateInfo.Reason
}
else {
while (-not $Pipeline.Output.endofpipeline) {
$Pipeline.Output.nonblockingread()
start-sleep -m 50 #don't overload the cpu
}
$Pipeline.output.Close()
}
}
That's it. 183 lines of code and it can't do a thing with out your inspiration so take it and build some network applications. I'll have to make a note of those three letter aliases again, because learning the dynamics of the whole thing using the full names is less than fun. I did enjoy the debug procedure using those aliases and funny runspace names.
Now I'll close with a simple example of the use of the script. The following example should be valid is you're using the aliases.
PS>nar foo
1
PS>iar foo { $h = "Hello"; $w = "world!" }
PS>"$(iar foo {$h}) $(iar foo {$w})"
Hello world!
Try it. Have fun, and all that will make sence in about 5 minutes.
December 07
I have decided to start posting my
PoSH scripts as I finish them.
I have just made one that seems to be unique. I wrote a script that will start a screen saver. It can either try to detect the current users settings from the registry, or it can search for any available screen saver, apply filters, and execute one randomly.
The script itself doesn't do much work, but with a little work on the user behalf it could help in creating a screen saver rotation application.
file: Start-ScreenSaver.ps1
# Start-ScreenSaver
# Version: 1.1
param ([String]$Include, [String]$Exclude)
#Avoid execution if a screen saver is already running
if (get-Process *.scr) {
return
}
#If filters are not provided default to user preference.
if ( -not $Include -and -not $Exclude) {
[String]$DefaultScreenSaver = (Get-ItemProperty 'HKCU:\Control Panel\Desktop').{SCRNSAVE.EXE}
if ($DefaultScreenSaver) {
& $DefaultScreenSaver
return
}
}
#Get a list of available screen savers and filter them.
[System.Management.Automation.ApplicationInfo[]]$ScreenSaverCommands = Get-Command *.scr -CommandType Application |
Where-Object {
$(
if ($Include) { $_.Name -like $Include -or $_.Name -eq $Include + '.scr' }
else {$true}
) -and
$(
if ($Exclude) { -not ( $_.Name -like $Exclude ) }
else {$true}
)
}
#Randomly choose a screen saver and execute it.
if ($ScreenSaverCommands) {
& $(($ScreenSaverCommands[$(New-Object System.Random).Next($ScreenSaverCommands.Length)]).Definition)
}
December 02
I'm becoming anxious to buy a new computer. I dont' feel like trying to maintain a new desktop computer and still continue to run experiments on Vista on another computer. So as of this post my Vista Preview is over.
I'm sorry to say that I didn't do much in this time. I did write a short essay that's half about Aero and half about the .NET Presentation Foundation. Other than that essay, I haven't done as much experimentation as I wanted due to work and playing many video games in this time. I did say that I was going to create a gadget for the Vista Sidebar, and doing so would be easy, but I couldn't think of anything useful and I found the Sidebar itself annoying.
I had intended to write several other essays. However, I'm a poor writer, and there are many other people with blogs that have equal or greater experience with Vista than I do and have greater mastery of the English language than I.
The essay is short, poorly written, and lacks detail. If you don't want to read it simply skip the rest of this post. If you do read it I hope you can find it useful or at least mildly entertaining for the next minute.
Aero And .NET 3.0 Presentation Foundation: The death of GDI
Everyone knows what GDI graphics look like. It's what was in use since Windows 95 up to Windows XP. Before XP everything was rectangular and had thin lines. Images could be used in some places. In Windows XP, the themes engine actively replaced the rectangular user controls with images that made the user interface smother and rounded, but those images would look stretched when scaled across large controls.
GDI controls had little hierarchial design. Pannels and tabs had the ability to contain other controls, but this was basically only by honor. Each individual control had absolute control of what was to be rendered in it's own little rectangle of the window. This ment there was little opportunity for special effects unless the programer was willing to code the entire window paint procedure on his or her own.
What I see as being Microsoft's flagship for shipping Vista to home users is Vista's desktop compositor, Aero, and the new GUI modal, the .NET 3.0 Presentation Foundation. The combination of these two can alow programers to make more interesting user interfaces with more impressive effects and more intuitive user flow metaphors.
The interface of Vista itself provides several improvements over its predecessors. The start menu has a built in interactive search bar. Windows Explorer has improvements that make navigating and searching through folders easier. As a developer, I see this as a potential benefit as if an illiterate end user finds the interface of the OS easy to use the user would be more willing to become literate and more literate users means more users that may make use of a developers applications.
From a developer point of view I see that Vista has a few methods that can be used to produce vector graphics that can be used in application GUIs. The full benefit of the use of vector graphics can not yet be seen today. However, the range of hardware available to residential customers is becoming wider. Having vector graphics rendering built into the GUI provided by the operating system will ease the process of developing software for widely varying display devices.
I can state no downside of Aero and Presentation Foundation. It's use is intuitive. New users will find it easy to use. Experienced users will need to learn little more. It is an improvement over older versions of Windows. This is to be expected.