Is it ok to turn off `#IfWin` context sensitivity with `#If`? - windows

Quoting from #IfWinActive / #IfWinNotActive / #IfWinExist / #IfWinNotExist
:
To turn off context-sensitivity, specify any #IfWin directive but omit all of its parameters. For example:
#IfWinActive
Do we have to use #IfWin to close #IfWin? I found that #If seems to do the job as well, but it's not documented.
If you're curious why I'd like to close #IfWin with #If, that's because my code is like the following:
#IfWinActive ahk_exe App.exe
#c::
Esc::WinClose
#If WinExist("ahk_exe App.exe")
#c::WinActivate ahk_exe App.exe
#If
#c::Call("App.exe")
#If
So it makes more sense to use #If to close.

Related

Can't call a Powershell script through the registry properly. A positional parameter cannot be found that accepts argument '$null'

Here is a simple test function called RegistryBoundParams.ps1:
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$Target,
[Parameter(Mandatory = $false)]
[switch]
$MySwitch
)
if(!(Test-IsAdmin)){
Request-AdminRights -NoExit
Exit
}
if($MySwitch){
"Do something" | Out-Host
}else {
"Do something else" | Out-Host
}
Show-AllArguments
If I call it via the PS terminal, everything works as expected:
Exact call: C:\Tools\scripts> .\RegistryBoundParams.ps1 -Target "C:\Test\" -MySwitch
If I call it through the registry (adding the command to a context menu), I get:
pwsh -noexit -file "C:\Tools\scripts\RegistryBoundParams.ps1" -Target "C:\Program Files\Python39\python.exe" -MySwitch
Plaintext of the error: RegistryBoundParams.ps1: A positional parameter cannot be found that accepts argument '$null'.
Here's a reg file that shows exactly what I added in the registry:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry]
#="Test Powershell Script from Registry"
"Icon"="C:\\Tools\\icons\\apps\\Powershell 1.ico,0"
"NeverDefault"=""
[HKEY_CLASSES_ROOT\*\shell\1_TestRegistry\command]
#="pwsh -noexit -file \"C:\\Tools\\scripts\\RegistryBoundParams.ps1\" -Target \"C:\\Program Files\\Python39\\python.exe\""
So somewhere along the lines $Null is being passed to the script, and I have no Idea why.
I could really, really use some help here.
Thanks so much for any guidance.
Edit:
I found that if I add a new string variable called $catchall, the script works. I suspect that when being called from the registry it's appending a null value for some reason. Which is why the script works when I define an additional "catch all" variable.
This is definitely not an ideal solution at all, so I am still looking for a solution here. Really appreciate any help!
Edit2:
It turns out that the Request-AdminRights script I was using that mklement0 authored had a bug that has now been fixed. Anyone who wants one-line self elevation with bound/unbound parameter support that's cross-platform... go get it!
The problem was a (since-fixed) bug in the code that you based your self-elevating function Request-AdminRights on:
The bug was that in the case of an advanced script such as yours, $args - which is never bound in advanced scripts - was mistakenly serialized as $null instead of translated to #(), resulting in that $null getting passed as an extra argument on re-invocation.
If you redefine your Request-AdminRights function based on the now updated body of the Ensure-Elevated function in the original answer, your problem should go away - no need to modify the enclosing script.

Stop AutoKey script if Window is not match and print the ShortCut literal

i use a AutoKey (autokey-gtk 0.95.10) script with the shortcut ü
it get errors with:
winTitle = window.get_active_title()
winClass = window.get_active_class()
if winTitle != "0 A.D." or winClass != 'pyrogenesis.pyrogenesis':
keyboard.send_keys("ü")
but not with shorter Version:
winTitle = window.get_active_title()
if winTitle != "0 A.D.":
keyboard.send_keys("ü")
got Errors and the CPU is really high at 7%:
If i run this AutoKey (autokey-gtk 0.95.10) script again it show no errors but does nothing.
CPU still high and i need to shutdown it (no normal exit).
i don't understand why this happens. BTW as workaround its good enough for me to check only the window name. The better solution should check for class and window name.

How to prevent WinDbg from attaching to specific child processes?

I'm debugging a script in WinDbg with .childdbg 1. (The script runs various test cases of software in infinite loop. This way I catch rare crashes.)
I need not to attach to specific child processes (for performance reasons and because they are third-party and crash often).
If I could specify them by process name, that would solve my problem. If you can propose an other debugger that can do what I need, I will be grateful.
NOTE: Configuring debugger to attach to specific processes via GFlags is not a solution in this specific case.
If you have activated .childdbg 1, you can make use of sxe cpr. With the -c switch, you can execute a command. Something like .if (yourcondition) {.detach} .else {g} could help.
Perhaps the cpr:ProcessName option is really helpful for you. It supports wildcard filters. I've never used it until now, see Controlling Exceptions and Events in WinDbg help.
I used the following .NET program to perform a test:
static void Main()
{
Console.WriteLine("Attach and press Enter");
Console.ReadLine();
Process.Start("Notepad.exe");
Process.Start("calc.exe");
Process.Start("Notepad.exe");
Process.Start("calc.exe");
Console.WriteLine("Started 4 processes");
Console.ReadLine();
}
I started the program under the debugger and did the following:
0:004> .childdbg 1
Processes created by the current process will be debugged
0:004> sxe -c ".detach;g" cpr:calc
0:004> g
...
77da12fb cc int 3
1:009> |
0 id: 1fe0 create name: DebugChildProcesses.exe
. 1 id: f60 child name: notepad.exe
1:009> g
...
77da12fb cc int 3
2:011> |
0 id: 1fe0 create name: DebugChildProcesses.exe
1 id: f60 child name: notepad.exe
. 2 id: 1d68 child name: notepad.exe
2:011> g
As you can see, the debugger is attached to Notepad only.
Unfortunately, you cannot use multiple sxe cpr:process commands. Whenever you use it again, it will overwrite the previous settings.
In that case, you need to use a generic CPR handler and do the rest inside the command. In my tests, !peb did not work well at that time, so I couldn't use it. However, WinDbg has already switched to that process, therefore |. gives us the process name along with some other information. To extract the process name only, .foreach /pS 6 (token {|.}) { .echo ${token}} worked for me.
With this, you can build trickier commands like
.foreach /pS 6 (token {|.}) {
.if (0==$scmp("${token}","notepad.exe")) {.echo "It's Notepad!"}
.elsif (0==$scmp("${token}","calc.exe")) {.echo "Do some math!"}
}
(formatted for readability, remove the line breaks)
When you try to combine this with sxe, you run into some nasty string escaping problems. Replace all quotes inside your command by \" to make it work:
sxe -c"
.foreach /pS 6 (token {|.}) {
.if (0==$scmp(\"${token}\",\"notepad.exe\")) {.echo \"It's Notepad!\"}
.elsif (0==$scmp(\"${token}\",\"calc.exe\")) {.echo \"Do some math!\"}
}
" cpr
(formatted for readability, remove the line breaks)
Now you can do whatever you like, e.g. .detach or .kill, just replace the .echo command in above example. You can execute several commands by separating them via semicolon (;) as usual.
BTW: If you use sxe cpr, you might perhaps want to turn off the initial process breakpoint by sxd ibp.

Is there any method to detect whether STDIN has been redirected within VBscript?

I'm trying to process/filter input within a VBscript, but only if the input has been piped into the script. I don't want the script processing user/keyboard input. I'd like to code this as something like this:
stdin_is_tty = ...
if not stdin_is_tty then
...
input = WScript.StdIn.ReadAll
end if
Otherwise, the script will hang, waiting on user input when it executes WScript.StdIn.ReadAll (or even earlier if I test the stream with WScript.StdIn.AtEndOfStream).
In C#, I'd use:
stdin_is_tty = not System.Console.IsInputRedirected // NET 4.5+
The accepted answer for Q: "How to detect if Console.In (stdin) has been redirected?" shows how to build that result using Win32 calls via P/Invoke, for versions of NET earlier than NET 4.5. But I don't know of any way to translate that method into VBscript.
I've constructed a clumsy, partial solution using SendKeys to send an end-of-stream sequence into the scripts' keyboard buffer. But the solution leaves keys in the buffer if STDIN is redirected, which I can't clean up unless I know that STDIN was redirected... so, same problem.
I'd prefer to keep the script in one packaged piece, so I'd rather avoid a separate wrapping script or anything not available on a generic Windows 7+ installation.
Any brilliant ideas or workarounds?
EDIT: added copy of initial solution
I've added a copy of my improved initial solution here (admittedly, a "hack"), which now cleans up after itself but still has several negatives:
input = ""
stdin_is_tty = False
test_string_length = 5 ' arbitrary N (coder determined to minimize collision with possible inputs)
sendkey_string = ""
test_string = ""
for i = 1 to test_string_size
sendkey_string = sendkey_string & "{TAB}"
test_string = test_string & CHR(9)
next
sendkey_string = sendkey_string & "{ENTER}"
wsh.sendkeys sendkey_string ' send keyboard string signal to self
set stdin = WScript.StdIn
do while not stdin.AtEndOfStream
input = input & stdin.ReadLine
if input = test_string then
stdin_is_tty = True
else
input = input & stdin.ReadAll
end if
exit do
loop
stdin.Close
if not stdin_is_tty then
set stdin = fso.OpenTextFile( "CON:", 1 )
text = stdin.ReadLine
stdin.Close
end if
This solution suffers from the three problems:
leaving a visible trace at the command line (though now, just a single blank line which is low visibility)
possible collision of the test string (a set series of N [coder determined] TABs followed by a NEWLINE) with the first line of any redirected input causing a false positive redirection determination. Since the number of TABs can be modified, this possibility can be made arbitrarily low by the coder.
a race condition that if another window receives focus before the SendKeys portion is executed, the wrong window will receive the code string, leading to a false negative redirection determination. My estimate is that the possibility of this circumstance occurring is very low.
In short, no, but ...
I've tested everything i could think of and have not found a reasonable way to do it.
None of the properties/methods exposed by the TextStream wrappers retrieved with WScript.StdIn or fso.GetStdStream give enough information to determine if the input is redirected/piped.
Trying to obtain information from the behaviour/environment of a spawned process (how to create the executable is other story) is also unlikely to be useful because
WshShell.Execute always spawns the process with its input and output handles redirected
WshShell.Run creates a new process that does not inherit the handles of the current one
Shell.Application.ShellExecute has the same problem as WshShell.Run
So, none of these methods allow the spawned process to inherit the handles of the current process to check if they are redirected or not.
Using WMI to retrieve information from the running process does not return anything usable (well, HandleCount property for the process differs when there is a redirection, but it is not reliable)
So, not being able to determine from vbs code if there is a redirection, the remaining options are
Don't detect it: If the piped input must be present, behave as the more command and in all cases try to retrieve it
Indicate it: If the pipe input is not always required, use an argument to determine if the stdin stream needs to be read.
In my case, I usually use a single slash / as argument (for coherence with some of the findstr arguments that also use a slash to indicate stdin input). Then in the vbs code
If WScript.Arguments.Named.Exists("") Then
' here the stdin read part
End If
Check before: Determine if there is redirection before starting the script. A wrapper .cmd is needed, but with some tricks both files (.cmd and .vbs) can be combined into one
To be saved as .cmd
<?xml : version="1.0" encoding="UTF-8" ?> ^<!------------------------- cmd ----
#echo off
setlocal enableextensions disabledelayedexpansion
timeout 1 >nul 2>nul && set "arg=" || set "arg=/"
endlocal & cscript //nologo "%~f0?.wsf" //job:mainJob %arg% %*
exit /b
---------------------------------------------------------------------- wsf --->
<package>
<job id="mainJob">
<script language="VBScript"><![CDATA[
If WScript.Arguments.Named.Exists("") Then
Do Until WScript.StdIn.AtEndOfStream
WScript.StdOut.WriteLine WScript.StdIn.ReadLine
Loop
Else
WScript.StdOut.WriteLine "Input is not redirected"
End If
]]></script>
</job>
</package>
It is a .wsf file stored inside a .cmd. The batch part determines if the input is redirected (timeout command fails to get a console handle on redirected input) and pass the argument to the script part.
Then, the process can be invoked as
< inputfile.txt scriptwrapper.cmd input redirected
type inputfile.txt | scriptwrapper.cmd input piped
scriptwapper.cmd no redirection
While this is a convenient way to handle it, the invocation of the .wsf part from the .cmd, while being stable and working without problems, relies in an undocumented behaviour of the script host / cmd combination.
Of course you can do the same but with two separate files. Not as clean, but the behaviour is documented.

Can a Win32 console application detect if it has been run from the explorer or not?

I have to create a console application which needs certain parameters. If they are missing or wrong I print out an error message.
Now the problem: If someone starts the program from the explorer by double-clicking the console window disappears immediately. (But the application is not entirely useless from the explorer, you could drag files onto it and it would work)
I could always wait for a keypress, but I don't want that if the user did start it from the command line.
Is there some way to distinguish between these situations?
See http://support.microsoft.com/kb/99115, "INFO: Preventing the Console Window from Disappearing".
The idea is to use GetConsoleScreenBufferInfo to determine that the cursor has not moved from the initial 0,0 position.
Code sample from #tomlogic, based on the referenced Knowledge Base article:
// call in main() before printing to stdout
// returns TRUE if program is in its own console (cursor at 0,0) or
// FALSE if it was launched from an existing console.
// See http://support.microsoft.com/kb/99115
#include <stdio.h>
#include <windows.h>
int separate_console( void)
{
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE), &csbi))
{
printf( "GetConsoleScreenBufferInfo failed: %lu\n", GetLastError());
return FALSE;
}
// if cursor position is (0,0) then we were launched in a separate console
return ((!csbi.dwCursorPosition.X) && (!csbi.dwCursorPosition.Y));
}
GetConsoleTitle()
I've seen code which performs
if (!GetConsoleTitle(NULL, 0) && GetLastError() == ERROR_SUCCESS) {
// Console
} else {
// GUI
}
BUT... I've found that AttachConsole() is more helpful
In C++ (off the top of my head, and I'm no C++ programmer)
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
// GUI
} else {
// Console, and you have a handle to the console that already exists.
}
Is more effective. Additionally, if you find yourself in a GUI environment and would like to stay there as long as you can, but later find something catastrophic has happened that could really use a dump to a console window (you can't be arsed writing an edit box window to lot it to or attach to the NT System log and throw up a MessageBox()) well then you can AllocConsole() later on in the process, when GUI methods have failed.
I've found a much better solution using GetConsoleProcessList to get the attached process count to the current console.
If this process is the only one attached it will be closed when the process exists.
I found it in a post https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922
But it had a bug (at least in windows 10) since the documentation forbids invoking this function with null.
My solution was:
DWORD procId;
DWORD count = GetConsoleProcessList(&procId, 1);
if (count < 2) ...
I believe cmd.exe sets the CMDCMDLINE and CMDEXTVERSION environemntal variables when it starts. So if these are set your program was most probably started from a shell.
This isn't foolproof but it's something.
It's also possible to determine your parent PID in a few convoluted and possibly unreliable ways, or so I gather. You may want to look into that.
Here is the excellent answer from #DanielBenSassoon adapted for C#. Tested in Visual Studio 2019 and Windows 10.
// Gets a list of the process IDs attached to this console
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
public static bool IsFinalProcess()
{
// See: https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922
uint[] procIDs = new uint[64];
uint processCount = GetConsoleProcessList(procIDs, 64);
return (processCount < 2);
}
This approach allows you to distinguish between four scenarios:
Debugging from the IDE (F5) [process count = 1]
Running from the IDE, but not debugging (Ctrl + F5) [process count = 2]
Double-clicking in Explorer [process count = 1]
Running from a command-prompt window [process count = 2]
When IsFinalProcess is true, you can use Console.ReadKey(false); to prevent the console window from disappearing after your application exits.
EDIT: I've been using this .bat/.cmd wrapper successfully for a couple of years now:
#ECHO OFF
REM Determine if script was executed from an interactive shell
(echo %CMDCMDLINE% | find /i "%~0" >NUL 2>&1) && (set IS_INTERACTIVE=false) || (set IS_INTERACTIVE=true)
<call console application here>
REM Wait for keystroke
if "%IS_INTERACTIVE%" == "false" (
echo.
echo Hit any key to close.
pause >NUL 2>&1
)
The advantage here is that this will work for all console applications, be it your own or someone else's (for which you might not have sources you could modify). The downside is, well, that you have a separate wrapper.
My original answer back in 2014 was this:
This works like a charm:
#echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" goto nonconsole
:console
<do something>
goto exit
:nonconsole
<do something>
pause
:exit
Copied from this thread. I also tried evaluating %cmdcmdline% myself, however there's an issue regarding quote characters ("), which prevents something like if "%cmdcmdline%" == "%ComSpec%" goto [target] from working.

Resources