Question Comment intercepter le Control-C ?

Plus d'informations
il y a 15 ans 5 mois #3194 par Laurent Dardenne
Je vous propose un code évolué mais la fonctionnalité est simple, intercepter la saisie de control-C et exécuter du code en fin de session PowerShell.

Première étape :
la création dynamique de code C# à la volée, à partir de différentes sources dispo sur le net :
[code:1]
#Set-ConsoleEndHandler
# code de Jeffrey Snover adapté par Greg Borota proposé sur \"microsoft.public.windows.powershell\"
# De mon coté j'ai modifié la recherche du chemin des assemblies
function Compile-Csharp ([string] $code, [Array]$References) {

# Get an instance of the CSharp code provider
$cp = New-Object Microsoft.CSharp.CSharpCodeProvider
# Récupère le répertoire des assemblies de Powershell
$PathAssemblies=Dir -path \"HKLM:\Software\Microsoft\.NETFramework\AssemblyFolders\"|`
? {$_.Name -Match \"PowerShell 1.0\"}|`
% {(gp -path $_.PsPath -ea SilentlyContinue).\"(default)\"}
$framework = [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()

$refs = New-Object Collections.ArrayList
$refs.AddRange( @(\"${framework}System.dll\",
\"${PathAssemblies}\System.Management.Automation.dll\",
\"${PathAssemblies}\Microsoft.PowerShell.ConsoleHost.dll\",
\"${framework}System.Windows.Forms.dll\",
\"${framework}System.Data.dll\",
\"${framework}System.Drawing.dll\",
\"${framework}System.XML.dll\"«»))
if ($References.Count -ge 1) {
$refs.AddRange($References)
}

# Build up a compiler params object...
$cpar = New-Object System.CodeDom.Compiler.CompilerParameters
$cpar.GenerateInMemory = $true
$cpar.GenerateExecutable = $false
$cpar.IncludeDebugInformation = $false
$cpar.CompilerOptions = \"/target:library\"
$cpar.ReferencedAssemblies.AddRange($refs)
$cr = $cp.CompileAssemblyFromSource($cpar, $code)

if ( $cr.Errors.Count) {
$codeLines = $code.Split(\"`n\"«»);
foreach ($ce in $cr.Errors) {
write-host \"Error: $($codeLines[$($ce.Line - 1)])\"
$ce | out-default
}
Throw \"INVALID DATA: Errors encountered while compiling code\"
}
}


$hookCode = '
using System;
using System.Runtime.InteropServices;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

/// <summary>
/// From www.hanselman.com/blog/MoreTipsFromSaira...soleApplication.aspx
/// Class to catch console control events (ie CTRL-C) in C#.
/// Calls SetConsoleCtrlHandler() in Win32 API
/// </summary>
public class ConsoleCtrl: IDisposable
{
/// <summary>
/// The event that occurred.
/// </summary>
public enum ConsoleEvent
{
CtrlC = 0,CtrlBreak = 1,CtrlClose = 2,CtrlLogoff = 5,CtrlShutdown = 6
}

/// <summary>
/// Handler to be called when a console event occurs.
/// </summary>
public delegate void ControlEventHandler(ConsoleEvent consoleEvent);

/// <summary>
/// Event fired when a console event occurs
/// </summary>
public event ControlEventHandler ControlEvent;

ControlEventHandler eventHandler;

public ConsoleCtrl()
{
// save this to a private var so the GC doesn t collect it...
eventHandler = new ControlEventHandler(Handler);
SetConsoleCtrlHandler(eventHandler, true);
}

~ConsoleCtrl()
{Dispose(false);}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

void Dispose(bool disposing)
{
if (eventHandler != null)
{
SetConsoleCtrlHandler(eventHandler, false);
eventHandler = null;
}
}

private void Handler(ConsoleEvent consoleEvent)
{
if (ControlEvent != null)
ControlEvent(consoleEvent);
}

[DllImport(\"kernel32.dll\"«»)]
static extern bool SetConsoleCtrlHandler(ControlEventHandler e, bool add);
}


public class ConsoleHook
{
//Exemple adapté de celui de Greg Borota proposé sur microsoft.public.windows.powershell
private static string script = null;
private static Runspace runspace = null;

public static void inputHandler(ConsoleCtrl.ConsoleEvent consoleEvent)
{ //Exécuter lorsque control-c est saisie dans la console
if (consoleEvent == ConsoleCtrl.ConsoleEvent.CtrlC)
{ // Code clavier : msdn.microsoft.com/en-us/library/ms683242(VS.85).aspx
try {
Console.WriteLine(\"Stopping due to user input\"«»);
if (runspace != null)
{
if (runspace.RunspaceStateInfo.State != RunspaceState.Closing
&& runspace.RunspaceStateInfo.State != RunspaceState.Closed)
{
runspace.Close();
System.Environment.Exit(-1);
}
}
else
{Console.WriteLine(\"Bug: Runspace is null. Press a key\"«»);
Console.ReadLine();
}
}
catch (Exception exc) {
Console.WriteLine(exc);
}
}
}
private static void HandleEvent(object sender, RunspaceStateEventArgs e) {
//Gestionnaire d evénement du runspace courant
// Est appelé pour les events \"closing\" et \"closed\"
if (e.RunspaceStateInfo.State == RunspaceState.Closing) {
try {
RunspaceInvoke ri = new RunspaceInvoke(runspace.RunspaceConfiguration);
Console.WriteLine(e.RunspaceStateInfo.State);
//On attend la fin du script
//Il n y a plus d affichage
ri.Invoke(script);
}
catch (Exception exc) {
Console.WriteLine(exc);
}
}
}

public static void SetExitHook(string block)
{ //Initialise notre gestionnaire d évenement pour la console
try {
ConsoleCtrl cc = new ConsoleCtrl();
cc.ControlEvent += new ConsoleCtrl.ControlEventHandler(inputHandler);

script = block;
runspace = Runspace.DefaultRunspace;
runspace.StateChanged += new EventHandler<RunspaceStateEventArgs>(HandleEvent);
}
catch (Exception exc) {
Console.WriteLine(exc);
}
}
}'

#On compile le code à la volée
Compile-Csharp $hookCode
Remove-variable hookCode -force
[/code:1]
La seconde étape :
on déclare notre gestionnaire d'évenement pour l'événement Control-C en lui passant en paramètre un scriptblock.
Dans cet exemple on visualise le résultat par l'exécution d'un nouvel explorer
[code:1]
[ConsoleHook]::«»SetExitHook(\"Explorer $PShome\"«»)
[/code:1]
D'autres tests.
[code:1]
# Pas d'affichage dans le pipe dans ce cas. Sauf celui du code C#
#[ConsoleHook]::«»SetExitHook({5..1|% {\"On ferme dans {0} secondes\" -F $_;Sleep 1}})

#Exception .NET dans la console -> la variable $host n'est plus accessible
#[ConsoleHook]::«»SetExitHook({5..1|% { $host.UI.RawUI.WindowTitle=(\"On ferme dans {0} secondes\" -F $_);Sleep 1}})

#on affiche un court message dans la barre de titre
[ConsoleHook]::«»SetExitHook({5..1|% { [System.Console]::Title=(\"On ferme dans {0} secondes\" -F $_);Sleep 1}})

#On enregistre l'historique
[ConsoleHook]::«»SetExitHook({Get-History -count $global:MaximumHistoryCount|Export-Csv \"$env:Temp\PSHistorique.csv\"})

#Toutes les variables ne sont plus accessibles à part quelques unes
gv *|% {$_.name} >\"$env:Temp\varAvant.xml\"
[ConsoleHook]::«»SetExitHook({gv *|% {$_.name} >\"$env:Temp\varAprès.xml\"})

#Outil gratuit comparaison/fusion de fichiers/répertoires,etc : winmerge.org/
#Winmerge \"$env:Temp\varAvant.xml\" \"$env:Temp\varAprès.xml\"

#Affichage dans le debugger
[ConsoleHook]::«»SetExitHook(
{
[int] $level=1
[string] $category=\"[ConsoleHook]\"
[string] $message=\"Event\"
$Sb={\"On ferme dans {0} secondes\" -F $_}
5..1|`
% {
$Msg=&$Sb
[System.Console]::Title=$Msg
[System.Diagnostics.Debugger]::Log($level, $category, $Msg)
Sleep 1}
}
)

#On s'assure en construisant les chaînes par expansion que les chemins seront bien valides
#On laisse la fin de session se terminer le plusrapidement possible en
#déléguant une opération longue à un autre process PowerShell.
# 0 = SW_HIDE, voir msdn.microsoft.com/en-us/library/bb774148(VS.85).aspx
[ConsoleHook]::«»SetExitHook(
{(new-object -com shell.application).ShellExecute(
\"$($PSHome)PowerShell.exe\",
\"-Command &{dir C:\|Export-CSV $env:Temp\DisqueC.csv}\",
\"\",
\"\",
0
)
})
[/code:1]
Pour ce dernier exemple le mieux serait de passer par la création de process sous .NET directement.
Je n'ai pas encore essayé d'intercepter un autre evénement par exemple la fin de session.
Cette fonctionnalité sera peut être intégrée dans la V2...

Bien évidemment mieux vaut éviter de placer ce code en prod :)<br><br>Message édité par: Laurent Dardenne, à: 9/11/08 16:12

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.343 secondes
Propulsé par Kunena