Hi Josh,
After a lot of digging around, I think I have partial solution; NOTE - This is my first attempt at writing ruby so I expect there are some issues with what I've written. I only had a single host (Server 2008 R2 64bit) to test this on, but I believe the changes I've made are generic enough to work on all puppet supported MS Operating Systems, 32 or 64bit. While the CMD.EXE is not required by the Agent once running, there Service Control Manager is still monitoring that process and if it dies, it will say the service is not running, even though the orphaned the RUBY.EXE process is still running. WINDOWS SERVICE CONFIG The process for the service doesn't need the entire environment as that of puppet, in order run. The way I see it, the service needs enough information to act as a Windows Service and to spawn child processes of Puppet. The Puppet.BAT calls Environment.BAT which does all the work to setup the environment variables on a per call basis. So what I did is change the ImagePath of the pe-puppet service to call ruby directly; HKLM\System\CurrentControlSet\Services\pe-puppet\ImagePath; FROM: "C:\Program Files (x86)\Puppet Labs\Puppet Enterprise\service\daemon.bat" TO: "C:\Program Files (x86)\Puppet Labs\Puppet Enterprise\sys\ruby\bin\rubyw.exe" -C"C:\Program Files (x86)\Puppet Labs\Puppet Enterprise\service" "C:\Program Files (x86)\Puppet Labs\Puppet Enterprise\service\daemon.rb" That is enough information for Ruby to run the service. Obviously the paths in these may differ depending on each host, BUT that can all be authored in the puppet MSI easily. DAEMON.RB I made some changes to daemon.rb (attached to this post); * I created a basic function for Windows EventLog logging Puppet Bug #21641. It doesn't register an application source so it's a bit of a hack and could really do with a more professional cleanup. * I fixed up the behaiour of Puppet Agent terminating once Paused Bug #22972 * A side effect of not running the daemon from a CMD.EXE was that the call to get to runinterval was failing. I suspect this is due to STDOUT not being available anymore. So I used the well worn method of pipe the output to a file and read that instead (Lines 60-79). I still need to try RUBY.EXE instead of RUBYW.EXE and see if it makes a difference. * I put the Puppet Agent run in an IF statement, which will only evaluate as true if the service is in a RUNNING or IDLE state (Lines 81-86) * I think may have found a bug in the Win32 Daemon code which was taking the service out of PAUSED and put it into a RUNNING state whenever a SERVICE_INTERROGATE event is recieved. I need to log this with the authours. (Lines 108-119). * I added in a little extra logging in the Resume and Pause events. I changed some of the wording in the main loop to reduce any confusion about "Service Resuming" Glenn. -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to puppet-users+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/puppet-users/f540d5d5-f21b-455f-8ce9-a561cac3ef79%40googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
#!/usr/bin/env ruby require 'fileutils' require 'win32/daemon' require 'win32/dir' require 'win32/process' require 'win32/eventlog' require 'windows/synchronize' require 'windows/handle' class WindowsDaemon < Win32::Daemon include Windows::Synchronize include Windows::Handle include Windows::Process LOG_FILE = File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'puppet', 'var', 'log', 'windows.log')) LEVELS = [:debug, :info, :notice, :err] LEVELS.each do |level| define_method("log_#{level}") do |msg| log(msg, level) end end def service_init FileUtils.mkdir_p(File.dirname(LOG_FILE)) end def service_main(*argv) args = argv.join(' ') @loglevel = LEVELS.index(argv.index('--debug') ? :debug : :notice) log_notice("Starting service: #{args}") puppetpid = -1 basedir = File.expand_path(File.join(File.dirname(__FILE__), '..')) puppet = File.join(basedir, 'bin', 'puppet.bat') raise_windows_event(Win32::EventLog::INFO,0x01,"Starting Puppet Agent using Puppet: #{puppet}") while running? do return if !running? log_notice('Service is running') unless File.exists?(puppet) log_err("File not found: '#{puppet}'") raise_windows_event(Win32::EventLog::ERROR,0x02,"Could not find Puppet: #{puppet}") return end return if !running? log_debug("Using '#{puppet}'") begin #DEBUG #runinterval = %x{ "#{puppet}" agent --configprint runinterval }.to_i # Stdout redirection seems to fail when using this service without a parent CMD.EXE. Using the old method of pipe to text file and read it. # Ineffecient, but it works. runinterval = 0 tempfile = "#{ENV['TEMP']}\\puppetinterval.tmp" if (File.exists?(tempfile)) File.delete(tempfile) end temppid = Process.create(:command_line => "#{ENV['COMSPEC']} /C \"#{puppet}\" agent --configprint runinterval > #{tempfile}").process_id Process.waitpid(temppid) if (!File.exists?(tempfile)) log_err("Agent failed to write runinterval to stdout") else runinterval = (File.read(tempfile)).to_i end if runinterval == 0 runinterval = 1800 log_err("Failed to determine runinterval, defaulting to #{runinterval} seconds") end rescue Exception => e log_exception(e) runinterval = 1800 end if (state == RUNNING || state == IDLE) puppetpid = Process.create(:command_line => "\"#{puppet}\" agent --onetime #{args}").process_id log_debug("Process created: #{puppetpid}") else log_debug("Service is not in a state to start Puppet") end log_debug("Service waiting for #{runinterval} seconds") sleep(runinterval) log_debug('Service woken up') end # TODO: Check if puppetpid is still running. If so raise a warning in the eventlog and log. Do I let the Puppet run continue or kill the process? # If you kill the process, it will only kill the CMD.EXE, not the child RUBY process. # Use Win32::Process.kill(0,puppetpid) to see if it's alive log_notice('Service stopped') rescue Exception => e log_exception(e) end def service_stop log_notice('Service stopping') Thread.main.wakeup end def service_pause # I don't know why it does, but the service state eventually comes out of Paused and goes into Running # I suspect this is more of a Ruby Win32Daemon issue than this script. # # Yep, confirmed: # From the Win32 Services Gem; daemon.c # ...Program Files (x86)\Puppet Labs\Puppet Enterprise\sys\ruby\lib\ruby\gems\1.9.1\gems\win32-service-0.7.2-x86-mingw32\ext\win32\daemon.c # Line 240: // Set the status of the service. # Line 241: SetTheServiceStatus(dwState, NO_ERROR, 0, 0); # # The preceding switch statement sets the dwState to RUNNING when a SERVICE_INTERROGATE event occurs, which is about every 60 seconds and then tells the SCM that this service is RUNNING # This is a fairly old version of the Win32 Daemon. v0.8.2 has been released but it looks like it has the same logic flow (Lines 107 to 132) # I need to raise a bug report in the Win32 Daemon Gem for this. log_notice('Service pausing. The service will not stay paused and will eventually go back into a running state.') end def service_resume log_notice('Service resuming') end def service_shutdown log_notice('Host is shutting down') end # Interrogation handler is just for debug. Can be commented out or removed entirely. def service_interrogate log_debug('Service is being intertogated') end def log_exception(e) log_err(e.message) log_err(e.backtrace.join("\n")) raise_windows_event(Win32::EventLog::ERROR,0x02,e.message) end def log(msg, level) if LEVELS.index(level) >= @loglevel File.open(LOG_FILE, 'a') { |f| f.puts("#{Time.now} Puppet (#{level}): #{msg}") } end end def raise_windows_event(type,id,message) begin eventlog = Win32::EventLog.open("Application") eventlog.report_event( :source => "Puppet Agent", :event_type => type, # Win32::EventLog::INFO or WARN, ERROR :event_id => id, # 0x01 or 0x02, 0x03 etc. :data => message # "the message" ) eventlog.close rescue Exception => e # Ignore all errors end end end if __FILE__ == $0 WindowsDaemon.mainloop end