Programmatically send key strokes to a window program in Groovy or bat script - batch-file

Backstory: I need to programmatically find the differences between two files. I want to use WinMerge to generate a report (Tools -> Generate Report) that I can parse to get the differences between two files. I need this done using either a Groovy script or a bat script.
I was hoping that WinMerge would offer command line options to generate the report and then I could just use a Groovy Process object to execute WinMergeU.exe with the arguments. No such luck according to the command options I've found for WinMerge.
Next, I was hoping to be able to start WinMerge and send keystrokes to step through the menus to generate the report(Alt+T, R, Diff.html, [Enter]). I don't see a way to do that from a Groovy Process and I haven't found a way to do this in a bat script. I'm looking for something similar to WshShell.Sendkeys in VB. Is this a wild-goose chase?
UPDATE/Answer with PowerShell in a bat file:
I was intrigued by Knuckle-Dragger's comment about using a PowerShell script in a bat file.
$folder = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $folder
Start-Sleep -m 1000
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
[Microsoft.VisualBasic.Interaction]::AppActivate("WinMerge")
Start-Sleep -m 100
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
Start-Sleep -m 100
[System.Windows.Forms.SendKeys]::SendWait("%F")
[System.Windows.Forms.SendKeys]::SendWait("o")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
[System.Windows.Forms.SendKeys]::SendWait("%T")
[System.Windows.Forms.SendKeys]::SendWait("r")
Start-Sleep -m 1000
[Microsoft.VisualBasic.Interaction]::AppActivate("Save As")
Start-Sleep -m 1000
[System.Windows.Forms.SendKeys]::SendWait("Diff.txt")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
To encapsulate this in a command window, save it to a file PowerShellScript.ps1:
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta *PowerShellScript.ps1*

Here is a powershell example to activate winmerge and send some keys.
EDIT: Reduced copy pasta with some .NET variables. $SK = Sendkeys $AA = AppActivate $LRA = Reflect .NET.
$startapp = "C:\DevTools\WinMerge\WinMergeU.exe"
ii $startapp
$SK = "[System.Windows.Forms.SendKeys]::SendWait"
$AA = "[Microsoft.VisualBasic.Interaction]::AppActivate"
$LRA = "[void][System.Reflection.Assembly]::LoadWithPartialName"
Start-Sleep 1
$LRA+'("Microsoft.VisualBasic")'
$AA+'("WinMerge")'
Start-Sleep -m 100
$LRA+'("System.Windows.Forms")'
Start-Sleep -m 100
$SK+'("%F")'
$SK+'("o")'
$SK+'("{ENTER}")'
$SK+'("%T")'
$SK+'("r")'
Start-Sleep 1
$AA+'("Save As")'
Start-Sleep 1
$SK+'("Diff.txt")'
$SK+'("{ENTER}")'
To encapsulate this in a command window, save it to a file PowerShellScript.ps1: Note, changed the command syntax a bit, should work if you use the & {.\dot\source\path}
start /b /wait powershell.exe -nologo -WindowStyle Hidden -sta -Command "& {.\PowerShellScript.ps1}"

This is something that I threw together without testing, and I don't have WinMerge so testing isn't really an option.
RunMe.bat
start "" "C:\Path\WinMerge.exe" "file1" "file2"
GenerateDiffFile.vbs
GenerateDiffFile.vbs
Set s = CreateObject("WScript.Shell")
wscript.sleep(1000) ' Sleep for 1 second to allow time for WinMerge to finish loading and merge the files
s.SendKeys("%tr")
wscript.sleep(250) ' Give time for dialog box to appear
s.SendKeys("Diff.html{Enter}")
This is completely untested, but I think it is very close to what you need... You can see that the batch-file runs WinMerge, passing the two files to merge on the command line. Then it launches the VBS script, which pauses long enough to allow the launched WinMerge to be able to accept keyboard input. I used this page from Microsoft to know what keys to send.
I've been sending keys to programs for years. It works very reliably, as long as the program you want to send keys to keeps focus.

All I do is this:
echo y | "Batchfile needing the keystroke"

Related

Can't run a Powershell command in a SQL Server Agent Job Step

I tried with "Powershell" option and commands (both don't work):
Get-Item "*.csv" | Out-File "d:\tracking\$(get-date -f yyyyMMdd-hhmmss).txt"
"Get-Item """*.csv""" | Out-File """d:\tracking\$(get-date -f yyyyMMdd-hhmmss).txt""""
And tried with "CmdExec" option and command (it doesn't work):
powershell.exe -Command "Get-Item """*.csv""" | Out-File """d:\tracking\$(get-date -f yyyyMMdd-hhmmss).txt""""
But the last command runs ok on a separate cmd.exe window
and the following also runs ok inside the powershell:
Get-Item "*.csv" | Out-File "d:\tracking\$(get-date -f yyyyMMdd-hhmmss).txt"
They create a text file eg. "20220607-112233.txt" containing the directory listing of CSV files
However I can't get this command to work from within the Job Step
The step finishes with "Unable to run. Syntax error"
Ok, I found a solution:
I created a batch (.bat file) with the code inside block 2:
powershell.exe -Command "Get-Item """*.csv""" | Out-File """d:\tracking\$(get-date -f yyyyMMdd-HHmmss).txt""""
(I only changed the hh for HH, for 24 hours based time)
Then, I configured the Job Step to run the bat file (setting the option "cmdExec")
That's all
I don't know what was going on, directory permissions were ok, the command of code block #2 above even works if I execute it with xp_cmdshell

Bat file for Windows 10 Custom Start Menu

I have a .bat file, (which makes a custom start menu from a custom .xml file), to deploy to a group of machines.
Here is what I got:
PowerShell.exe -Command "&Import-StartLayout –LayoutPath C:\Installs\StartMenu.xml –MountPath $env:SystemDrive\"
When I run the PowerShell command itself it works but for some reason I cannot get it to work from the .bat file.
Here are my comments, put together as an answer:
PowerShell -C "&{Import-StartLayout -LayoutPath C:\Installs\StartMenu.xml -MountPath %SystemDrive%\}"
I had to change my code to:
PowerShell.exe -Command "&{Import-StartLayout -LayoutPath C:\Installs\StartMenu.xml -MountPath $env:%SystemDrive%\}"
The suggestions from Compo seemed to work.

Batch icon message on task bar

Is it possible to make a Batch, or .vbs if needed, is it possible to make batch have a little icon down there with the flag, battery and volume.
I want it to be a "Shutting down in xx minutes/hours", and mayyybbe clicking it cancels' it.
Thanks in advance :)
Well, I know how to accomplish part of what you want -- making a systray balloon tip by borrowing from PowerShell. But I don't know how to make it listen for dismissal of the balloon. Maybe someone else can offer another answer building upon mine?
Anyway, I use this for a conversion script I made to convert flac to mp3 in batches. Feel free to hack it for your own evil purposes.
#echo off
setlocal
for %%I in (*.flac) do (
rem // This initiates the systray balloon.
call :systray converting from "%%~nxI" to "%%~nI.mp3"
)
goto :EOF
rem // Here's the :systray function
:systray <message>
setlocal enabledelayedexpansion
set "args=%*"
set "args=!args:'=''!"
set "code="[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');^
$o=New-Object Windows.Forms.NotifyIcon;$o.Icon='%systemroot%\System32\PerfCenterCpl.ico';^
$o.BalloonTipIcon='Info';$o.BalloonTipText='!args!';$o.BalloonTipTitle='%~nx0';$o.Visible=1;^
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit""
start /b "" "powershell" %code%
endlocal & goto :EOF
The n values in $o.ShowBalloonTip(n1) and Start-Sleep -Mn2 are in milliseconds. Salt to taste.
Update: I found a bit about registering an event for $o.BalloonTipClicked as well as a lovely example in the wild. Basically, replace this:
$o.ShowBalloonTip(10000);Start-Sleep -M 12000;$o.Dispose();Exit
... with this:
$o.ShowBalloonTip(10000);register-objectevent $o BalloonTipClicked clicked;^
if (wait-event clicked -t 12) {$true} else {$false}; $o.Dispose(); Exit
You also need to execute powershell in a single threaded apartment for the event to work.
start /b "" "powershell" -STA %code%
Now, you need to figure out how to make that relevant back in the context of your batch process. For one thing, you'd probably no longer be able to use start /b to make the balloon tip non-blocking, and you'd probably use a for /F loop to capture the output of the powershell command.
Adding to your worries, I propose that "Shutting down in xx minutes" is not entirely user-friendly. What if "Shutting down in 30 minutes" appeared 29 minutes ago, but the user just now saw it? "Shutting down at 9:51 AM" might be better.
So with all this in mind, since what you want is event driven and since the batch language doesn't handle date-time math all that easily, I suggest doing the whole damn thing in PowerShell. Save this with a .ps1 extension. Right-click and run with PowerShell. Or if you want to execute it from a cmd console, do powershell ".\scriptname.ps1".
set-executionpolicy remotesigned
if ([threading.thread]::CurrentThread.GetApartmentState() -eq "MTA") {
& powershell.exe -window minimized -sta $MyInvocation.MyCommand.Definition
exit
}
[void] [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$minutes = 30
$launch_time = (Get-Date).AddMinutes($minutes).ToShortTimeString()
$o = New-Object Windows.Forms.NotifyIcon
$o.Icon = "$env:SystemRoot\System32\PerfCenterCpl.ico"
$o.BalloonTipIcon = "Info"
$o.BalloonTipText = "Shutting down at $launch_time"
$o.BalloonTipTitle = "Shutdown pending..."
$o.Visible = 1
function show-balloon { $o.ShowBalloonTip($minutes * 60 * 1000) }
show-balloon
$o_hover = [Windows.Forms.MouseEventHandler]{ show-balloon }
$o.add_MouseMove($o_hover)
register-objectevent $o BalloonTipClicked clicked
if (wait-event clicked -t ($minutes * 60)) {
remove-event clicked
$o.BalloonTipText = "Have a nice day!"
$o.BalloonTipTitle = "Shutdown aborted"
$o.ShowBalloonTip(10000)
if (wait-event clicked -t 10) { remove-event clicked }
} else {
# Initiate shutdown sequence on my mark. Authorization rojo alpha 3. Mark.
stop-computer
}
unregister-event clicked
$o.Dispose()
Bill_Stewart, if you're reading this, I know you're pleased. As it happens, PowerShell is indeed the correct tool for the job this time.

Error passing multiple commands to Cisco CLI via plink

I've gotten some help with an earlier part of this batch file, but now I'm having trouble with the final component.
I've tried a few things with no success. I tried changing the CRLF to LF which did nothing. I also tried rephrasing the commands a few ways but I am still not getting anywhere. The following is my main batch file.
#echo on
REM delete deauth command file
SET OutFile="C:\temp\Out2.txt"
IF EXIST "%OutFile%" DEL "%OutFile%"
plink -v -ssh *#x.x.x.x -pw PW -m "c:\temp\WirelessDump.txt" > "C:\temp\output.txt"
setlocal
for /f %%a in (C:\temp\output.txt) do >> "Out2.txt" echo wir cli mac-address %%a deauth forced
REM Use commands in out2 to deauth
plink -v -ssh *#x.x.x.x -pw PW -m "c:\temp\Out2.txt"
pause
Below this sentence is the command found in Out2 which I think is giving the actual trouble. The number of lines varies but they are all this particular command just with differing MACs.
wir cli mac-address xxxx.xxxx.xxxx deauth forced
If Out2 has only a single line it runs fine, no issues. But when there are multiple lines, it fails with an error stating that the Line has an invalid autocommand. It's almost as if it was reading it as one contiguous command. As I mentioned above I changed from CRLF to LF hoping IOS would like it better, but that failed. I've tried adding extra lines between the commands, and I've tried calling the login every time from that file.
I am hoping that there is a way to tailor the commands to pass all lines one at a time to keep this down to a minimum of files.
I had another thought but it is kinda/very clunky. If there was a way to output each of those MAC deauth commands to their own file in a saperate folder (out1, out2, out3), and have the BAT able to run all the randomly generated files in that folder so that each one is a separated plink session.
Let me know if I need to change/add/elaborate on anything. Thanks in advance for anything you guys are willing to help with. I appreciate it.
EDIT: Martin has pointed out what the limitation actually is. It appears to be a limitation on Cisco to accept blocks of commands through SSH. So I still have the same question really, I just need some help figuring a workaround to this issue. I'm thinking the multiple file solution I mentioned above may have some possibility. But I'm too much of a noob to know how to make that work. I'll update if I have any breakthroughs though. Thanks for any contributions!
It's actually a known limitation of Cisco, that it does not support multiple commands in an SSH "exec" channel command.
Quoting section 3.8.3.6 -m: read a remote command or script from a file of PuTTY/Plink manual:
With some servers (particularly Unix systems), you can even put multiple lines in this file and execute more than one command in sequence, or a whole shell script; but this is arguably an abuse, and cannot be expected to work on all servers. In particular, it is known not to work with certain ‘embedded’ servers, such as Cisco routers.
Though you can probably still feed multiple commands to Plink input:
(
echo command 1
echo command 2
echo command 3
echo exit
) | plink -v -ssh user#host -pw password > output.txt
Or you can simply use an input file:
plink -v -ssh user#host -pw password < input.txt > output.txt
Similar question: A way of typing multiple commands in cmd.txt file using PuTTY batch against Cisco
This works without cmd.exe and using files:
function Invoke-PlinkCommandsIOS {
param (
[Parameter(Mandatory=$true)][string] $Host,
[Parameter(Mandatory=$true)][System.Management.Automation.PSCredential] $Credential,
[Parameter(Mandatory=$true)][string] $Commands,
[Switch] $ConnectOnceToAcceptHostKey = $false
)
$PlinkPath="$PSScriptRoot\plink.exe"
$commands | & "$PSScriptRoot\plink.exe" -ssh -2 -l $Credential.GetNetworkCredential().username -pw "$($Credential.GetNetworkCredential().password)" $Host -batch
}
Usage: dont forget your exit's and terminal length 0 or it will hang
PS C:\> $Command = "terminal lenght 0
>> show running-config
>> exit
>> "
>>
PS C:\> Invoke-PlinkCommandsIOS -Host ace-dc1 -Credential $cred -Commands $Command
....
Sounds like your file 'Out2.txt' has only LF at end of line. Simple way to convert that to CRLF is to use MORE command and redirect output to a new file and then use the new file.
more Out2.txt > Out2CRLF.txt
I ran into the same issue when trying to pull the full list of ACLs on an ASA via plink in powershell.
Essentially, due to the abuse issue referenced in the documentation: https://the.earth.li/~sgtatham/putty/0.72/htmldoc/Chapter3.html#using-cmdline-m, I was getting inconsistent results in pulling the ACLs. Sometimes I would get 0, sometimes only 1 or 2, and sometimes I would get all of them. (I personally, had about a 1 in 5 success rate).
As I would occasionally be successful I used a while loop that would catch the unsuccessful attempts and retry. Just be sure to put some timing on the while loop to prevent it from spamming ssh connections too much.
It is not a good solution, but it worked as a last resort.

Running Powershell command in a command line/batch file

I am creating a batch file which involves converting a SID to local/domain username. Since we can not achieve this using the command prompt I am planning to use powershell and I have the PS command as well. I am able to run it in powershell console without any issue, but not sure how to use it in command prompt as a SINGLE LINE(to use it in batch file). I have already tried the below.
Powershell command which works perfectly in PS console -
([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value
Command lines which I have already tried but with no success -
powershell -command ([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value
powershell -command {([System.Security.Principal.SecurityIdentifier]("S-1-5-32-544")).Translate([System.Security.Principal.NTAccount]).Value}
What am I doing wrong? Is it due to any escape characters or am I missing any powershell command parameters? Any help is greatly appreciated.
powershell -command "([System.Security.Principal.SecurityIdentifier]('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value"
worked for me. Just a question of changing the innermost double-quotes to singles around the SID.
Alternatively, escape them with a backslash
powershell -command "([System.Security.Principal.SecurityIdentifier](\"S-1-5-32-544\")).Translate([System.Security.Principal.NTAccount]).Value"
Here is a short VBScript script that uses WMI to do the conversion for you:
Dim SID
SID = WScript.Arguments.Item(0)
Dim SWbemServices, SWbemObject
Set SWbemServices = GetObject("winmgmts:root/CIMV2")
Set SWbemObject = SWbemServices.Get("Win32_SID.SID='" & SID & "'")
WScript.Echo SWbemObject.ReferencedDomainName & "\" & SWbemObject.AccountName
You would of course need to capture this script's output from your shell script (batch file).
You're actually really close. What you want to do is this:
powershell -command "& {([System.Security.Principal.SecurityIdentifier]('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value}"
I tested this using the Get-WmiObject cmdlet. In a standard, but elevated cmd shell I entered:
powershell -command "& {get-wmiobject win32_operatingsystem}"
At that point the wmi object data as returned by powershell was written to the console. You can also load the command into a variable like so:
set wmi=powershell -command "& {get-wmiobject win32_operatingsystem}"
If you call the variable %wmi% there's a delay before it prints. The command itself is in the variable so everytime you call the variable it'll execute the powershell code and return the result.
An alternative answer is to use a base64 encodedcommand switch.
#ECHO OFF
powershell -encodedcommand "KABbAFMAeQBzAHQAZQBtAC4AUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBTAGUAYwB1AHIAaQB0AHkASQBkAGUAbgB0AGkAZgBpAGUAcgBdACgAIgBTAC0AMQAtADUALQAzADIALQA1ADQANAAiACkAKQAuAFQAcgBhAG4AcwBsAGEAdABlACgAWwBTAHkAcwB0AGUAbQAuAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4ATgBUAEEAYwBjAG8AdQBuAHQAXQApAC4AVgBhAGwAdQBlAA=="
PAUSE
When decoded, you'll see it's the OP's original snippet (with the double quotes preserved). Maybe overkill for the OP, but useful for dev's with larger scripts. Plus my original answer was identical to someone elses, so I had to edit.
powershell.exe -EncodedCommand
Accepts a base-64-encoded string version of a command. Use this parameter
to submit commands to Windows PowerShell that require complex quotation
marks or curly braces.

Resources