However, Google has decided that it would be downgraded in rankings because of its presentation.
Now reactive, responsive web design was in its infancy back when this content was created and small screens were rare and Android didn’t really exist, but Google now says I have to have a presentation that looks good on mobile devices.
So I switched to a modern, yet generic WordPress theme. I don’t like it much.
I wonder if Google will start down-ranking museums because the content doesn’t meet its modern standards?
]]>
Grr is now at http://github.com/soapyfrog/grrr and other misc scripts are at http://github.com/soapyfrog/miscps1 – feel free to do what you will with them, but note the CC license.
]]>
This is partly because my full time work does not use nor need PowerShell and my free time has become more precious.
Playing with PS1 has been fun and I’ve learned a lot, but ultimately, it’s just a toy to me, and there comes a time when you must put your toys away — one might argue that, at nearly 40, I should have put my toys away long ago
Now, I’ve been inundated with literally 3 requests for the source code to Grrr, the snapin-based framework behind Big Invaders, so here it is:
Like the rest of the stuff here, it’s released under the creative commons license.
So long, and thanks for all the fish.
]]>I especially like the FAQ on why it’s free
]]>If someone else had a cmdlet called, say, out-banner
, and that cmdlet was already loaded in a snap-in (perhaps mandated by site policy), would I be able to register and add the Grrr snapin, which also defines it?
I’ve scanned the available documentation and the language grammar, used introspection in the shell and so on, but could not find anything to deal with this problem.
With projects like PowerShell Community Extensions seemingly laying claim to hundreds of common nouns and verbs, I was concerned that the scope for writing unique cmdlets would dry up real fast.
To determine what would happen, I registered and added Soapyfrog.Grrr
, then wrote a new snapin, Soapyfrog.Conflict
, with a single cmdlet, out-banner
, conflicting with the one in Grrr.
I registered and added this, and was surprised that no warning of a conflict was given. To see if both were added, I tried this:
PS> get-command out-banner CommandType Name Definition ----------- ---- ---------- Cmdlet Out-Banner Out-Banner [-Text] <Str... Cmdlet Out-Banner Out-Banner [-Text] <Str...
Interesting. Both are available, so what happens when I try to invoke it?
PS> out-banner hello The term 'out-banner' resolved to a cmdlet name that is ambiguous. Pos sible matches include: Soapyfrog.Grrr\Out-Banner soapyfrog.conflict\Ou t-Banner. At line:1 char:11 + out-banner <<<< hello
OK, so at this point, we know it’s possible to break existing PowerShell scripts by simply registering and adding a new snapin with a same-named cmdlet. Not good. Is there a work-around?
PS> Soapyfrog.Grrr\out-banner hello -fg 'o' o o ooooo o o ooo o o o o o o o ooooo ooo o o o o o o o o o o o o o ooooo ooooo ooooo ooo
Yes! We can fully qualify the cmdlet name with the snapin name and a backslash.
So, it’s not as bad as I first feared, but as I noted above, you need to be very careful about how you code and run scripts in environments where you can’t predict what snapins are registered and installed.
I suppose you can create the correct shell environment with Export-Console and run your script with:
powershell -psconsolefile xxx.psc1 -noprofile -command myscript.ps1
Or if your script requires a snapin, add the snapin in the script and fully qualify each cmdlet you use.
]]>Grrr 1.1 is now Cmdlet based and comes as a Snap-In. The most obvious benefit from this is performance, as I can now present a playable version of PowerShell Big Invaders.
BigInvaders is one of the demo PowerShell scripts that makes use of the Grrr.
Download this prerelease version 1.1 of the Grrr snap-in.
Update: this should now install on Vista – thanks to Chris Warwick for pointing out some issues
There’s no installer (yet) so to get going, follow this instructions:
Unzip the archive where you want to use it and CD to the top level director where the README file is.
Type: ./installgrrr.ps1 -r
The -r
switch forces it to re-register the snap-in if a (possibly) older version exists. It then adds the snap-in to the current shell.
From here you can CD into the demos
directory and run any of the scripts. Each one shows a feature of Grrr, but perhaps the most interesting is in the biginvaders
directory.
To run BigInvaders, you need to have a very large console. To achieve this you probably need to set the font size of your console window to 6×8.
If you want sound, you need to install DirectX DirectSound. More on why later.
It should work without, silently, but this hasn’t had much testing as all my XP boxes have it installed If there are exceptions, start it with the -nosound
switch.
Assuming all is well, type ./biginvaders.ps1
and you should see this title screen:
Hit ESC to quit or Space to play. In play, Space fires a missile and arrow keys move left and right. Hit F to toggle FPS display in the top-righthand corner.
The target FPS is 33. I achieve this easily on my MacPro, and my wife’s Dell (a core 1 duo, 1.66ghz) also just manages.
There are still a few snags to iron out here and I want to add proper PowerShell help and an installer, but it is functional.
I’ve learned a lot about C#, PowerShell SDK and .NET these last weeks and will write about my findings over the next days. It’s not all been rosy.
I’ll also be writing about the features of Grrr, and where I want to go with it.
That’s it for now.
]]>Anyways, I’ve almost completed the transition of Grrr to Cmdlets and it’s looking very promising as this screen shot shows (I get 50fps with only 20% cpu load doing this):
I intend to redo the original PSInvaders using Grrr, with more authentic gameplay and original graphics.
The scrolling tilemap support is looking good, so games like Gauntlet, Scramble and Super Mario Brothers should be possible… all scripted in PowerShell.
So apologies for the low post volume recently and please do watch this space.
]]>Watch this space.
]]>A full client takes time, so with a nod toward PowerShell’s administrative uses, I wrote a script that forms the basis of an IRC chat bot.
This script can be used to pipe messages to a chat channel and/or join a set of channels, returning all messages posted there.
I’ve sure most of you out there use Microsoft MSN Windows Live Messenger, or even Yahoo! or AOL, so this may just serve as an example of synchronous TCP/IP messaging in PowerShell.
You can download the script here: chat-irc.ps1.
You may need to unblock it according the the instructions in help about_signing
.
Summary of use
This script operates in two modes, sender, or monitor (or both).
Regardless, you need to set up connection info in a hash, eg:
$coninfo = @{ server="chat.freenode.net" port=6667 nick="mynick" user="myuser" pwd="my password if required" realname="This is my real name" hostname"This is my host name, but is generally ignored" }
I’ve been using chat.freenode.net and my own local hybrid irc server for testing.
To send a message to the #test channel and quit:
chat-irc -coninfo $coninfo -sendto "#test" -message "Hello, world"
To send output from the pipeline, eg a file:
gc file.txt | chat-irc -coninfo $coninfo
To monitor a channel for messages:
chat-irc -coninfo $coninfo -monitor "#test"
This will stay connected until you ctrl+C, are killed by the server, etc, or if chat-irc
is sent the message “stopstopstop”.
The output is formatted strings containing from/to/message, however these strings are annotated with noteproperties, so you can do:
chat-irc -coninfo $coninfo -monitor "#test" select *
The properties include sender info (user
,host
,nick
,full
), date
, to
and message
.
These two modes can be used together, and the monitor parameter can take
more than one channel, so for example:
chat-irc -coninfo $coninfo -monitor "#foo","#bar" -sendto "#test" -message "hello"
will send “hello” to #test then monitor #foo,#bar and #test for messages.
By default, only messages sent to the monitored channel(s) are output, but you can include private messages with the switch -incprivate
, the message of the day with -incmotd
, notices with -incnotice
and other with -incother
.
You can see debug info with -debug and verbose output (all messages)
with -verbose. These latter switches just set the $DebugPreference and $VerbosePreference variables to “Continue” in the script’s scope See Debug and Verbose Colouring for more information on that.
Examples
Here we get chat-irc to monitor the channel #test2 whilst I (millinad) chat to it in a normal IRC chat client.
PS> chat-irc -coninfo $coninfo -monitor "#test2" | select date,nick,message|ft -wrap date nick message ---- ---- ------- 31/01/2007 14:21:20 millinad hello soapybot 31/01/2007 14:21:30 millinad nice to see you here 31/01/2007 14:21:38 millinad time for you to go now 31/01/2007 14:21:40 millinad stopstopstop
and in the chat client:
* soapybot (n=soapybot@*******) has joined #test2hello soapybot nice to see you here time for you to go now stopstopstop * soapybot (n=soapybot@*******) has left #test2
Now lets do the same thing again, but with debug switched on and private messages enabled. We’ll turn off the pipeline formatting.
PS> chat-irc -coninfo $coninfo -monitor "#test2" -incprivate -debug DEBUG: Using connection info: DEBUG: server: chat.freenode.net DEBUG: port: 6667 DEBUG: user: soapybot DEBUG: nick: soapybot DEBUG: pwd: ************ DEBUG: realname: powershell bot using soapyfrog inout-irc.ps1 DEBUG: hostname: localhost freenode-connect : soapybot : ☺VERSION☺ DEBUG: I *may* have joined channel #test2 millinad : #test2 : hello again millinad : #test2 : i see you received a ctcp message above. pity you don't understa nd them. millinad : #test2 : time to go again millinad : soapybot : stopstopstop
In the last line, I sent soapybot a direct message to stopstopstop rather than to the channel. Without -incprivate
this message would not have been output.
How it works
The script connects to the chat server using System.Net.Sockets.TcpClient
. Messages are sent using a System.IO.StreamWriter
with ASCII encoding. .NET doesn’t seem to mind of non ASCII chars are translated in this way.
Reading messages is done differently. We can’t use a System.IO.StreamReader
, because ReadLine
blocks if there isn’t a full line available.
We can’t use asynchronous reading because it’s quite hard to use scriptblocks as delegates (although that might change in future versions of PowerShell), and threading is a bit of an unknown quantity in PowerShell.
Instead I use the Net.Sockets.NetworkStream
with its DataAvailable
and ReadByte
. This means I can check if data is available, and if not, idle or send messages, then check again. Reading byte by byte makes the code easier to read, and it’s not too big a performance hit, we’re bound by the network and the speed of people typing
Lines are processed in the process-line
function where the line is parsed and checked for known numeric responses and commands.
The default handling of a message is in the function _onprivmsg
where the message is just formatted and decorated and place into the output pipeline.
If you wanted to have this bot do something else, you would do it here.
A good chat citizen
This script tries to be a good citizen by throttling it’s message sending with 1 second delays between messages. This is set with the -throttledelay
parameter (milliseconds). The amount of time the script idles when there is no data from the server is 2 seconds, set with the -idledelay
parameter.
Even with these values, it’s still possibly to be killed by a server for flooding. You may want to ask server admin to reduce the flood spec for your bot, or increase the throttledelay.
For example, at chat.freenode.net, piping the output from a directory listing would still cause a flood.
Enjoy
]]>By default, Write-Verbose
and Write-Debug
cmdlets write nothing to the console, because the the value of the variables $VerbosePreference
and $DebugPreference
respectively are “SilentlyContinue”.
If you set them both to “Continue” the text is written to the console, but both in bright yellow on black (I wonder why?), like this:
PS> $DebugPreference="continue" PS> $VerbosePreference="continue" PS> write-debug "debug msg" DEBUG: debug msg PS> write-verbose "verbose msg" VERBOSE: verbose msg
These colours can be changed so you can more easily distinguish between debug and verbose messages, and also not have the real output swamped.
The trick is to change the values of the colours in $host.privatedata
, and I do this:
$defbg="darkmagenta" $deffg="darkyellow" # normal colours $r=$host.ui.rawui $r.BackgroundColor=$defbg $r.foregroundColor=$deffg $p=$host.privatedata # error colours $p.ErrorBackgroundColor=$defbg $p.ErrorForegroundColor="red" # warning colours $p.WarningBackgroundColor=$defbg $p.WarningForegroundColor="yellow" # debug colours $p.DebugBackgroundColor=$defbg $p.DebugForegroundColor="darkgray" # verbose colours $p.VerboseBackgroundColor=$defbg $p.VerboseForegroundColor="darkcyan" # progress colours $p.ProgressBackgroundColor="darkcyan" $p.ProgressForegroundColor="yellow"
Now, the IRC app I’m working on, when I run with debug/verbose switched on, yields an output like this:
PS> test DEBUG: Using connection info: DEBUG: port: 6667 DEBUG: nick: soapybot DEBUG: server: chat.freenode.net DEBUG: hostname: localhost DEBUG: user: soapybot DEBUG: realname: inout-irc as soapybot VERBOSE: >> NICK soapybot VERBOSE: >> USER soapybot localhost chat.freenode.net :inout-irc as soapybot VERBOSE: << NOTICE AUTH :*** Looking up your hostname... VERBOSE: << NOTICE AUTH :*** Found your hostname, welcome back VERBOSE: << NOTICE AUTH :*** Checking ident VERBOSE: << NOTICE AUTH :*** No identd (auth) response VERBOSE: << :brown.freenode.net 001 soapybot :Welcome to the freenode IRC Network soapybot VERBOSE: << :brown.freenode.net 002 soapybot :Your host is brown.freenode.net[brown.freenode.net/6667], running version hyperion-1.0.2b VERBOSE: << NOTICE soapybot :*** Your host is brown.freenode.net[brown.freenode.net/6667], running version hyperion-1.0.2b VERBOSE: << :brown.freenode.net 003 soapybot :This server was created Fri Dec 22 00:07:52 UTC 2006 VERBOSE: << :brown.freenode.net 004 soapybot brown.freenode.net hyperion-1.0.2b aAbBcCdDeEfFGhHiIjkKlLmMnNopPQrRsStTuUvVwWxXyYzZ01234569*@ bcdefFhiIklmnoPqstv VERBOSE: << :brown.freenode.net 005 soapybot IRCD=dancer CAPAB CHANTYPES=# EXCEPTS INVEX CHANMODES=bdeIq,k,lfJD,cgijLmnPQrRstz CHANLIMIT=#:20 PREFIX=(ov)@+ MAXLIST=bdeI:50 MODES=4 STATUSMSG=@ KNOCK NICKLEN=16 :are supported by this server VERBOSE: << :brown.freenode.net 005 soapybot SAFELIST CASEMAPPING=ascii CHANNELLEN=30 TOPICLEN=450 KICKLEN=450 KEYLEN=23 USERLEN=10 HOSTLEN=63 SILENCE=50 :are supported by this server VERBOSE: << :brown.freenode.net 251 soapybot :There are 18601 listed and 16168 unlisted users on 24 servers VERBOSE: << :brown.freenode.net 375 soapybot :- brown.freenode.net Message of the Day - VERBOSE: << :brown.freenode.net 372 soapybot :- Freenode is a service of Peer-Directed Projects Center, an VERBOSE: << :brown.freenode.net 372 soapybot :- IRS 501(c)(3) not-for-profit organization. Our yearly VERBOSE: << :brown.freenode.net 372 soapybot :- fundraiser will begin soon; if you'd like to donate early, VERBOSE: << :brown.freenode.net 372 soapybot :- please see http://freenode.net/pdpc_donations.shtml for more VERBOSE: << :brown.freenode.net 372 soapybot :- information. Thank you for using freenode! VERBOSE: << :brown.freenode.net 372 soapybot :- VERBOSE: << :brown.freenode.net 376 soapybot :End of /MOTD command. VERBOSE: >> JOIN #test,#test2,#archlinux VERBOSE: << :freenode-connect!freenode@freenode/bot/connect PRIVMSG soapybot :☺VERSION☺ freenode-connect : soapybot : VERSION VERBOSE: << :soapybot!n=soapybot@*********** JOIN :#test DEBUG: We may have joined channel #test VERBOSE: << :brown.freenode.net 332 soapybot #test :Welcome to #test - This channel is for testing only. Don't harass or annoy other users here. Don't paste senceless crap here. If you need a response for tests type '#say foobar' or '#moo' - Have a nice time in here... RIP lilo aka rob levin :'( DEBUG: We have joined channel #test VERBOSE: << :soapybot!n=soapybot@*************** JOIN :#test2 DEBUG: We may have joined channel #test2 VERBOSE: >> PRIVMSG #test2 :hello VERBOSE: << :soapybot!n=soapybot@*************** JOIN :#archlinux DEBUG: We may have joined channel #archlinux VERBOSE: << :brown.freenode.net 332 soapybot #archlinux :[ Welcome to #archlinux! (!rules) || main: archlinux.org || forums: bbs.archlinux.org || wiki: wiki.archlinux.org || bugs: bugs.archlinux.org || AUR: aur.archlinux.org || iso: ftp://ftp.archlinux.org/other/0.8 ] DEBUG: We have joined channel #archlinux VERBOSE: << :schnappi!n=joy@debiancenter/user/schnappi PRIVMSG #test :Good after-noon, soapybot. It's 17:28h.GMT schnappi : #test : Good after-noon, soapybot. It's 17:28h.GMT VERBOSE: << :Danny|!n=Danny@89-172-171-230.adsl.net.t-com.hr PART #archlinux :"So Say We All." VERBOSE: << :millinad!n=millinad@*************** PRIVMSG #test2 :hello soapybot millinad : #test2 : hello soapybot VERBOSE: << :millinad!n=millinad@*************** PRIVMSG #test2 :go now millinad : #test2 : go now VERBOSE: << :millinad!n=millinad@*************** PRIVMSG #test2 :wibble millinad : #test2 : wibble VERBOSE: >> PART #test2 VERBOSE: >> PART #archlinux VERBOSE: >> PART #test VERBOSE: >> QUIT :bye bye
(some of the above has been trimmed for clarity and privacy).
The brighter white output is the normal output from the script, the grey is debug messages and cyan, verbose. It gives me all the output I want, without making it too hard to pick out the real information.
Once I’m happy that all is well, I can revert the $VerbosePreference
and $DebugPreference
variables back to “SilentlyContinue” and all you get is the default white output.
However, making a function constant
does not prevent it from being shadowed in child scopes. Given that a child scope is created in any script, new function definition or script block, this makes it very unconstant.
Consider this:
# define a constant function, one that cannot be replaced set-item function:/funca -option constant { write-host "this is the outer funca" # redefine the function in this new scope function funca { write-host "this is the inner funca" } # call this new funca funca } # call the outer one funca
If you run it, you will see that the inner funca is defined and executed. So much for being constant.
You can, however, make it a bit more permanent by adding the allscope
option to the outer function, like this:
# define a constant function, one that cannot be replaced set-item function:/funca -option constant,allscope { write-host "this is the outer funca" # redefine the function in this new scope - this will fail! function funca { write-host "this is the inner funca" } } # call the outer one funca
When we run this, we get an error:
PS> funcs this is the outer funca Cannot write to function funca because it is read-only or constant. At C:/Documents and Settings/adrian/My Documents/proj/ps1/funcs.ps1:5 char:17 + function funca <<<< {
Why? Because allscope
copies the function to all child scopes that are created. The default behaviour, when finding a function (or variable) is to look in the current scope, then the parent, and so on up to global
. By automatically copying the constant function to all scopes, you can prevent the function from ever being redefined.
So to make a function as constant as possible, define it with constant,allscope
in your $profile
.
PS. It appears that you can use scope modifiers with test-path
. These two commands do seem to work, although I’ve not seen any documentation regarding it:
test-path function:/funca test-path function:/script:funca
However, get-childitem function:
only shows one.
I fixed some bugs and added a couple of new features to the script get-bufferhtml.ps1, and have updated the article with examples.
Enjoy!
]]>Yes? Well it surprisingly simple using PowerShell to script the System.Net.HttpListener
. Here’s a working example with some genuine uses (well useful for me).
I’ll leave all the security and risk concerns as an exercise for the reader
What we have here is a small PowerShell script, httpd.ps1, that listens on a port of your choosing (default 8888) using the HTTP protocol, executes supplied PowerShell expressions, the returns the pipeline output to the caller.
To use it, type this in a PowerShell window:
./httpd
This will set the web server listening on http://yourhostname:8888/
. You can change this to be something else by supplying a prefix as a parameter, eg:
./httpd http://192.168.0.28:8888/
or safer (in case it crashes)
powershell -command ./httpd http://192.168.0.28:8888/
This parameter must end in a slash.
You’ll need to open the appropriate port in your firewall, if you intend to try to use this from a different machine.
Once running (and using that example above) you can enter that url into your web browser, and the response you’ll get should be something like:
No command supplied. Syntax is: http://server/?cmd=xxxx Where xxxx is 'quit' to tell the server to quit or any PowerShell command. Eg: http://server/?cmd=get-process
You can see from this that you need to supply a command. Try this:
http://192.168.0.28:8888/?cmd=get-process
What you get back is a process listing for the host machine.
I use this mostly with the command line tool curl to get command output on my Mac, eg:
macpro:~ adrian$ curl -g 'http://192.168.0.21:8888/?cmd=ps|where%20{$_.ws%20-gt%2010mb}' Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 836 15 42864 55092 236 8.58 3440 dexplore 512 14 15736 27316 94 17.20 1512 explorer 192 6 16092 11160 126 0.12 2748 httpd.vshost 657 17 20224 31776 112 10.32 3792 iexplore 419 14 6680 11564 75 9.12 1412 KService 1004 6 45232 43740 156 2.93 3324 powershell 185 5 22108 28624 126 0.54 3368 powershell 163 7 35812 34668 138 1.74 3640 powershell 1588 57 45812 54516 231 495.46 808 svchost
You have to escape the URL for curl, but this executed ps|where {$_.ws -gt 10mb}
(all processes with a working set greater than 10mb).
Note that this script does not show you the output from write-host
or any errors, but if you ask for $Error
you will get the recent errors returned.
An alternative would be to modify the script to use get-bufferhtml to return a full copy of the PowerShell buffer.
Anyways, I learned how easy it is to write a mini web server in .NET and PowerShell, so I’m happy
Oh, one last thing. To stop the script, send it a cmd=quit
. You could just ctrl+break in the PowerShell window, but I find that causes the whole host to disappear.
For example, cmd1 | cmd2
will run the two commands in separate processes.
PowerShell doesn’t do this. Everything runs in one thread, and pipelined objects are processed in batches, one bit at a time. You can control the batch size with the -outBuffer
parameter, but things are still done sequentially, with one command in the pipe being busy whilst all others are sitting idle.
Even the venerable CMD.exe will put these two commands tree | more
into separate processes.
This aside, what about simply running an arbitrary command (or scriptblock) in the background whilst doing something else?
PowerShell doesn’t have any obvious way to do this, but it is possible with runspaces. You can create a new runspace and then invoke a pipeline asynchronously.
Here is an example script, but lets go through it step by step.
$rscfg = [management.automation.runspaces.runspaceconfiguration]::Create() $rs=[management.automation.runspaces.runspacefactory]::CreateRunspace($rscfg) $rs.Open()
Here, we create a runspace
with a default configuration. There is an overloaded version of CreateRunspace
that takes a PSHost
object, so we could pass in $host
. In this case we just use the default. We then open the runspace
.
$block = { $sum=0 "adding numbers in background!" foreach($i in $input) { $sum+=$i start-sleep 1 # pretent it takes ages to compute this "adding number $i" } "that's it, sum is $sum" } $pipe=$rs.CreatePipeline($block)
Here we create the scriptblock $block
that we want to run in the background and create a pipeline $pipe
for it. The block itself simply takes it time adding up numbers passed to it, yielding textual information to the pipe, ending with the result.
$writer = $pipe.Input $pipe.InvokeAsync()
Here we simply grab hold of the pipe’s input then invoke it asynchronously.
$numwritten = $writer.Write(1..5,$true) $writer.Close()
This is where we give the background pipe some data to play with. Write(1..5,$true)
means write the numbers 1 through 5 into the pipeline ($true
means send the numbers one at a time instead of all at once).
1..3 | foreach { start-sleep 1; "busy in foreground $_" }
The whole purpose of this example is to show how two different things can be done at once, so here we simply spend 3 seconds pretending to be busy
$reader = $pipe.Output while (-not $reader.EndOfPipeline) { $o = $reader.Read() write-host "$o" }
Here we get the output $reader
from the pipeline and whilst there is data, read an object and display it.
We didn’t have to do it this way; we could have read the whole thing in one go like this: $out = $reader.ReadToEnd()
. The latter might be better if the background work produces a single result, but the line-by-line version may suit something with a lot of output. Although we simple do a write-host "$o"
here, this could be a lengthy operation that would run whilst the background operation continues to do work.
$pipe.Error.ReadToEnd() $pipe.Dispose() $rs.Close()
Finally we consume any error output and clean up.
If you run the example script, you will see something like this:
busy in foreground 1 busy in foreground 2 busy in foreground 3 adding numbers in background! adding number 1 adding number 2 adding number 3 adding number 4 adding number 5 that's it, sum is 15
This is great, but why is this post categorised as a rant? Well, it’s very long-winded and in my mind, async job scheduling is something that all shells should do.
It’s a pity PowerShell doesn’t let you do something like this:
{ foo | bar ; wibble}.InvokeAsync()
or
invoke-command -async { foo | bar ; wibble }
You can do this, however:
{ foo | bar ; wibble}.Invoke()
So how would all this be used? Would it make space invaders any quicker on a core duo pc? Only a profiler would know.
A single runspace can only have one asynchronous pipeline running and the pipeline cannot be reused. The setup costs are quite high, so you’d probably have to mess about with a single background pipeline reading work requests and writing them back.
]]>
It’s a script, so execute it directly. It takes a string (or array of strings) as the first argument and two optional parameters, -fg char to set the foreground character and -bg char for the background character. The defaults are a solid block and a space. For example:
PS> banner "hello","world" -fg "/" / / ///// / / /// / / / / / / / ///// /// / / / / / / / / / / / / / ///// ///// ///// /// / / /// //// / //// / / / / / / / / / / / / / / //// / / / // // / / / / / / / / / /// / / ///// //// PS>
You can download the script here: banner.ps1.
Don’t copy/paste this – save it directly from the above link. It contains formatted text that may get corrupted.
Enjoy!
[Update] The script will also take input from the pipeline so you can do things like:
[enum]::getnames([consolecolor]) | where { $_ -notlike "dark*"} | banner -fg "#"
to get this:
#### # ### ### # # # # # # # # # # # #### # ##### # ### # # # # # # # # # #### ##### # # ### # # ### #### ### # # # # # # # # # # ## #### ##### # # # # # # # # ### # # # # # #### # # # ##### # # # # # # #### # # # ### # # # # # # #### ##### ### ##### ### #### ##### ##### # # # # # # # ## # # ## #### ### ### # # # # # # # # # # ## ### # # ##### ##### # # ### # # ### # # # # # # # # ## # # # ##### # # # # # # # # # ## ### # # # # # #### ##### #### # # # # # #### ### # # # # # # # # # ##### #### # # ### ### ##### # # ##### ### ## ## # # # # ## # # # # # # # ##### # ## ### # # # # ##### # # # # # # # # ## # # # # # # # ### ##### # # # # # # # ##### # # ### # # # # # # # # # # # # ### # # # # # # # # # # # # # ## ## # ##### ##### ##### ### # # # # # # ### ##### ##### # # # # # # # # # # ##### # # ### ## ## # # # # # # # # # ### # #####]]>