I want to run a command in PowerShell and use the response in AutoHotkey. I have found a lot of information on how to run a PowerShell script, but none saying how I can use the response from it in AutoHotkey.
I have tried this:
MsgBox % ComObjCreate("WScript.Shell").Exec("powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -noProfile -nologo dir").StdOut.ReadAll()
But this still flashes a window for a very brief time. I loop this command for every 25ms, so letting a window blink that often is not a valid solution.
Edit:
Ended up with this as the simplest solution:
cmd = powershell.exe -command "(Get-Process -Id " %pid% ").Threads[1].WaitReason"
shell := setup()
Loop {
string := shell.exec(cmd).stdout.readall()
...}
setup() {
detecthiddenwindows on
run %comspec% /k ,, hide useerrorlevel, pid
winwait ahk_pid %pid%,, 10
DllCall("AttachConsole", "uint", pid)
con := DllCall("CreateFile"
, "str", "CONOUT$", "uint", 0xC0000000, "uint", 7, "uint", 0, "uint", 3, "uint", 0, "uint", 0)
oshell := comobjcreate("wscript.shell")
return oshell
}
Note: Using AHK (AutoHotkey) with an external PowerShell process is ill-suited to a task that must run every 25ms, as you've discovered yourself - there's too much processing overhead.
If getting a directory listing is all that is needed, you can do that with built-in AHK features, using the Loop command for files - see this answer.
The solution below generally demonstrates how to run a console program:
hidden (no flashing windows)
synchronously (wait for it exit)
with its output captured
from AHK.
You can't use ComObjCreate("WScript.Shell").Exec() to run a console application hidden.
Conversely, while you can use RunWait to run hidden, you cannot use it to capture (console) output.
The workaround is to:
Use RunWait.
Add an output redirection to a (temporary) file to your console-program invocation.
Read that file's content with FileRead afterwards (and delete the temp. file).
; Get a temporary file path
tempFile := A_Temp "\" DllCall("GetCurrentProcessId") ".txt" ; "
; Run the console program hidden, redirecting its output to
; the temp. file (with a program other than powershell.exe or cmd.exe,
; prepend %ComSpec% /c; use 2> to redirect error output), and wait for it to exit.
RunWait, powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -noProfile dir > %tempFile%,, Hide
; Read the temp file into a variable and then delete it.
FileRead, content, %tempFile%
FileDelete, %tempFile%
; Display the result.
MsgBox % content
Related
When I execute the following hello.ps1 in PowerShell, I get an output like I would expect, a Hello World! with a newline.
#powershell script hello.ps1
"Hello World!"
Output of run
PS C:\test>.\hello.ps1
Hello World!
PS C:\test>
But when I call the script from a bat file, I get two (2) newlines output in Windows console.
:: Windows CMD script name: mytest.bat
powershell -ExecutionPolicy Bypass hello.ps1
Now the output has two newlines, note the empty line after Hello World!.
C:\test>mytest.bat
Hello World!
C:\test
How can I avoid this second newline?
Mandragor -
Firstly, that is not two lines, it's just one.
Secondly, this is not a PowerShell specific thing.
You are calling one shell from another, and both must provide you a response and return to interactive mode. Calling an external command for one shell to another must be processed by the called shell first and results returned from the called shell to the calling shell, then exit the called process, and that calling shell must complete its process and any stdout stuff, and exit that process.
Double-clicking a bat/cmd file, starts cmd.exe, task manager shows
that process is running
In your bat/cmd you are calling another standalone exe, task manager
shows that the process is running. It must execute and return stdout
results, then close/exit.
cmd.exe the completes, and returns its stdout stuff, if any, and places the cursor at the next line for more interactive work.
Hence, the two responses, only if you ask for cmd.exe stdout stuff will you see it, vs just null.
You'll always have one more line return to get you back to the calling process.
Also, point of note:
Write-Host except in specific circumstances, and what you are doing
is not one of them.
You only need that bypass policy setting if you are in a restricted
environment. RemoteSigned is now the PowerShell default, meaning all local scripts will run, and remote scripts must be signed.
Simple strings should use single quotes in most cases. Double quotes
are for expanding variable content and other formatting scenarios.
Try the same thing calling any other shell, you'll get the same results. Heck, even calling another bat/cmd from a bat/cmd.
Example:
Running mytest.bat in cmd.exe that only contains these two lines, to show that only 1 CRLF is actually returned, per process, then you get bat the interactive shell.
powershell -File D:\Temp\hello.ps1
call d:\temp\hello.bat
# Results
Hello World!
D:\Temp>call d:\temp\hello.bat
D:\Temp>echo 'Hello World!'
'Hello World!'
D:\Temp>
You can change your powershell script like this:
#powershell script hello.ps1
if ($args[0] -eq "cmd") {
write-host "Hello World!" -nonewline
}
else {
"Hello World!"
}
Then change the caller batch file:
#echo off
powershell -executionpolicy Bypass TheNameOfPowershellScript.ps1 "cmd"
Now when run from cmd use the caller batch file and from powershell directly use the ps1 script. It will give the expected result(Tested).
I compiled it to an executable, but to open it I have to right-click and press "Run as administrator". I want it to request admin privileges each time I run it, but how to do it?
I can't do this:
Because then it doesn't work when I copy it to a second computer.
Try adding this to the auto-execute section (top of the script):
; If the script is not elevated, relaunch as administrator and kill current instance:
full_command_line := DllCall("GetCommandLine", "str")
if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)"))
{
try ; leads to having the script re-launching itself as administrator
{
if A_IsCompiled
Run *RunAs "%A_ScriptFullPath%" /restart
else
Run *RunAs "%A_AhkPath%" /restart "%A_ScriptFullPath%"
}
ExitApp
}
and recompile the script.
For more details read https://autohotkey.com/docs/commands/Run.htm#RunAs.
Here's a much simpler code for this purpose:
#SingleInstance Force
if not A_IsAdmin
Run *RunAs "%A_ScriptFullPath%"
It will run the script as Admin if it's not already running as Admin.
If you don't have #SingleInstance Force on top of your script, it will ask that if you want to replace the running script (not admin) with admin. So to prevent that, add the mentioned line on top of your script.
If you might compile your script in the future, it's better to use this one instead to make it future-proof:
#SingleInstance Force
if !A_IsAdmin
Run, % "*RunAs " (A_IsCompiled ? "" : A_AhkPath " ") Chr(34) A_ScriptFullPath Chr(34)
Chr(34) returns character "
Source: https://www.autohotkey.com/boards/viewtopic.php?t=39647
I have the following script:
^!c::
Run stop
Return
Stop is configured to run a program via environment variables.
So if I open up cmd and type “stop” and hit enter the program opens as intended, even if I push winkey + R it does the same thing. However if I use the script with ctrl+alt+c. I do not get the same result.
Why is the script doing something different?
How can I change my script to behave the same way as if it was typed into cmd or winkey + R?
Simple:
run, %comspec% /c stop
Or if this doesn't work you could just start a cmd window and send it directly
run, %comspec% /k
WinWait, %comspec%
WinActivate
Send stop{Enter}
/c tells the console window to close after execution, /k lets it stay open
or you could use an COM object and even get the output.
objShell := ComObjCreate("WScript.Shell")
objExec := objShell.Exec(ComSpec " /c stop")
strStdOut := ""
while, !objExec.StdOut.AtEndOfStream
{
strStdOut := objExec.StdOut.ReadAll()
}
Update:
Without the run command at all:
SetTitleMatchMode, 2
send #r
WinWait, TITLE_OF_THE_RUN_WINDOW
WinActivate
send cmd{Enter}
WinWait, cmd.exe
WinActivate
WinGetTitle, title
Send stop{Enter}
WinWait, %title%,,, stop
WinClose,
TITLE_OF_THE_RUN_WINDOW replace this with the title of the window, which opens on Win+r. A windows cmd window has the command in its title while it gets executed. So we save the title of the command window, and wait for it to drop the command ("stop") and close it then.
UPDATE: Cmd window close added to solution 4
I have a batch file that allows me to go to particular folder based on my input.
d:
cd d:\test\bits
#ECHO off
cls
:start
ECHO.
ECHO 1. Perl
ECHO 2. Python
set choice=
set /p choice=type in number to go to appropriate code folder:
if not '%choice%'=='' set choice=%choice:~0,1%
if '%choice%'=='1' goto pl
if '%choice%'=='2' goto py
ECHO "%choice%" is not valid, try again
ECHO.
goto start
:pl
cd code\pl
goto end
:py
cd code\py
goto end
:end
start "bits"
At the end of execution, a command prompt window with the title "bits" opens up and is in the specified directory corresponding to the input choice. This is all good. But I want to have the same thing done with Powershell.
If, instead of start "bits", I put, start powershell, in the last line, I can get Powershell console to open. By doing this, I have two issues.
Powershell console is still in d:\test\bits folder and not in the one I intended it to go.
I cannot get the title to be bits
How do I get the functionality I want with Powershell?
From what I expected and what I was able to reproduce with your script, the current directory is set to the intended one (d:\test\bits\code\pl if I enter 1)
For the title part, you can do the following:
start powershell -NoExit -command "$Host.UI.RawUI.WindowTitle = 'bits'"
If you add this to your powershell profile.ps1 you can get the window title to show the current running script and if you are just opening a window with no script then 'pwsh' will be displayed.
Will be systematic with no need to add a line on top of each script. The other answers
combined with $MyInvocation.MyCommand seem to give the name of the profile.ps1 instead when running a script from the context menu.
This can also be tweaked to change the result.
[console]::title = Split-Path -Leaf ([Environment]::GetCommandLineArgs()[-1]).Replace('pwsh.dll','pwsh')
Works on both PS 5 and 7 . For ver. 5 replace pwsh.dll by powershell.exe
How do I force the vbs script to run in the cscript host as opposed to the WScript host?
How do I go about reliably determining if vbs is running from the Command Prompt in XP/Vista/7?
Also, if its not running from the command prompt, how would I get the script to launch itself into command prompt?
I'm looking for a short snippet.
There is no property or anything like that you can set, so you are left with ugly hacks like this:
Function ForceCScript()
On Error Resume Next
WScript.StdErr.Write(Chr(7))
If Err.Number <> 0 Then
Err.Clear
On Error GoTo 0
set WshSh=WScript.CreateObject("WScript.Shell")
sh=WshSh.ExpandEnvironmentStrings("%COMSPEC%")
If InStr(sh,"%") = 1 Then sh="cmd.exe"
WshSh.Run(sh&" /K cscript /nologo """&WScript.ScriptFullName&"""")
WScript.Quit()
End If
End Function
call ForceCScript()