util:CloseApplication from Windows service - winapi

I am trying to close "Tray Application" during updating my application. Functionality to close tray from "Windows Service" looks like this:
<util:CloseApplication
Id="CloseTrayAgent"
Target="$(var.TrayAgentBinName).exe"
RebootPrompt="no"
CloseMessage="yes"
Description="Tray application is still running"/>
<InstallExecuteSequence>
<Custom Action="WixCloseApplications" Before="RemoveFiles" />
</InstallExecuteSequence>
That scenario works if update is invoked from "User" (just from user console) but it does not work if it is invoked from "Windows Service".
I checked msi log but it seems to me there is no errors.
Where is my mistake? Thanks.

Services run in session zero, which is isolated from the interactive desktop session. This article explicitly points out the issue with services that use APIs like SendMessage to send a message to a an app running in the interactive session:
https://blogs.technet.microsoft.com/askperf/2007/04/27/application-compatibility-session-0-isolation/
A search for -services session isolation- or -session 0- will give more information.

Related

Don't override the service account when upgrading a service using WiX toolset

I am trying to setup a WiX installer that install a windows service and handles upgrades and updates.
The installer works like a charm, the user installs the service under the LocalSystem account then a service engineer has to assign a domain account to that service.
Here is my service installer component:
<Component Id="my_exe_Component">
<File Id="Myexe" Source="$(var.Myproject.TargetPath)" KeyPath="yes" />
<ServiceInstall Id="my_exe" Type="ownProcess" Vital="no" Name="NME" DisplayName="My intaller" Description="My installer description" Start="auto" Account="LocalSystem" ErrorControl="ignore" Interactive="no">
<util:ServiceConfig
FirstFailureActionType="restart"
SecondFailureActionType="restart"
ThirdFailureActionType="restart"
RestartServiceDelayInSeconds="0"
ResetPeriodInDays="1"/>
</ServiceInstall>
<ServiceControl Id="my_exe" Stop="both" Remove="uninstall" Name="NME" Wait="yes" />
</Component>
When I perform an upgrade to the installer, the account set to the service get overwritten back to the LocalSystem account, how do I persist the account set to my service when performing an upgrade?
My upgrade clause is set like so:
<MajorUpgrade AllowSameVersionUpgrades="yes" AllowDowngrades="no" DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallExecute" />
Any help would be appreciated.
This is a classic source of truth problem. MSI wants to be the source of truth and doesn't have anything in it's code to account for this scenario. It thinks the service should be LocalSystem and so it wants to fix it. ( This isn't just upgrades ... a repair will do the same thing. )
So, what to do?
Option A:
Bring username /password configuration into the MSI. UI work, credential validation and encryption of the creds persisting it on the machine somewhere so that subsequent transactions can decrypt and reuse the credentials.
Note: Risk. The creds can be reverse engineered.. I got news though... so can windows LSA secrets and Application Pool Identities and such.
Option B:
Use custom actions to create the service. This way you can do logic to not touch the service during subsequent transactions.
Option C:
Put conditional expressions on CreateServices standard action to only apply during first time installation and not major upgrades.
Risk: If you ever change anything else about the service it won't get deployed by an upgrade because it's been bypassed. Also this is for all services in your MSI not just this one.
Option D:
Embrace running as a built in service account and use active directory permissions to grant that computer object rights to whatever it connects to.
Option E:
If the user applies creds after install, then they can simply do it again after upgrade. They can deal with it.

WiX: Installing Service as LocalService

I am trying to get my application an installer via WiX 3.0. The exact code is:
<File Id="ServiceComponentMain" Name="$(var.myProgramService.TargetFileName)" Source="$(var.myProgramService.TargetPath)" DiskId="1" Vital="yes"/>
<!-- service will need to be installed under Local Service -->
<ServiceInstall
Id="MyProgramServiceInstaller"
Type="ownProcess"
Vital="yes"
Name="MyProgramAddon"
DisplayName="[removed]"
Description="[removed]"
Start="auto"
Account="LocalService"
ErrorControl="ignore"
Interactive="no"/>
<ServiceControl Id="StartDDService" Name="MyProgramServiceInstaller" Start="install" Wait="no" />
<ServiceControl Id="StopDDService" Name="MyProgramServiceInstaller" Stop="both" Wait="yes" Remove="uninstall" />
Thing is, for some reason LocalService fails on the "Installing services" step, and if I change it to "LocalSystem" then the installer times out while trying to start the service.
The service starts fine manually and at system startup, and for all intents and purposes works great. I've heard there are issues getting services to work right under LocalService, but Google isnt really helping as everyone's responses have been "got it to work kthx".
Just looking to get this service set up and started during installation, that's all. Any help? Thanks!
Make sure the services.msc window is closed when you install
Have you tried ...
NT AUTHORITY\LocalService
Per this doc ...
... but the name of the account must be NT
AUTHORITY\LocalService when you call
CreateService, regardless of the
locale, or unexpected results can
occur.
reference: ServiceControl Table
The MSI documentation for ServiceControl Table states that the 'Name' is the string name of the service. In your code snipet, your ServiceControl 'Name' is set to the 'ID' for the ServiceInstall and not its 'Name'. So, your ServiceControl elements should read:
<ServiceControl Id="StartDDService" Name="MyProgramAddon" Start="install" Wait="no" />
<ServiceControl Id="StopDDService" Name="MyProgramAddon" Stop="both" Wait="yes" Remove="uninstall" />
Here is another case where a localsystem service can fail to install with error 1923: if you have another service already installed with the same DisplayName (but different internal service name, path, etc). I just had this happen to me.
I spent a while looking into this and discovered it was because I had the keypath attribute set on the the component not on the file. My wix file now looks like:
<Component Id="comp_WF_HOST_18" DiskId="1" KeyPath="no" Guid="3343967A-7DF8-4464-90CA-7126C555A254">
<File Id="file_WF_HOST_18" Checksum="yes" Source="C:\Projects\GouldTechnology\Infrastructure\WorkflowHost\WorkflowHost\bin\Release\WorkflowHost.exe" KeyPath="yes"/>
<ServiceInstall
Id="workflowHostInstaller"
Type="ownProcess"
Vital="yes"
Name="WorkflowHost"
DisplayName="Workflow Host"
Start="demand"
Account="[WORKFLOW_HOST_USER_ACCOUNT]"
Password="[WORKFLOW_HOST_USER_PASSWORD]"
ErrorControl="critical"
Interactive="no"/>
<ServiceControl Id="StartWFService" Name="workflowHostInstaller" Start="install" Stop="both" Remove="both" Wait="no" />
</Component>
Now I just need to work out how to give it the correct permissions...
I had the same problem. It turns out that I had a typo in the <ServiceControl Id="StartService" Name="MyServiceName" where my Name did not match the service name I specified in the service project when I created it.
This was also the problem with my service not uninstalling.
Had the same problem but with specified accounts, got bored of it and created a CA to start the service after the install was completed instead. Just don't bother trying to start it with MSI, just leave it to a CA, unless you get some quality info from somewhere.
BTW using LocalSystem and a manually started service works fine. Never got any other variations to work.
I'll just echo aristippus303's advice: Don't try to start a service with Windows Installer, and don't set any account, just accept the default of LocalSystem during installation. Trying to do anything else is too problematic. Windows Installer waits for the service to indicate it has started, and there are too many things that can go wrong, what with permissions and rights and firewall settings and missing files and so on, so then Windows Installer ends up frozen or terminating with an error and your install has failed.
What you want to do is to specify in your documentation that the user should manually change the service's account (if necessary) and manually start the service after the install is done, and to trouble-shoot any problems that turn up at that point. Or just tell the user to reboot so the auto-start option will start the service if you're fairly sure that there won't be problems.
Please pay attention that in the documentation for ServiceInstall element it is written about the Account attribute that "The account under which to start the service. Valid only when ServiceType is ownProcess.". In your example you did not specify the ownProcess service type which may be the problem.
We had the same problem occuring only on Windows XP machines were the service could not be installed. In the end we found that on XP the name setting from the WiX file is ignored and it instead used the service name set in the C# code. We happened to have a name in the code that contained white-space, i. e. "Blah Blah Service", when this was set to the same name as the WiX file used on Windows 7 it worked well.

Can users interact with services directly on Microsoft Vista?

From the Microsoft website (see msdn.microsoft.com/en-us/library/ms683502(VS.85).aspx) it clearly says "Services cannot directly interact with a user as of Windows Vista".
So I decided to test this by using "psexec -s cmd.exe". As far as I know, "psexec" creates a service in order to open a command prompt. Needless to say it worked. I then decided to use "EnumWinSta GUI" in combination with psexec to switch to the winlogon desktop. To my surprise, I could even start "cmd.exe" on this desktop. Does this mean a new process created from a service can be interactive?
Or is it because psexec does some kind of black magic? If so how does it do it?
I am trying to display a full screen window from a service into the winlogon desktop object in Vista as well as XP.
Code running within a service cannot directly interact with an interactive session.
However, code running as a service with sufficient privileges can create a new process running within a specific user's desktop - getting the interactive session's user's token and calling CreateProcessAsUser, for example.
You can use WTSGetActiveConsoleSessionId to get the active console session, the session of the user who is actually on the machine. WTSQueryUserToken can then be used to get the token.
Your service can also use session change notifications in its handler function to see when users logon/logoff, unlock their session, and so on.

On Terminal Server, how does a service start a process in a user's session?

From a Windows Service running on a Terminal Server (in global space), we would like to be able to start up a process running a windows application in a specific user's Terminal Server sessions.
How does one go about doing this?
The Scenerio: the windows service starts at boot time. After the user has logged into a Terminal Server user session, based on some criteria known only to the windows service, the windows service wants to start a process in the user's session running a windows application.
An example: We would like to display a 'Shutdown in 5 minutes' warning to the users. The windows service would detect this condition, and start up a process in each user session that starts the windows app that displays the warning. And, yes, I know there are other ways of displaying a warning dialog, this is the example, what we want to do is much more invasive.
You can use CreateProcessAsUser to do this - but it requires a bit of effort. I believe the following steps are the basic required procedure:
Get the user's session (WTSQuerySessionInformation).
Get a token for that user (WTSQueryUserToken).
Create a duplicate token for your use (DuplicateTokenEx).
Use the token to create an environment block (CreateEnvironmentBlock).
Launch the application with CreateProcessAsUser, using the block above.
You'll also want to make sure to clean up all of the appropriate handles, tokens, etc., after you've launched the process.
Really late reply but maybe somebody will find this helpful.
You can use PsExec to launch an application on a remote (or local) server inside a specified session by using the following command:
psexec \\COMPUTER_NAME -i SESSION_ID APPLICATION_NAME
Where SESSION_ID indicates the session id in which to launch the application.
You will need to know what sessions are active on the server and which session id maps to which user login. The following thread provides a nice code sample for this exact problem: How do you retrieve a list of logged-in/connected users in .NET?
Late reply but in the answer above DuplicateToken is not necessary since WTSQueryUserToken already returns a primary token.

Do AutoIt scripts, executed as service, function for GUI actions?

I'm using an AutoIt script to start and automate a GUI application. I need to activate the script each hour.
Will AutoIt scripts (which perform actions on a GUI) work when used as a service? The script will be run as a service (not scheduled task).
You can easily make an autoit script run as a service using service.au3 written by archer of the autoit forums. Unfortunately or fortunately since it is a security measure. A service needs to start independent of the current user session (before login). It cant access send APIs for input manipulation of the current user session from there. It does sound much more like you need a scheduled task and not a service.
As mentioned above, a scheduled task is what you're looking for. To run a script as a service read this:
Q4. How can I run my script as a service?
This is also a question with multiple answers, and none of them are the only way to do it. The first question to ask yourself is whether or not you wish to install the service on other computers besides your own.
A1. If you only wish to install the service on your own computer, The easiest way to do this is to use Pirmasoft RunAsSvc. This program makes services easy to install and easy to remove when necessary.
A2. If you wish to make the service available to anyone running your script, you can use SRVANY.EXE and ServiceControl.au3. You can then use this code to install your script as a service:
#include "ServiceControl.au3"
$servicename = "MyServiceName"
_CreateService("", $servicename, "My AutoIt Script", "C:\Path_to_srvany.exe", "LocalSystem", "", 0x110)
RegWrite("HKLM\SYSTEM\CurrentControlSet\Services\" & $servicename & "\Parameters", "Application", "REG_SZ", #ScriptFullPath)
or use the following code to delete this service:
#include "ServiceControl.au3"
$servicename = "MyServiceName"
_DeleteService("", $servicename)
There is one caveat to setting up AutoIt as a service. If the service is not installed using the above code, it must have the "allow service to interact with the desktop" setting or else automation functions such as Control* or Win* functions will not function. To assure the service does indeed have this setting, use the following code:
RegWrite("HKLM\SYSTEM\CurrentControlSet\Services[ServiceName]", "Type", "REG_DWORD", 0x110)
Taken from the FAQ topic on the AutoIt Forums. www.autoitscript.com/forum/index.php?showtopic=37289)
It sounds like you're want to use a scheduled task instead of a service. Scheduled tasks can execute every hour, while you're logged in, and should also be able to interact with your desktop. Just remember that a task run as a normal user can not interact (send input) to a elevated program if you're using Vista/Windows Server 2008 with User Account Control enabled.

Resources