A dual Windows Service / GUI applicaton with Delphi - windows-services

Is a dual Windows Service / GUI applicaton possible with Delphi?
I mean, when its exeutable file is started normally, it appears as a normal GUI application that is used for something other than its Windows Service function. For example, a configurator for Windows Service.
Or, for example, in GUI mode it could be a database client with TDBGrid etc and in Sevice mode it starts its own HTTP server and acts as a web application. Both are sharing the same database access code.
I looked into TServiceApplication.Run and didn't understand how can I distinguish between normal start and start as a service.
Of course, I could put some command line switch branching around this construct:
if GuiModeSwitch then begin
Forms.Application.Initialize;
Forms.Application.CreateForm(TConfigForm, ConfigForm);
Forms.Application.Run;
end
else begin
SvcMgr.Application.Initialize;
SvcMgr.Application.CreateForm(TMyService, MyService);
SvcMgr.Application.Run;
end;
But I would like that the GUI mode is started when no command line parameters are specified.

Related

How to make container installation behave like host machine installation

I'm working with the following:
Docker for Windows v20.10.11
Docker running in Windows container mode
mcr.microsoft.com/windows:1903 base image
Proprietary application installed on top of this base image
Each year we create a Docker image with the latest version of our company's software. However this year's version behaves differently. Host machine installation runs fine. Containerized installation fails to run in certain situations. I can start the application as a simple EXE, for example using the Docker run command. The app will start and show up in "tasklist". However I can't start the app via the COM API, which is a critical requirement. The problem appears to be COM related. Normally we can create COM objects for our software just like for any other application. For example, IE returns a COM object just fine:
Creating these objects for our application works outside containers. However inside the container, our latest installation gives this error:
Access permissions appear to be ok. I tried a couple tests to prove this. First I can install other software like MS Word into a container and create COM objects for that:
Second I tried retrieving + modifying the application's DACL in PowerShell.
Changing access masks or trustees can cause an Access Denied error:
This also appears to confirm the access permissions were Ok by default.
Next I made sure COM is aware of the application. This appears to be fine. I get the same result on host machine and container when running this PS script:
gci HKLM:\Software\Classes -ea 0| ? {$.PSChildName -match '^\w+.\w+$' -and
(gp "$($.PSPath)\CLSID" -ea 0)} | ft PSChildName
The application shows up just like any other. The details show up fine when querying by AppID. LocalServer32 points to the correct EXE:
Some other things I tried:
Querying registry keys. There are 7 keys created when installing our software. These appear identical on host machine install and container install.
Even though permissions appear fine, I still tried logging into the container as alternate users. For example "nt authority\system" is another virtual admin user. I also changed the password of the "builtin\administrator" user to enable logging in with that one. Lastly tried creating new users entirely and adding them to the Administrators user group. All these attempts had the same errors as "builtin\containeradministrator" (default user).
A minor check was ensuring CMD.exe / Powershell is running as x64:
Re-registering the DLLs associated with the installation using regsvr32.
Starting from different base images. https://learn.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-base-images. The full Win Server base image behaves exactly the same way regarding errors. The smaller Win Server Core base image is even more problematic, as I can't even start the app's EXE manually using that base. Lastly I tried other tags of the full Windows base image such as 20H2 and 2004. Same result from those. Multiarch or x64 makes no difference.
Included the "Ogawa hack" which was historically needed to make MS Office apps function correctly with COM: https://stackoverflow.com/a/1680214/7991646. It could be necessary for other COM apps too, but didn't help with my specific installation.
Is there anything else I can do to diagnose or solve this COM issue?
There are several things to consider:
The Considerations for server-side Automation of Office article states the following:
Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, ASP.NET, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when Office is run in this environment.
If you are building a solution that runs in a server-side context, you should try to use components that have been made safe for unattended execution. Or, you should try to find alternatives that allow at least part of the code to run client-side. If you use an Office application from a server-side solution, the application will lack many of the necessary capabilities to run successfully. Additionally, you will be taking risks with the stability of your overall solution.
The When CoCreateInstance returns 0x80080005 (CO_E_SERVER_EXEC_FAILURE) page describes possible reasons.
If many COM+ applications run under different user accounts that are specified in the This User property, the computer cannot allocate memory to create a new desktop heap for the new user. Therefore, the process cannot start. See Error when you start many COM+ applications: Error code 80080005 -- server execution failed for more information.
Finally, you may find a similar thread here helpful, see Server execution failed (Exception from HRESULT: 0x80080005 (CO_E_SERVER_EXEC_FAILURE)).

Running Service Continuously - Delphi

My service does not seem to continuously run.
procedure TService1.ServiceExecute(Sender: TService);
begin
While not Terminated do
ServiceThread.ProcessRequests(True);
end;
This is what I currently have that is suppose to be keeping the service running, but it does not do so.
This bit of code only runs when the service is started from the Windows Service Manager. Once you have your service .exe file, use a command prompt to start it with the /install command line argument, and use net start <your-service-name> or the Service Management dailog from the Administrative Tools from the Start Menu or Control Panel to start the service.
If you want to run the service in the Delphi debugger, start Delphi with administrative privileges and attach to the running process. But it's more advisable to have all logic in separate units, and have a separate version of the project as a 'plain exe' the run the project's procedures that you can run in the debugger.
The ServiceThread.ProcessRequests call is needed to allow the service manager requests to be processed (starting/pausing/stopping the service) but you don't call it yourself, nor should you override the TServiceThread instance. The default TServiceThread instance takes care of calling ProcessRequests and also calls the OnStart/OnStop etc handlers of your TService instance.
I normally create a thread in the TService instance OnStart event handler, and let that thread run the service function.
If the service manager stops your service after a short while, it is usually an indication that the ProcessRequests call isn't being called correctly or frequently enough.

Generate PDF using QuickReport from a Windows Service in Delphi

I'm writing a windows service using Delphi XE3. The service is going to read from a database, generate a pdf using quickreport 5.05.1.
I plan to generate the pdf using TQRPDFDocumentFilter.
Everything works fine in a normal VCL application, but when I implement it in a windows service the service hangs (without any exceptions) when I do a QuickRep.Prepare.
I have read that it is possible to use QuickReport in a windows service, but I do not know how. Any suggestions?
Where is the code:
procedure foo
var
pdfFilter: TQRPDFDocumentFilter;
begin
with TForm2.Create(Self) do
begin
ClientDataSet1.Open;
QuickRep1.Prepare;
pdfFilter := TQRPDFDocumentFilter.Create(GetApplicationFolder() + 'test.pdf');
try
QuickRep1.QRPrinter.ExportToFilter(pdfFilter);
finally
pdfFilter.Free;
ClientDataSet1.Close;
end;
end;
end;
Edit:
I have also tried turning off "show progress" on the QuickReport as suggested in another thread.
Writing some code to catch an exception reveals that it indeed throws one. The message is "There is no default printer currently selected".
So this leads me to believe that the local system user that the service is running under does not have any printers installed and that this is the problem.
I have resolved a similar problem (printing to a shared network printer from a Java server running as a Windows service) with these steps:
log on as the user who will run the service
install the printer
IIRC with Delphi applications, the printer name is not case sensitive (with Java it is).

Is it possible to run a hidden console application from a Windows service?

I've written a server in Delphi 2010 that needs to launch a console application every now and again to back up a database. The console application can send log information to the console window, but it is not required.
This works fine when running as an application, but when run as a service I get an access violation when launching the console application. This is the case even if I launch it hidden (SW_HIDE).
Is it possible to launch a hidden console application from a Windows service? The solution needs to work on XP, Vista and Windows 7.
EDIT: The access violation happens when I call ShellExecute.
If you are using ShellExecute, then don't: it won't work inside a service, and is almost never the best way to start a process.
Use CreateProcess in stead.
See this bunch of ShellExecute / CreateProcess question threads on stackoverflow.
--jeroen

QuickReport throws "There Is No Default Printer Currently Selected" Exception

I have created a Delphi Service which prints TQuickReports. Everything works fine if compiled and run as a Windows Application. But when converted to operate as a service trying to create a form containing a TQuickRep component throws the exception.
This service runs fine on many other boxes but not this one in particular. Here are some details:
Using QuickReport version 4.07
Box is a Windows Server 2008 operating system.
Using Delphi 2007
Printer.Printers.Count is returning a positive value. In fact I can list out all of the printers.
I have tried running the service both using Local System Account and Logged on as an Admin.
Is there a default printer set up in session 0? Remember that under Vista / Server 2008 / Windows 7, services run in a separate session. Whether or not the logged-in user has a default printer set is not relevant - it's a per-session setting and doesn't affect session 0.
Can you rewrite the code to gracefully handle that exception and pick a printer to use?
You can solve this problem by creating a new dword UserSelectedDefault with the value: 1 in
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows\SessionDefaultDevices\Session_ID
Make sure you have a local printer selected.
You might give the user a way to select the printer for the service. The Windows service probably does not have a default printer set.
Set TQuickRep.PrinterSettings.PrinterIndex to set the printer number. Then, TQuickRep.Print to print the report.
A colleague ended up finding the solution. I should have added these are "network" printers and not Local printers (at the time I didn't think this was related to the problem). So the service needed to be installed with "NetworkService" as the user account under the logon tab. From the Windows Help:
To specify that the service uses the Network Service account, click This account, and
then type NT AUTHORITY\NetworkService
We had a simular problem here. Using TS servers, Citrix and Powerfuse 9.
Powerfuse had all printers capitalized, however they were shared in a mixed case.
This combination caused Delphi/QReport to crash
When all printers are from printserver to powerfuse in the same case (not important upper or lower or even mixed), the problem was gone
Actually it is a Delphi(5) problem. The comparison of the available printers and the default printer is case sensitive (Printers.pas):
if TPrinterDevice(Objects[I]).Device = Device then
begin
with TPrinterDevice(Objects[I]) do
SetPrinter(PChar(Device), PChar(Driver), PChar(Port), 0);
Exit;
end;
Changing the comparison to:
if lowercase(TPrinterDevice(Objects[I]).Device) = lowercase(Device)
solves the problem.
If using terminal services 2008, same user for multiple sessions, you should look into the:
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows\SessionDefaultDevices\Session_ID
instead of
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows\
I solved a similar problem: If a Delphi application (or service) uses QuickReport, it runs before the system loads the default printer (or printers).
When QuickReport executes TQRPrinter.Init, the printer.printers.count is zero,
shortly after the system loads, the printer.printers.count is the number of printers,
but tqrprinter.int has already executed, so TQRPrinter.FPrinterOK is false,
you then see this error when you try open a QuickReport.
The solution for me was wait until the printers were loaded before launching the application (in citrix and terminal server). I solved this in two ways, either by overwriting tqrprinter or delay the dpr.

Resources