JFLarvoire / SysToolsLib

A library of Windows and Linux system management tools
Apache License 2.0
314 stars 93 forks source link

PSService.ps1 finally clause #1

Closed KienHui closed 8 years ago

KienHui commented 8 years ago

Finally clause never triggered. Therefore, script can't cleanup after itself, such as killing any child processes it created.

JFLarvoire commented 8 years ago

Hello, Which version of Windows are you running? What commands did you run to start then stop the service?

KienHui commented 8 years ago

Win Server 2008R2 Standard, with Powershell V2. For various reasons, I'm mostly likely stuck with this configuration of OS and Powershell. Commands were: .\service.ps1 -start and .\service.ps1 -stop

The service block looks like so:

if ($Service) {                 # Run the service
  Write-EventLog -LogName $logName -Source $serviceName -EventId 1005 -EntryType Information -Message "$scriptName -Service # Beginning background job"
  Log "$scriptName -Service # Beginning background job"
  # Do the service background job
  try {
    ############ TO DO: Implement your own service code here.  #############
    ## Starts perl script, which runs forever. Then sleep until stopped ##
    cd $scriptpath
    $process = start-process perl $script -PassThru #save the process info
    while ($true) { # Keep running until killed by the -Stop handler
      Start-Sleep 3600 # just keeps the powershell script from spinning too fast.
    }
  } catch { # An exception occurred while runnning the service
    $msg = $_.Exception.Message
    Log "$script -Service # Error: $msg"
  } finally { # Invoked in all cases: Exception or killing by -Stop
    #find the perl process with script
    Log "$scriptName -Stop: Stopping PID $($process.Id)"
    Stop-Process -Id $process.Id -Force
    # Record a termination event, no matter what the cause is.
    Write-EventLog -LogName $logName -Source $serviceName -EventId 1006 -EntryType Information -Message "$script -Service # Exiting"
    Log "$script -Service # Exiting"
  }
  return
}

And I find when it's stopped, the perl executable is hanging around as a semi-zombie process. I need the perl script to run on system startup, but still want to kill it just by hitting stop on the service. And unfortunately, I can't modify the perl script to not run forever.

KienHui commented 8 years ago

More intriguingly, When I run Get-WmiObject Win32_Process -filter "Name = 'powershell.exe'" Manually, using an administrator instance of PS, none of the processes have values for CommandLine, excepting the instance I'm running in.

Which likely means the Powershell script itself is going zombie. Looking closer at the log file, Log "$scriptName -Stop: Stopping PID $($process.ProcessId)" Never gets executed, which indicates that $processes, set a couple lines above, is always empty.

JFLarvoire commented 8 years ago

You're right, the finally block is not invoked when the task is killed. It was invoked when I did early tests with multiple script instances in multiple PowerShell windows, and I killed one script instance using Ctrl-C. But not anymore in the final script version using a task termination command. :-( I'll have to find another way to send the termination request. Maybe using a pipe? I'll need to investigate. Any suggestion of a clean and simple mechanism welcome!

KienHui commented 8 years ago

start and stop commands to the service stub run powershell, which then does something with another powershell instance. If the service stub started the powershell service code, then stashed the process id, then when the service stub got the stop command, it could use the pid it previously stashed away. I think using "CloseMainWindow" will cause powershell to exit gracefully, but I'm not sure if it works here.

JFLarvoire commented 8 years ago

I've pushed an update yesterday that fixes this issue.

Note that this issue is due to an over-simplification of the sample code initially published for the MSDN magazine article. I tried to make it as lean and clean as possible, but went too far! Earlier unpublished versions of the code were more complex, but did not have the issue.

Anyway I’ve been experimenting with various solutions for a month… But unfortunately I could not find any simple one. :-( Eventually I chose to do the following:

Drawback of all that: The PSService.ps1 sample code is now significantly longer than before. Advantage of all that: I’ve added thread management routines (worth another article :-)) that can be reused for other service needs. If your service implementation cannot easily be made event driven, then put it in its own thread, using the named pipeline handler thread as an example.