admin管理员组

文章数量:1387396

I’m messing with PowerShell scripting and wanted to know if this was possible. I know when you use try / finally the code in the finally block will execute even when Ctrl+C is typed. However, how can you run code it someone terminates it by closing the window? When I try try / finally the code in the finally block never executes when exiting this way. Is this even possible?

I’m messing with PowerShell scripting and wanted to know if this was possible. I know when you use try / finally the code in the finally block will execute even when Ctrl+C is typed. However, how can you run code it someone terminates it by closing the window? When I try try / finally the code in the finally block never executes when exiting this way. Is this even possible?

Share Improve this question edited Mar 25 at 23:23 Santiago Squarzon 61.8k5 gold badges24 silver badges54 bronze badges asked Mar 18 at 1:03 Ryan CruzRyan Cruz 355 bronze badges 9
  • @SantiagoSquarzon Makes sense. So I’d have to create a UI and hide the cmd window and just handle it when they close the UI window no? – Ryan Cruz Commented Mar 18 at 1:26
  • If you have a powershell script that starts another powershell, you could subscribe to the Process.Exited event of the started script and that might work. – Santiago Squarzon Commented Mar 18 at 1:37
  • As noted in GitHub issue #8000, reacting to a console window getting closed is possible in principle, but implementing this in PowerShell was seemingly never done; see the linked issue for a - lengthy - discussion. – mklement0 Commented Mar 18 at 1:42
  • 2 The PowerShell.Exiting event does work for me from within the PowerShell session that is shutting down, but only in Windows Terminal, not in "plain" powershell.exe or pwsh.exe consoles. What does seem to work though is setting a console handler by p/invoking SetConsoleCtrlHandler. This answer shows a workaround for a possible GC issue, when using SetConsoleCtrlHandler. – zett42 Commented Mar 18 at 12:19
  • 1 @SantiagoSquarzon Compiled handler: gist.github/zett42/4edd4444aea9b8422cc566779d058195 Seems to work in terminal as well as powershell 5.1 and 7.5 consoles. – zett42 Commented Mar 18 at 18:55
 |  Show 4 more comments

2 Answers 2

Reset to default 3

Adding this as a new answer that can complement my previous one and doesn't require an orchestration script.

Thanks to zett42's research, it seems that SetConsoleCtrlHandler function can be used to invoke handler when the PowerShell console is closed, you can see his attempt in this gist using a compiled handler. However, note, this approach does not work if the process is killed, i.e.: via Process.Kill() or Stop-Process.

After some testing it does seem to also work correctly passing a script block as your HandlerRoutine callback function, however it requires a workaround using a new runspace, if you try to invoke the script block itself you'd get an exception stating that there is no available runspace. So, I've decided to add this cmdlet that you can compile ad-hoc with Add-Type to set an exit handler:

using System.Collections.Generic;
using System.Management.Automation;
using System.Runtime.InteropServices;

public enum ConsoleCtrlEvent : uint
{
    // A CTRL+C signal was received, typically from the user pressing Ctrl+C.
    CTRL_C_EVENT = 0,
    // A CTRL+BREAK signal was received, typically from the user pressing Ctrl+Break.
    CTRL_BREAK_EVENT = 1,
    // A signal that the console window is being closed.
    CTRL_CLOSE_EVENT = 2,
    // A signal that the user is logging off the system.
    CTRL_LOGOFF_EVENT = 5,
    // A signal that the system is shutting down.
    CTRL_SHUTDOWN_EVENT = 6
}

[Cmdlet(VerbsCommon.Add, "ConsoleCtrlHandler")]
[OutputType(typeof(bool))]
public class ConsoleHelper : PSCmdlet
{
    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);

    private delegate bool HandlerRoutine(ConsoleCtrlEvent ctrlType);

    private static Dictionary<PowerShell, HandlerRoutine> s_handlers;

    [Parameter(Mandatory = true, Position = 0)]
    public ScriptBlock ScriptBlock { get; set; }

    protected override void EndProcessing()
    {
        if (s_handlers == null)
        {
            s_handlers = new Dictionary<PowerShell, HandlerRoutine>();
        }

        PowerShell powershell = PowerShell
            .Create(RunspaceMode.NewRunspace)
            .AddScript(ScriptBlock.ToString());

        s_handlers[powershell] = new HandlerRoutine(eventType =>
        {
            powershell.AddArgument(eventType).Invoke();
            return false;
        });

        WriteObject(SetConsoleCtrlHandler(s_handlers[powershell], true));
    }
}

The way to use it, if using Add-Type versus pre-compile it:

  1. Store the code in a file or inline it in your PowerShell script itself, e.g.: $code = @'...'@.
  2. Add-Type the code with -PassThru that you can then pass-in to Import-Module -Assembly.
  3. Set the exit handler using the Add-ConsoleCtrlHandler { ... } cmdlet.

In summary, in this example assumes the C# code is stored in a file:

# `-Raw` is important here, don't miss it!
$code = Get-Content -Path path\to\myCmdlet.cs -Raw

# compile it and import it as a module
Add-Type $code -PassThru | Import-Module -Assembly { $_.Assembly }

# set the handler
Add-ConsoleCtrlHandler {
    param([ConsoleCtrlEvent] $ctrlType)

    # here goes the code that should execute before powershell exits
}

# the actual code for your script goes here
# ....

What you're looking for is possible, but does require pinvoke, see this answer for details.

As a workaround without it, you could have one orchestration script that starts your actual script in a new process that you can subscribe to its Exited event.

$script = 'C:\path\to\theScript.ps1'
$proc = Start-Process powershell -ArgumentList '-NoProfile', '-File', $script -PassThru
$null = Register-ObjectEvent -InputObject $proc -EventName Exited -Action {
    # here goes the code that should execute before powershell exits
}
$proc | Wait-Process

Then you should start this orchestration script using -WindowStyle Hidden and -NoProfile, e.g.:

powershell -WindowStyle Hidden -NoProfile -File myOrchestration.ps1

本文标签: powershellIs there a way to run code if someone tries exiting the programStack Overflow