Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Handling CTRL+C in function

1,266 views
Skip to first unread message

J. McConnell

unread,
Oct 23, 2006, 11:46:24 AM10/23/06
to
I'm new to PowerShell and wanted to create something simple to get my
feet wet. I decided to write a simple ping function whose output
mimiced the output of ping on linux systems. Like "ping.exe -t", ping
on linux systems will continue to ping until the user enters CTRL+C. At
that point, like ping.exe, it prints out some useful aggregate info
about all requests, like the percentage that were successful, etc.

I wrote the following function as a start, but I haven't been able to
find an easy way to capture a CTRL+C.

function ping([string] $hostToPing) {
$ping = new-object Net.NetworkInformation.Ping

$ip = $ping.Send($hostToPing).Address
write-host -s '' "PING $hostToPing ($ip): 32 data bytes"

$count = 0
while (1) {
$r = $ping.Send($hostToPing)
$count++
if ($r.Status -eq [Net.NetworkInformation.IPStatus]::Success) {
write-host -s '' -n "$($r.Buffer.Length) bytes from $($r.Address): "
write-host -s '' -n "icmp_seq=$count ttl=$($r.Options.Ttl) "
write-host -s '' "time=$($r.RoundtripTime) ms"
}
else {
write-host $r.Status
}
sleep 1
}
}

What I'd like is to have something like:

.{
trap SOME_CTRL_C_EXCEPTION { ...; continue }
$count = 0
while (1) { ... }
}
write-host 'summary info'

Is there any way of accomplishing something like this?

Thanks for any help,

- J.

J. McConnell

unread,
Oct 24, 2006, 11:15:53 AM10/24/06
to
On 2006-10-23, J. McConnell <j-...@j-dotonline.com> wrote:
> What I'd like is to have something like:
>
> .{
> trap SOME_CTRL_C_EXCEPTION { ...; continue }
> $count = 0
> while (1) { ... }
> }
> write-host 'summary info'
>
> Is there any way of accomplishing something like this?

I may have answered my own question. In looking at this further, it
seems there is no way to do this in a function, but I can in a cmdlet by
overriding the StopProcessing method, right?

Has any thought been given to providing a way to implement cmdlets from
within PowerShell?

- J.

George Xie [MSFT]

unread,
Oct 24, 2006, 1:54:01 PM10/24/06
to
The way Ctrl-C works is that console will catch the event and notify the
current running command to stop. This is not done through exceptions, so it
cannot be caught through traps.

Powershell does provide a way to write cmdlets through scripts. For example,

function scriptcmdlet
{
begin
{
$a = 5;
}
process
{
$_ * $a;
}
end
{
"every item is scaled by $a"
}
}

This script can work like a cmdlet. Begin/process/end blocks correspond to
BeginProcessing, ProcessingRecord, EndProcessing in cmdlet interface.
However, there is no block corresponding to StopProcessing for now.

Thanks,

--
George Xie [MSFT]
Microsoft Command Shell Development
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

"J. McConnell" <j-...@kant.overstock.com> wrote in message
news:slrnejsbh9...@kant.overstock.com...

Jacques Barathon [MS]

unread,
Oct 24, 2006, 2:19:54 PM10/24/06
to
You can however use the Console .NET class and properties to capture CTRL+C
from within your script. Try this:

--- test-ctrlc.ps1 ---
[console]::TreatControlCAsInput = $true
while ($true)
{
write-host "Processing..."
if ([console]::KeyAvailable)
{
$key = [system.console]::readkey($true)
if (($key.modifiers -band [consolemodifiers]"control") -and
($key.key -eq "C"))
{
"Terminating..."
break
}
}
}
--- end of script ---

Regards,
Jacques

"George Xie [MSFT]" <gx...@online.microsoft.com> wrote in message
news:%23gJOyV5...@TK2MSFTNGP04.phx.gbl...

J. McConnell

unread,
Oct 24, 2006, 2:28:19 PM10/24/06
to
On 2006-10-24, George Xie [MSFT] <gx...@online.microsoft.com> wrote:
> Powershell does provide a way to write cmdlets through scripts. For example,
>
> function scriptcmdlet
> {
> begin
> {
> $a = 5;
> }
> process
> {
> $_ * $a;
> }
> end
> {
> "every item is scaled by $a"
> }
> }
>
> This script can work like a cmdlet. Begin/process/end blocks correspond to
> BeginProcessing, ProcessingRecord, EndProcessing in cmdlet interface.

I like this syntax a lot. Thank you for this information.

> However, there is no block corresponding to StopProcessing for now.

Are there any plans to add a "stop" block?

- J.

J. McConnell

unread,
Oct 24, 2006, 2:32:14 PM10/24/06
to
On 2006-10-24, Jacques Barathon [MS] <jbar...@online.microsoft.com> wrote:
> You can however use the Console .NET class and properties to capture CTRL+C
> from within your script. Try this:
>
> --- test-ctrlc.ps1 ---
> [console]::TreatControlCAsInput = $true
> while ($true)
> {
> write-host "Processing..."
> if ([console]::KeyAvailable)
> {
> $key = [system.console]::readkey($true)
> if (($key.modifiers -band [consolemodifiers]"control") -and
> ($key.key -eq "C"))
> {
> "Terminating..."
> break
> }
> }
> }
> --- end of script ---

Ahh, that's great. Thank you for the info, that's what I'll go with.

From a function, would it be recommended that the function restore the
value of [console]::TreatControlCAsInput? E.g.

fuction test {
$oldValue = [console]::TreatControlCAsInput
[console]::TreatControlCAsInput = $true
while ($true) { ... }
[console]::TreatControlCAsInput = $oldValue
}

Thanks,

- J.

Jacques Barathon [MS]

unread,
Oct 24, 2006, 2:46:36 PM10/24/06
to
"J. McConnell" <j-...@kant.overstock.com> wrote in message
news:slrnejsn1e...@kant.overstock.com...

Good catch. If you don't dot source your script, the change implemented in
the script will be lost as the script ends so other scripts won't be
impacted. But saving and restoring a "global" value is a good practice in
general.

Jacques

George Xie [MSFT]

unread,
Oct 24, 2006, 4:52:42 PM10/24/06
to
Definitely we will consider adding a stop block in v2. To make the case
stronger, do you have some particular scenario where you really need to have
logic for StopProcessing?

Thanks,

--
George Xie [MSFT]
Microsoft Command Shell Development
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

"J. McConnell" <j-...@kant.overstock.com> wrote in message

news:slrnejsmq3...@kant.overstock.com...

Jean

unread,
Oct 24, 2006, 6:16:37 PM10/24/06
to
> Definitely we will consider adding a stop block in v2

As you're speaking about blocks, what about a "preparam" block (or
eventualy allow Begin block before param() definition) ?

As a scenario :

We want to write a script using an assembly not loaded by default.
If a "preparam" block exists we are able to load the assembly before
"param" and then use assembly's types to define param parameters.
AFAIK we can't do that in RC2 (we can load assembly in Begin but it's
too late to use its types in param).

Something like :

#---8<---
preparam{
[void][System.Reflection.Assembly]::`
LoadWithPartialName('System.Drawing')
}

param([drawing.fontstyle]$style='Bold')

begin{
#....code
}

process{
#....code
}

end{
#....code
}
#---8<---

Regards,

--
Jean - JMST
Belgium


Jean

unread,
Oct 24, 2006, 6:21:04 PM10/24/06
to
> (or eventualy allow Begin block before param() definition)

Strike out those words :-)

George Xie [MSFT]

unread,
Oct 24, 2006, 9:47:07 PM10/24/06
to
For the scenario you mentioned below, you can do the assembly loading
outside of the script block. Param statement normally happens during
parameter binding which is even before the script starts. Putting a
preparam block in script block will need us twist the script block quite a
bit since it doesn't have same scoping rules as other parts of script block.

--
George Xie [MSFT]
Microsoft Command Shell Development
Microsoft Corporation
This posting is provided "AS IS" with no warranties, and confers no rights.

"Jean" <repo...@groupe.svp> wrote in message
news:mn.c8107d6a8ca49192.56820@windows...

J. McConnell

unread,
Oct 25, 2006, 11:11:07 PM10/25/06
to
On 2006-10-24, George Xie [MSFT] <gx...@online.microsoft.com> wrote:
> Definitely we will consider adding a stop block in v2. To make the case
> stronger, do you have some particular scenario where you really need to have
> logic for StopProcessing?

After Jacque pointed out that I could handle a CTRL+C from within a
function, my need to implement StopProcessing went away. I was only
considering a cmdlet because I didn't realize there was another suitable
way for handling a CTRL+C.

In case anyone is interested, I wrote a helper function for this
purpose. Here it is:

function handleCtrlC($codeToRun) {
function handleIt($whatToDo) {
if ([console]::KeyAvailable) {
$key = [console]::readKey($true)


if (($key.modifiers -band [consolemodifiers]"control") `
-and ($key.key -eq "C")) {

&$whatToDo
}
}
}

$oldControlCValue = [console]::TreatControlCAsInput
[console]::TreatControlCAsInput = $true

&$codeToRun

[console]::TreatControlCAsInput = $oldControlCValue
}

It accepts a function that it calls after setting
[console]::TreatControlCAsInput to $true. From within that function,
you can call the "handleIt" function, which accepts a function that it
runs when the user has entered CTRL+C. I imagine it should be useful
anywhere you have a while ($true) { ... } loop.

Here's an example of what it looks like:

handleCtrlC {
function getTemp() { return (new-object Random).Next(100) }
$exit = $false
while ($true) {
$curTemp = getTemp
write-host "the current temperature is: $curTemp F"
$totalTemp += $curTemp
$count++
for ($i = 0; $i -lt 10; $i++) {
handleIt { set-variable exit $true -scope 2; break }
sleep -milliseconds 100
}
if ($exit) {
break
}
}
write-host "the temperature averaged $($totalTemp / $count) F"
}

While the example is pretty contrived, you can imagine the getTemp
function hitting some web service and all of a sudden it's almost
useful. I'm using this for a simple ping implementation which is
actually useful.

- J.

Alex K. Angelopoulos [MVP]

unread,
Oct 26, 2006, 8:24:11 AM10/26/06
to
I've asked about this before, and the primary rationale is "as above, so
below" - cmdlets and scripts should both provide similar features and
functionality. There's an entire shopping list of items for this, but
restricting it to the stop-processing case, any function/script that might
be terminated can gain useful functionality from this; it also makes some
new things possible, including special augmentation of cmdlets. Here are a
few specific examples.

+ Scripts/functions that use out-of-process applications to perform some
work may leave the application running if aborted. Any special application
started as a new process and any out-of-process COM server are examples.
Word in particular is a good illustration; if you have Word doing work for a
script, it will not be visible by default and if the script/function
invoking it is aborted while it has a "dirty" document, it may continue
running. A stop block would permit you to directly exit Word.

+ Temp file cleanup

+ Logging information about an important script being halted.

+ Using a script _specifically_ to log information or perform cleanup for
cmdlets or console applications that don't clean up nicely when halted.

"George Xie [MSFT]" <gx...@online.microsoft.com> wrote in message
news:%23EWVu56...@TK2MSFTNGP04.phx.gbl...

Alex K. Angelopoulos [MVP]

unread,
Oct 26, 2006, 8:29:36 AM10/26/06
to
FYI, a more "powershellish" way to do this is to collect the information
into a hashtable rather than returning a bunch of text. This can be awkward
in some circumstances depending on how you want information displayed, but
it makes it much easier to integrate the pinger into other tools.

If you don't know what I mean or want to try that, post back; if you're
still looking at other bits of the process, ignore this for now. :)


"J. McConnell" <j-...@j-dotonline.com> wrote in message
news:slrnejpoug...@kant.overstock.com...

0 new messages