One tricky part of updating an application is that you might run into the situation that the user doesn’t have admin rights. For example, I have two users on my machine, my user which is of course the administrator, and another user without admin rights. If the other user is logged in and receive any updates on the apps I install, he would not be able to install those updates.
Of course, we can prolong the conversation on the basis that the vendors should do “per-user” applications and so on, but this is not the topic of this article. In this article, we will have a look at the following:
- How Windows Services operate
- How to create a Windows Service in C#
- How to configure the download and install of an MSI in the Windows Service at a particular interval
- How to install the service by using the InstallUtil.exe
- Test the service
How Windows Services operate
Starting with Windows Vista, Windows Services are not running in the context of a particular user by default. Unlike regular Windows application, services are running in an isolated session under the System account, and these are prohibited to interact with a logged in user.
If you are aware of PSexec, you know that the tool is used to connect to the System account in order to test different scenarios.However, the tool offers the -i parameter, which allows user interaction.
However, that means that you cannot create a Windows Service that throws any GUI messages for the user, which means that everything will be happening silently in the background without the user knowledge.
But, because a Windows Service has the ability to run in the System account, it means it has access to install per-machine applications without asking the user for any credentials or admin rights, which is exactly what we are trying to achieve here.
How to create a Windows Service in C#
Now that we know how Windows Services operate, its time to get busy and start doing one.
To do this, open Visual Studio and search for Windows Service (.NET Framework)
Next, give it a desired name and select the desired Framework. In our case I opted for .NET Framework 4.6 which comes installed by default with Windows 10
Once the project is loaded, right click the blank area in the Service1.cs[Design] and select Add Installer
After we added the installer, a new ProjectInstaller.cs[Design] will be added into the project and the solution explorer will look something like this
With the ProjectInstaller.cs opened, right-click the blank area and click View Code or press F7
Once the code is opened, we can see that it has a constructor which contains the InitializeComponent method. This contains to logic which created and initializes the user interface object dragged on the forming surface and provides the property grid of the Form Designed.
As a special mention, never call another method before the call of the Initialize component().
Next, select the InitializeComponent and press F12 to go to the definition
Once you pressed F12, you should see the following
Next, add the following lines:
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceInstaller1.Description = “Update the MSI silently”;
this.serviceInstaller1.DisplayName = “UpdateApplication”;
Next, in the Solution Explorer on the right, double-click on Service1.cs, and when the page loads press F7 to view the code. You should have the InitializeComponent visible and the OnStart and OnStop methods
Congratulations, you now have the basics on how to create a Windows Service. Now it’s time to add our code to it.
How to configure the download and install of an MSI in the Windows Service at a particular interval
Now that we have the service configured, its time to add our code to it. The first thing we need to do is add a Timer which we will configure it to run at a certain interval.
What we did is declare the timer object, and OnStart of the service, at every 2 elapsed minutes, we are going to call the OnElapsedTime function.
I also want to log what the service is doing doing its running timeline, so I added a function which will write in a file.
Its finally time to add the code which will download the MSI from the website and start the installation. This code will be added in the OnElapsedTime function which will be triggered every 2 minutes
I also added some more logging details in the OnStart and OnStop functions.
Now that everything is ready, save your project (CTRL+S), then in the Solution Explorer, right-click UpdateInstaller (or whatever name you placed there) and click on Rebuild.
Once the rebuild is finished, right-click and select Open folder in File Explorer
Once the file explorer is opened, navigate to the bin > Debug folder and you should have your exported EXE
The full code for this Windows Service is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace UpdateApplication
{
public partial class Service1 : ServiceBase
{
Timer timer = new Timer(); // name space(using System.Timers;)
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
timer.Interval = (60 * 2000); //number in milisecinds
timer.Enabled = true;
}
protected override void OnStop()
{
}
public void WriteToFile(string Message)
{
string path = Environment.ExpandEnvironmentVariables(“%windir%”) + “\\Temp\\Logs”;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string filepath = Environment.ExpandEnvironmentVariables(“%windir%”) + “\\Temp\\Logs\\ServiceLog_de_test.txt”;
if (!File.Exists(filepath))
{
// Create a file to write to.
using (StreamWriter sw = File.CreateText(filepath))
{
sw.WriteLine(Message);
}
}
else
{
using (StreamWriter sw = File.AppendText(filepath))
{
sw.WriteLine(Message);
}
}
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
WriteToFile(“Downloading and installing new app: ” + DateTime.Now);
WebClient Client = new WebClient();
Client.DownloadFile(“https://www.alexandrumarin.com/download/AppInstalledviaServices.msi”, @”C:\temp\AppInstalledviaServices.msi”);
Process installerProcess = new Process();
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.Arguments = @”/i C:\temp\AppInstalledviaServices.msi /qn /l*v C:\temp\AppInstalledviaServices.log”;
processInfo.FileName = “msiexec”;
installerProcess.StartInfo = processInfo;
installerProcess.Start();
installerProcess.WaitForExit();
WriteToFile(“Installation exit code: ” + installerProcess.ExitCode);
}
}
}
How to install the service by using the InstallUtil.exe
Now that our Windows Service is created, we need to test it. There are multiple 3rd party tools out there to manipulate services, however I am going to show you a simple method by using the InstallUtil.exe present in the OS.
First, open a CMD as administrator and navigate to the following folder:
cd C:\Windows\Microsoft.NET\Framework\v4.0.30319
Next, run the InstallUtil.exe utility like this:
InstallUtil.exe + Path To the service + \your service name + .exe
Example: InstallUtil.exe C:\Users\theje\source\repos\UpdateApplication\UpdateApplication\bin\Debug\UpdateApplication.exe
And that is it, the service is now installed, however this is not started. If you want to start the service, click on the Start then type services.msc and search for the service
Then right-click the service and click on Start
To uninstall the service using InstallUtil.exe, you can use the following command line:
InstallUtil.exe -u + Path To the service + \your service name + .exe
Example: InstallUtil.exe -u C:\Users\theje\source\repos\UpdateApplication\UpdateApplication\bin\Debug\UpdateApplication.exe
Test the service
Now that the service is installed and running, it is time to log in on a user that doesn’t have administrative rights. I will post the following GIF showing how the service is running:
As you can see, after a while, the service download the MSI from my website and starts the installation without asking the user for admin rights. In the function I have also added a separate log for the MSI, but the exit code of the operation is also written in the general Windows Service log that we create.
Conclusion
This is an excellent way to update your installers for the users without admin rights, and in my opinion it will bring so many benefits for the system administrators who will handle your application.
Of course, this service here cannot be considered reliable. At every 2 minutes we are downloading the MSI and install it, which is not ok. A solution to see if an updated version is present on the website should be considered, and only then the app should be updated. It would also be nice to check if the application is running before starting the installation.