Catch errorlevel from cmd command in shellexecute - winapi

I have batch script, to create backup of some 30 locations on work network, and it works.
Now I am programming the same app, but in MFC VC++, so im using little trick to set parameters for xcopy in string, then execute it with ShellExecute.
m_destination is variable from editbox, as destination input, where files are going.
Code is:
if (m_line1.GetCheck() == BST_CHECKED)
{
temp_dest = _T("/min /c xcopy \"\\\\pc_name.sub_domain.domain.local\\c$\\Users\\Test\\Desktop\\Test\\*.*\" \"") + m_destination + _T("\" /Y /E /Q");
ShellExecute(
GetSafeHwnd(),
L"open", // open edit print
L"C:\\Windows\\System32\\cmd.exe", // FILE PATH,
temp_dest, // PARAMETERS
NULL, // WORKING DIR
SW_HIDE); // WINDOW SHOW HIDE
m_status = "Line 1 - OK\r\n";
}
This code above works, but there are some cases, when target PC is shutdown, and in cmd window, it says "path has changed or destination unreachable" (something like that). In that case, %errorlevel% has some value, and some other if copying successfully done. Then u have condition to trigger different warnings to user.
The thing is, I dont have clue how to catch it to indicate unsuccessful copying.
I need some kind of warning that copying from that PC is not done. How can I retrieve error code after copying in this way?
Or if there is simple function to replace copyx with (*.*) (all contents) copying, with ability to be directed to specific directory, I would like to hear about it.
Thanks in advance.

I don't think there's a straightforward way to do this with ShellExecute because it doesn't give you a process handle. However, ShellExecuteEx does, via the hProcess field of the SHELLEXECUTEINFO struct you pass in. With that handle, you can wait (WaitForSingleObject) and then get the exit code (GetExitCodeProcess). However, you can also do this with a simple CreateProcess. Also SHFileOperation supports some xcopy-like operations and gives you additional programmatic control.

Related

Post-Build Event Works With one Project But Not the Other

I have 2 projects for which I am trying to create a generic Post-Build event batch file.
Here is the command in Visual Studio:
Post-Build event
if $(ConfigurationName) == Release ("$(ProjectDir)PostBuildRelease.bat" "$(TargetDir)" #(VersionNumber) "$(TargetFileName)" "$(TargetName)")
So I am calling the file PostBuildRelease.bat with 4 parameters:
Bin\Release Directory
Project Version
File Name With Extension
File Name Without Extension
Project 1
This works perfectly with this batch script:
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET "productionpath=Z:\Unused\Apps\LyncVdiChecker\"
MOVE %productionpath%%3 %productionpath%"_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 %productionpath%
Where the assembly is copied to Z:\Unused\Apps\LyncVdiChecker\ and the existing version copied to _archive in the same folder. The archived version also has the date and version number replace the file extension.
Project 2
This batch script also works perfectly (it does the same thing but in a different folder and for a different project):
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET "productionpath=Z:\Unused\Apps\IT Support App\"
MOVE "Z:\Unused\Apps\IT Support App\"%3 "Z:\Unused\Apps\IT Support App\_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 "Z:\Unused\Apps\IT Support App"
However, if I try using the same script from Project1 (the more generic version) in Project2, I get errors, even though the 2 scripts are equivalent:
Errors
The command "if Release == Release ("C:\Users\Seb.Kotze\Source\Repos\Applications\ITSelfHelp\ITHelp\PostBuildRelease.bat" "C:\Users\Seb.Kotze\Source\Repos\Applications\ITSelfHelp\ITHelp\bin\Release\" 2.0.6100.20905 "IT Self Help.exe" "IT Self Help")" exited with code 4.
Output Window:
The syntax of the command is incorrect.
Invalid number of parameters
This error is rather unhelpful, so I tried commenting out the 2 lines MOVE and XCOPY and build again:
Removed MOVE
Same error as above.
Output window:
Invalid number of parameters
Remove XCOPY
No Visual Studio Error, but this appears in the output window:
The syntax of the command is incorrect.
Parameter Output
When I echo out the parameters being used in Project2, everything seems to be in order:
"Path\to\Bin\Release"
2.0.6100.21082
"IT Self Help.exe"
"IT Self Help"
Z:\Unused\Apps\IT Support App\
How can I debug this issue? How is it possible that my script runs fine without any issues, but when run against a different project none of the commands are recognised? Any help with this is much appreciated!
You should normalize all your arguments, so they don't contain outer quotes.
Then you can use them in a reliable way.
The syntax set "variable=%~1" avoids outer quotes in the variable itself.
set "TargetDir=%~1"
set "VersionNumber=%~2"
set "TargetFileName=%~3"
set "TargetName=%~4"
SET "productionpath=Z:\IT Support App\"
set "dateStamp=%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"
MOVE "Z:\IT App\%TargetFileName%" "Z:\IT App\_archive\%TargetName%.%dateStamp%-%VersionNumber%"
XCOPY "%TargetFileName%" "Z:\IT App"
The problem is that the script is messing with the double quotes resulting in invalid paths and invalid number of arguments passed. When dealing with paths built dynamically, it's best to strip any existing " from the parts, and after the path is complete, surround it in ".
Dealing with batch arguments is explained on MSDN. Same thing for variables can be found on SS64.
I've played a bit with the file, and I was able to run it (from command line). The changes you should make in your (Project1) file:
SET productionpath="Z:\Unused\Apps\LyncVdiChecker\"
MOVE "%productionpath:"=%%~3" "%productionpath:"=%_archive\%~4.%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%-%~2"
XCOPY "%~3" "%productionpath:"=%"
I moved the " from the productionpath line to the beginning of its contents. That way will work with paths that contain SPACE s.
In the MOVE and XCOPY lines, I did what I explained above: even if the syntax is not that clear, it's more robust (the last "%productionpath:"=%" could be simple written as %productionpath%, but I left it in the the 1st form for consistency).
Note: You could remove the CMD command at the beginning of your batch, since it starts a new cmd instance(process) that doesn't end.
I found a solution to this, but I am still not sure what the cause was.
I suspect it has something to do with either one of:
Spaces in productionpath causing the command parameter declaration to escape
Quotes around one or more of the parameters creating a non-existent file path
After trying out a few changes to the script, I found that changing the productionpath declaration to SET productionpath="Z:\Unused\Apps\IT Support App\" solved the issue:
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET productionpath="Z:\Unused\Apps\IT Support App\"
MOVE "Z:\Unused\Apps\IT Support App\"%3 "Z:\Unused\Apps\IT Support App\_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 "Z:\Unused\Apps\IT Support App"
Making the same change to the Project1 script did not cause that to break either, so this seems safe.
Update
After reading some of the other answers, I amended the script once again to the following:
CMD
SET "TargetDir=%~1"
SET "VersionNumber=%~2"
SET "TargetFileName=%~3"
SET "TargetName=%~4"
SET "ProductionPath=Z:\Unused\Apps\IT Support App\"
SET "ArchivePath=%ProductionPath%_archive\"
SET "DateStamp=%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"
MOVE "%ProductionPath%%TargetFileName%" "%ArchivePath%%TargetName%.%DateStamp%-%VersionNumber%"
XCOPY "%TargetFileName%" "%ProductionPath%"
Notice the "normalisation" of the paramaters - this removes all quotation marks from their values.
Also now using named parameters.

Printing PDFs from Windows Command Line

I'm trying to print all pdfs in current dir.
When I call this bash script in cmd (singlepdf.sh):
'"C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe"' /t Gemeinde_348_BioID_842_alt.pdf everything's working fine.
When calling multiplepdfs.sh with this content:
declare -a pdfs=(*.pdf)
for pdf in ${pdfs[#]}; do
echo -e "\nprinting **$pdf** with AcroRd32.exe...\n"
'"C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe"' /t $pdf
sleep 3
done
The echo shows that files are addressed correctly in the loop - but then I get the error "C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe": No such file or directory
Can someone help out with this issue?
Edit:
BTW, I have msys mingw installed
I know this is and old question, but i was faced with the same problem recently and none of the answers worked for me:
Couldn't find an old Foxit Reader version
As #pilkch said 2Printer adds a report page
Adobe Reader opens a gui
After searching a little more i found this: http://www.columbia.edu/~em36/pdftoprinter.html.
It's a simple exe that you call with the filename and it prints to the default printer (or one that you specify).
From the site:
PDFtoPrinter is a program for printing PDF files from the Windows command line. The program is designed generally for the Windows command line and also for use with the vDos DOS emulator.
To print a PDF file to the default Windows printer, use this command:
PDFtoPrinter.exe filename.pdf
To print to a specific printer, add the name of the printer in quotation marks:
PDFtoPrinter.exe filename.pdf "Name of Printer"
If you want to print to a network printer, use the name that appears in Windows print dialogs, like this (and be careful to note the two backslashes at the start of the name and the single backslash after the servername):
PDFtoPrinter.exe filename.pdf "\\SERVER\PrinterName"
I had two problems with using Acrobat Reader for this task.
The command line API is not officially supported, so it could change or be removed without warning.
Send a print command to Reader loads up the GUI, with seemingly no way to prevent it. I needed the process to be transparent to the user.
I stumbled across this blog, that suggests using Foxit Reader. Foxit Reader is free, the API is almost identical to Acrobat Reader, but crucially is documented and does not load the GUI for print jobs.
A word of warning, don't just click through the install process without paying attention, it tries to install unrelated software as well. Why are software vendors still doing this???
Looks like you are missing the printer name, driver, and port - in that order. Your final command should resemble:
AcroRd32.exe /t <file.pdf> <printer_name> <printer_driver> <printer_port>
For example:
"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe" /t "C:\Folder\File.pdf" "Brother MFC-7820N USB Printer" "Brother MFC-7820N USB Printer" "IP_192.168.10.110"
Note: To find the printer information, right click your printer and choose properties. In my case shown above, the printer name and driver name matched - but your information may differ.
The error message is telling you.
Try just
"C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe" /t "$pdf"
When you enclose the string in single-quotes, this makes everything inside a valid string, including the " chars. By removing the single-quotes, the shell will process the dbl-quotes as string "wrappers".
I would also wrap the filename variable in dbl-quotes so you can easily process files with spaces in their names, i.e.
"C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe" /t "$pdf"
IHTH
I had the similar problem with printing multiple PDF files in a row and found only workaround by using 2Printer software.
Command line example to print PDF files:
2Printer.exe -s "C:\In\*.PDF" -prn "HP LasetJet 1100"
It is free for non-commercial use at http://doc2prn.com/
First response - wanted to finally give back to a helpful community...
Wanted to add this to the responses for people still looking for simple a solution. I'm using a free product by Foxit Software - FoxItReader.
Here is the link to the version that works with the silent print - newer versions the silent print feature is still not working.
FoxitReader623.815_Setup
FOR %%f IN (*.pdf) DO ("C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe" /t %%f "SPST-SMPICK" %%f & del %%f)
I simply created a command to loop through the directory and for each pdf file (FOR %%f IN *.pdf) open the reader silently (/t) get the next PDF (%%f) and send it to the print queue (SPST-SMPICK), then delete each PDF after I send it to the print queue (del%%f). Shashank showed an example of moving the files to another directory if that what you need to do
FOR %%X in ("%dir1%*.pdf") DO (move "%%~dpnX.pdf" p/)
Using Acrobat reader is not a good solution, especially command line attributes are not documented. Additionally Acrobat reader's window stays open after printing process. PDF files are well known by printer drivers, so you may find better tools, like 2Printer.exe or RawFilePrinter.exe. In my opinion RawFilePrinter has better support and clear licencing process (you pay donation once and you can redistribute RawFilePrinter in many project you like - even new versions work with previously purchased license)
RawFilePrinter.exe -p "c:\Users\Me\Desktop\mypdffile.pdf" "Canon Printer"
IF %ERRORLEVEL% 1(
echo "Error!"
)
Latest version to download: http://bigdotsoftware.pl/index.php/rawfileprinter
The following batch script should achieve what you want. While it will leave an instance of Acrobat Reader running when finished, this will not cause any problems the next time this script is run.
#echo off
for %%f in (*.pdf) do (
echo Printing %cd%\%%f with Adobe Acrobat Reader...
start /b "Printing %%f" "C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" /p /h "%cd%\%%f"
)
While you could separately kill the Acrobat Reader process afterwards there is the possibility this will close other PDF documents that are open that you didn't want closed.
Here is another solution:
1) Download SumatraPDF (portable version) - https://www.sumatrapdfreader.org/download-free-pdf-viewer.html
2) Create a class library project and unzip the SumatraPDF.exe to the project directory root and unblock it.
3) Inside the project Properties, go to the Resoruces tab and add the exe as a file.
4) Add the following class to your library:
public class SumatraWrapper : IDisposable
{
private readonly FileInfo _tempFileForExe = null;
private readonly FileInfo _exe = null;
public SumatraWrapper()
{
_exe = ExtractExe();
}
public SumatraWrapper(FileInfo tempFileForExe)
: this()
{
_tempFileForExe = tempFileForExe ?? throw new ArgumentNullException(nameof(tempFileForExe));
}
private FileInfo ExtractExe()
{
string tempfile =
_tempFileForExe != null ?
_tempFileForExe.FullName :
Path.GetTempFileName() + ".exe";
FileInfo exe = new FileInfo(tempfile);
byte[] bytes = Properties.Resources.SumatraPDF;
using (FileStream fs = exe.OpenWrite())
{
fs.Write(bytes, 0, bytes.Length);
}
return exe;
}
public bool Print(FileInfo file, string printerName)
{
string arguments = $"-print-to \"{printerName}\" \"{file.FullName}\"";
ProcessStartInfo processStartInfo = new ProcessStartInfo(_exe.FullName, arguments)
{
CreateNoWindow = true
};
using (Process process = Process.Start(processStartInfo))
{
process.WaitForExit();
return process.ExitCode == 0;
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
try
{
File.Delete(_exe.FullName);
}
catch
{
}
disposedValue = true;
}
}
// TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
// ~PdfToPrinterWrapper() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
// GC.SuppressFinalize(this);
}
#endregion
}
5) Enjoy printing pdf files from your code.
Use like this:
FileInfo file = new FileInfo(#"c:\Sandbox\dummy file.pdf");
SumatraWrapper pdfToPrinter =
new SumatraWrapper();
pdfToPrinter.Print(file, "My Printer");
#ECHO off set "dir1=C:\TicketDownload"
FOR %%X in ("%dir1%*.pdf") DO ( "C:\Program Files (x86)\Adobe\Reader 9.0\Reader\AcroRd32.exe" /t "%%~dpnX.pdf" "Microsoft XPS Document Writer" )
FOR %%X in ("%dir1%*.pdf") DO (move "%%~dpnX.pdf" p/)
Try this..May be u have some other version of Reader so that is the problem..
Today I was looking for this very solution and I tried PDFtoPrinter which I had an issue with (the PDFs I tried printing suggested they used incorrect paper size which hung the print job and nothing else printed until resolved). In my effort to find an alternative, I remembered GhostScript and utilities associated with it. I found
GSView and it's associated program GSPrint (reference https://www.ghostscript.com/). Both these require GhostScript (https://www.ghostscript.com/) but when all the components are installed, GSPrint worked flawlessly and I was able to create a scheduled task that printed PDFs automatically overnight.
Another solution "out of the box"
FOR %X in ("*.pdf") DO (C:\Windows\System32\print.exe /d:"\\printername" "%X.pdf")
Edit :
As mentionned by "huysentruitw", this only works for txt files ! Sorry !
When I double checked i realized I'm using GhostScript, as "Multiverse IT" proposed.
It looks like so :
"C:\Program Files (x86)\gs\gs\bin\gswin32c.exe" -dPrinted -dBATCH -dNOPAUSE -dNOSAFER -q -dNumCopies=1 -sDEVICE=mswinpr2 -sOutputFile="%printer%My-Printer-Name" "c:\My-Pdf-File.pdf"

Capture CMD output with AutoHotkey

I'm trying to read Windows CMD's stdout with AutoHotkey. For example, I'd like to have the output of the setconsole command inside AHK stored in a variable. I already achieved it a while ago, which makes me all the more perplex why it's not working now.
In the AHK forums, there's a rather old thread about CMDret, a DLL based functionality to do exactly what I want. The first problem was to find a working download for it, since all the links in the post were dead. Google gave me another site, hosting v3.1.2. Altough there seems to be a newer one (v3.2.1 respectively 4d Beta), I checked it out and tested a simple example:
msgbox % CMDret(COMSPEC " /C set")
CMDret(CMD)
{
VarSetCapacity(StrOut, 10000)
RetVal := DllCall("cmdret.dll\RunReturn", "str", CMD, "str", StrOut)
Return, %StrOut%
}
Unfortunately, the MsgBox contained nothing. I then checked out RetVal which had a value of 0; and the attached readme says:
If the function fails, the return value is zero.
Further down, it says:
Note: only 32 bit console applications will currently work with the
this dll version of CMDret (v3.1.2 or lower). Calls that require
command.com will likely not produce any output and may crash. To avoid
this I have included a file named "cmdstub.exe" with the download (in
the Win9x folder). This file should be used when calling 16 bit
console applications to enable returning output.
In conclusion, I am not sure what the problem is. My machine is running on 64 bit. But is the corresponding clause in the readme supposed to solely exclude 16 bit systems or does it rather only include 32 bit?
If the computing architecture is probably not the problem, then what could be?
What I am looking for is either one of the following:
Can I fix the problem and keep using v3.1.2?
Has anyone a working source (or even a local copy) of a newer version I could check out?
Is there another approach [library, .ahk code, etc.] I could use for my purpose? (preferably similar, because CMDret seems very straightforward)
If you don't need a live output, you could use the cmd box itself to save a text file of itself and then you could have autohotkey detect when the console's PID finished (using the returned result of runwait and read the outputted file into memory.
So you could do this in your run command (just a regular cmd parameter):
ipconfig > myoutput.txt
exit
Now you have a text file with the ipconfig output in it.
OR
you could do the same thing, but instead of outputting to a text file, you could output to the clipboard, like this:
ipconfig | clip
Then, since the output is on the clipboard, you can easily grab it into autohotkey.
New recommended 2 ways of doing as of Nov 2019 - https://www.autohotkey.com/docs/FAQ.htm#output:
How can the output of a command line operation be retrieved?
Testing shows that due to file caching, a temporary file can be very fast for relatively small outputs. In fact, if the file is deleted immediately after use, it often does not actually get written to disk. For example:
RunWait %ComSpec% /c dir > C:\My Temp File.txt
FileRead, VarToContainContents, C:\My Temp File.txt
FileDelete, C:\My Temp File.txt
To avoid using a temporary file (especially if the output is large), consider using the
Shell.Exec() method as shown in the examples for the Run command.
Example for the Run command - https://www.autohotkey.com/docs/commands/Run.htm#StdOut:
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
return exec.StdOut.ReadAll()
}
Note: the latter method (shell.Exec) will cause quick display of a cmd window.
You can reduce the duration of its appearance by putting these lines at the top of your script, which will also cause the flickering to happen only once the first time you call the cmd command. From https://autohotkey.com/board/topic/92032-how-to-hide-a-wscript-comspec-window/:
;Following 2 lines : the cmd window will flash only once and quickly
DllCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
How about this script, StdoutToVar ?
It has support for 64bit consoles.
http://www.autohotkey.com/board/topic/15455-stdouttovar/page-7
This has been bugging me for some time now - and finally this works !
The only prerequisite for this is MS sqlcmd.exe, a database called AHK_Dev
and of course AHK_DBA to read the value when you wish to make use of it.
PS. make sure you replace {yourDir} and {yourServer} with you own values!
USE AHK_DEV
CREATE TABLE [dbo].[AHK_DOS]([dos_out] [varchar](max) NULL) ON [PRIMARY];
insert into ahk_dos select 'empty'
Create the follow script ... call it dos_out.bat
#echo off
if "%1" == "" (
set v_cmd=""
) else (
set v_cmd=%1
)
set v_cmd=%v_cmd:~1,-1%
SETLOCAL ENABLEDELAYEDEXPANSION
if "!v_cmd!" == "" (
set v_cmd="echo ... %COMPUTERNAME% %USERNAME% %DATE% %TIME%"
set v_cmd=!v_cmd:~1,-1!
)
set v_data=""
FOR /F "usebackq delims=¬" %%i in (`!v_cmd!`) do (
set v_data="!v_data:~1,-1!%%i~"
)
set q_cmd="set nocount on;update ahk_dos set dos_out=N'!v_data:~1,-1!'"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W
set q_cmd="set nocount on;select len(dos_out) as out_len, dos_out from ahk_dos"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W -w 8000
pause
you can run it from AHK using...
dosCmd2db(c) {
runwait, {yourDir\}dos_out.bat "%c%", , , dospid
msgbox %dospid% closed
}
dosCmd2db("")
dosCmd2db("echo This is a test")
dosCmd2db("dir")
As the same field is being updated each time, you would clearly need to do something between each one to make this example useful!
Try it, and let me know how you get on
Regards, Geoff
Just an update to #amynbe answer.
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
return exec.StdOut.ReadAll() }
Note: the latter method (shell.Exec)
will cause quick display of a cmd window. You can reduce
> the duration of its appearance by putting these lines at the top of
> your script, which will also cause the flickering to happen only once
> the first time you call the cmd command.
You can just do this below to hide cmd and avoid flashing.
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
DetectHiddenWindows On
Run %ComSpec%,, Hide, pid
WinWait ahk_pid %pid%
DllCall("AttachConsole", "UInt", pid)
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
DllCall( "FreeConsole" )
return exec.StdOut.ReadAll()
}
I found a script only solution that works for AutoHotKey L 64bit at:
http://www.autohotkey.com/board/topic/67687-ahkahk-lusing-wsh-to-interact-with-command-line-progs/
After playing with it a bit I was able to capthre the entire output of a 40k text file that I listed using the DOS Type command. There is a demo that shows how you can interact with time command, which is nice if you need limited two way interaction with a dos command or batch script.

How to capture display output from a command prompt in Windows at any moment?

I'm tring to capture de information shown in a command prompt (cmd window) at a specific moment and send it to a text file.
I have an app in C/C++ that is launched by a batch script like this:
c:\myProgramInC.exe
echo "Ending with error"
PAUSE
myProgramInC.exe is always running (with an infinite loop) so if my script gets to the echo command it means my app ended with an abend.
What I want to get is the previous lines before the end execution of the script since my myProgramInC.exe always prints info about what is going on and it would be very useful to see what it was happening when the error ocurred. Something like this
c:\myProgramInC.exe
**** Execution of process to get te previous N lines in the command prompt ****
echo "Ending with error"
PAUSE
I know the cmd window have a buffer and I've been thinking to capture all of this data from such buffer. It is any way to get this info as text to store it in a file?
I'v been trying with something more professional shuch as to make a dump of the memory with ProcDump but since I have various myProgramInC.exe running at the same time (each one stored in a different location) I just get the message "Multiple processes match the specified name." and the rest of the options are just not useful for me since my app doesn't get unresponsive, it simply ends.
Any ideas?
Quick trick would be to execute in context of for /f, you do not even need a batch file for that, you could execute directly from cmd line:
for /f "tokens=*" %F in ('c:\myProgramInC.exe') do #echo %F >>myPrograminC.log
This will suppress all output until your program abends and only then would write all messages to file. If your app writes log messages infrequently (or fails quickly :-)) it should work. I tested it with 10 000 lines.
Batch code below is based on same idea - please note that even it writes only 5 last lines, it still has to scan through all of them so I'm not sure it's any better than above 1 liner.
#echo off
setlocal
for /f "tokens=*" %%P in ('c:\myProgramInC.exe') do (
for /L %%C in (4,-1,1) do (
set /A next=%%C+1
call set line_%%next%%=%%line_%%C%%
)
set line_1=%%P
)
echo program ended abnormally! %date% %time%
for /L %%C in (5,-1,1) do call echo %%line_%%C%%
Ok, there may be a more elegant way to do this, but this will work assuming you have PowerShell.
Create a PowerShell script file called Get-ConsoleAsText.ps1 that contains the script below. Note, I did not create this script. I found it at Windows PowerShell Blog - Capture console screen.
#################################################################################################################
# Get-ConsoleAsText.ps1
#
# The script captures console screen buffer up to the current cursor position and returns it in plain text format.
#
# Returns: ASCII-encoded string.
#
# Example:
#
# $textFileName = "$env:temp\ConsoleBuffer.txt"
# .\Get-ConsoleAsText | out-file $textFileName -encoding ascii
# $null = [System.Diagnostics.Process]::Start("$textFileName")
#
# Check the host name and exit if the host is not the Windows PowerShell console host.
if ($host.Name -ne 'ConsoleHost')
{
write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
exit -1
}
# Initialize string builder.
$textBuilder = new-object system.text.stringbuilder
# Grab the console screen buffer contents using the Host console API.
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0, 0, ($bufferWidth), $bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
# Iterate through the lines in the console buffer.
for($i = 0; $i -lt $bufferHeight; $i++)
{
for($j = 0; $j -lt $bufferWidth; $j++)
{
$cell = $buffer[$i, $j]
$null = $textBuilder.Append($cell.Character)
}
$null = $textBuilder.Append("`r`n")
}
return $textBuilder.ToString()
If you call the PowerShell script by itself, it will read the console buffer and write it back to the screen
PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1"
You can also call it like this to capture the contents to a file:
PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1 | Out-File MyOutput.txt -encoding ascii"
If you want to process it and perform some action within the batch file, you can call it and process the output using a FOR command. I will leave that exercise to you.
So, for example, your batch file would look like this to capture the console output to a file:
c:\myProgramInC.exe
echo "Ending with error"
PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1 | Out-File MyOutput.txt -encoding ascii"
PAUSE
You can do this in C easily enough, using the ReadConsoleOutputCharacter function.
Finally this is what I get to make it work. Following the advise of Harry Johnston
I searched about the way ReadConsoleOutputCharacter function works and I got the next program running.
#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <fstream>
#define BUFFER_SIZE 50000
int main(){
HANDLE hOut;
COORD location = {0, 0};
char *buffer=NULL;
DWORD numberRead;
std::ofstream fileLog;
buffer = new TCHAR[BUFFER_SIZE];
if ( buffer==NULL )
{
printf("\nError: memory could not be allocated.\n");
return 1;
}
fileLog.open ("logger.txt");
if ( fileLog.fail() )
{
printf("\nError: file could not be opened.\n");
return 1;
}
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
ReadConsoleOutputCharacter(hOut, buffer, BUFFER_SIZE, location, &numberRead);
fileLog << buffer ;
free(buffer);
fileLog.close();
return 0;
}
Much of the code I found it in internet and right now I don't have the URL (besides it's from another forum site and I don't know if it's OK to make references to competitor sites) but with a single search in google wouldn't be difficult to find it.
I added the part where it writes to a file and allocation of memory. It run's perfectly in VS C++ 6.
This works fine for me although could be better. Two things aren't very nice like
It writes all the content from the buffer to a file but it doesn't keep the newlines so I get a text file with all the information in ONE single line.
Can not be defined how much lines to be captured. It just takes everything from the beginnig of the buffer to the point where the program starts, wich is not bad but since this app should run in different servers where each one has a different buffer for each set of command prompt windows, is not much efficient just to use a buffer of 50000 bytes when in some cases the buffer is smaller. I think that could be a lot but since it is allocated in real time maybe is not so wrong to use as much memory.
If any error messages are going to be reported back to the command output stream, it can be easily redirected via Redirection Operators for STDOUT (Standard Output) and STDERR (Standard Errors). Depending on whether or not you want to capture output and/or errors, is up to you. More than likely, you are looking for something like:
command 2>> filename
The "2" is a part of the redirection operator, and indicates that STDERR will be redirected and appended to filename.
Sources, examples, and documentation:
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx
http://ss64.com/nt/syntax-redirection.html
One thing that comes in minde immediately is redirecting output of your command line to to a file:
c:\myProgramInC.exe >> myProgramInC.log
then you can see what is in log file.

Why is FindNextFile failing on Windows 7

This code works on Windows XP at home but fails at work on 64bit Windows 7. The loop isn't entered even once although there are more than 50 files in the supplied folder. Not only it doesn't enter, it also returns ERROR_NO_MORE_FILES for GetLastError. Why?
string dir = "d:\\validfolder";
WIN32_FIND_DATA ffd;
HANDLE h = FindFirstFile(dir.c_str(), &ffd);
while(FindNextFile(h, &ffd))
{
// some operation
}
DWORD dw = GetLastError();// returns ERROR_NO_MORE_FILES
I tried Wow64DisableWow64FsRedirection but that has no effect.
You need to add a file wildcard to your dir:
string dir = "d:\\validfolder\\*";
For it to list the files in a directory. Otherwise you are only asking for information about the directory itself.
At least that's how I read the documentation for FindFirstFile
To examine a directory that is not a root directory, use the path to
that directory, without a trailing backslash. For example, an argument
of "C:\Windows" returns information about the directory "C:\Windows",
not about a directory or file in "C:\Windows". To examine the files
and directories in "C:\Windows", use an lpFileName of "C:\Windows*".
I don't know why it's working for you on XP
This code is incorrect in a number of ways.
You must check the return value of FindFirstFile. If the call to FindFirstFile succeeds then you already have the first file in ffd. As your code stands, you throw away the first file. So you need to re-jig your loop logic to account for that. Naturally, if GetLastError returns ERROR_NO_MORE_FILES then that means the search has exhausted all files.
So, what is probably happening is you ask for the first file matching the search string "d:\\validfolder". This is returned in ffd after the call to FindFirstFile. You then ignore that information and ask for the next match. But there is no subsequent match since there is only one object matching "d:\\validfolder" since you included no wildcards in your search pattern.
This code will behave exactly the same on XP as it does on Windows 7 and I suspect that you are not running the same code on both systems.
If you want to enumerate the contents of the folder then you need to search for "d:\\validfolder\\*". Something like this:
string dir = "d:\\validfolder\\*";
WIN32_FIND_DATA ffd;
HANDLE h = FindFirstFile(dir.c_str(), &ffd);
BOOL success = h<>INVALID_HANDLE_VALUE;
while(success)
{
// do something with ffd
success = FindNextFile(h, &ffd));
}

Resources