DeviceIoControl - GetLastError: ERROR_NOACCESS - 998 - delphi

I have a kernel driver written in C, where it is expecting a text of type PCWSTR. What's the Delphi type equivalent to send a control code? I tried sending using the following code but GetLastError reports ERROR_NOACCESS. How to solve that?
program Driverloader;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
WinSvc,
SysUtils;
function InstallAndStartDriver(DriverPath, DriverName: WideString; out DriverDevice: THandle): Boolean;
var
hSCManager, hService: THandle;
lpServiceArgVectors: PWideChar;
begin
Result := False;
hSCManager := 0;
hSCManager := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if hSCManager <> 0 then
begin
try
Writeln('OpenSCManagerW() - OK');
hService := 0;
hService := CreateServiceW(hSCManager, PWideChar(DriverName), PWideChar(DriverName), SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, PWideChar(DriverPath), nil, nil, nil, nil, nil);
hService := 0;
lpServiceArgVectors := nil;
hService := OpenServiceW(hSCManager, PWideChar(DriverName), SERVICE_ALL_ACCESS);
if hService <> 0 then
begin
try
Writeln('OpenServiceW() - OK');
if StartServiceW(hService, 0, PWideChar(lpServiceArgVectors)) then
begin
Writeln('StartServiceW() - OK');
Result := True;
end;
finally
CloseServiceHandle(hService);
end;
end;
finally
CloseServiceHandle(hSCManager);
end;
end;
if Result the
begin
DriverDevice := CreateFileW(PWideChar('\\.\' + DriverName), GENERIC_READ or GENERIC_WRITE, 0, PSECURITY_DESCRIPTOR(nil), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := GetLastError() = ERROR_SUCCESS;
Writeln('CreateFileW() - ' + IntToStr(GetLastError));
end;
end;
function CTL_CODE(DeviceType, _Function, Method, Access: Cardinal): Cardinal;
begin
Result := (DeviceType shl 16) or (Access shl 14) or (_Function shl 2) or (Method);
end;
var
driver: THandle;
BytesReturned, IOCTL_PATH_DELETE: Cardinal;
szInput, szOutput: array[0..255] of WideChar;
begin
try
IOCTL_PATH_DELETE := CTL_CODE(FILE_DEVICE_UNKNOWN, $500, METHOD_BUFFERED, FILE_ANY_ACCESS);
lstrcpy(szInput, '\??\C:\Program Files\Software Folder');
if InstallAndStartDriver(IncludeTrailingPathDelimiter(GetCurrentDir) + 'MyDriver.sys', 'MyDriver', driver) then
begin
Writeln('InstallAndStartDriver() - OK');
Sleep(2000);
if not DeviceIOControl(driver, IOCTL_PATH_DELETE, PWideChar(szInput[0]), SizeOf(szInput), PWideChar(szOutput[0]), SizeOf(szOutput) * MAXBYTE, BytesReturned, nil) then
Writeln('DeviceIOControl() - Error: ' + IntToStr(GetLastError))
else
Writeln('Success! - ' + szOutput);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Edit
Receiving text on kernel driver "Dispatch" method:
NTSTATUS DrvDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION irpStack;
PVOID ioBuffer;
ULONG ioControlCode;
NTSTATUS ntStatus;
PCWSTR Path;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
irpStack = IoGetCurrentIrpStackLocation(Irp);
ioBuffer = Irp->AssociatedIrp.SystemBuffer;
switch (irpStack->MajorFunction) {
case IRP_MJ_DEVICE_CONTROL:
ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
switch (ioControlCode) {
case IOCTL_PATH_DELETE: {
Path = *(PCWSTR*)ioBuffer; // <-- fails and stop here
dprintf("%s\n", Path);
break;
}
default:
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
break;
}
break;
}
ntStatus = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
}

While calling DeviceIOControl(IOCTL_PATH_DELETE), when you pass in szInput and szOutput, you are type-casting a single WideChar to a PWideChar, so whatever numeric value that WideChar contains will be mis-interpreted as a memory address, which is wrong. So you end up passing in invalid memory addresses, which can easily account for the ERROR_NOACCESS error.
Change PWideChar(szInput[0]) to PWideChar(#szInput[0]) or simply get rid of the type-cast altogether, passing #szInput as-is. Same with szOutput.
if not DeviceIOControl(driver, IOCTL_PATH_DELETE, #szInput, SizeOf(szInput), #szOutput, SizeOf(szOutput), BytesReturned, nil) then
Also, your use of GetLastError() on CreateFileW() is wrong. The return value of GetLastError() is indeterminate and not valid to use unless CreateFileW() returns INVALID_HANDLE_VALUE, OR if you use dwCreationDisposition=CREATE_ALWAYS or dwCreationDisposition=OPEN_ALWAYS, neither of which you are using.
DriverDevice := CreateFileW(...);
Result := DriverDevice <> INVALID_HANDLE_VALUE;
if not Result then
Writeln('CreateFileW() - Error: ' + IntToStr(GetLastError))
else
Writeln('CreateFileW() - Success!');
UPDATE: your kernel driver is expecting a pointer to a pointer to a null-terminated wide string. But your Delphi code is passing in a pointer to a null-terminated string. That is why your kernel code is crashing. You need to remove the unnecessary level of indirection in the kernel code:
//Path = *(PCWSTR*)ioBuffer;
Path = (PCWSTR)ioBuffer;
Also, your call to dprintf() is expecting a narrow string, but you are passing it a wide string. To print a wide string, you need to use %S instead of %s, eg:
dprintf("%S\n", Path);
On a side note, your Delphi code is leaking the handle returned by CreateServiceW(). You need to call CloseServiceHandle():
hService := CreateServiceW(...);
if hService <> 0 then
CloseServiceHandle(hService); // <-- ADD THIS!
lpServiceArgVectors := nil;
hService := OpenServiceW(...);
if hService <> 0 then
begin
...
end;
However, there is no good reason to immediately close a created service just to re-open it again. Use the handle that CreateServiceW() gives you:
hService := CreateServiceW(...);
lpServiceArgVectors := nil;
if hService <> 0 then
begin
...
end;

Related

Delphi pass multiple params to IActiveScript IDispatch.Invoke

Using Delphi 11.1, I want to add scripting to my application using IActiveScript. I created a small VBScript to test passing multiple parameters from Delphi to the Script:
Function TestParams(a, b, c)
TestParams = c
End Function
VB script load OK, but I have trouble passing multiple params. Delphi code:
procedure TForm1.Button14Click(Sender: TObject);
var
v: OleVariant;
Disp: IDispatch;
Arg: TArray<TVariantArg>;
Res: OleVariant;
DispParams: TDispParams;
i,n: Integer;
s: string;
begin
v := VarArrayOf(['Wrong...', 'huh', 'OK!']);
s := 'TestParams';
Memo2.Lines.Text := VarToStr(MyScriptingHost1.Run('TestParams', v));
exit;
OleCheck(MyScriptingHost1.FScript.GetScriptDispatch(nil, Disp));
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
setlength(arg, 3);
for i := 0 to High(Arg) do
begin
n := High(Arg) - i;
Arg[n].vt := VarType(v[i]);
Arg[n].bstrVal := PWideChar(VarToWideStr(v[i]));
end;
//At this point, my Delphi 11.1 assignes the same value to Arg[]0, Arg[1], arg[2]
//this works
//Arg[0].vt := VT_BSTR;
//Arg[0].bstrVal := 'test3';
//
//Arg[1].vt := VT_BSTR;
//Arg[1].bstrVal := 'test2';
//
//Arg[2].vt := VT_BSTR;
//Arg[2].bstrVal := 'test1';
DispParams.rgvarg := #Arg[0]; //#Arg gives error
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := High(Arg) + 1;
DispParams.cNamedArgs := 0;
//passing pointer to DispParams gives errors
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
end;
For some reason, multiple params gives different results for 32/64 bits, and using the code above, All params get usually the same value. Very strange.
Even more strange, running this several times gives sometimes different results.
The above code works without problems in case of only 1 param.
Anyone who knows what is wrong here?
As I explained in reply to your earlier question, you MUST use WideString when interfacing with COM, not string (ie, when calling Disp.GetIDsOfNames()).
Also, your use of VarToWideStr() is producing temporary WideStrings that are no longer valid by the time you pass the Arg array to Disp.Invoke(), so store the WideStrings in another array to keep them in scope until Invoke() exits.
Try this:
procedure TForm1.Button14Click(Sender: TObject);
var
v: OleVariant;
Disp: IDispatch;
Arg: TArray<TVariantArg>;
ArgStrs: TArray<WideString>;
Res: OleVariant;
DispParams: TDispParams;
i, n: Integer;
s: WideString;
begin
v := VarArrayOf(['Wrong...', 'huh', 'OK!']);
s := 'TestParams';
//Memo2.Lines.Text := VarToStr(MyScriptingHost1.Run('TestParams', v));
OleCheck(MyScriptingHost1.FScript.GetScriptDispatch(nil, Disp));
OleCheck(Disp.GetIDsOfNames(GUID_NULL, #s, 1, 1033, #n));
SetLength(Arg, 3);
SetLength(ArgStrs, 3);
for i := 0 to High(Arg) do
begin
ArgStrs[i] := VarToWideStr(v[i]);
n := High(Arg) - i;
Arg[n].vt := VT_BSTR;
Arg[n].bstrVal := PWideChar(ArgStrs[i]);
end;
DispParams.rgvarg := #Arg[0]; //#Arg gives error
DispParams.rgdispidNamedArgs := nil;
DispParams.cArgs := Length(Arg);
DispParams.cNamedArgs := 0;
OleCheck(Disp.Invoke(n, GUID_NULL, 1033, DISPATCH_METHOD, DispParams, #res, nil, nil));
end;

TLS (Thread-Local-Storage) Callback support in Delphi 10 versions

I'm reading this article that explain how to set a TLS callback in Delphi. The article author says the example works on "Delphi: 2007, 2010, XE4, XE10". But I have tested on Delphi 10 Seattle, Berlin, and Rio, and it does not work (the TLS callback is not executed), but when i test it on Delphi XE5, it works fine.
I also noted that the size of the .map file when compiling the test_app project in Delphi XE5 and Delphi 10 are different. The .map file in Delphi 10 is 5x bigger than the .map file in Delphi XE5 (something around 25KB and 125KB, respectively).
What detail am I missing here?
Following is the code with a reasonable translation to English of the add_tls project and the test_app project.
PS: The test_app project needs to be set to generate a .map file. Project > Options > Linking > Map file => Detailed.
add_tls:
program add_tls;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
Classes,
SysUtils,
Generics.Collections;
procedure ShowHelp;
begin
Writeln('Usage: AddTls.exe "executable path"');
Writeln('Return Codes:');
Writeln(' - 0: TLS Callback successfully added');
Writeln(' - 1: the path to the executable file is not specified');
Writeln(' - 2: executable not found');
Writeln(' - 3: MAP file not found matching the specified file');
Writeln(' - 4: MAP file parsing error');
Writeln(' - 5: error accessing executable file');
Writeln(' - 6: there is no initialized TLS section in the executable file');
end;
type
TSectionData = record
Index: Integer;
StartAddr: DWORD;
SectionName: ShortString;
end;
TSectionDataList = TList<TSectionData>;
const
HardcodeTLS32Offset = 12;
//
// This is an easy way to search for TLS BUT tables - only in projects,
// collected in XE and above
// If the executable is built by another compiler, it will not work naturally
// but the article is not about that :)
// so:
// =============================================================================
function GetTlsTableAddr(const FilePath: string): DWORD;
var
F: TFileStream;
DOS: TImageDosHeader;
NT: TImageNtHeaders;
I: Integer;
Section: TImageSectionHeader;
begin
Result := 0;
// open the file for reading
F := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
try
// read DOS header to go to NT
F.ReadBuffer(DOS, SizeOf(TImageDosHeader));
F.Position := DOS._lfanew;
// We read the NT header to get the number of sections
F.ReadBuffer(NT, SizeOf(TImageNtHeaders));
// read sections and look for TLS
for I := 0 to NT.FileHeader.NumberOfSections - 1 do
begin
F.ReadBuffer(Section, SizeOf(TImageSectionHeader));
if PAnsiChar(#Section.Name[0]) = '.tls' then
begin
// found IMAGE_TLS_DIRECTORY, we immediately correct the AddressOfCallback field
Result := Section.PointerToRawData + HardcodeTLS32Offset;
Break;
end;
end;
finally
F.Free;
end;
end;
// just parse the map file and look for the addresses of the sections
function GetSectionDataList(const FilePath: string; var Index: Integer): TSectionDataList;
var
S: TStringList;
Line: string;
Section: TSectionData;
begin
Result := TSectionDataList.Create;
try
S := TStringList.Create;
try
S.LoadFromFile(FilePath);
Index := 0;
Writeln('I am looking for a table of sections...');
while Copy(Trim(S[Index]), 1, 5) <> 'Start' do
Inc(Index);
Inc(Index);
while Trim(S[Index]) <> '' do
begin
Line := Trim(S[Index]);
Section.Index := StrToInt(Copy(Line, 1, 4));
Delete(Line, 1, 5);
Section.StartAddr := StrToInt('$' + Copy(Line, 1, 8));
Delete(Line, 1, 19);
Section.SectionName := ShortString(Trim(Copy(Line, 1, 8)));
Result.Add(Section);
Inc(Index);
end;
Writeln('Total sections found: ', Result.Count);
finally
S.Free;
end;
except
// we suppress all exceptions. there are error codes
on E: Exception do
Writeln('GetSectionDataList: ' + E.ClassName + ': ' + E.Message);
end;
end;
// again, parse the mapfile and look for the address of the function called tls_callback
// which (if found) we summarize with the address of the section in which it is located
function GetTlsCallbackAddr(const FilePath: string;
SectionDataList: TSectionDataList; Index: Integer): DWORD;
var
S: TStringList;
Line: string;
SectionIndex, TlsAddr: Integer;
begin
Result := 0;
try
S := TStringList.Create;
try
S.LoadFromFile(FilePath);
Writeln('Looking for tls_callback...');
repeat
Line := Trim(S[Index]);
Inc(Index);
if Index = S.Count then Break;
until Pos('.tls_callback', Line) <> 0;
if Pos('.tls_callback', Line) = 0 then
begin
Writeln('No tls_callback entry found in MAP file');
Exit;
end;
SectionIndex := StrToInt(Copy(Line, 1, 4));
Delete(Line, 1, 5);
TlsAddr := StrToInt('$' + Copy(Line, 1, 8));
Writeln('tls_callback found, offset: ', IntToHex(TlsAddr, 8), ', section: ', SectionIndex);
Writeln('Looking for a record about the section...');
for Index := 0 to SectionDataList.Count - 1 do
if SectionDataList[Index].Index = SectionIndex then
begin
Result := SectionDataList[Index].StartAddr + DWORD(TlsAddr);
Writeln('TLS Callback, found in section "', SectionDataList[Index].SectionName,
'", offset sections: ', IntToHex(SectionDataList[Index].StartAddr, 8),
', calculated addressc: ', IntToHex(Result, 8));
Break;
end;
if Result = 0 then
Writeln('Section containing tls_callback not found')
finally
S.Free;
end;
except
// we suppress all exceptions. there are error codes
on E: Exception do
Writeln('GetTlsCallbackAddr: ' + E.ClassName + ': ' + E.Message);
end;
end;
// directly patch file
function Patch(const FilePath, MapPath: string; TlsTable, CallbackAddr: DWORD): Boolean;
var
F: TFileStream;
NewFilePath, BackUpFilePath: string;
OldCallbackTableAddr: DWORD;
begin
Result := False;
try
NewFilePath := ExtractFilePath(FilePath) + 'tls_aded_' +
ExtractFileName(FilePath);
Writeln('I create a copy of the file, the path: ', NewFilePath);
CopyFile(PChar(FilePath), PChar(NewFilePath), False);
F := TFileStream.Create(NewFilePath, fmOpenReadWrite);
try
Writeln('File open');
F.Position := TlsTable;
// read the address where the previous callback referred
F.ReadBuffer(OldCallbackTableAddr, 4);
// in a delphi image, it refers to the SizeOfZeroFill structure of IMAGE_TLS_DIRECTORY
// in which both last fields are filled with zeros (supposedly there is no callback chain)
// Therefore, we will not spoil the working structure and make it refer to the address
// immediately outside of this structure (plus 2 yards in 32 bit, in 64 bit)
Inc(OldCallbackTableAddr, SizeOf(DWORD) * 2);
F.Position := TlsTable;
// write a new address to the old place
F.WriteBuffer(OldCallbackTableAddr, 4);
Writeln('Assigned a new address to the chain of processors, offset: ', IntToHex(TlsTable, 8),
', new value: ', IntToHex(OldCallbackTableAddr, 8));
// now we jump to the place where the VA address of the handler (not RVA) should be written
// skip SizeOfZeroFill and Characteristics and get right behind them
F.Position := TlsTable + SizeOf(DWORD) * 3;
// and now write the address of our callback
F.WriteBuffer(CallbackAddr, 4);
Writeln('Callback address set, offset: ', IntToHex(TlsTable + SizeOf(DWORD) * 3, 8));
// after which we write zero to indicate the end of the callback chain
CallbackAddr := 0;
F.WriteBuffer(CallbackAddr, 4);
finally
F.Free;
end;
// if everything is fine, then rename back
Writeln('I create a backup');
BackUpFilePath := FilePath + '.bak';
DeleteFile(BackUpFilePath);
RenameFile(FilePath, BackUpFilePath);
Writeln('I keep the result');
RenameFile(NewFilePath, FilePath);
Writeln('All tasks completed');
Result := True;
except
// we suppress all exceptions. there are error codes
on E: Exception do
begin
// in the event of an error, we clean ourselves up - returning everything back
DeleteFile(NewFilePath);
RenameFile(BackUpFilePath, FilePath);
Writeln('Patch: ' + E.ClassName + ': ' + E.Message);
end;
end;
end;
var
MapPath: string;
TlsTable, CallbackAddr: DWORD;
SectionDataList: TSectionDataList;
Index: Integer;
begin
ExitCode := 0;
if ParamCount = 0 then
begin
ShowHelp;
ExitCode := 1;
ExitProcess(ExitCode);
end;
if not FileExists(ParamStr(1)) then
begin
Writeln('No executable found: ', ParamStr(1));
ExitCode := 2;
ExitProcess(ExitCode);
end;
TlsTable := GetTlsTableAddr(ParamStr(1));
if TlsTable = 0 then
begin
ExitCode := 6;
ExitProcess(ExitCode);
end;
MapPath := ChangeFileExt(ParamStr(1), '.map');
if not FileExists(MapPath) then
begin
Writeln('MAP file not found: ', MapPath);
ExitCode := 3;
ExitProcess(ExitCode);
end;
Index := 0;
SectionDataList := GetSectionDataList(MapPath, Index);
try
if SectionDataList.Count = 0 then
begin
Writeln('Could not build partition table');
ExitCode := 9;
ExitProcess(ExitCode);
end;
CallbackAddr := GetTlsCallbackAddr(MapPath, SectionDataList, Index);
if CallbackAddr = 0 then
begin
ExitCode := 4;
ExitProcess(ExitCode);
end;
if not Patch(ParamStr(1), MapPath, TlsTable, CallbackAddr) then
ExitCode := 5;
finally
SectionDataList.Free;
end;
ExitProcess(ExitCode);
end.
test_app:
program test_app;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows;
// this callback will be called if the file is correctly patched
procedure tls_callback(hModule: HMODULE;
ul_reason_for_call: DWORD; lpReserved: Pointer); stdcall;
begin
if ul_reason_for_call = DLL_PROCESS_ATTACH then
MessageBox(0, 'TLS Callback Message', nil, 0);
end;
const
ptls_callback: Pointer = #tls_callback;
begin
// so that the tls_callback procedure appears in the MAP file
// you need a link to it, it’s corny like this:
if ptls_callback <> nil then
MessageBox(0, 'Entry Point Message', nil, 0);
end.
If your aim is to have some code execute as soon as possible, here is something which works on any Delphi revision, and on any platform (not only Windows).
Create a small unit with no dependency (no uses clause at all).
unit FirstLoaded;
interface
// NO "uses" clause!
implementation
procedure SomeThingToDoEarly;
begin
end;
initialization
SomeThingToDoEarly;
end.
Then put it as first unit in the uses clause of your project .dpr - before anything else.
program Project1;
uses
FirstLoaded, // before anything!
Forms,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
The code in the initialization part of your unit will be called just after system.pas.
Be aware that if you add something in your uses clause of your unit, those units (and their dependencies) would be initialized first.

Permissions error setting recovery option with changeserviceconfig2

I'm installing a service and wanting to set the service recovery options (using admin in an XP environment). I can change the description happily enough, but if the sfa.cActions is anything but zero it fails with a error 87 (parameter error).
//
// Actions to take on service failure
//
{$EXTERNALSYM _SC_ACTION_TYPE}
_SC_ACTION_TYPE = (SC_ACTION_NONE, SC_ACTION_RESTART, SC_ACTION_REBOOT, SC_ACTION_RUN_COMMAND);
{$EXTERNALSYM SC_ACTION_TYPE}
SC_ACTION_TYPE = _SC_ACTION_TYPE;
PServiceAction = ^TServiceAction;
{$EXTERNALSYM _SC_ACTION}
_SC_ACTION = record
aType : SC_ACTION_TYPE;
Delay : DWORD;
end;
{$EXTERNALSYM SC_ACTION}
SC_ACTION = _SC_ACTION;
TServiceAction = _SC_ACTION;
procedure TXyz_Service_Module.SetDescription(const Desc: ansistring);
var
hSCM: SC_HANDLE;
hService: SC_HANDLE;
sd: SERVICE_DESCRIPTION;
begin
hSCM := WinSvc.OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if hSCM = 0 then Exit;
hService := WinSvc.OpenService(hSCM, PChar(Self.Name), SERVICE_CHANGE_CONFIG);
if hService = 0 then Exit;
sd.lpDescription := PAnsiChar(Desc);
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, #sd);
WinSvc.CloseServiceHandle(hService);
WinSvc.CloseServiceHandle(hSCM);
end;
procedure TXyz_Service_Module.SetRecovery;
var
hSCM: SC_HANDLE;
hService: SC_HANDLE;
sfa: SERVICE_FAILURE_ACTIONS;
actions: array [0 .. 2] of SC_ACTION;
begin
hSCM := WinSvc.OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
if hSCM = 0 then Exit;
hService := WinSvc.OpenService(hSCM, PChar(Self.Name), SERVICE_ALL_ACCESS);
if hService = 0 then Exit;
sfa.dwResetPeriod := 999; //INFINITE;
sfa.lpCommand := nil;
sfa.lpRebootMsg := nil;
sfa.cActions := 1;
sfa.lpsaActions := #actions[0];
actions[0].aType := SC_ACTION_RESTART;
actions[0].Delay := 5000;
(*actions[1].aType := SC_ACTION_RESTART;
actions[1].Delay := 5000;
actions[2].aType := SC_ACTION_RESTART;
actions[2].Delay := 5000;*)
if not changeserviceconfig2(hservice,SERVICE_CONFIG_FAILURE_ACTIONS,#sfa) then begin
showmessage('Error : '+inttostr(getlasterror));
end;
WinSvc.CloseServiceHandle(hService);
WinSvc.CloseServiceHandle(hSCM);
end;
procedure TXyz_Service_Module.ServiceAfterInstall(Sender: TService);
begin
self.SetDescription('Bananas are yellow');
self.SetRecovery;
end;
From the ChangeServiceConfig2() documentation:
hService [in]
A handle to the service. This handle is returned by the OpenService or CreateService function and must have the SERVICE_CHANGE_CONFIG access right. For more information, see Service Security and Access Rights.
If the service controller handles the SC_ACTION_RESTART action, hService must have the SERVICE_START access right.
So, SetRecovery() will need to use this at a minimum:
hService := WinSvc.OpenService(hSCM, PChar(Self.Name), SERVICE_CHANGE_CONFIG or SERVICE_START);
It's the enumeration value.
_SC_ACTION_TYPE = (SC_ACTION_NONE, SC_ACTION_RESTART, SC_ACTION_REBOOT, SC_ACTION_RUN_COMMAND);
needs to be
{$MinEnumSize=4}
_SC_ACTION_TYPE = (SC_ACTION_NONE, SC_ACTION_RESTART, SC_ACTION_REBOOT, SC_ACTION_RUN_COMMAND);
As recommended in the comments by David Heffernan.

out parameter and "ShowMessage" function

I have a function declare like this :
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer; //The "OutValues" is a out parameter.
And I call this function like this:
procedure TForm1.Button6Click(Sender: TObject);
var
v:integer;
s:pchar;
begin
Memo1.Clear;
v := execProc(pchar('PROC_TEST'),pchar('aaa'),s);
showmessage(inttostr(v)); //mark line
Memo1.Lines.Add(strpas(s));
end;
when i delete the mark line(showmessage(inttostr(v))),i will have a correct result display in the Memo1,but if i keep use the showmessage(), the memo1 will dispaly an error string : "Messag" ,Why?
Thanks for any help!
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer;
var
str: TStrings;
InValue,OutValue: string;
i,j,scount: integer;
begin
Result := -100;
i := 0;
j := 0;
str := TStringList.Create;
try
sCount := ExtractStrings(['|'], [], InValues, str);
with kbmMWClientStoredProc1 do
begin
Close;
Params.Clear;
StoredProcName := StrPas(ProcName);
FieldDefs.Updated := False;
FieldDefs.Update;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptUnknown) or
(Params[i].ParamType = ptInput) or
(Params[i].ParamType = ptInputOutput) then
begin
inc(j);
InValue := str[j-1];
Params[i].Value := InValue;
end;
end;
try
ExecProc;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptOutput) or
(Params[i].ParamType = ptInputOutput) then
OutValue := OutValue + '|' + Params[i].AsString;
end;
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
Result := 0;
except
on E:Exception do
begin
if E.Message = 'Connection lost.' then Result := -101;//服务器连接失败
if E.Message = 'Authorization failed.' then Result := -102;//身份验证失败
Writelog(E.Message);
end;
end;
end;
finally
str.Free;
end;
end;
The problem is in the design of your interface and the use of PChar.
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
This is implemented by making an implicit, hidden, local string variable which holds the value
Copy(OutValue,2,Length(OutValue)-1)
When the function returns, that string variable is destroyed and so OutValues points at deallocated memory. Sometimes your program appears to work but that's really just down to chance. Any small change can disturb that, as you have observed.
The problem is easy enough to fix. Simply use string parameters rather than PChar. This will make the code easier to read as well as making it work correctly.
function execProc(ProcName, InValues: string; out OutValues: string): integer;

Not getting path of various system processes by GetModuleFileNameEx()

I have created this function to get the path of various network processes, like svchost, Firefox, etc. Here is the code:
function GetProcessPath(var pId:Integer):String;
var
Handle: THandle;
begin
Result := '';
try
Handle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, pID);
if Handle <> 0 then
begin
try
SetLength(Result, MAX_PATH);
if GetModuleFileNameEx(Handle, 0, PChar(Result), MAX_PATH) > 0 then
SetLength(Result, StrLen(PChar(Result)))
else
Result := '';
finally
CloseHandle(Handle);
end;
end;
except
on E:Exception do
ShowMessage(E.ClassName + ':' + E.Message);
end;
end;
My problem is that I do not get the path of all the processes. It works fine for getting the path of Firefox, and other similar user level processes. But for processes like alg, Svchost, I cannot get the path by this method. My guess is I must use some different API. How can I fix this problem?
I am using Windows XP, 32 bits.
You need to set debug privileges. Here is how it is done:
function NTSetPrivilege(sPrivilege: string; bEnabled: Boolean): Boolean;
var
hToken: THandle;
TokenPriv: TOKEN_PRIVILEGES;
PrevTokenPriv: TOKEN_PRIVILEGES;
ReturnLength: Cardinal;
begin
Result := True;
// Only for Windows NT/2000/XP and later.
if not (Win32Platform = VER_PLATFORM_WIN32_NT) then Exit;
Result := False;
// Obtain the processes token
if OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
begin
try
// Get the locally unique identifier (LUID) .
if LookupPrivilegeValue(nil, PChar(sPrivilege),
TokenPriv.Privileges[0].Luid) then
begin
TokenPriv.PrivilegeCount := 1; // One privilege to set
case bEnabled of
True: TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
False: TokenPriv.Privileges[0].Attributes := 0;
end;
ReturnLength := 0; // Replaces a var parameter
PrevTokenPriv := TokenPriv;
// Enable or disable the privilege
AdjustTokenPrivileges(hToken, False, TokenPriv, SizeOf(PrevTokenPriv),
PrevTokenPriv, ReturnLength);
end;
finally
CloseHandle(hToken);
end;
end;
end;
NtSetPrivilege('SeDebugPrivilege', TRUE); // Call this on form create

Resources