Reading StdErr and StdOut with Autohotkey - winapi

I am trying to write a function that executes console commands in the background and listens StdOut and StdErr. In case of an error, it throws StdErr and StdOut. I am trying to do it in Autohotkey. First I tried using WScript.Shell COM Object, but StdErr and StdOut always empty, although StdOut definitely should be non-empty. There isn't much information on WScript.Shell COM Object and all Microsoft docs are in tech archive.
Util_Command(command) {
dhw := A_DetectHiddenWindows
DetectHiddenWindows On
Run "%ComSpec%" /k,, Hide, pid
while !(hConsole := WinExist("ahk_pid" pid))
Sleep 10
DllCall("AttachConsole", "UInt", pid)
DetectHiddenWindows %dhw%
objShell := ComObjCreate("WScript.Shell")
objExec := objShell.Exec(command)
While (!objExec.Status) {
Sleep 100
}
err := objExec.ExitCode
StdErr := objExec.StdErr.ReadAll()
StdOut := objExec.StdOut.ReadAll()
DllCall("FreeConsole")
Process Exist, %pid%
if (ErrorLevel == pid) {
Process Close, %pid%
}
if (err) {
if (!StdErr) {
throw StdOut
}
throw StdErr
}
}
try {
Util_Command("""C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe"" -open ""non/existent/dir"" -save ""C:\Users\486B~1\AppData\Local\Temp\~temp5812406.res"" -action compile")
} catch e {
MsgBox % e
}
Then I tried doing it with GetStdHandle. In SciTe4Autohotkeys (or in any console) handle gets created successfully, but when trying to write to StdInn or read StdOut/StdErr I get ERROR_ACCESS_DENIED (5). However, writing to StdOut/StdErr or reading StdInn works fine but useless to me. Then I tried launching it without a console and I can't create a handle with error ERROR_SEM_NOT_FOUND (187). At the beginning of the script, it launches cmd in hidden mode ant attaches to it. How can I get handles of the attached console with proper read/write access?
Util_Command(lpCommand) {
dhw := A_DetectHiddenWindows
DetectHiddenWindows On
Run "%ComSpec%" /k,, Hide, pid
while !(hConsole := WinExist("ahk_pid" pid))
Sleep 10
DllCall("AttachConsole", "UInt", pid)
DetectHiddenWindows %dhw%
hStdIn := DllCall("GetStdHandle", "Int", -10)
hStdOut := DllCall("GetStdHandle", "Int", -11)
hStdErr := DllCall("GetStdHandle", "Int", -12)
StrPut(lpCommand, "UTF-8")
VarSetCapacity(lpCommand, 1024)
VarSetCapacity(lpStdErrBuffer, 2048)
a := DllCall("WriteFile"
, "Ptr", hStdIn
, "Ptr", &lpCommand
, "UInt", 1024
, "UInt", 0
, "UInt", 0)
a := DllCall("ReadFile"
, "Ptr", hStdOut
, "Ptr", &lpStdOutBuffer
, "UInt", 2048
, "UInt", 0
, "UInt", 0)
a := DllCall("ReadFile"
, "Ptr", hStdErr
, "Ptr", &lpStdErrBuffer
, "UInt", 2048
, "UInt", 0
, "UInt", 0)
FileAppend, stdErr - %lpStdErrBuffer%`nstdOut - %lpStdOutBuffer%`n, *
DllCall("FreeConsole")
Process Exist, %pid%
if (ErrorLevel == pid) {
Process Close, %pid%
}
if (err) {
if (!StdErr) {
throw StdOut
}
throw StdErr
}
}
try {
Util_Command("""C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe"" -open ""non/existent/dir"" -save ""C:\Users\486B~1\AppData\Local\Temp\~temp5812406.res"" -action compile\n")
} catch e {
MsgBox % e
}

It can be done, but I'm not sure what you are trying to accomplish.
You use the WScript.Shell then pass the command in the .Exec(ComSpec " /C cscript " command) method and return the exec.StdOut.ReadAll()
I have a working example as follows, maybe it helps you:
;// The following runs a command (add.vbs)
;// and retrieves its output via StdOut:
InputBox, x,,"Enter two numbers"
MsgBox % """" RunWaitOne("add.vbs " x) """" ;// result in quotes
ExitApp
RunWaitOne(command)
{
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C cscript /nologo " command)
return exec.StdOut.ReadAll()
}
/* This is the external "add.vbs" command:
a=3
b=4
if WScript.Arguments.Count > 0 Then
a=cint(WScript.Arguments.Item(0))
b=cint(WScript.Arguments.Item(1))
End If
Dim StdOut : Set StdOut = CreateObject("Scripting.FileSystemObject").GetStandardStream(1)
x=a+b
StdOut.Write x
*/
Hth,

Related

Why `#f::true ? MsgBox true : Run notepad` does nothing?

I expect #f::true ? MsgBox true : Run notepad will open a message box with "true" when pressing #f, but it does nothing in practice, can anyone explain the mechanism behind this behavior?
Commands only work at the start of a line, you can't use them inline.
What you're doing, is concatenating two variables together. A variable named MsgBox and a variable named true (or Run and notepad).
You can only use expressions inline.
In AHK v1, you could, of course wrap the legacy commands in a function and then use that function inline:
#f::true ? MsgBoxFunc(true) : RunFunc("notepad")
MsgBoxFunc(Options := "", Title := "", Text := "", Timeout := "")
{
if (Options && (!Title && !Text && !Timeout))
MsgBox, % Options
else
MsgBox, % Options, % Title, % Text, % Timeout
}
RunFunc(Target, WorkingDir := "", Options := "", ByRef OutputVarPID := "")
{
Run, % Target, % WorkingDir, % Options, pid
OutputVarPID := pid
}
Or in AHK v2, this problem of course doesn't exist:
#f::true ? MsgBox(true) : Run("notepad")

How to get starting time of a File Explorer window?

I'm building Autohotkey script in order to take a backup of opened File Explorer windows list.
I'm looking for a way in order to get starting time of each window (time when I've opened the window).
I've this function:
list_opened_folders(byref file_explorer_windows) {
; file_explorer_windows := [] ; array of file_explorer_windows
for window in ComObjCreate("Shell.Application").Windows {
file_explorer_windows[a_index] := {}
file_explorer_windows[a_index].path := window.Document.Folder.Self.Path
file_explorer_windows[a_index].id := window.HWND
file_explorer_windows[a_index].started_time := window.Document.Folder.Self.Time ; Line I'm trying to add (I know this is invalid but to illustrate my idea)
}
}
Try:
#Persistent
DetectHiddenWindows, On
SetTitleMatchMode, 2
file_explorer_windows := {}
SetTimer, GetExplorer, 100
GetExplorer:
Process, Exist, Explorer.exe
If ErrorLevel
{
for window in ComObjCreate("Shell.Application").Windows {
if (file_explorer_windows[window.HWND]["id"] == "") {
file_explorer_windows[window.HWND] := {id: window.HWND
, path: window.Document.Folder.Self.Path
, start: A_NowUTC}
}
}
}
Return

Create new folder and highlight it for renaming

I was trying to create an AutoIt script to create a new folder and then highlight it for renaming like if we right click and create a new folder.
Here is my script. What I wanted to achieve is create the new folder, highlight it, then press F2.
#include <WinAPI.au3>
HotKeySet("#y", "NewFolder")
While 1
Sleep(200)
WEnd
Func NewFolder()
Local $hWnd = WinGetHandle("[ACTIVE]")
Local $class = _WinAPI_GetClassName($hWnd)
If StringCompare($class, "CabinetWClass") = 0 Then
Local $sSelected_Path = _GetWindowsExplorerPath($hWnd) & "\NewFolder" & $count
Local $iFileExists = FileExists($sSelected_Path)
If $iFileExists Then
$count += 1
Else
Local $success = DirCreate($sSelected_Path)
Sleep(500)
If $success = 1 Then
Local $Finditem = ControlListView($hWnd, "", "[CLASS:SysListView32; INSTANCE:1]", "Finditem", "NewFolder")
MsgBox(0, "", $Finditem)
Local $Select = ControlListView($hWnd, "", "[CLASS:SysListView32; INSTANCE:1]", "Select", $Finditem)
$count += 1
EndIf
EndIf
EndIf
EndFunc
Func _GetWindowsExplorerPath($hWnd)
Local $pv, $pidl, $return = "", $ret, $hMem, $pid, $folderPath = DllStructCreate("char[260]"), $className
Local $bPIDL = False
Local Const $CWM_GETPATH = 0x400 + 12;
; Check the classname of the window first
$className = DllCall("user32.dll", "int", "GetClassName", "hwnd", $hWnd, "str", "", "int", 4096)
If #error Then Return SetError(2, 0, "")
If ($className[2] <> "ExploreWClass" And $className[2] <> "CabinetWClass") Then Return SetError(1, 0, "")
; Retrieve the process ID for our process
$pid = DllCall("kernel32.dll", "int", "GetCurrentProcessId")
If #error Then Return SetError(2, 0, "")
; Send the CWM_GETPATH message to the window
$hMem = DllCall("user32.dll", "lparam", "SendMessage", "hwnd", $hWnd, "int", $CWM_GETPATH, "wparam", $pid[0], "lparam", 0)
If #error Then Return SetError(2, 0, "")
If $hMem[0] = 0 Then Return SetError(1, 0, "")
; Lock the shared memory
$pv = DllCall("shell32.dll", "ptr", "SHLockShared", "uint", $hMem[0], "uint", $pid[0])
If #error Then Return SetError(2, 0, "")
If $pv[0] Then
$pidl = DllCall("shell32.dll", "ptr", "ILClone", "uint", $pv[0]) ; Clone the PIDL
If #error Then Return SetError(2, 0, "")
$bPIDL = True
DllCall("shell32.dll", "int", "SHUnlockShared", "uint", $pv) ; Unlock the shared memory
EndIf
DllCall("shell32.dll", "int", "SHFreeShared", "uint", $hMem, "uint", $pid) ; Free the shared memory
If $bPIDL Then
; Retrieve the path from the PIDL
$ret = DllCall("shell32.dll", "int", "SHGetPathFromIDList", "ptr", $pidl[0], "ptr", DllStructGetPtr($folderPath))
If (#error = 0) And ($ret[0] <> 0) Then $return = DllStructGetData($folderPath, 1) ; Retrieve the value
DllCall("shell32.dll", "none", "ILFree", "ptr", $pidl[0]) ; Free up the PIDL that we cloned
Return SetError(0, 0, $return) ; Success
EndIf
Return SetError(2, 0, "") ; Failed a WinAPI call
EndFunc
This works for me in Win7 - should work in xp as long as address bar is turned on. MUCH simpler than using the DLL calls. I left you a little bit of work to do :)
HotKeySet("#y","_NewFolder")
HotKeySet("#n","_EXIT")
While 1
Sleep(250)
WEnd
FUNC _NewFolder() ; make all vars local
$TEXT = WinGetText("[active]")
; handle error here
$SPLIT = StringSplit($TEXT,#CR & #LF & #CRLF) ;split on new lines (idk which one of the three it uses)
IF IsArray($SPLIT) Then
; trigger = true
FOR $I = 1 to $SPLIT[0]
IF StringInStr($SPLIT[$i],"Address: ") Then
; trigger = false
$STRING = StringReplace($SPLIT[$i],"Address: ","")
$INPUT = InputBox("New Folder","Name your new folder")
; add some enforcement to input box, i like do until loops
$PATH = $STRING & "\" & $INPUT
$CREATE = DirCreate($PATH)
; handle error here
Return SetError(0,0,$PATH)
EndIf
Next
; if trigger then error
Else
Return SetError(1,0,0) ;if split is not an array - could add some more here in case address is the only line returned
EndIf
EndFunc
Func _Exit()
Exit
EndFunc
"create a new folder and then highlight it for renaming like its supposed to"
As per Documentation - Function Reference - DirCreate() :
Creates a directory/folder.
Example:
Global Const $g_sPathFolder = #DesktopDir & '\'
Global Const $g_sPromptText = 'Enter folder name :'
Global Const $g_sPromptExmp = 'NewFolder'
Global $g_sNameFolder = InputBox(#ScriptName, $g_sPromptText, $g_sPromptExmp)
DirCreate($g_sPathFolder & $g_sNameFolder)
ShellExecute($g_sPathFolder & $g_sNameFolder)

WinRar exit code different behavior in SP2/SP3 and 7

I am new to windows programming and have written a small utility with mingw which will unrar a package. The code is as provided below
Descrition:
When the below program is run, the results are as follows
XPSP2 32 bit and Windows 7
Untar Operation : Success
CreateProcess return code : Non Zero (Success)
exit Code : 0 (Success)
XP2SP3 32 bit
Untar Operation : Success
CreateProcess return code : Non Zero (Success)
exit Code : 3221225477
Problem Statement
I am not sure why in XP2SP3 patch only, the winRar operation provides the exit code as Huge positive value. Do you find any problem in the below code? Please help in this regard.
int main()
{
string ProgramName = "C:\\Program Files\\WinRAR\\WinRAR.exe";
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcessInfo;
memset(&StartupInfo, 0, sizeof(STARTUPINFO));
memset(&ProcessInfo, 0, sizeof(PROCESS_INFORMATION)
if (CreateProcess((LPCTSTR)ProgramName.c_str(),(LPCTSTR)"WinRAR.exe x -y -ibck d:\\abc.tar d:\\"),NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS,
NULL,
NULL,
&StartupInfo,
&ProcessInfo) == 0)
{
string tmpStr("Error executing");
tmpStr += ProgramName;
cout<<"StmtDesigner"<<tmpStr<<"CreateProcess failed"<<endl;
}
else
{
string tmpStr("Succes executing");
tmpStr += ProgramName;
cout<<"StmtDesigner"<<tmpStr<<"CreateProcess Success"<<endl;
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
DWORD exitCode=0;
if (GetExitCodeProcess(ProcessInfo.hProcess, &exitCode))
{
string tmpStr("GetExitCodeProcess");
tmpStr += ProgramName;
cout<<tmpStr<<"WinRAR.exe x -y -ibc<<endl;
}
}
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
getch();
return 0;
}
PS : WinRar 3.8 version trail Mode is used for above testing.
That huge positive number, in hexadecimal, is 0xC0000005. It's a common Windows error, which means "Access Violation". Why exactly are you getting it really depends on what winrar is trying to do, but the problem might be with access rights to files. I suggest you give it a try with ProcMon watching your program's file activity. If accessing one of the files is denied, you'll see it in the log.

Best way to wrap rsync progress in a gui?

I use rsync to synchronize files to Windows clients in a server agnostic way. What methods are available to send the progress of rsync to the parent process for display in a gui progress bar?
I imagine two or three choices exist. (1) Watch STDOUT (2) Watch rsync.exe log file, similar to unix tail (3) Watch rsync console output in memory.
Which one is best/preferred?
For this type of tasks, I use my own AutoIt script (freeware, Windows only). The script redirects the standard output into a graphical window, displaying it with the ability to scroll back, etc (very useful in long processes like XCOPYs / PKZIPs to check if any error did happen).
I use AutoIt because it's free, very easy to use, and can compile quickly into an .EXE. I think it's an excellent alternative to a complete programming language for this type of tasks. The downside is that it's for Windows only.
$sCmd = "DIR E:\*.AU3 /S" ; Test command
$nAutoTimeout = 10 ; Time in seconds to close window after finish
$nDeskPct = 60 ; % of desktop size (if percent)
; $nHeight = 480 ; height/width of the main window (if fixed)
; $nWidth = 480
$sTitRun = "Executing process. Wait...." ;
$sTitDone = "Process done" ;
$sSound = #WindowsDir & "\Media\Ding.wav" ; End Sound
$sButRun = "Cancel" ; Caption of "Exec" button
$sButDone = "Close" ; Caption of "Close" button
#include <GUIConstants.au3>
#include <Constants.au3>
#Include <GuiList.au3>
Opt("GUIOnEventMode", 1)
if $nDeskPct > 0 Then
$nHeight = #DesktopHeight * ($nDeskPct / 100)
$nWidth = #DesktopWidth * ($nDeskPct / 100)
EndIf
If $CmdLine[0] > 0 Then
$sCmd = ""
For $nCmd = 1 To $CmdLine[0]
$sCmd = $sCmd & " " & $CmdLine[$nCmd]
Next
; MsgBox (1,"",$sCmd)
EndIf
; AutoItSetOption("GUIDataSeparatorChar", Chr(13)+Chr(10))
$nForm = GUICreate($sTitRun, $nWidth, $nHeight)
GUISetOnEvent($GUI_EVENT_CLOSE, "CloseForm")
$nList = GUICtrlCreateList ("", 10, 10, $nWidth - 20, $nHeight - 50, $WS_BORDER + $WS_VSCROLL)
GUICtrlSetFont (-1, 9, 0, 0, "Courier New")
$nClose = GUICtrlCreateButton ($sButRun, $nWidth - 100, $nHeight - 40, 80, 30)
GUICtrlSetOnEvent (-1, "CloseForm")
GUISetState(#SW_SHOW) ;, $nForm)
$nPID = Run(#ComSpec & " /C " & $sCmd, ".", #SW_HIDE, $STDOUT_CHILD)
; $nPID = Run(#ComSpec & " /C _RunErrl.bat " & $sCmd, ".", #SW_HIDE, $STDOUT_CHILD) ; # Con ésto devuelve el errorlevel en _ERRL.TMP
While 1
$sLine = StdoutRead($nPID)
If #error Then ExitLoop
If StringLen ($sLine) > 0 then
$sLine = StringReplace ($sLine, Chr(13), "|")
$sLine = StringReplace ($sLine, Chr(10), "")
if StringLeft($sLine, 1)="|" Then
$sLine = " " & $sLine
endif
GUICtrlSetData ($nList, $sLine)
_GUICtrlListSelectIndex ($nList, _GUICtrlListCount ($nList) - 1)
EndIf
Wend
$sLine = " ||"
GUICtrlSetData ($nList, $sLine)
_GUICtrlListSelectIndex ($nList, _GUICtrlListCount ($nList) - 1)
GUICtrlSetData ($nClose, $sButDone)
WinSetTitle ($sTitRun, "", $sTitDone)
If $sSound <> "" Then
SoundPlay ($sSound)
EndIf
$rInfo = DllStructCreate("uint;dword") ; # LASTINPUTINFO
DllStructSetData($rInfo, 1, DllStructGetSize($rInfo));
DllCall("user32.dll", "int", "GetLastInputInfo", "ptr", DllStructGetPtr($rInfo))
$nLastInput = DllStructGetData($rInfo, 2)
$nTime = TimerInit()
While 1
If $nAutoTimeout > 0 Then
DllCall("user32.dll", "int", "GetLastInputInfo", "ptr", DllStructGetPtr($rInfo))
If DllStructGetData($rInfo, 2) <> $nLastInput Then
; Tocó una tecla
$nAutoTimeout = 0
EndIf
EndIf
If $nAutoTimeout > 0 And TimerDiff ($nTime) > $nAutoTimeOut * 1000 Then
ExitLoop
EndIf
Sleep (100)
Wend
Func CloseForm()
Exit
EndFunc
.NET has a pretty straight forward way to read and watch STDOUT.
I guess this would be the cleanest way, since it is not dependent on any external files, just the path to rsync. I would not be too surprised if there is a wrapper library out there either. If not, write and open source it :)
I've built my own simple object for this, I get a lot of reuse out of it, I can wrap it with a cmdline, web page, webservice, write output to a file, etc---
The commented items contain some rsync examples--
what I'd like to do sometime is embed rsync (and cygwin) into a resource & make a single .net executable out of it--
Here you go:
Imports System.IO
Namespace cds
Public Class proc
Public _cmdString As String
Public _workingDir As String
Public _arg As String
Public Function basic() As String
Dim sOut As String = ""
Try
'Set start information.
'Dim startinfo As New ProcessStartInfo("C:\Program Files\cwRsync\bin\rsync", "-avzrbP 192.168.42.6::cdsERP /cygdrive/s/cdsERP_rsync/gwy")
'Dim startinfo As New ProcessStartInfo("C:\Program Files\cwRsync\bin\rsync", "-avzrbP 10.1.1.6::user /cygdrive/s/cdsERP_rsync/gws/user")
'Dim startinfo As New ProcessStartInfo("C:\windows\system32\cscript", "//NoLogo c:\windows\system32\prnmngr.vbs -l")
Dim si As New ProcessStartInfo(_cmdString, _arg)
si.UseShellExecute = False
si.CreateNoWindow = True
si.RedirectStandardOutput = True
si.RedirectStandardError = True
si.WorkingDirectory = _workingDir
' Make the process and set its start information.
Dim p As New Process()
p.StartInfo = si
' Start the process.
p.Start()
' Attach to stdout and stderr.
Dim stdout As StreamReader = p.StandardOutput()
Dim stderr As StreamReader = p.StandardError()
sOut = stdout.ReadToEnd() & ControlChars.NewLine & stderr.ReadToEnd()
'Dim writer As New StreamWriter("out.txt", FileMode.CreateNew)
'writer.Write(sOut)
'writer.Close()
stdout.Close()
stderr.Close()
p.Close()
Catch ex As Exception
sOut = ex.Message
End Try
Return sOut
End Function
End Class
End Namespace
Check out DeltaCopy. It is a Windows GUI for rsync.
Check NAsBackup Its open source software that give Windows user Rsync GUI using Watch STDOUT.

Resources