module Puppet::Util::Windows::Service

This module is designed to provide an API between the windows system and puppet for service management.

for an overview of the service state transitions see: docs.microsoft.com/en-us/windows/desktop/Services/service-status-transitions

Constants

DEFAULT_TIMEOUT

integer value of the floor for timeouts when waiting for service pending states. puppet will wait the length of dwWaitHint if it is longer than this value, but no shorter

Public Class Methods

exists?(service_name) click to toggle source

Returns true if the service exists, false otherwise.

@param [String] service_name name of the service

   # File lib/puppet/util/windows/service.rb
29 def exists?(service_name)
30   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
31     true
32   end
33 rescue Puppet::Util::Windows::Error => e
34   return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST
35   raise e
36 end
logon_account(service_name) click to toggle source

Query the configuration of a service using QueryServiceConfigW to find its current logon account

@return [String] logon_account account currently set for the service's logon

in the format "DOMAIN\Account" or ".\Account" if it's a local account
    # File lib/puppet/util/windows/service.rb
154 def logon_account(service_name)
155   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
156     query_config(service) do |config|
157       return config[:lpServiceStartName].read_arbitrary_wide_string_up_to(Puppet::Util::Windows::ADSI::User::MAX_USERNAME_LENGTH)
158     end
159   end
160 end
resume(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Resume a paused windows service

@param [String] service_name name of the service to resume @param optional [Integer] :timeout the minumum number of seconds to wait before timing out

    # File lib/puppet/util/windows/service.rb
 83 def resume(service_name, timeout: DEFAULT_TIMEOUT)
 84   Puppet.debug _("Resuming the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
 85 
 86   valid_initial_states = [
 87     SERVICE_PAUSE_PENDING,
 88     SERVICE_PAUSED,
 89     SERVICE_CONTINUE_PENDING
 90   ]
 91 
 92   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
 93     # The SERVICE_CONTROL_CONTINUE signal can only be sent when
 94     # the service is in the SERVICE_PAUSED state
 95     wait_on_pending_state(service, SERVICE_PAUSE_PENDING, timeout)
 96 
 97     send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
 98   end
 99 
100   Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
101 end
send_service_control_signal(service, signal) click to toggle source

@api private Sends a service control signal to a service

@param [:handle] service handle to the service @param [Integer] signal the service control signal to send

    # File lib/puppet/util/windows/service.rb
564 def send_service_control_signal(service, signal)
565   FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
566     status = SERVICE_STATUS.new(status_ptr)
567     if ControlService(service, signal, status) == FFI::WIN32_FALSE
568       raise Puppet::Util::Windows::Error, _("Failed to send the %{control_signal} signal to the service. Its current state is %{current_state}. Reason for failure:") % { control_signal: SERVICE_CONTROL_SIGNALS[signal], current_state: SERVICE_STATES[status[:dwCurrentState]] }
569     end
570   end
571 end
service_start_type(service_name) click to toggle source

Query the configuration of a service using QueryServiceConfigW or QueryServiceConfig2W

@param [String] service_name name of the service to query @return [QUERY_SERVICE_CONFIGW.struct] the configuration of the service

    # File lib/puppet/util/windows/service.rb
127 def service_start_type(service_name)
128   start_type = nil
129   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
130     query_config(service) do |config|
131       start_type = SERVICE_START_TYPES[config[:dwStartType]]
132     end
133   end
134   # if the service has type AUTO_START, check if it's a delayed service
135   if start_type == :SERVICE_AUTO_START
136     open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
137       query_config2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO) do |config|
138         return :SERVICE_DELAYED_AUTO_START if config[:fDelayedAutostart] == 1
139       end
140     end
141   end
142   if start_type.nil?
143     raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name})
144   end
145   start_type
146 end
service_state(service_name) click to toggle source

Query the state of a service using QueryServiceStatusEx

@param [string] service_name name of the service to query @return [string] the status of the service

    # File lib/puppet/util/windows/service.rb
108 def service_state(service_name)
109   state = nil
110   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
111     query_status(service) do |status|
112       state = SERVICE_STATES[status[:dwCurrentState]]
113     end
114   end
115   if state.nil?
116     raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name})
117   end
118   state
119 end
services() click to toggle source

enumerate over all services in all states and return them as a hash

@return [Hash] a hash containing services:

{ 'service name' => {
                      'display_name' => 'display name',
                      'service_status_process' => SERVICE_STATUS_PROCESS struct
                    }
}
    # File lib/puppet/util/windows/service.rb
212 def services
213   services = {}
214   open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
215     size_required = 0
216     services_returned = 0
217     FFI::MemoryPointer.new(:dword) do |bytes_pointer|
218       FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
219         FFI::MemoryPointer.new(:dword) do |resume_ptr|
220           resume_ptr.write_dword(0)
221           # Fetch the bytes of memory required to be allocated
222           # for QueryServiceConfigW to return succesfully. This
223           # is done by sending NULL and 0 for the pointer and size
224           # respectively, letting the command fail, then reading the
225           # value of pcbBytesNeeded
226           #
227           # return value will be false from this call, since it's designed
228           # to fail. Just ignore it
229           EnumServicesStatusExW(
230             scm,
231             :SC_ENUM_PROCESS_INFO,
232             ALL_SERVICE_TYPES,
233             SERVICE_STATE_ALL,
234             FFI::Pointer::NULL,
235             0,
236             bytes_pointer,
237             svcs_ret_ptr,
238             resume_ptr,
239             FFI::Pointer::NULL
240           )
241           size_required = bytes_pointer.read_dword
242           FFI::MemoryPointer.new(size_required) do |buffer_ptr|
243             resume_ptr.write_dword(0)
244             svcs_ret_ptr.write_dword(0)
245             success = EnumServicesStatusExW(
246               scm,
247               :SC_ENUM_PROCESS_INFO,
248               ALL_SERVICE_TYPES,
249               SERVICE_STATE_ALL,
250               buffer_ptr,
251               buffer_ptr.size,
252               bytes_pointer,
253               svcs_ret_ptr,
254               resume_ptr,
255               FFI::Pointer::NULL
256             )
257             if success == FFI::WIN32_FALSE
258               raise Puppet::Util::Windows::Error.new(_("Failed to fetch services"))
259             end
260             # Now that the buffer is populated with services
261             # we pull the data from memory using pointer arithmetic:
262             # the number of services returned by the function is
263             # available to be read from svcs_ret_ptr, and we iterate
264             # that many times moving the cursor pointer the length of
265             # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
266             # over the buffer and extract each struct.
267             services_returned = svcs_ret_ptr.read_dword
268             cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
269             0.upto(services_returned - 1) do |index|
270               service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
271               services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
272                 :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
273                 :service_status_process => service[:ServiceStatusProcess]
274               }
275             end
276           end # buffer_ptr
277         end # resume_ptr
278       end # scvs_ret_ptr
279     end # bytes_ptr
280   end # open_scm
281   services
282 end
set_startup_configuration(service_name, options: {}) click to toggle source

Set the startup configuration of a windows service

@param [String] service_name the name of the service to modify @param [Hash] options the configuration to be applied. Expected option keys:

- [Integer] startup_type a code corresponding to a start type for
    windows service, see the "Service start type codes" section in the
    Puppet::Util::Windows::Service file for the list of available codes
- [String] logon_account the account to be used by the service for logon
- [String] logon_password the provided logon_account's password to be used by the service for logon
- [Bool] delayed whether the service should be started with a delay
    # File lib/puppet/util/windows/service.rb
173 def set_startup_configuration(service_name, options: {})
174   options[:startup_type] = SERVICE_START_TYPES.key(options[:startup_type]) || SERVICE_NO_CHANGE
175   options[:logon_account] = wide_string(options[:logon_account]) || FFI::Pointer::NULL
176   options[:logon_password] = wide_string(options[:logon_password]) || FFI::Pointer::NULL
177 
178   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
179     success = ChangeServiceConfigW(
180       service,
181       SERVICE_NO_CHANGE,        # dwServiceType
182       options[:startup_type],   # dwStartType
183       SERVICE_NO_CHANGE,        # dwErrorControl
184       FFI::Pointer::NULL,       # lpBinaryPathName
185       FFI::Pointer::NULL,       # lpLoadOrderGroup
186       FFI::Pointer::NULL,       # lpdwTagId
187       FFI::Pointer::NULL,       # lpDependencies
188       options[:logon_account],  # lpServiceStartName
189       options[:logon_password], # lpPassword
190       FFI::Pointer::NULL        # lpDisplayName
191     )
192     if success == FFI::WIN32_FALSE
193       raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration"))
194     end
195   end
196 
197   if options[:startup_type]
198     options[:delayed] ||= false
199     set_startup_mode_delayed(service_name, options[:delayed])
200   end
201 end
start(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Start a windows service

@param [String] service_name name of the service to start @param optional [Integer] timeout the minumum number of seconds to wait before timing out

   # File lib/puppet/util/windows/service.rb
43 def start(service_name, timeout: DEFAULT_TIMEOUT)
44   Puppet.debug _("Starting the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
45 
46   valid_initial_states = [
47     SERVICE_STOP_PENDING,
48     SERVICE_STOPPED,
49     SERVICE_START_PENDING
50   ]
51 
52   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
53     if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
54       raise Puppet::Util::Windows::Error, _("Failed to start the service")
55     end
56   end
57 
58   Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
59 end
stop(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Stop a windows service

@param [String] service_name name of the service to stop @param optional [Integer] timeout the minumum number of seconds to wait before timing out

   # File lib/puppet/util/windows/service.rb
66 def stop(service_name, timeout: DEFAULT_TIMEOUT)
67   Puppet.debug _("Stopping the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
68 
69   valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
70 
71   transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED, timeout) do |service|
72     send_service_control_signal(service, SERVICE_CONTROL_STOP)
73   end
74 
75   Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
76 end

Private Class Methods

milliseconds_to_seconds(wait_hint) click to toggle source

@api private

process the wait hint listed by a service to something usable by ruby sleep

@param [Integer] wait_hint the wait hint of a service in milliseconds @return [Integer] wait_hint in seconds

    # File lib/puppet/util/windows/service.rb
695 def milliseconds_to_seconds(wait_hint)
696   wait_hint / 1000;
697 end
open_scm(scm_access) { |scm| ... } click to toggle source

@api private

Opens a handle to the service control manager

@param [Integer] scm_access code corresponding to the access type requested for the scm

    # File lib/puppet/util/windows/service.rb
322 def open_scm(scm_access, &block)
323   scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access)
324   raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service control manager")) if scm == FFI::Pointer::NULL_HANDLE
325   yield scm
326 ensure
327   CloseServiceHandle(scm)
328 end
open_service(service_name, scm_access, service_access) { |service| ... } click to toggle source

@api private Opens a connection to the SCManager on windows then uses that handle to create a handle to a specific service in windows corresponding to service_name

this function takes a block that executes within the context of the open service handler, and will close the service and SCManager handles once the block finishes

@param [string] service_name the name of the service to open @param [Integer] scm_access code corresponding to the access type requested for the scm @param [Integer] service_access code corresponding to the access type requested for the service @yieldparam [:handle] service the windows native handle used to access

the service

@return the result of the block

    # File lib/puppet/util/windows/service.rb
301 def open_service(service_name, scm_access, service_access, &block)
302   service = FFI::Pointer::NULL_HANDLE
303 
304   result = nil
305   open_scm(scm_access) do |scm|
306     service = OpenServiceW(scm, wide_string(service_name), service_access)
307     raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service")) if service == FFI::Pointer::NULL_HANDLE
308     result = yield service
309   end
310 
311   result
312 ensure
313   CloseServiceHandle(service)
314 end
query_config(service) { |config| ... } click to toggle source

@api private perform QueryServiceConfigW on a windows service and return the result

@param [:handle] service handle of the service to query @return [QUERY_SERVICE_CONFIGW struct] the result of the query

    # File lib/puppet/util/windows/service.rb
453 def query_config(service, &block)
454   config = nil
455   size_required = nil
456   # Fetch the bytes of memory required to be allocated
457   # for QueryServiceConfigW to return succesfully. This
458   # is done by sending NULL and 0 for the pointer and size
459   # respectively, letting the command fail, then reading the
460   # value of pcbBytesNeeded
461   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
462     # return value will be false from this call, since it's designed
463     # to fail. Just ignore it
464     QueryServiceConfigW(service, FFI::Pointer::NULL, 0, bytes_pointer)
465     size_required = bytes_pointer.read_dword
466     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
467       config = QUERY_SERVICE_CONFIGW.new(ssp_ptr)
468       success = QueryServiceConfigW(
469         service,
470         ssp_ptr,
471         size_required,
472         bytes_pointer
473       )
474       if success == FFI::WIN32_FALSE
475         raise Puppet::Util::Windows::Error.new(_("Service query failed"))
476       end
477       yield config
478     end
479   end
480 end
query_config2(service, info_level) { |config| ... } click to toggle source

@api private perform QueryServiceConfig2W on a windows service and return the result

@param [:handle] service handle of the service to query @param [Integer] info_level the configuration information to be queried @return [QUERY_SERVICE_CONFIG2W struct] the result of the query

    # File lib/puppet/util/windows/service.rb
490 def query_config2(service, info_level, &block)
491   config = nil
492   size_required = nil
493   # Fetch the bytes of memory required to be allocated
494   # for QueryServiceConfig2W to return succesfully. This
495   # is done by sending NULL and 0 for the pointer and size
496   # respectively, letting the command fail, then reading the
497   # value of pcbBytesNeeded
498   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
499     # return value will be false from this call, since it's designed
500     # to fail. Just ignore it
501     QueryServiceConfig2W(service, info_level, FFI::Pointer::NULL, 0, bytes_pointer)
502     size_required = bytes_pointer.read_dword
503     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
504       # We need to supply the appropriate struct to be created based on
505       # the info_level
506       case info_level
507       when SERVICE_CONFIG_DELAYED_AUTO_START_INFO
508         config = SERVICE_DELAYED_AUTO_START_INFO.new(ssp_ptr)
509       end
510       success = QueryServiceConfig2W(
511         service,
512         info_level,
513         ssp_ptr,
514         size_required,
515         bytes_pointer
516       )
517       if success == FFI::WIN32_FALSE
518         raise Puppet::Util::Windows::Error.new(_("Service query for %{parameter_name} failed") % { parameter_name: SERVICE_CONFIG_TYPES[info_level] } )
519       end
520       yield config
521     end
522   end
523 end
query_status(service) { |status| ... } click to toggle source

@api private perform QueryServiceStatusEx on a windows service and return the result

@param [:handle] service handle of the service to query @return [SERVICE_STATUS_PROCESS struct] the result of the query

    # File lib/puppet/util/windows/service.rb
410 def query_status(service)
411   size_required = nil
412   status = nil
413   # Fetch the bytes of memory required to be allocated
414   # for QueryServiceConfigW to return succesfully. This
415   # is done by sending NULL and 0 for the pointer and size
416   # respectively, letting the command fail, then reading the
417   # value of pcbBytesNeeded
418   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
419     # return value will be false from this call, since it's designed
420     # to fail. Just ignore it
421     QueryServiceStatusEx(
422       service,
423       :SC_STATUS_PROCESS_INFO,
424       FFI::Pointer::NULL,
425       0,
426       bytes_pointer
427     )
428     size_required = bytes_pointer.read_dword
429     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
430       status = SERVICE_STATUS_PROCESS.new(ssp_ptr)
431       success = QueryServiceStatusEx(
432         service,
433         :SC_STATUS_PROCESS_INFO,
434         ssp_ptr,
435         size_required,
436         bytes_pointer
437       )
438       if success == FFI::WIN32_FALSE
439         raise Puppet::Util::Windows::Error.new(_("Service query failed"))
440       end
441       yield status
442     end
443   end
444 end
set_optional_parameter(service_name, change, value) click to toggle source

@api private Sets an optional parameter on a service by calling ChangeServiceConfig2W

@param [String] service_name name of service @param [Integer] change parameter to change @param [struct] value appropriate struct based on the parameter to change

    # File lib/puppet/util/windows/service.rb
533 def set_optional_parameter(service_name, change, value)
534   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
535     success = ChangeServiceConfig2W(
536       service,
537       change, # dwInfoLevel
538       value,  # lpInfo
539     )
540     if success == FFI::WIN32_FALSE
541       raise Puppet::Util::windows::Error.new(_("Failed to update service %{change} configuration") % { change: change } )
542     end
543   end
544 end
set_startup_mode_delayed(service_name, delayed) click to toggle source

@api private Controls the delayed auto-start setting of a service

@param [String] service_name name of service @param [Bool] delayed whether the service should be started with a delay or not

    # File lib/puppet/util/windows/service.rb
552 def set_startup_mode_delayed(service_name, delayed)
553   delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
554   delayed_start[:fDelayedAutostart] = delayed
555   set_optional_parameter(service_name, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayed_start)
556 end
transition_service_state(service_name, valid_initial_states, final_state, timeout) { |service| ... } click to toggle source

@api private Transition the service to the specified state. The block should perform the actual transition.

@param [String] service_name the name of the service to transition @param [[Integer]] valid_initial_states an array of valid states that the service can transition from @param [Integer] final_state the state that the service will transition to @param [Integer] timeout the minumum number of seconds to wait before timing out

    # File lib/puppet/util/windows/service.rb
339 def transition_service_state(service_name, valid_initial_states, final_state, timeout, &block)
340   service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS
341   open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service|
342     query_status(service) do |status|
343       initial_state = status[:dwCurrentState]
344       # If the service is already in the final_state, then
345       # no further work needs to be done
346       if initial_state == final_state
347         Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] }
348 
349         next
350       end
351 
352       # Check that initial_state corresponds to a valid
353       # initial state
354       unless valid_initial_states.include?(initial_state)
355         valid_initial_states_str = valid_initial_states.map do |state|
356           SERVICE_STATES[state]
357         end.join(", ")
358 
359         raise Puppet::Error, _("The service must be in one of the %{valid_initial_states} states to perform this transition. It is currently in the %{current_state} state.") % { valid_initial_states: valid_initial_states_str, current_state: SERVICE_STATES[initial_state] }
360       end
361 
362       # Check if there's a pending transition to the final_state. If so, then wait for
363       # that transition to finish.
364       possible_pending_states = FINAL_STATES.keys.select do |pending_state|
365         # SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and
366         # SERVICE_CONTINUE_PENDING. That is why we need the #select here
367         FINAL_STATES[pending_state] == final_state
368       end
369       if possible_pending_states.include?(initial_state)
370         Puppet.debug _("There is already a pending transition to the %{final_state} state for the %{service_name} service.")  % { final_state: SERVICE_STATES[final_state], service_name: service_name }
371         wait_on_pending_state(service, initial_state, timeout)
372 
373         next
374       end
375 
376       # If we are in an unsafe pending state like SERVICE_START_PENDING
377       # or SERVICE_STOP_PENDING, then we want to wait for that pending
378       # transition to finish before transitioning the service state.
379       # The reason we do this is because SERVICE_START_PENDING is when
380       # the service thread is being created and initialized, while
381       # SERVICE_STOP_PENDING is when the service thread is being cleaned
382       # up and destroyed. Thus there is a chance that when the service is
383       # in either of these states, its service thread may not yet be ready
384       # to perform the state transition (it may not even exist).
385       if UNSAFE_PENDING_STATES.include?(initial_state)
386         Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] }
387         wait_on_pending_state(service, initial_state, timeout)
388         initial_state = FINAL_STATES[initial_state]
389       end
390 
391       Puppet.debug _("Transitioning the %{service_name} service from %{initial_state} to %{final_state}") % { service_name: service_name, initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state] }
392 
393       yield service
394 
395       Puppet.debug _("Waiting for the transition to finish")
396       wait_on_state_transition(service, initial_state, final_state, timeout)
397     end
398   end
399 rescue => detail
400   raise Puppet::Error, _("Failed to transition the %{service_name} service to the %{final_state} state. Detail: %{detail}") % { service_name: service_name, final_state: SERVICE_STATES[final_state], detail: detail }, detail.backtrace
401 end
wait_hint_to_wait_time(wait_hint) click to toggle source

@api private

create a usable wait time to wait between querying the service.

@param [Integer] wait_hint the wait hint of a service in milliseconds @return [Integer] the time to wait in seconds between querying the service

    # File lib/puppet/util/windows/service.rb
678 def wait_hint_to_wait_time(wait_hint)
679   # Wait 1/10th the wait_hint, but no less than 1 and
680   # no more than 10 seconds
681   wait_time = milliseconds_to_seconds(wait_hint) / 10;
682   wait_time = 1 if wait_time < 1
683   wait_time = 10 if wait_time > 10
684   wait_time
685 end
wait_on_pending_state(service, pending_state, timeout) click to toggle source

@api private Waits for a service to finish transitioning from a pending state. The service must be in the pending state before invoking this routine.

@param [:handle] service handle to the service to wait on @param [Integer] pending_state the pending state @param [Integer] timeout the minumum number of seconds to wait before timing out

    # File lib/puppet/util/windows/service.rb
629 def wait_on_pending_state(service, pending_state, timeout)
630   final_state = FINAL_STATES[pending_state]
631 
632   Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] }
633 
634   elapsed_time = 0
635   last_checkpoint = -1
636   loop do
637     query_status(service) do |status|
638       state = status[:dwCurrentState]
639       checkpoint = status[:dwCheckPoint]
640       wait_hint = status[:dwWaitHint]
641       # Check if our service has finished transitioning to
642       # the final_state OR if an unexpected transition
643       # has occurred
644       return if state == final_state
645       unless state == pending_state
646         raise Puppet::Error, _("Unexpected transition to the %{current_state} state while waiting for the pending transition from %{pending_state} to %{final_state} to finish.") % { current_state: SERVICE_STATES[state], pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state] }
647       end
648 
649       # Check if any progress has been made since our last sleep
650       # using the dwCheckPoint. If no progress has been made then
651       # check if we've timed out, and raise an error if so
652       if checkpoint > last_checkpoint
653         elapsed_time = 0
654         last_checkpoint = checkpoint
655       else
656         wait_hint = milliseconds_to_seconds(status[:dwWaitHint])
657         timeout = wait_hint < timeout ? timeout : wait_hint
658 
659         if elapsed_time >= timeout
660           raise Puppet::Error, _("Timed out while waiting for the pending transition from %{pending_state} to %{final_state} to finish. The current state is %{current_state}.") % { pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state], current_state: SERVICE_STATES[state] }
661         end
662       end
663       wait_time = wait_hint_to_wait_time(wait_hint)
664       # Wait a bit before rechecking the service's state
665       sleep(wait_time)
666       elapsed_time += wait_time
667     end
668   end
669 end
wait_on_state_transition(service, initial_state, final_state, timeout) click to toggle source

@api private Waits for a service to transition from one state to another state.

@param [:handle] service handle to the service to wait on @param [Integer] initial_state the state that the service is transitioning from. @param [Integer] final_state the state that the service is transitioning to @param [Integer] timeout the minumum number of seconds to wait before timing out

    # File lib/puppet/util/windows/service.rb
581 def wait_on_state_transition(service, initial_state, final_state, timeout)
582   # Get the pending state for this transition. Note that SERVICE_RUNNING
583   # has two possible pending states, which is why we need this logic.
584   if final_state != SERVICE_RUNNING
585     pending_state = FINAL_STATES.key(final_state)
586   elsif initial_state == SERVICE_STOPPED
587     # SERVICE_STOPPED => SERVICE_RUNNING
588     pending_state = SERVICE_START_PENDING
589   else
590     # SERVICE_PAUSED => SERVICE_RUNNING
591     pending_state = SERVICE_CONTINUE_PENDING
592   end
593 
594   # Wait for the transition to finish
595   state = nil
596   elapsed_time = 0
597   while elapsed_time <= timeout
598 
599     query_status(service) do |status|
600       state = status[:dwCurrentState]
601       return if state == final_state
602       if state == pending_state
603         Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] }
604         wait_on_pending_state(service, pending_state, timeout)
605         return
606       end
607       sleep(1)
608       elapsed_time += 1
609     end
610   end
611   # Timed out while waiting for the transition to finish. Raise an error
612   # We can still use the state variable read from the FFI struct because
613   # FFI creates new Integer objects during an assignment of an integer value
614   # stored in an FFI struct. We verified that the '=' operater is safe
615   # from the freed memory since the new ruby object created during the
616   # assignment will remain in ruby memory and remain immutable and constant.
617   raise Puppet::Error, _("Timed out while waiting for the service to transition from %{initial_state} to %{final_state} OR from %{initial_state} to %{pending_state} to %{final_state}. The service's current state is %{current_state}.") % { initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state], pending_state: SERVICE_STATES[pending_state], current_state: SERVICE_STATES[state] }
618 end

Private Instance Methods

exists?(service_name) click to toggle source

Returns true if the service exists, false otherwise.

@param [String] service_name name of the service

   # File lib/puppet/util/windows/service.rb
29 def exists?(service_name)
30   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
31     true
32   end
33 rescue Puppet::Util::Windows::Error => e
34   return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST
35   raise e
36 end
logon_account(service_name) click to toggle source

Query the configuration of a service using QueryServiceConfigW to find its current logon account

@return [String] logon_account account currently set for the service's logon

in the format "DOMAIN\Account" or ".\Account" if it's a local account
    # File lib/puppet/util/windows/service.rb
154 def logon_account(service_name)
155   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
156     query_config(service) do |config|
157       return config[:lpServiceStartName].read_arbitrary_wide_string_up_to(Puppet::Util::Windows::ADSI::User::MAX_USERNAME_LENGTH)
158     end
159   end
160 end
resume(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Resume a paused windows service

@param [String] service_name name of the service to resume @param optional [Integer] :timeout the minumum number of seconds to wait before timing out

    # File lib/puppet/util/windows/service.rb
 83 def resume(service_name, timeout: DEFAULT_TIMEOUT)
 84   Puppet.debug _("Resuming the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
 85 
 86   valid_initial_states = [
 87     SERVICE_PAUSE_PENDING,
 88     SERVICE_PAUSED,
 89     SERVICE_CONTINUE_PENDING
 90   ]
 91 
 92   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
 93     # The SERVICE_CONTROL_CONTINUE signal can only be sent when
 94     # the service is in the SERVICE_PAUSED state
 95     wait_on_pending_state(service, SERVICE_PAUSE_PENDING, timeout)
 96 
 97     send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
 98   end
 99 
100   Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
101 end
service_start_type(service_name) click to toggle source

Query the configuration of a service using QueryServiceConfigW or QueryServiceConfig2W

@param [String] service_name name of the service to query @return [QUERY_SERVICE_CONFIGW.struct] the configuration of the service

    # File lib/puppet/util/windows/service.rb
127 def service_start_type(service_name)
128   start_type = nil
129   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
130     query_config(service) do |config|
131       start_type = SERVICE_START_TYPES[config[:dwStartType]]
132     end
133   end
134   # if the service has type AUTO_START, check if it's a delayed service
135   if start_type == :SERVICE_AUTO_START
136     open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
137       query_config2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO) do |config|
138         return :SERVICE_DELAYED_AUTO_START if config[:fDelayedAutostart] == 1
139       end
140     end
141   end
142   if start_type.nil?
143     raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name})
144   end
145   start_type
146 end
service_state(service_name) click to toggle source

Query the state of a service using QueryServiceStatusEx

@param [string] service_name name of the service to query @return [string] the status of the service

    # File lib/puppet/util/windows/service.rb
108 def service_state(service_name)
109   state = nil
110   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
111     query_status(service) do |status|
112       state = SERVICE_STATES[status[:dwCurrentState]]
113     end
114   end
115   if state.nil?
116     raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name})
117   end
118   state
119 end
services() click to toggle source

enumerate over all services in all states and return them as a hash

@return [Hash] a hash containing services:

{ 'service name' => {
                      'display_name' => 'display name',
                      'service_status_process' => SERVICE_STATUS_PROCESS struct
                    }
}
    # File lib/puppet/util/windows/service.rb
212 def services
213   services = {}
214   open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
215     size_required = 0
216     services_returned = 0
217     FFI::MemoryPointer.new(:dword) do |bytes_pointer|
218       FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
219         FFI::MemoryPointer.new(:dword) do |resume_ptr|
220           resume_ptr.write_dword(0)
221           # Fetch the bytes of memory required to be allocated
222           # for QueryServiceConfigW to return succesfully. This
223           # is done by sending NULL and 0 for the pointer and size
224           # respectively, letting the command fail, then reading the
225           # value of pcbBytesNeeded
226           #
227           # return value will be false from this call, since it's designed
228           # to fail. Just ignore it
229           EnumServicesStatusExW(
230             scm,
231             :SC_ENUM_PROCESS_INFO,
232             ALL_SERVICE_TYPES,
233             SERVICE_STATE_ALL,
234             FFI::Pointer::NULL,
235             0,
236             bytes_pointer,
237             svcs_ret_ptr,
238             resume_ptr,
239             FFI::Pointer::NULL
240           )
241           size_required = bytes_pointer.read_dword
242           FFI::MemoryPointer.new(size_required) do |buffer_ptr|
243             resume_ptr.write_dword(0)
244             svcs_ret_ptr.write_dword(0)
245             success = EnumServicesStatusExW(
246               scm,
247               :SC_ENUM_PROCESS_INFO,
248               ALL_SERVICE_TYPES,
249               SERVICE_STATE_ALL,
250               buffer_ptr,
251               buffer_ptr.size,
252               bytes_pointer,
253               svcs_ret_ptr,
254               resume_ptr,
255               FFI::Pointer::NULL
256             )
257             if success == FFI::WIN32_FALSE
258               raise Puppet::Util::Windows::Error.new(_("Failed to fetch services"))
259             end
260             # Now that the buffer is populated with services
261             # we pull the data from memory using pointer arithmetic:
262             # the number of services returned by the function is
263             # available to be read from svcs_ret_ptr, and we iterate
264             # that many times moving the cursor pointer the length of
265             # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
266             # over the buffer and extract each struct.
267             services_returned = svcs_ret_ptr.read_dword
268             cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
269             0.upto(services_returned - 1) do |index|
270               service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
271               services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
272                 :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
273                 :service_status_process => service[:ServiceStatusProcess]
274               }
275             end
276           end # buffer_ptr
277         end # resume_ptr
278       end # scvs_ret_ptr
279     end # bytes_ptr
280   end # open_scm
281   services
282 end
set_startup_configuration(service_name, options: {}) click to toggle source

Set the startup configuration of a windows service

@param [String] service_name the name of the service to modify @param [Hash] options the configuration to be applied. Expected option keys:

- [Integer] startup_type a code corresponding to a start type for
    windows service, see the "Service start type codes" section in the
    Puppet::Util::Windows::Service file for the list of available codes
- [String] logon_account the account to be used by the service for logon
- [String] logon_password the provided logon_account's password to be used by the service for logon
- [Bool] delayed whether the service should be started with a delay
    # File lib/puppet/util/windows/service.rb
173 def set_startup_configuration(service_name, options: {})
174   options[:startup_type] = SERVICE_START_TYPES.key(options[:startup_type]) || SERVICE_NO_CHANGE
175   options[:logon_account] = wide_string(options[:logon_account]) || FFI::Pointer::NULL
176   options[:logon_password] = wide_string(options[:logon_password]) || FFI::Pointer::NULL
177 
178   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
179     success = ChangeServiceConfigW(
180       service,
181       SERVICE_NO_CHANGE,        # dwServiceType
182       options[:startup_type],   # dwStartType
183       SERVICE_NO_CHANGE,        # dwErrorControl
184       FFI::Pointer::NULL,       # lpBinaryPathName
185       FFI::Pointer::NULL,       # lpLoadOrderGroup
186       FFI::Pointer::NULL,       # lpdwTagId
187       FFI::Pointer::NULL,       # lpDependencies
188       options[:logon_account],  # lpServiceStartName
189       options[:logon_password], # lpPassword
190       FFI::Pointer::NULL        # lpDisplayName
191     )
192     if success == FFI::WIN32_FALSE
193       raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration"))
194     end
195   end
196 
197   if options[:startup_type]
198     options[:delayed] ||= false
199     set_startup_mode_delayed(service_name, options[:delayed])
200   end
201 end
start(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Start a windows service

@param [String] service_name name of the service to start @param optional [Integer] timeout the minumum number of seconds to wait before timing out

   # File lib/puppet/util/windows/service.rb
43 def start(service_name, timeout: DEFAULT_TIMEOUT)
44   Puppet.debug _("Starting the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
45 
46   valid_initial_states = [
47     SERVICE_STOP_PENDING,
48     SERVICE_STOPPED,
49     SERVICE_START_PENDING
50   ]
51 
52   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
53     if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
54       raise Puppet::Util::Windows::Error, _("Failed to start the service")
55     end
56   end
57 
58   Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
59 end
stop(service_name, timeout: DEFAULT_TIMEOUT) click to toggle source

Stop a windows service

@param [String] service_name name of the service to stop @param optional [Integer] timeout the minumum number of seconds to wait before timing out

   # File lib/puppet/util/windows/service.rb
66 def stop(service_name, timeout: DEFAULT_TIMEOUT)
67   Puppet.debug _("Stopping the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
68 
69   valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
70 
71   transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED, timeout) do |service|
72     send_service_control_signal(service, SERVICE_CONTROL_STOP)
73   end
74 
75   Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
76 end