Search code examples
c#batch-filewindows-servicesauto-update

how to have a windows service self update?


All the solutions I can find on this topic are very old and none of them appear to answer my question...

I am trying to create a windows service that can self update (or auto update by some external trigger). In the past, I had created a windows service that was installed with InstallShield and we were able to update auto update the service in a hacky way by making the service write a batch script to the local machine and then run the batch script, which would stop the service, overwrite the service executable and other files with the new ones, and restart the service. This surprisingly worked.

However, I have updated the service to use InstallUtil.exe and this auto update script no longer works... I assume it's something to do with the way InstallShield handles the service install vs how InstallUtil does it... but I can only make guesses as I don't fully understand what each is doing to the registry.

Since I can't just overwrite the files and restart the service with the InstallUtil method, I thought I'd write a batch script that runs sc.exe to stop the service, uninstall it entirely, write the new files, install the new service files, and then start it... unfortunately, I can't seem to get sc.exe to run from a windows service automatically because it requires admin permissions... I tried to force it to self-elevate to admin using this snippet, but it doesn't appear to work as a service (it works fine if I run it from command line not as a service)

if not "%1"=="am_admin" (powershell start -verb runas '%0' am_admin & exit /b)

Does anyone know how I can cause a windows service to self update? I can look into updating to a .NET Core Worker service if there is some method of self update in .NET Core that I'm unaware of... Any ideas are much appreciated... it really shouldn't be this hard to accomplish...

For reference, here is the batch script I am currently using (ignore odd variables and such as I am dynamically replacing some of them, it works great when launched manually, just doesn't work when the service tries to run it):

@echo off
setlocal enableextensions enabledelayedexpansion


::make sure to run whole script as admin (this restarts scripts as admin if not already in admin mode)
if not "%1"=="am_admin" (powershell start -verb runas '%0' am_admin & exit /b)
pushd %networkDirectory%

::stop running service
for /F "tokens=3 delims=: " %%H in ('sc query %serviceName% ^| findstr "        STATE"') do (
    if /I "%%H" NEQ "STOPPED" (
        net stop %serviceName%
        if errorlevel 1 goto :stop
    )

    ::delete existing service after stopping
    sc delete %serviceName%
)

:: install updated service files
set "releaseDir=%networkDirectory%\Release"
set "programFilesCopyDir=%ProgramFiles%\{_companyDirectory}\%serviceName%\Release"

:: copy service Release dir to local system program files
xcopy "%releaseDir%" "%programFilesCopyDir%" /S /Y /Q

::execute the install
pushd "%programFilesCopyDir%"
CALL %serviceName%.exe --install

::start service
sc start %serviceName%

Solution

  • For anyone else trying to accomplish this that stumbles on this... I ended up finding a solution. I use the same script posted in my question above, but I wrote code to set up a scheduled task with Windows Task Scheduler. The scheduled task runs the above script as a one time scheduled task. This works like a charm.

    I used this NuGet package to write the Task Scheduler code I needed: https://www.nuget.org/packages/TaskScheduler/2.8.20?_src=template