Capture all commands output - winapi

I want to capture cmd.exe output and show it in a diffrent gui I am making. I want to make a command interpreter with extended functionality. The dir command works flawless, the problem ocurrs when I try to execute another process like ipconfig.
I don't see ipconfig output. Is there a workarround on that ?!
I use TProcess component from Lazarus (FreePascal)
proc := TProcess.Create(nil);
proc.Options:= [poUsePipes, poNoConsole];
proc.ShowWindow:= swoHIDE;
proc.Executable:= 'cmd';
Reading output thread:
if (Length(cmd) > 0) then
begin
cmd := cmd + #13#10;
proc.Input.Write(cmd[1], Length(cmd)); // here I write command from user
strikes := 0;
end
else
if proc.Output.NumBytesAvailable > 0 then
begin
while proc.Output.NumBytesAvailable > 0 do
begin
FillChar(buf, sizeof(buf), #0);
proc.Output.Read(buf, sizeof(buf) - 1);
data := data + buf;
end;
// data gets echoed to user

It works fine for me (I use FPC 3.1.1 & Lazarus 1.5 but I hope it does not matter):
proc.Options:= [poUsePipes];
proc.ShowWindow:= swoHIDE;
proc.Executable:= 'cmd';
...
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
var
cmd: String;
begin
if Key = #13 then
begin
Key := #0;
if not proc.Active then
proc.Active := True;
cmd := Edit1.Text + LineEnding;
proc.Input.Write(cmd[1], Length(cmd));
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
buf: array[0..65535] of Char;
begin
if proc.Output.NumBytesAvailable > 0 then
begin
while proc.Output.NumBytesAvailable > 0 do
begin
FillChar(buf, sizeof(buf), #0);
proc.Output.Read(buf, sizeof(buf) - 1);
Memo1.Lines.Add(buf);
end;
end;
end;
I guess that you just does not catch process output properly.
Good luck.
PS: If you need to create some windows console-like application, the best way, I think, is using Windows console API instead of cross-platform Lazarus components.
PPS: To emulate console look and behavior with Lazarus use CmdLine component.

In general it is smart to first check if the short examples don't solve the problem:
e.g.
uses process;
var s : ansistring;
begin
runcommand('ipconfig',['/all'],s);
writeln(s);
end.
works fine, and saves a whole lot of trouble. (FPC 2.6.2+ though)

Related

How to compile a Delphi source code file for execution from the command line with parameters?

I use Delphi 7 IDE. I want to run .exe from the command line with parameters (to be defined).
How can I know if my app is running with the CMD command?
How can I read the parameter with source code?
How can I know if my app is running with the CMD command?
You can't, nor do you ever need to. If your project is a console app, then the .exe is always run inside of a console window process. If the project is not a console app, then there is no console, but you can create one if needed using AllocConsole(). Any process, whether it is a console app or not, can receive command-line parameter, though.
How can I read the parameter with source code?
Use the ParamCount() and ParamStr() functions in the System unit, or the FindCmdLineSwitch() function in the SysUtils unit.
Or, use GetCommandLine() in the Windows unit and parse the raw command-line data yourself.
var
bHideForm = boolean ;
begin
bHideForm := False;
bHideForm := ParamCount > 0;
if bHideForm then
bMetadataExport := GetParamValue(C_MENU, sMetaDataMenu);
{--------------------- function GetParamValue ----------------------------- }
function GetParamValue(aParamName: string; out oParamValue: string): Boolean;
var
i: Integer;
s: string;
begin
Result := False;
for i := 1 to ParamCount do
begin
s := ParamStr(i);
Result := pos(aParamName, UpperCase(s)) = 1;
if Result then
begin
oParamValue := copy(s, length(aParamName)+2, MaxInt);
Exit;
end;
end;
end;
end;

Problem with file reading, stuck without output when run

I'm trying to do a program that reads numbers from a file, outputs them into a vector and then writes them. The code compiles nicely, but when run, it gets stuck with just a prompt without delivering any output.
Program LectorDeEnteros;
type
Arreglo = array [1..30] of integer;
var
//Arch:text;
Prom:byte;
i:integer;
ArregloA:Arreglo;
Procedure CargadorVectorialdeArchivo (var ArregloA:Arreglo);
var
Arch:text;
i:integer;
Begin
assign (Arch,'Numeros.txt');
reset (Arch);
i := 1;
while not eof(Arch) do
Write(Arch);Read(ArregloA[i]);
i := i + 1;
End;
Begin
CargadorVectorialdeArchivo(ArregloA);
for i := 1 to 14 do
WriteLn(ArregloA[i]:3);
End.
As i said, there are no error messages, just the prompt and no output. I have to CTRL-Z to get it out of this "loop". The expected output would be the numbers of the array, one on each line.
Rewrite the procedure as this:
Procedure CargadorVectorialdeArchivo (var ArregloA:Arreglo);
var
Arch:text;
i:integer;
Begin
assign (Arch,'Numeros.txt');
reset (Arch);
i := 1;
while not eof(Arch) do
begin
Read(Arch,ArregloA[i]);
i := i + 1;
end;
End;
Putting Arch in front of the file tells the compiler that you want to read the contents from that file, not from the keyboard.

How to get versions of files (exe's and dll's) which are included to Inno Setup installer exe file?

As in topic, is it possible? And, I want to display them on one page of installer if parameter (e.g. parameter passed to exe file) is set to true.
I know how to display some page:
if dev then
PageWersjePlikow :=
CreateOutputMsgMemoPage(
1, 'Wersje plików zawarte w instalatorze',
'Lista plików niewidoczna dla klienta',
'Pliki:', 'TU WPISAĆ WERSJE PLIKÓW');
I have some ideas, but every idea is based on .txt file built while compiling exe installer and then read from it...
Use GetVersionNumbers or GetVersionNumbersString support functions.
The GetVersionNumbersString returns version in format Major.Minor.Rev.Build.
If you need a different format, you need to use GetVersionNumbers and format the version components, the way you need (like Major.Minor.Rev):
function MyGetVersionNumbersString(
const Filename: String; var Version: String): Boolean;
var
MS, LS: Cardinal;
Major, Minor, Rev, Build: Cardinal;
begin
Result := GetVersionNumbers(Filename, MS, LS);
if Result then
begin
Major := MS shr 16;
Minor := MS and $FFFF;
Rev := LS shr 16;
Build := LS and $FFFF;
Version := Format('%d.%d.%d', [Major, Minor, Rev]);
end
end;
Thank you! I have found solution for checking cmd parameter:
function GetParam: Boolean;
var
param: string;
i: integer;
begin
Result := False;
for i:= 0 to ParamCount do
begin
param := ParamStr(i);
if (param = '-p') then
begin
Result := True;
break;
end;
end;
end;
With my function I can just run my installer with '-p' parameter and it will show my page containing information which I want :-)

How to Recognize that an Application Intends to Execute \ Run a File?

I need to recognize and fire an event when a file is going to be executed or run by an application. I know I can do it by hooking windows procedures, but I don't know what procedure or event of windows fires.
For example, when an autorun file going to execute, my application should recognize it, Like an antivirus application.
I'm not sure that hooking is useful for my purpose, if solution isn't hooking, please give me a true solution.
try using the PsSetCreateProcessNotifyRoutine, this function adds a driver-supplied callback routine to, or removes it from, a list of routines to be called whenever a process is created or deleted.
you can find a very nice sample int this link written in c++
Detecting Windows NT/2K process execution
UPDATE
Another option is use the WMI events, check the Win32_Process class, the ExecNotificationQuery method and the SWbemEventSource.NextEvent function.
Check this sample tested in delphi 7 and Windows 7, you must run this application from outside of the Delphi IDE or disable the exception notification for the EOleException exception (check this link), to avoid the EOleException wich is intercepted by the IDE.
program GetWMI_InstanceCreationEvent;
{$APPTYPE CONSOLE}
uses
SysUtils
,Windows
,ComObj
,ActiveX
,Variants;
Function KeyPressed:boolean; //detect if an key is pressed
var
NumEvents : DWORD;
ir : _INPUT_RECORD;
bufcount : DWORD;
StdIn : THandle;
begin
Result:=false;
StdIn := GetStdHandle(STD_INPUT_HANDLE);
NumEvents:=0;
GetNumberOfConsoleInputEvents(StdIn,NumEvents);
if NumEvents<> 0 then
begin
PeekConsoleInput(StdIn,ir,1,bufcount);
if bufcount <> 0 then
begin
if ir.EventType = KEY_EVENT then
begin
if ir.Event.KeyEvent.bKeyDown then
result:=true
else
FlushConsoleInputBuffer(StdIn);
end
else
FlushConsoleInputBuffer(StdIn);
end;
end;
end;
function VarStrNUll(VarStr:OleVariant):string;//dummy function to handle null variants
begin
Result:='';
if not VarIsNull(VarStr) then
Result:=VarToStr(VarStr);
end;
function GetWMIObject(const objectName: String): IDispatch; //create a wmi object instance
var
chEaten: Integer;
BindCtx: IBindCtx;
Moniker: IMoniker;
begin
OleCheck(CreateBindCtx(0, bindCtx));
OleCheck(MkParseDisplayName(BindCtx, StringToOleStr(objectName), chEaten, Moniker));
OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
end;
Procedure GetWin32_InstanceCreationEvent;
var
objWMIService : OLEVariant;
colMonitoredProcesses : OLEVariant;
objLatestProcess : OLEVariant;
begin
objWMIService := GetWMIObject('winmgmts:\\localhost\root\cimv2');
colMonitoredProcesses := objWMIService.ExecNotificationQuery('Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA ''Win32_Process'''); //Get the event listener
while not KeyPressed do
begin
try
objLatestProcess := colMonitoredProcesses.NextEvent(100);//set the max time to wait (ms)
except
on E:EOleException do
if EOleException(E).ErrorCode=HRESULT($80043001) then //Check for the timeout error wbemErrTimedOut 0x80043001
objLatestProcess:=Null
else
raise;
end;
if not VarIsNull(objLatestProcess) then
begin
Writeln('Process Started '+VarStrNUll(objLatestProcess.TargetInstance.Name));
Writeln('CommandLine '+VarStrNUll(objLatestProcess.TargetInstance.CommandLine));
Writeln('PID '+VarStrNUll(objLatestProcess.TargetInstance.ProcessID));
end;
end;
end;
begin
try
CoInitialize(nil);
try
Writeln('Press Any key to exit');
GetWin32_InstanceCreationEvent;
finally
CoUninitialize;
end;
except
on E:Exception do
Begin
Writeln(E.Classname, ': ', E.Message);
Readln;
End;
end;
end.

Inno Setup: How to automatically uninstall previous installed version?

I'm using Inno Setup to create an installer.
I want the installer to automatically uninstall the previous installed version, instead of overwriting it. How can I do that?
I have used the following. I'm not sure it's the simplest way to do it but it works.
This uses {#emit SetupSetting("AppId")} which relies on the Inno Setup Preprocessor. If you don't use that, cut-and-paste your App ID in directly.
[Code]
{ ///////////////////////////////////////////////////////////////////// }
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
{ ///////////////////////////////////////////////////////////////////// }
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
{ ///////////////////////////////////////////////////////////////////// }
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
{ Return Values: }
{ 1 - uninstall string is empty }
{ 2 - error executing the UnInstallString }
{ 3 - successfully executed the UnInstallString }
{ default return value }
Result := 0;
{ get the uninstall string of the old app }
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
{ ///////////////////////////////////////////////////////////////////// }
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
Alternatives
See also this blog post "Inno Setup Script Sample for Version Comparison" which goes one step further, and reads the version number of any previously installed version, and compares that version number with that of the current installation package.
You should be able to read the uninstall string from the registry, given the AppId (i.e. the value you used for AppID in the [Setup]-section). It could be found under Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\ (could be either HKLM or HKCU, so best check both) where {AppId} should be substituted with the actual value you used. Look for the UninstallString or QuietUninstallString values and use the Exec function to run it from your InitializeSetup() event function.
If you "just want to remove the old icons" (because yours have changed/updated) you can use this:
; attempt to remove previous versions' icons
[InstallDelete]
Type: filesandordirs; Name: {group}\*;
This is run "at the beginning of installation" so basically removes the old icons, and your new ones will still be installed there after this is completely done.
I just do this with every install "in case anything has changed" icon wise (it all gets reinstalled anyway).
When using Inno Setup, there's no reason to uninstall a previous version unless that version was installed by a different installer program. Otherwise upgrades are handled automatically.
Here is a simplified version based on answer from Craig McQueen:
const
UninstallRegisterPath = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + '{#emit SetupSetting("AppName")}' + '_is1';
function GetUninstallerPath(): String;
begin
result := '';
if (not RegQueryStringValue(HKLM, UninstallRegisterPath, 'UninstallString', result)) then
RegQueryStringValue(HKCU, UninstallRegisterPath, 'UninstallString', result);
end;
procedure UninstallOldVersion();
var
UninstallerPath: String;
ResultCode: Integer;
begin
UninstallerPath := GetUninstallerPath();
if (UninstallerPath <> '') then begin
Exec(UninstallerPath, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep = ssInstall) then
begin
UninstallOldVersion();
end;
end;
Note: in my case, I use AppName instead of AppId.
The answer provided by Craig McQueen is totally viable. Although, I would add those comments:
The {#emit SetupSetting("AppId")} code does not work for me, so I just add my App ID.
I didn't want to execute my uninstallation program, because I have a INI config file stored in the AppData/ folder which is removed by the uninstaller, and I don't want it to be erased when installing a new version. So, I modified a bit the code provided by Craig McQueen to remove the directory where is installed the program, after retrieving its path.
So, regarding the code of Craig McQueen, changes are:
Retrieve the InstallLocation key instead of the UninstallString key.
Use the DelTree function instead of the Exec(sUnInstallString, ...)
For anyone that uses the GetUninstallString() suggested above to force an uninstall inside CurStepChanged() and has disk caching issues, see below for a related solution that actually waits a while after unistallation for the uninstaller exe to be deleted!
Disk caching issue with inno-setup?
For those interested, I wrote a DLL for Inno Setup 6 and later that provides a simple mechanism for supporting automatic uninstall.
The DLL provides a way to detect if the package you are installing is already installed (via AppId) and to decide, based on the installed version, if you want to automatically uninstall it (for example, you might want to automatically uninstall if user is downgrading).
https://github.com/Bill-Stewart/UninsIS
You can exec an uninstaller in the [code] section. You have to figure out how to get the path to the existing uninstaller. For simplicity when I install my apps I add a registry string value that points to the folder containing the uninstaller, and just exec the uninstaller in the InitializeWizard callback.
Keep in mind that Inno setup uninstaller names are all of the form uninsnnn.exe, you need to take that into account in your code.
i got edited #Crain Mc-Queen code , i think this code is better because not need to modified in different project :
[Code]
function GetNumber(var temp: String): Integer;
var
part: String;
pos1: Integer;
begin
if Length(temp) = 0 then
begin
Result := -1;
Exit;
end;
pos1 := Pos('.', temp);
if (pos1 = 0) then
begin
Result := StrToInt(temp);
temp := '';
end
else
begin
part := Copy(temp, 1, pos1 - 1);
temp := Copy(temp, pos1 + 1, Length(temp));
Result := StrToInt(part);
end;
end;
function CompareInner(var temp1, temp2: String): Integer;
var
num1, num2: Integer;
begin
num1 := GetNumber(temp1);
num2 := GetNumber(temp2);
if (num1 = -1) or (num2 = -1) then
begin
Result := 0;
Exit;
end;
if (num1 > num2) then
begin
Result := 1;
end
else if (num1 < num2) then
begin
Result := -1;
end
else
begin
Result := CompareInner(temp1, temp2);
end;
end;
function CompareVersion(str1, str2: String): Integer;
var
temp1, temp2: String;
begin
temp1 := str1;
temp2 := str2;
Result := CompareInner(temp1, temp2);
end;
function InitializeSetup(): Boolean;
var
oldVersion: String;
uninstaller: String;
ErrorCode: Integer;
vCurID :String;
vCurAppName :String;
begin
vCurID:= '{#SetupSetting("AppId")}';
vCurAppName:= '{#SetupSetting("AppName")}';
//remove first "{" of ID
vCurID:= Copy(vCurID, 2, Length(vCurID) - 1);
//
if RegKeyExists(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1') then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
'DisplayVersion', oldVersion);
if (CompareVersion(oldVersion, '{#SetupSetting("AppVersion")}') < 0) then
begin
if MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. Continue to use this old version?',
mbConfirmation, MB_YESNO) = IDYES then
begin
Result := False;
end
else
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
'UninstallString', uninstaller);
ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
Result := True;
end;
end
else
begin
MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. This installer will exit.',
mbInformation, MB_OK);
Result := False;
end;
end
else
begin
Result := True;
end;
end;
I must be missing something.
The new files are copied to the target directory before the removal of the old installation occurs.
Then comes the uninstaller deletes them and remove the directory.
Do not use the [Run] section, but the [UninstallRun].
Infact, the program under [Run] are executed after the installation, causing to uninstall your program immediately after the installation :-|
Instead, the [UninstallRun] section is evaluated before the installation.
Follow this link: http://news.jrsoftware.org/news/innosetup/msg55323.html
In InitializeSetup() function, you can call "MSIEXEC /x {your program ID}" after user prompt to uninstall old old version

Resources