Jelly bean
Jelly bean

Reputation: 90

Sending keys to an open windows in PowerShell

I made a program that opens a certain program, then Ctrl+C it after x amount of time.

I am now using this [System.Windows.Forms.SendKeys]::SendWait("^{c}"). Will this target that certain window or just randomly send it to the current window?

How can I change it to a certain window?

This is my code:

Write-Host "Safe Botting V0.1"
Write-Host "Initializing..."
Start-Sleep -s 3
Write-Host "Program started successfully with no errors."

While($true)
{
    Write-Host "Starting bot..."
    Start-Sleep -s 3
    Start-Process -FilePath E:\Documents\bot.exe
    Write-Host "Bot started successfully"
    $rnd = Get-Random -Minimum 1800 -Maximum 10800
    Write-Host "The bot will run for:"
    Write-Host $rnd
    Start-Sleep -s $rnd
    Write-Host "Bot will now stop!"
    [System.Windows.Forms.SendKeys]::SendWait("^{c}") 
    Write-Host "Bot terminated"
    Write-Host "Starting cooldown time"
    $rnb = Get-Random -Minimum 14400 -Maximum 28800
    Write-Host "The bot will cooldown for"
    Write-host $rnb
    Start-Sleep -s $rnb
    Write-Host "Cooldown Finished, Restarting"
    Start-Sleep -s 5
}

Upvotes: 4

Views: 12321

Answers (2)

GeorgesZ
GeorgesZ

Reputation: 199

Thanks to jimhark, I found a way to make it work without spawning a separate PowerShell process to send the Ctrl-C. It works if the PowerShell process sending the Ctrl-C also spawned, using for example Start-Process, the process it is sending the Ctrl-C to:

$ProcessID = 1234
$MemberDefinition = '
    [DllImport("kernel32.dll")]public static extern bool FreeConsole();
    [DllImport("kernel32.dll")]public static extern bool AttachConsole(uint p);
    [DllImport("kernel32.dll")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);
    public static void SendCtrlC(uint p) {
        FreeConsole();
        AttachConsole(p);
        GenerateConsoleCtrlEvent(0, p);
        FreeConsole();
        AttachConsole(uint.MaxValue);
    }'
Add-Type -Name 'dummyName' -Namespace 'dummyNamespace' -MemberDefinition $MemberDefinition
[dummyNamespace.dummyName]::SendCtrlC($ProcessID) }

What made things work was sending the GenerateConsoleCtrlEvent to the desired process group instead of all processes that share the console of the calling process and AttachConsole back to the console of the parent of the current process.

Upvotes: 5

jimhark
jimhark

Reputation: 5046

You could send the CTRL_C_EVENT signal to the process if you have the process id. In your case you can get that from Start-Process (read the docs if you don't know how to get the process id). It's also possible to get the process id from a Window Handle:

Find process id by window's handle

Sending the signal is non trivial, but thanks to @Nemo1024, @KindDragon, and Stack Overflow it's been worked out:

Can I send a ctrl-C (SIGINT) to an application on Windows?

Unfortunately, using the best approach I could find also terminated the calling PowerShell process and the only workaround I could come up with was to send the signal from a fresh PowerShell instance that I launch.

In PowerShell it looks something like this:

# be sure to set $ProcessID properly. Sending CTRL_C_EVENT signal can disrupt or terminate a process
$ProcessID = 1234
$encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("Add-Type -Names 'w' -Name 'k' -M '[DllImport(""kernel32.dll"")]public static extern bool FreeConsole();[DllImport(""kernel32.dll"")]public static extern bool AttachConsole(uint p);[DllImport(""kernel32.dll"")]public static extern bool SetConsoleCtrlHandler(uint h, bool a);[DllImport(""kernel32.dll"")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);public static void SendCtrlC(uint p){FreeConsole();AttachConsole(p);GenerateConsoleCtrlEvent(0, 0);}';[w.k]::SendCtrlC($ProcessID)"))
start-process powershell.exe -argument "-nologo -noprofile -executionpolicy bypass -EncodedCommand $encodedCommand"

Yes, I know this is VERY ugly.

Upvotes: 12

Related Questions