Background
We need to run a GUI application from a Windows Service, set to Log On as Local System (and without enabling interact with desktop).
The GUI application takes one command-line parameter, performs a specific task and then self-terminates. It is a GUI app because some of its components require a parent TForm, so a console app doesn't work. There are no dialogs or any UI a user would see. In fact, it creates itself as a hidden form with no taskbar icon:
Application.Initialize;
Application.MainFormOnTaskbar := False; // <- No taskbar icon
Application.ShowMainForm := False; // <- Main form is hidden
Application.CreateForm(TForm1, Form1);
Application.Run;
It is possible that the GUI app may be launched multiple times simultaneously, each with its own command-line parameter. Since a GUI app can't be spawned directly in the Session 0 process of the service, I created an Administrator user account so the service can log on the admin user and run the GUI app as the admin user. Once I get it to work once, I will leave this user logged in so the service can quickly launch the GUI app without the login/logout overhead each time it spawns the GUI app.
What I've Done
I'm using the following code, formed from dozens of discussions on this topic, even though most of them wanted the GUI app to be seen by a logged on user.
function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll';
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll';
var
_usertoken: THandle;
_si: _STARTUPINFOW;
_pi: _PROCESS_INFORMATION;
_env: Pointer;
_sid: Cardinal;
begin
if LogonUser(PChar(Username), PChar('localhost'), PChar(Password), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, _usertoken) then
try
ZeroMemory(#_si, SizeOf(_si));
_si.cb := SizeOf(_si);
// _si.lpDesktop := 'WinSta0\Default'; // <- behaves the same with or without this
if CreateEnvironmentBlock(_env, _usertoken, False) then
try
if CreateProcessAsUser(_usertoken, nil, PChar(sCMD), nil, nil, False, CREATE_UNICODE_ENVIRONMENT, _env, nil, _si, _pi) then
begin
WaitForSingleObject(_pi.hProcess, 30000);
CloseHandle(_pi.hThread);
CloseHandle(_pi.hProcess);
end
else
_handle_error('CreateProcessAsUser() failed.');
finally
DestroyEnvironmentBlock(_env);
end
else
_handle_error('CreateEnvironmentBlock() failed.');
finally
CloseHandle(_usertoken);
end
else
_handle_error('LogonUser() failed.');
end;
The Windows Event Viewer [Security Log] shows an entry when LogonUser() is called. The following privileges appear in the log entry:
SeTcbPrivilege
SeSecurityPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeSystemEnvironmentPrivilege
SeImpersonatePrivilege
sCmd is set to "c:\path\myapp.exe" "parameter". When sCmd was not properly set, CreateProcessAsUser() would fail with an error of 2 - The system cannot find the file specified. Once I fixed that, CreateProcessAsUser() returns True, but it never actually launches the GUI application.
Question
I'm not sure what I'm missing. I would appreciate any help with getting the service to launch the GUI app under the logged on Username/Password profile, if that's the right way to do this. Or, if there is a better way to do it, I would appreciate any direction and insight.
Thanks to David, Remy and Andy for comments. It helped me to step back and look at the issues from a fresh point-of-view. The solutions ended up being very simple.
Problem 1 - GUI Apps and "Session 0" Services Processes
A service cannot have UI elements or spawn a program that has UI elements. I thought this meant I couldn't use any GUI type controls, like TForm or TWinControl components. So I was trying to figure out how to launch a GUI program from a service (e.g., to the interactive desktop or by logging a user on and launching it to their desktop).
Turns out that as long as you don't have a dialog or some visual control that a user needs to interact with or respond to, it works perfectly fine to include GUI components in a service or an app the service spawns.
Problem 2 - "Control has no parent window"
I found one instance in my code where I created a control at runtime and didn't set its parent. Hard to track down, but fixed.
Problem 3 - External App only launched under "my" user profile
I set the service to Log On three ways, 1) as Local System, 2) as my username/password, and 3) as another admin user's username/password (with identical rights as me).
In all three instances, the return code from within the service to launch the external app indicated it successfully launched the app. However, only when the service was set to Log On using my username/password would the app actually run.
I found a information message in the system event log that said a BPL wasn't found for both of the other two instances. This was because I have a personal user path environment variable that includes an entry for the BPL directory. The other admin user and the Local System accounts do not have this. So of course the app failed to load the needed BPLs and therefore could not run.
Problem 4 - LocalSystem Access to File System
When we pushed the new code and modules to our production servers, the external app failed to properly launch and perform its task (but this time with no messages in the Windows event logs).
There are several parameters that the external app needs (too many to pass on the command line) so the service places all of the parameters into a uniquely named INI file and pass the name of the INI file to the external app. Once the external app finishes its task, it deletes the INI file.
As it turns out, the external app was launched using the Local System account, which didn't have access to the file system and therefore could not open/use the INI file. Once we granted the appropriate rights to the Local System account, it started working correctly in our production environment as well.
Everything works great now.
Related
I'm trying to have my application write into a text file. When the program is ran on a windows admin account it works but not when ran with a regular windows account. The application is not running as admin both times. Can the account the program is running on affect writing rights? Is there a way to work around it?
Both the directory and the file have write permission for any user.
I'm using the Append and WriteLn procedures from System.
The file is found in the Program Files dir although we change the access to our own directory to be writeable by regular users.
EDIT:
Here's the code
var
f: TextFile;
AssignFile(f, sFile);
if not FileExists(sfile) then
begin
Rewrite(f);
end
else
begin
//Will append to the file
Append(f);
end;
Writeln(f, sInfo);
Flush(f);
CloseFile(f);
Yes, the account the program is run under determines where it can and can't write to.
Non-admin users can't write to program files, and a lot of other places.
You'll need to do some research to find out where a non-admin user can write to, but start with: System.IOUtils.TPath.GetHomePath
As part of a project which runs as a service that spawns a process in the login screen (for desktop control) we call OpenProcessToken(), which is then duplicated and a process created. This works successfully as expected under LocalSystem, however this does not work under a domain account. The code fragment is below...
procedure LaunchProcess;
var dwPid, dwSessionId: DWord;
hUserToken, hProcess: THANDLE;
begin
dwPid := GetProcessID('winlogon.exe', WTSGetActiveConsoleSessionId);
hProcess := OpenProcess(MAXIMUM_ALLOWED, FALSE, dwPid);
if (not OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY or TOKEN_DUPLICATE or
TOKEN_ASSIGN_PRIMARY or TOKEN_ADJUST_SESSIONID or TOKEN_READ or TOKEN_WRITE, hUserToken)) then
raise Exception.Create('OpenProcessToken failed (' + SysErrorMessage(GetLastError) + ').');
{...go on to duplicate token, create environment and launch process...}
end;
Full source of the surrounding supporting function can be found here.
This is where it gets a little vague. I understand OpenProcessToken() requires privileges, which is ultimately why I am getting the error, however it wasn't clear what privilege I require, and how to effectively assign that against a domain account.
This would suggest that the required privilege is SeTcbPrivilege ("Act as part of the operating system").
I've read the Microsoft page (can't link, not enough reputation - sorry) on privileges which suggests that the SeTcbPrivilege can be assigned to a domain account using Local or Group Security Policy. It has also been suggested that the destination process (i.e. winlogon.exe) may simply not allow anything other than LocalSystem to obtain its token.
I have tried to configure the domain account explicitly using account the service, but in the Local Security and Group Policy, have restarted and performed gporesult to ensure the policy has taken effect, but each time whoami /priv returns SeTcbPrivilege is disabled
My question is if this is even possible (can I obtain winlogon.exe token using a domain account) and if so can the privilege be set programmatically, or does this need to be through GPO? (and if so, given my previous attempts at using GPO had no effect, how is it possible)
You can Activate Rights using LsaAddAccountRights, you still need to at least log off/on possibly reboot.
You then need to enable the Privileges for those rights in your code. Lots of things happen automatically for Local-system that do not for Users.
For a quick test I Activated and Enabled SE_TCB_NAME, SE_ASSIGNPRIMARYTOKEN_NAME and SE_INCREASE_QUOTA_NAME
I was then able to successfully call OpenProcessToken with only TOKEN_DUPLICATE
Activating those right gave me a new SessionID after Log off/on. SessionID of 2 so your call to WTSGetActiveConsoleSessionId would have falsely returned 1
All this was done as elevated Admin
Does your application run successfully when you run it with administrator privileges? If so, go to your project options, select Application and check "Enable Administrator Privileges" under Manifest File.
First time I tried on Delphi 10 (Seattle) to compile an android application and got an disappointment. Its a small test application using sqlite database. Database has only one table with few records (created just for testing purposes).
I link everything right (using firedac components) and in Delphi IDE my data shows. It also compiles without an error. The application also shows on my phone (HTC M8) but right after the splash screen, the screen goes black. Nothing !
If I disconnect the FDConnection and compile then the screen shows OK on the phone (without any data ofcourse). Then I tried this way :added a button on the form which I used to open the connection and the table manually but(when run) got a firedac error (unable to open database file).
What am I missing here ? Why cant the application open the database? Is there something else I must do ? Maybe trivial but I never did an android application before.
The mobile app cannot find the database in the phone's storage. You need to create one if it does not exist. This must be done before you can connecting to the database. Basically assign the database parameter to the FDConnection in the BeforeConnect event. also ensure that FDConnection's OpenMode = CreateUTF8
procedure TMainDataModule.FDConnection1BeforeConnect(Sender: TObject);
begin
{$IFDEF ANDROID}
FDConnection1.Params.Values['Database'] := TPath.Combine(TPath.GetDocumentsPath, 'mydatabase.sdb');
{$ENDIF}
end;
After googling for some time I found the information here
mobile tutorial
that :
"The database file is not available on your mobile device unless you
copy it to the mobile device or create it on the fly."
So basically, I can not connect to my existing database on the PC from my phone.
I am facing an issue in my windows7 32bit Pc(on i3).I have Outlook 2010 and Delphi 7 on it.
I am using following code to detect Outlook is running or not.
ClassID := ProgIDToClassID(ClassName);
Result := (GetActiveObject(ClassID, nil, Unknown) = S_OK);
This fails, ie, result become false and yet in other PCs this working fine.
The error I'm getting is MK_E_Unavailable.
Update:
May be it just happening with me only.
procedure TForm1.Button1Click(Sender: TObject);
function IsObjectActive(ClassName: string): Boolean;
var
ClassID: TCLSID;
Unknown: IUnknown;
begin
try
ClassID := ProgIDToClassID(ClassName);
Result := (GetActiveObject(ClassID, nil, Unknown) =S_OK );
except
Result := False;
end;
end;
begin
if IsObjectActive('Outlook.Application') Then
ShowMessage('OutLook is there.')
else
ShowMessage('OutLook is not there.')
end;
Plz note OL is running and
When I am running the created exe, I am getting message "OutLook is there".
when I am running from Delphi IDE, I am getting Message 'OutLook is not there.'
This happens always, I am using Delphi 7 on Windows 7, running with Run as Admin. Kindly tell me why this happening and how can I fix this.
What's the issue of Delphi 7 on Windows 7.
Please suggest.
Here's the entry for GetActiveObject.
http://msdn.microsoft.com/en-us/library/a276e30c-6a7f-4cde-9639-21a9f5170b62%28VS.85%29
If you want to decode the error, you need to find out what the HResult means.
Wikipedia has a link to a ERR.EXE utility from MS that will translate the HResult code into an error description. For COM HResults see: http://matthewbass.com/2005/11/15/decoding-com-hresult-error-codes/.
note the download link in the article is broken, here's a working link: http://www.softlookup.com/display.asp?id=7113
Once you know what the error is, update the question.
If you want to know whether a process is running without using OLE, see: How to check if a process is running using Delphi?
Another option might to use FindWindowEx to check for Outlook 2010 specific windows.
You can use WinID (a spy++ clone) to see the windows used by Outlook 2010.
I was facing the same issue and I found the solution.
It's simple, If Outlook is already running, it MUST have the same rights as the process that is trying to use it.
In simple words, if you are running Outlook with admin rights, you must execute your app with admin rights.
Your problem must be that you are running Outlook without admin rights, and Delphi IDE with admin rights. So when you are launching your app from within IDE, the rights doesn't match, and you get the error. This is why when running your app outside the IDE it works as expected. Because outside IDE your app runs without admin rights.
Try to match the rights. This is something to take into account for the end user environment too.
Also, the UAC under Windows Vista and later is known to cause multiple issues with these type of things. If everything else fails, disable UAC (User Account Control, you will find it under your account options) and see what happens.
try to use rctrl_renwnd32
try this:
(FindWindow('rctrl_renwnd32', nil) <> 0)
http://users.skynet.be/am044448/Programmeren/VBA/vba_class_names.htm
There is an application which is running on several machines(say roughly on 2).This application updates an shared mdb placed on network.Both users are trying to update the shared mdb at one time but the problem is only one user is able to update mdb at one time.Another user is not able to open it.Can anyone suggest that access support multiuser environment?
edit:
There is one form TFormRoadAttrib.When it activates following function is called
procedure TFrmRoadAttrib.FormActivate(Sender: TObject);
if dmTimeDomain <> nil then
begin
if not (dmTimeDomain.dbTimeDomain.InTransaction) then
begin
dmTimeDomain.dbTimeDomain.BeginTrans;
end;
end;
where dbTimeDomain=TADOConnection and its value is
'Provider=Microsoft.ACE.OLEDB.12.0;
Mode=Share Deny None;
Extended Properties="";
Locale Identifier=1033;
Jet OLEDB:Registry Path="";
Jet OLEDB:Database Password="";
Jet OLEDB:Engine Type=4;
Jet OLEDB:Database Locking Mode=0;
Jet OLEDB:Global Partial Bulk Ops=2;
Jet OLEDB:Global Bulk Transactions=1;
Jet OLEDB:New Database Password="";
Jet OLEDB:Create System Database=False;
Jet OLEDB:Encrypt Database=False;
Jet OLEDB:Don't Copy Locale on Compact=False;
Jet OLEDB:Compact Without Replica Repair=False;
Jet OLEDB:SFP=False;
Data Source=Q:\BEL_01\BEL_GADM\ACCESS\Restrictions.mdb;
Jet OLEDB:System database=C:\Program Files\Tele Atlas\Common Files\DPT.MDW;
User ID=dbadpt;
Password=dbadpt;
When we click on Ok button following code executes
if dmTimeDomain <> nil then
begin
if (dmTimeDomain.dbTimeDomain.InTransaction) then
dmTimeDomain.dbTimeDomain.CommitTrans;
end;
end;
Kindly suggest.
Access definitely supports a multi-user environment, but your permissions must be set correctly. All users must be able to create files in the directory where the database is located, and all users must have permission to modify files created in that directory by other users. There are many ways to mess that up. This is because Access uses a separate .ldb file as part of its mechanism for managing concurrent, multi-user access.
A good test is to have one user create a text file in the shared directory, and then make sure the other user can open that file, and then save a change to it.
Both should be able to use the app. If one user is editing a form or table, the others are locked out of editing those same objects. But that should have no bearing on the app once it's in "production" state. A few years ago I helped convert a large app to MS SQL Server backend (stil MS Access frontend) and until that point, they had been successfully using the app with 15 users simultaneously. The app just got too big (100 forms, 100 tables, some with a million rows) so they moved for performance reasons. Otherwise they'd still be totally on Access.
Consider using an Access Project (adp extension) instead of a normal access mdb file. An access project works with SQL server desktop engine which you will find as a separate install file on you Office CD. This essentially means you have a slightly watered down version of SQL server running and this server will take care of all your concurrency issues for you. Also, if your DB becomes too big for an Access Project, you can easily port your DB to a fully fledged SQL Server machine. You can do most of the things in an Access Project that you can do with SQL Server, including Stored Procedures etc, and it's pretty easy to setup and connect to.