Delphi 7 - ShellExecute command not working in situations - delphi

I have made a Game Launcher and I use this command:
procedure TFMain.ImgBtn1Click(Sender: TObject);
begin
ShellExecute(TForm(Owner).Handle, nil, 'starter.exe', '-lang rus', nil, SW_SHOWNORMAL);
end;
with '-lang rus' as a parameter. Everything works fine. The Game Launches and the language is in russian(if i put '-lang eng' it still works fine and the game is in english).
The starter.exe application is inside a folder named ''bin''. When i want to relocate the launcher outside this folder i use this command:
procedure TFMain.ImgBtn1Click(Sender: TObject);
begin
ShellExecute(TForm(Owner).Handle, nil, 'bin\starter.exe', '-lang rus', nil, SW_SHOWNORMAL);
end;
But then the game isn't launching. Actually nothing happens.
What should i change?

You have to use full path to the application you are trying to start.
ExtractFilePath(Application.ExeName) will give you full path to your launcher exe.
Solution 1: using ShellExecute
procedure TFMain.ImgBtn1Click(Sender: TObject);
var
ExecuteResult: integer;
Path: string;
begin
Path := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));
ExecuteResult := ShellExecute(0, nil, PChar(Path + 'bin\starter.exe'), '-lang rus', nil, SW_SHOWNORMAL);
if ExecuteResult <= 32 then ShowMessage('Error: ' + IntToStr(ExecuteResult));
end;
You can find list of error codes at: ShellExecute function documentation
Most common error codes:
ERROR_FILE_NOT_FOUND 0x2
ERROR_PATH_NOT_FOUND 0x3
Solution 2: using ShellExecuteEx
var
FileName, Parameters, Folder: string;
sei: TShellExecuteInfo;
Error: DWORD;
OK: boolean;
begin
Folder := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) + 'bin\';
FileName := Folder + 'starter.exe';
Parameters := '-lang rus';
ZeroMemory(#sei, SizeOf(sei));
sei.cbSize := SizeOf(sei);
sei.lpFile := PChar(FileName);
sei.lpParameters := PChar(Parameters);
sei.lpDirectory := PChar(Folder);
sei.nShow := SW_SHOWNORMAL;
OK := ShellExecuteEx(#sei);
if not OK then
begin
Error := GetLastError;
ShowMessage('Error: ' + IntToStr(Error));
end;
end;
ShellExecuteEx documentation
Solution 3: using CreateProcess
function ExecuteProcess(const FileName, Params: string; Folder: string; WaitUntilTerminated, WaitUntilIdle, RunMinimized: boolean;
var ErrorCode: integer): boolean;
var
CmdLine: string;
WorkingDirP: pchar;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
begin
Result := true;
CmdLine := '"' + FileName + '" ' + Params;
if Folder = '' then Folder := ExcludeTrailingPathDelimiter(ExtractFilePath(FileName));
ZeroMemory(#StartupInfo, SizeOf(StartupInfo));
StartupInfo.cb := SizeOf(StartupInfo);
if RunMinimized then
begin
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_SHOWMINIMIZED;
end;
if Folder <> '' then WorkingDirP := pchar(Folder)
else WorkingDirP := nil;
if not CreateProcess(nil, pchar(CmdLine), nil, nil, false, 0, nil, WorkingDirP, StartupInfo, ProcessInfo) then
begin
Result := false;
ErrorCode := GetLastError;
exit;
end;
with ProcessInfo do
begin
CloseHandle(hThread);
if WaitUntilIdle then WaitForInputIdle(hProcess, INFINITE);
if WaitUntilTerminated then
repeat
Application.ProcessMessages;
until MsgWaitForMultipleObjects(1, hProcess, false, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0 + 1;
CloseHandle(hProcess);
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
var
FileName, Parameters, Folder: string;
Error: integer;
OK: boolean;
begin
Folder := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) + 'bin\';
FileName := Folder + 'starter.exe';
Parameters := '-lang rus';
OK := ExecuteProcess(FileName, Parameters, Folder, false, false, false, Error);
if not OK then
begin
Error := GetLastError;
ShowMessage('Error: ' + IntToStr(Error));
end;
end;
CreateProcess documentation

You should use fully-qualified (absolute) paths. For instance, if you know that the path is
C:\Program Files (x86)\My Company\My Game\bin\starter.exe
you should pass that string. Of course, you should never hard-code such a string, since it may be different on different systems. If your application is a general application launcher, you get the path from the user. If your application launches your own company's games, you have to figure out a clever way to communicate paths.
It is not clear from your question, but if bin\starter.exe is relative to the path of your application, you can use
ExtractFilePath(Application.ExeName) + 'bin\starter.exe'
By the way, you could have figured all this out by yourself by looking at the return value of ShellExecute. Of course, you have read the ShellExecute documentation carefully, so you know what the return values are. So, you would easily have recognised ERROR_FILE_NOT_FOUND and realised you need a fully-qualified path.

Related

How return the complete path of java.exe file ignoring java version installed?

I want associate my .jar file to open with java.exe using Windows registry and have a doubt about how return the complete path of java.exe file ignoring java version installed on computer.
Ex:
in my case i have:
C:\Program Files\Java\jdk1.7.0_45\bin\java.exe
then how access java.exe file ignoring this part 1.7.0_45?
uses
Windows, Registry;
function GetProgramFilesDir: string;
var
reg: TRegistry;
begin
reg := TRegistry.Create;
try
reg.RootKey := HKEY_LOCAL_MACHINE;
reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion', False);
Result := reg.ReadString('ProgramFilesDir');
finally
reg.Free;
end;
end;
procedure RegisterFileType(cMyExt, cMyFileType, ExeName: string);
var
reg: TRegistry;
begin
reg := TRegistry.Create;
try
reg.RootKey := HKEY_CURRENT_USER;
if reg.OpenKey('\Software\Classes\.jar', True) then
reg.WriteString('', 'MyAppDataFile');
if reg.OpenKey('\Software\Classes\MyAppDataFile', True) then
reg.WriteString('', 'myappname');
if reg.OpenKey('\Software\Classes\MyAppDataFile\DefaultIcon', True) then
reg.WriteString('', GetProgramFilesDir + '\Java\jdk1.7.0_45\bin\java.exe');
if reg.OpenKey('\Software\Classes\MyAppDataFile\shell\open\command', True)
then
reg.WriteString('', GetProgramFilesDir + '\Java\jdk1.7.0_45\bin\java.exe "%1"');
finally
reg.Free;
end;
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
end;
Don't use the Registry to discover the path to system folders, like Program Files. Use SHGetFolderPath() or SHGetKnownFolderPath() instead, eg:
function GetProgramFilesDir: string;
var
path: array[MAX_PATH] of Char;
begin
if SHGetFolderPath(0, CSIDL_PROGRAM_FILES, 0, SHGFP_TYPE_CURRENT, path) = S_OK then
Result := IncludeTrailingPathDelimiter(path)
else
Result := '';
end;
function GetProgramFilesDir: string;
var
path: PChar;
begin
if SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, 0, path) = S_OK then
begin
try
Result := IncludeTrailingPathDelimiter(path);
finally
CoTaskMemFree(path);
end;
end else
Result := '';
end;
That being said, to get the current installed path of java.exe, there are a few options you can try:
check if the %JAVA_HOME% environment variable is set.
check the HKLM\SOFTWARE\JavaSoft\Java Runtime Environment\<version> Registry key for a JavaHome value (there may be multiple <version> subkeys!).
search the %PATH% environment variable for any listed folder that has java.exe in it (there may be multiple folders!). You can parse the %PATH% yourself manually, or you can use SearchPath() with its lpPath parameter set to NULL (if you only care about the first copy of java.exe found).
function GetJavaPathViaEnvironment: string;
begin
Result := GetEnvironmentVariable('JAVA_HOME');
if Result <> '' then
begin
Result := IncludeTrailingPathDelimiter(Result) + 'bin' + PathDelim + 'java.exe';
// if not FileExists(Result) then Result := '';
end;
end;
function GetJavaPathViaRegistry: string;
const
JAVA_KEY: string = '\SOFTWARE\JavaSoft\Java Runtime Environment\';
Wow64Flags: array[0..2] of DWORD = (0, KEY_WOW64_32KEY, KEY_WOW64_64KEY);
var
reg: TRegistry;
s: string;
i: integer;
begin
Result := '';
reg := TRegistry.Create;
try
reg.RootKey := HKEY_LOCAL_MACHINE;
for i := Low(Wow64Flags) to High(Wow64Flags) do
begin
reg.Access := (reg.Access and not KEY_WOW64_RES) or Wow64Flags[i];
if reg.OpenKeyReadOnly(JAVA_KEY) then
begin
s := reg.ReadString('CurrentVersion');
if s <> '' then
begin
if reg.OpenKeyReadOnly(s) then
begin
s := reg.ReadString('JavaHome');
if s <> '' then
begin
Result := IncludeTrailingPathDelimiter(s) + 'bin' + PathDelim + 'java.exe';
// if not FileExists(Result) then Result := '' else
Exit;
end;
end;
end;
reg.CloseKey;
end;
end;
finally
reg.Free;
end;
end;
function GetJavaPathViaSearchPath: string;
var
path: array[0..MAX_PATH] of Char;
s: string;
len: DWORD;
begin
Result := '';
len := SearchPath(nil, 'java.exe', nil, Length(path), path, PPChar(nil)^);
if len <= Length(path) then
SetString(Result, path, len)
else
begin
repeat
SetLength(s, len);
len := SearchPath(nil, 'java.exe', nil, len, PChar(s), PPChar(nil)^);
until len <= Length(s);
SetString(Result, PChar(s), len);
end;
end;
function GetJavaPath: string;
begin
Result := GetJavaPathViaEnvironment;
if Result = '' then
Result := GetJavaPathViaRegistry;
if Result = '' then
Result := GetJavaPathViaSearchPath;
end;
Also, don't forget that paths with spaces must be wrapped in double-quotes. You can use Delphi's AnsiQuotedStr() to help you with that, eg:
reg.WriteString('', AnsiQuotedStr(GetJavaPath, '"') + ' "%1"');

delphi: run process as user and SYSTEM account environment variables

I need to run a process under the current user from another process that runs under SYSTEM (system process runs another process as current user).
I can run it with this code, but there is an issue with environment variables.. e.g. in the new 'user-mode' process, I see that the APPDATA value is C:\Windows\System32\config\systemprofile\AppData\Roaming instead of C:\Users\username\AppData\Roaming
function RunProcessAsCurrentUser(FileName: string): Boolean;
var
ProcessId: Integer;
hWindow, hProcess, TokenHandle: THandle;
si: Tstartupinfo;
p: Tprocessinformation;
begin
Result := False;
hWindow := FindWindow('Progman', 'Program Manager');
GetWindowThreadProcessID(hWindow, #ProcessID);
hProcess := OpenProcess (PROCESS_ALL_ACCESS, FALSE, ProcessID);
if OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, TokenHandle) then
begin
FillChar(si,SizeOf(si),0);
with Si do begin
cb := SizeOf( Si);
dwFlags := startf_UseShowWindow;
wShowWindow := SW_NORMAL;
lpDesktop := PChar('winsta0\default');
end;
Result := CreateProcessAsUser(TokenHandle, nil,
PChar('"'+FileName+'"'),
nil, nil, false, Create_default_error_mode, nil, nil, si, p);
end;
end;
the issue is actual in win7,8,10
I supposed that all process settings are copied from explorer.exe (and new process runs as user in the TaskManager) but looks like something stays from the SYSTEM... Please help to resolve
When using CreateProcessAsUser(), you should retrieve the user's environment using CreateEnvironmentBlock():
Retrieves the environment variables for the specified user. This block can then be passed to the CreateProcessAsUser function.
Pass that value to the lpEnvironment parameter of CreateProcessAsUser(). Otherwise, the new process inherits the environment of the calling process instead.
For example:
function RunProcessAsCurrentUser(FileName: string): Boolean;
var
ProcessId: Integer;
hWindow, hProcess, TokenHandle: THandle;
si: Tstartupinfo;
p: Tprocessinformation;
lpEnvironment: Pointer;
begin
Result := False;
hWindow := FindWindow('Progman', 'Program Manager');
if hWindow = 0 then Exit;
GetWindowThreadProcessID(hWindow, #ProcessID);
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessID);
if hProcess = 0 then Exit;
try
if not OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, TokenHandle) then Exit;
FillChar(si,SizeOf(si),0);
with Si do begin
cb := SizeOf( Si);
dwFlags := startf_UseShowWindow;
wShowWindow := SW_NORMAL;
lpDesktop := PChar('winsta0\default');
end;
lpEnvironment := nil;
CreateEnvironmentBlock(#lpEnvironment, TokenHandle, FALSE);
try
Result := CreateProcessAsUser(TokenHandle, nil,
PChar('"'+FileName+'"'),
nil, nil, FALSE, CREATE_DEFAULT_ERROR_MODE,
lpEnvironment, nil, si, p);
finally
DestroyEnvironmentBlock(lpEnvironment);
end;
finally
CloseHandle(hProcess);
end;
end;

ShellExecuteEx 7z Delphi

So I'm trying to do a archive using delphi and ShellExecuteEx my code is :
Result := False;
DecodeDate(now,y,m,d);
NumeFisier := dir+'\Export_'+IntToStr(y)+'.'+IntToStr(m)+'.'+IntToStr(d)+'.zip';
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do
begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := GetActiveWindow();
exInfo.lpVerb := nil;
exInfo.lpFile := PAnsiChar('C:\Windows\System32\cmd.exe');
exInfo.lpParameters := PAnsiChar('C:\Program Files\7-Zip\7z.exe ' +'a ' + NumeFisier + ' ' + dir);
nShow := SW_SHOWNORMAL;
end;
if ShellExecuteEx(#exInfo) then
Ph := exInfo.hProcess
else
begin
ShowMessage(SysErrorMessage(GetLastError));
Result := true;
exit;
end;
while WaitForSingleObject(exInfo.hProcess, 50) <> WAIT_OBJECT_0 do
Application.ProcessMessages;
CloseHandle(Ph);
Result := true;
For some reason this only opens the Command Prompt and doesn't execute the archiving. How can I make it execute the 7z.exe file.
I tried with ShellExecute and it works great, but I have to check then the process is finished, so I'm stuck with ShellExecuteEx
There's no need to involve cmd.exe. That's the command interpreter. You want to execute a different executable so do that directly.
You don't want to use ShellExecuteEx since that has far more generality than you need. All that ShellExecuteEx is doing here is calling CreateProcess. You should do that directly and avoid the middle man. What's more, calling CreateProcess allows you to hide the console window easily. Pass CREATE_NO_WINDOW to achieve that.
Finally, there are better ways to wait than your code. Using MsgWaitForMultipleObjects allows you to avoid polling. And putting this code into a thread would allow you to avoid calls to Application.ProcessMessages.
procedure WaitUntilSignaled(Handle: THandle; ProcessMessages: Boolean);
var
retval: DWORD;
begin
if ProcessMessages then begin
Application.ProcessMessages;//in case there are messages already in the queue
while True do begin
retval := MsgWaitForMultipleObjects(1, Handle, False, INFINITE, QS_ALLEVENTS);
case retval of
WAIT_OBJECT_0,WAIT_ABANDONED_0:
break;
WAIT_OBJECT_0+1:
Application.ProcessMessages;
WAIT_FAILED:
RaiseLastOSError;
end;
end;
end else begin
Win32Check(WaitForSingleObject(Handle, INFINITE)<>WAIT_FAILED);
end;
end;
procedure ExecuteProcess(
const ExecutablePath: string;
const Arguments: string;
const CurrentDirectory: string;
const Wait: Boolean;
const CreationFlags: DWORD
);
var
si: TStartupInfo;
pi: TProcessInformation;
MyCurrentDirectory: PChar;
begin
ZeroMemory(#si, SizeOf(si));
si.cb := SizeOf(si);
if CurrentDirectory <> '' then begin
MyCurrentDirectory := PChar(CurrentDirectory);
end else begin
MyCurrentDirectory := nil;
end;
Win32Check(CreateProcess(
nil,
PChar('"' + ExecutablePath + '" ' + Arguments),
nil,
nil,
False,
CreationFlags,
nil,
MyCurrentDirectory,
si,
pi
));
try
if Wait then begin
WaitUntilSignaled(pi.hProcess, True);
end;
finally
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
end;

How to register an Active X .ocx library in Delphi XE4

I am trying to register an Active X .ocx Library in a Delphi program i have tried the following code with out success no errors and the program runs through all of the code but when it has finished the Active X Library hasn't been registered. What am i doing wrong ?
procedure RegisterOCX;
type
TRegFunc = function : HResult; stdcall;
var
ARegFunc : TRegFunc;
aHandle : THandle;
ocxPath,AppPath : string;
begin
GetDir(0, AppPath);
try
ocxPath := AppPath + '\VOIP.ocx';
aHandle := LoadLibrary(PChar(ocxPath));
if aHandle <> 0 then
begin
ARegFunc := GetProcAddress(aHandle,'DllRegisterServer');
if Assigned(ARegFunc) then
begin
ExecAndWait('regsvr32','/s ' + ocxPath);
end;
FreeLibrary(aHandle);
end;
except
ShowMessage('Unable to register ');
end;
end;
function ExecAndWait(const ExecuteFile, ParamString : string): boolean;
var
SEInfo: TShellExecuteInfo;
ExitCode: DWORD;
begin
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo);
with SEInfo do begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(ExecuteFile);
lpParameters := PChar(ParamString);
nShow := SW_HIDE;
end;
if ShellExecuteEx(#SEInfo) then
begin
repeat
Application.ProcessMessages;
GetExitCodeProcess(SEInfo.hProcess, ExitCode);
until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
Result:=True;
end
else Result:=False;
end;
You are making life hard for yourself by using regsvr32. You've gone 99% of the way to doing without. Instead of calling regsvr32, just call DllRegisterServer. After all, that's all that regsvr32 is going to do!
Your code becomes:
if Assigned(ARegFunc) then
OleCheck(ARegFunc());
You can then remove ExecAndWait altogether. Which is nice because it saves me discussing the busy loop, and the leaked handle!
It would make sense to me to rename the variable that you called ARegFunc as DllRegisterServer. So the code might then look like this:
aHandle := LoadLibrary(PChar(ocxPath));
if aHandle = 0 then
RaiseLastWin32Error;
try
DllRegisterServer := GetProcAddress(aHandle,'DllRegisterServer');
if Assigned(DllRegisterServer) then
OleCheck(DllRegisterServer());
finally
FreeLibrary(aHandle);
end;
The most likely failure mode for a call to DllRegisterServer will be a failure to run your registration code elevated.
As an aside, LoadLibrary returns HMODULE rather than THandle.

Error when running program with VirtualShellTools from a service

I create a service in Delphi. I need this service to run my program. In Windows 7, I use this code to execute a program :
procedure ExecuteProcessAsLoggedOnUser(FileName: string);
implementation
function GetShellProcessName: string;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKeyReadOnly
('Software\Microsoft\Windows NT\CurrentVersion\WinLogon');
Result := Reg.ReadString('Shell');
finally
Reg.Free;
end;
end;
function GetShellProcessPid(const Name: string): Longword;
var
Snapshot: THandle;
Process: TProcessEntry32;
B: Boolean;
begin
Result := 0;
Snapshot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if Snapshot <> INVALID_HANDLE_VALUE then
try
FillChar(Process, SizeOf(Process), 0);
Process.dwSize := SizeOf(Process);
B := Process32First(Snapshot, Process);
while B do
begin
if CompareText(Process.szExeFile, Name) = 0 then
begin
Result := Process.th32ProcessID;
Break;
end;
B := Process32Next(Snapshot, Process);
end;
finally
CloseHandle(Snapshot);
end;
end;
function GetShellHandle: THandle;
var
Pid: Longword;
begin
Pid := GetShellProcessPid(GetShellProcessName);
Result := OpenProcess(PROCESS_ALL_ACCESS, False, Pid);
end;
procedure ExecuteProcessAsLoggedOnUser(FileName: string);
var
ph: THandle;
hToken, nToken: THandle;
ProcInfo: TProcessInformation;
StartInfo: TStartupInfo;
begin
ph := GetShellHandle;
if ph > 0 then
begin
if OpenProcessToken(ph, TOKEN_DUPLICATE or TOKEN_QUERY, hToken) then
begin
if DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_DUPLICATE or
TOKEN_QUERY, nil, SecurityImpersonation, TokenPrimary, nToken) then
begin
if ImpersonateLoggedOnUser(nToken) then
begin
// Initialize then STARTUPINFO structure
FillChar(StartInfo, SizeOf(TStartupInfo), 0);
StartInfo.cb := SizeOf(TStartupInfo);
// Specify that the process runs in the interactive desktop
StartInfo.lpDesktop := PChar('WinSta0\Default');
// Launch the process in the client's logon session
CreateProcessAsUser(nToken, nil, PChar(FileName), nil, nil, False,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartInfo,
ProcInfo);
// End impersonation of client
RevertToSelf();
end;
CloseHandle(nToken);
end;
CloseHandle(hToken);
end;
end;
end;
The code works fine for an "empty" program. So I drop TVirtualExpolorerTreeview onto the form of my program. if I start my service then there will be an error when the program is being called. I guess the program can't enumerate PIDL or blabla (I don't know much about Windows Shell). How do I force the program so it can run normally?
Your WinSta0 might be the cause:
Starting with Windows Vista, the way that services (and processes started by services) can interact with the desktop changed, as services no longer run in the same session as the user at the console.
By default, they cannot interact with the desktop any more.
See this thread for some nice links on this matter.

Resources