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

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
25 def exists?(service_name)
26   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
27     true
28   end
29 rescue Puppet::Util::Windows::Error => e
30   return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST
31   raise e
32 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
150 def logon_account(service_name)
151   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
152     query_config(service) do |config|
153       return config[:lpServiceStartName].read_arbitrary_wide_string_up_to(Puppet::Util::Windows::ADSI::User::MAX_USERNAME_LENGTH)
154     end
155   end
156 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
79 def resume(service_name, timeout: DEFAULT_TIMEOUT)
80   Puppet.debug _("Resuming the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
81 
82   valid_initial_states = [
83     SERVICE_PAUSE_PENDING,
84     SERVICE_PAUSED,
85     SERVICE_CONTINUE_PENDING
86   ]
87 
88   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
89     # The SERVICE_CONTROL_CONTINUE signal can only be sent when
90     # the service is in the SERVICE_PAUSED state
91     wait_on_pending_state(service, SERVICE_PAUSE_PENDING, timeout)
92 
93     send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
94   end
95 
96   Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
97 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
560 def send_service_control_signal(service, signal)
561   FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
562     status = SERVICE_STATUS.new(status_ptr)
563     if ControlService(service, signal, status) == FFI::WIN32_FALSE
564       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]] }
565     end
566   end
567 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
123 def service_start_type(service_name)
124   start_type = nil
125   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
126     query_config(service) do |config|
127       start_type = SERVICE_START_TYPES[config[:dwStartType]]
128     end
129   end
130   # if the service has type AUTO_START, check if it's a delayed service
131   if start_type == :SERVICE_AUTO_START
132     open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
133       query_config2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO) do |config|
134         return :SERVICE_DELAYED_AUTO_START if config[:fDelayedAutostart] == 1
135       end
136     end
137   end
138   if start_type.nil?
139     raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name})
140   end
141   start_type
142 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
104 def service_state(service_name)
105   state = nil
106   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
107     query_status(service) do |status|
108       state = SERVICE_STATES[status[:dwCurrentState]]
109     end
110   end
111   if state.nil?
112     raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name})
113   end
114   state
115 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
208 def services
209   services = {}
210   open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
211     size_required = 0
212     services_returned = 0
213     FFI::MemoryPointer.new(:dword) do |bytes_pointer|
214       FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
215         FFI::MemoryPointer.new(:dword) do |resume_ptr|
216           resume_ptr.write_dword(0)
217           # Fetch the bytes of memory required to be allocated
218           # for QueryServiceConfigW to return succesfully. This
219           # is done by sending NULL and 0 for the pointer and size
220           # respectively, letting the command fail, then reading the
221           # value of pcbBytesNeeded
222           #
223           # return value will be false from this call, since it's designed
224           # to fail. Just ignore it
225           EnumServicesStatusExW(
226             scm,
227             :SC_ENUM_PROCESS_INFO,
228             ALL_SERVICE_TYPES,
229             SERVICE_STATE_ALL,
230             FFI::Pointer::NULL,
231             0,
232             bytes_pointer,
233             svcs_ret_ptr,
234             resume_ptr,
235             FFI::Pointer::NULL
236           )
237           size_required = bytes_pointer.read_dword
238           FFI::MemoryPointer.new(size_required) do |buffer_ptr|
239             resume_ptr.write_dword(0)
240             svcs_ret_ptr.write_dword(0)
241             success = EnumServicesStatusExW(
242               scm,
243               :SC_ENUM_PROCESS_INFO,
244               ALL_SERVICE_TYPES,
245               SERVICE_STATE_ALL,
246               buffer_ptr,
247               buffer_ptr.size,
248               bytes_pointer,
249               svcs_ret_ptr,
250               resume_ptr,
251               FFI::Pointer::NULL
252             )
253             if success == FFI::WIN32_FALSE
254               raise Puppet::Util::Windows::Error.new(_("Failed to fetch services"))
255             end
256             # Now that the buffer is populated with services
257             # we pull the data from memory using pointer arithmetic:
258             # the number of services returned by the function is
259             # available to be read from svcs_ret_ptr, and we iterate
260             # that many times moving the cursor pointer the length of
261             # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
262             # over the buffer and extract each struct.
263             services_returned = svcs_ret_ptr.read_dword
264             cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
265             0.upto(services_returned - 1) do |index|
266               service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
267               services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
268                 :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
269                 :service_status_process => service[:ServiceStatusProcess]
270               }
271             end
272           end # buffer_ptr
273         end # resume_ptr
274       end # scvs_ret_ptr
275     end # bytes_ptr
276   end # open_scm
277   services
278 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
169 def set_startup_configuration(service_name, options: {})
170   options[:startup_type] = SERVICE_START_TYPES.key(options[:startup_type]) || SERVICE_NO_CHANGE
171   options[:logon_account] = wide_string(options[:logon_account]) || FFI::Pointer::NULL
172   options[:logon_password] = wide_string(options[:logon_password]) || FFI::Pointer::NULL
173 
174   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
175     success = ChangeServiceConfigW(
176       service,
177       SERVICE_NO_CHANGE,        # dwServiceType
178       options[:startup_type],   # dwStartType
179       SERVICE_NO_CHANGE,        # dwErrorControl
180       FFI::Pointer::NULL,       # lpBinaryPathName
181       FFI::Pointer::NULL,       # lpLoadOrderGroup
182       FFI::Pointer::NULL,       # lpdwTagId
183       FFI::Pointer::NULL,       # lpDependencies
184       options[:logon_account],  # lpServiceStartName
185       options[:logon_password], # lpPassword
186       FFI::Pointer::NULL        # lpDisplayName
187     )
188     if success == FFI::WIN32_FALSE
189       raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration"))
190     end
191   end
192 
193   if options[:startup_type]
194     options[:delayed] ||= false
195     set_startup_mode_delayed(service_name, options[:delayed])
196   end
197 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
39 def start(service_name, timeout: DEFAULT_TIMEOUT)
40   Puppet.debug _("Starting the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
41 
42   valid_initial_states = [
43     SERVICE_STOP_PENDING,
44     SERVICE_STOPPED,
45     SERVICE_START_PENDING
46   ]
47 
48   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
49     if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
50       raise Puppet::Util::Windows::Error, _("Failed to start the service")
51     end
52   end
53 
54   Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
55 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
62 def stop(service_name, timeout: DEFAULT_TIMEOUT)
63   Puppet.debug _("Stopping the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
64 
65   valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
66 
67   transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED, timeout) do |service|
68     send_service_control_signal(service, SERVICE_CONTROL_STOP)
69   end
70 
71   Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
72 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
691 def milliseconds_to_seconds(wait_hint)
692   wait_hint / 1000;
693 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
318 def open_scm(scm_access, &block)
319   scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access)
320   raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service control manager")) if scm == FFI::Pointer::NULL_HANDLE
321   yield scm
322 ensure
323   CloseServiceHandle(scm)
324 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
297 def open_service(service_name, scm_access, service_access, &block)
298   service = FFI::Pointer::NULL_HANDLE
299 
300   result = nil
301   open_scm(scm_access) do |scm|
302     service = OpenServiceW(scm, wide_string(service_name), service_access)
303     raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service")) if service == FFI::Pointer::NULL_HANDLE
304     result = yield service
305   end
306 
307   result
308 ensure
309   CloseServiceHandle(service)
310 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
449 def query_config(service, &block)
450   config = nil
451   size_required = nil
452   # Fetch the bytes of memory required to be allocated
453   # for QueryServiceConfigW to return succesfully. This
454   # is done by sending NULL and 0 for the pointer and size
455   # respectively, letting the command fail, then reading the
456   # value of pcbBytesNeeded
457   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
458     # return value will be false from this call, since it's designed
459     # to fail. Just ignore it
460     QueryServiceConfigW(service, FFI::Pointer::NULL, 0, bytes_pointer)
461     size_required = bytes_pointer.read_dword
462     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
463       config = QUERY_SERVICE_CONFIGW.new(ssp_ptr)
464       success = QueryServiceConfigW(
465         service,
466         ssp_ptr,
467         size_required,
468         bytes_pointer
469       )
470       if success == FFI::WIN32_FALSE
471         raise Puppet::Util::Windows::Error.new(_("Service query failed"))
472       end
473       yield config
474     end
475   end
476 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
486 def query_config2(service, info_level, &block)
487   config = nil
488   size_required = nil
489   # Fetch the bytes of memory required to be allocated
490   # for QueryServiceConfig2W to return succesfully. This
491   # is done by sending NULL and 0 for the pointer and size
492   # respectively, letting the command fail, then reading the
493   # value of pcbBytesNeeded
494   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
495     # return value will be false from this call, since it's designed
496     # to fail. Just ignore it
497     QueryServiceConfig2W(service, info_level, FFI::Pointer::NULL, 0, bytes_pointer)
498     size_required = bytes_pointer.read_dword
499     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
500       # We need to supply the appropriate struct to be created based on
501       # the info_level
502       case info_level
503       when SERVICE_CONFIG_DELAYED_AUTO_START_INFO
504         config = SERVICE_DELAYED_AUTO_START_INFO.new(ssp_ptr)
505       end
506       success = QueryServiceConfig2W(
507         service,
508         info_level,
509         ssp_ptr,
510         size_required,
511         bytes_pointer
512       )
513       if success == FFI::WIN32_FALSE
514         raise Puppet::Util::Windows::Error.new(_("Service query for %{parameter_name} failed") % { parameter_name: SERVICE_CONFIG_TYPES[info_level] } )
515       end
516       yield config
517     end
518   end
519 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
406 def query_status(service)
407   size_required = nil
408   status = nil
409   # Fetch the bytes of memory required to be allocated
410   # for QueryServiceConfigW to return succesfully. This
411   # is done by sending NULL and 0 for the pointer and size
412   # respectively, letting the command fail, then reading the
413   # value of pcbBytesNeeded
414   FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
415     # return value will be false from this call, since it's designed
416     # to fail. Just ignore it
417     QueryServiceStatusEx(
418       service,
419       :SC_STATUS_PROCESS_INFO,
420       FFI::Pointer::NULL,
421       0,
422       bytes_pointer
423     )
424     size_required = bytes_pointer.read_dword
425     FFI::MemoryPointer.new(size_required) do |ssp_ptr|
426       status = SERVICE_STATUS_PROCESS.new(ssp_ptr)
427       success = QueryServiceStatusEx(
428         service,
429         :SC_STATUS_PROCESS_INFO,
430         ssp_ptr,
431         size_required,
432         bytes_pointer
433       )
434       if success == FFI::WIN32_FALSE
435         raise Puppet::Util::Windows::Error.new(_("Service query failed"))
436       end
437       yield status
438     end
439   end
440 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
529 def set_optional_parameter(service_name, change, value)
530   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
531     success = ChangeServiceConfig2W(
532       service,
533       change, # dwInfoLevel
534       value,  # lpInfo
535     )
536     if success == FFI::WIN32_FALSE
537       raise Puppet::Util::windows::Error.new(_("Failed to update service %{change} configuration") % { change: change } )
538     end
539   end
540 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
548 def set_startup_mode_delayed(service_name, delayed)
549   delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
550   delayed_start[:fDelayedAutostart] = delayed
551   set_optional_parameter(service_name, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayed_start)
552 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
335 def transition_service_state(service_name, valid_initial_states, final_state, timeout, &block)
336   service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS
337   open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service|
338     query_status(service) do |status|
339       initial_state = status[:dwCurrentState]
340       # If the service is already in the final_state, then
341       # no further work needs to be done
342       if initial_state == final_state
343         Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] }
344 
345         next
346       end
347 
348       # Check that initial_state corresponds to a valid
349       # initial state
350       unless valid_initial_states.include?(initial_state)
351         valid_initial_states_str = valid_initial_states.map do |state|
352           SERVICE_STATES[state]
353         end.join(", ")
354 
355         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] }
356       end
357 
358       # Check if there's a pending transition to the final_state. If so, then wait for
359       # that transition to finish.
360       possible_pending_states = FINAL_STATES.keys.select do |pending_state|
361         # SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and
362         # SERVICE_CONTINUE_PENDING. That is why we need the #select here
363         FINAL_STATES[pending_state] == final_state
364       end
365       if possible_pending_states.include?(initial_state)
366         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 }
367         wait_on_pending_state(service, initial_state, timeout)
368 
369         next
370       end
371 
372       # If we are in an unsafe pending state like SERVICE_START_PENDING
373       # or SERVICE_STOP_PENDING, then we want to wait for that pending
374       # transition to finish before transitioning the service state.
375       # The reason we do this is because SERVICE_START_PENDING is when
376       # the service thread is being created and initialized, while
377       # SERVICE_STOP_PENDING is when the service thread is being cleaned
378       # up and destroyed. Thus there is a chance that when the service is
379       # in either of these states, its service thread may not yet be ready
380       # to perform the state transition (it may not even exist).
381       if UNSAFE_PENDING_STATES.include?(initial_state)
382         Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] }
383         wait_on_pending_state(service, initial_state, timeout)
384         initial_state = FINAL_STATES[initial_state]
385       end
386 
387       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] }
388 
389       yield service
390 
391       Puppet.debug _("Waiting for the transition to finish")
392       wait_on_state_transition(service, initial_state, final_state, timeout)
393     end
394   end
395 rescue => detail
396   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
397 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
674 def wait_hint_to_wait_time(wait_hint)
675   # Wait 1/10th the wait_hint, but no less than 1 and
676   # no more than 10 seconds
677   wait_time = milliseconds_to_seconds(wait_hint) / 10;
678   wait_time = 1 if wait_time < 1
679   wait_time = 10 if wait_time > 10
680   wait_time
681 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
625 def wait_on_pending_state(service, pending_state, timeout)
626   final_state = FINAL_STATES[pending_state]
627 
628   Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] }
629 
630   elapsed_time = 0
631   last_checkpoint = -1
632   loop do
633     query_status(service) do |status|
634       state = status[:dwCurrentState]
635       checkpoint = status[:dwCheckPoint]
636       wait_hint = status[:dwWaitHint]
637       # Check if our service has finished transitioning to
638       # the final_state OR if an unexpected transition
639       # has occurred
640       return if state == final_state
641       unless state == pending_state
642         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] }
643       end
644 
645       # Check if any progress has been made since our last sleep
646       # using the dwCheckPoint. If no progress has been made then
647       # check if we've timed out, and raise an error if so
648       if checkpoint > last_checkpoint
649         elapsed_time = 0
650         last_checkpoint = checkpoint
651       else
652         wait_hint = milliseconds_to_seconds(status[:dwWaitHint])
653         timeout = wait_hint < timeout ? timeout : wait_hint
654 
655         if elapsed_time >= timeout
656           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] }
657         end
658       end
659       wait_time = wait_hint_to_wait_time(wait_hint)
660       # Wait a bit before rechecking the service's state
661       sleep(wait_time)
662       elapsed_time += wait_time
663     end
664   end
665 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
577 def wait_on_state_transition(service, initial_state, final_state, timeout)
578   # Get the pending state for this transition. Note that SERVICE_RUNNING
579   # has two possible pending states, which is why we need this logic.
580   if final_state != SERVICE_RUNNING
581     pending_state = FINAL_STATES.key(final_state)
582   elsif initial_state == SERVICE_STOPPED
583     # SERVICE_STOPPED => SERVICE_RUNNING
584     pending_state = SERVICE_START_PENDING
585   else
586     # SERVICE_PAUSED => SERVICE_RUNNING
587     pending_state = SERVICE_CONTINUE_PENDING
588   end
589 
590   # Wait for the transition to finish
591   state = nil
592   elapsed_time = 0
593   while elapsed_time <= timeout
594 
595     query_status(service) do |status|
596       state = status[:dwCurrentState]
597       return if state == final_state
598       if state == pending_state
599         Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] }
600         wait_on_pending_state(service, pending_state, timeout)
601         return
602       end
603       sleep(1)
604       elapsed_time += 1
605     end
606   end
607   # Timed out while waiting for the transition to finish. Raise an error
608   # We can still use the state variable read from the FFI struct because
609   # FFI creates new Integer objects during an assignment of an integer value
610   # stored in an FFI struct. We verified that the '=' operater is safe
611   # from the freed memory since the new ruby object created during the
612   # assignment will remain in ruby memory and remain immutable and constant.
613   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] }
614 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
25 def exists?(service_name)
26   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
27     true
28   end
29 rescue Puppet::Util::Windows::Error => e
30   return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST
31   raise e
32 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
150 def logon_account(service_name)
151   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
152     query_config(service) do |config|
153       return config[:lpServiceStartName].read_arbitrary_wide_string_up_to(Puppet::Util::Windows::ADSI::User::MAX_USERNAME_LENGTH)
154     end
155   end
156 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
79 def resume(service_name, timeout: DEFAULT_TIMEOUT)
80   Puppet.debug _("Resuming the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
81 
82   valid_initial_states = [
83     SERVICE_PAUSE_PENDING,
84     SERVICE_PAUSED,
85     SERVICE_CONTINUE_PENDING
86   ]
87 
88   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
89     # The SERVICE_CONTROL_CONTINUE signal can only be sent when
90     # the service is in the SERVICE_PAUSED state
91     wait_on_pending_state(service, SERVICE_PAUSE_PENDING, timeout)
92 
93     send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
94   end
95 
96   Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
97 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
123 def service_start_type(service_name)
124   start_type = nil
125   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
126     query_config(service) do |config|
127       start_type = SERVICE_START_TYPES[config[:dwStartType]]
128     end
129   end
130   # if the service has type AUTO_START, check if it's a delayed service
131   if start_type == :SERVICE_AUTO_START
132     open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
133       query_config2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO) do |config|
134         return :SERVICE_DELAYED_AUTO_START if config[:fDelayedAutostart] == 1
135       end
136     end
137   end
138   if start_type.nil?
139     raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name})
140   end
141   start_type
142 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
104 def service_state(service_name)
105   state = nil
106   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
107     query_status(service) do |status|
108       state = SERVICE_STATES[status[:dwCurrentState]]
109     end
110   end
111   if state.nil?
112     raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name})
113   end
114   state
115 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
208 def services
209   services = {}
210   open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
211     size_required = 0
212     services_returned = 0
213     FFI::MemoryPointer.new(:dword) do |bytes_pointer|
214       FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
215         FFI::MemoryPointer.new(:dword) do |resume_ptr|
216           resume_ptr.write_dword(0)
217           # Fetch the bytes of memory required to be allocated
218           # for QueryServiceConfigW to return succesfully. This
219           # is done by sending NULL and 0 for the pointer and size
220           # respectively, letting the command fail, then reading the
221           # value of pcbBytesNeeded
222           #
223           # return value will be false from this call, since it's designed
224           # to fail. Just ignore it
225           EnumServicesStatusExW(
226             scm,
227             :SC_ENUM_PROCESS_INFO,
228             ALL_SERVICE_TYPES,
229             SERVICE_STATE_ALL,
230             FFI::Pointer::NULL,
231             0,
232             bytes_pointer,
233             svcs_ret_ptr,
234             resume_ptr,
235             FFI::Pointer::NULL
236           )
237           size_required = bytes_pointer.read_dword
238           FFI::MemoryPointer.new(size_required) do |buffer_ptr|
239             resume_ptr.write_dword(0)
240             svcs_ret_ptr.write_dword(0)
241             success = EnumServicesStatusExW(
242               scm,
243               :SC_ENUM_PROCESS_INFO,
244               ALL_SERVICE_TYPES,
245               SERVICE_STATE_ALL,
246               buffer_ptr,
247               buffer_ptr.size,
248               bytes_pointer,
249               svcs_ret_ptr,
250               resume_ptr,
251               FFI::Pointer::NULL
252             )
253             if success == FFI::WIN32_FALSE
254               raise Puppet::Util::Windows::Error.new(_("Failed to fetch services"))
255             end
256             # Now that the buffer is populated with services
257             # we pull the data from memory using pointer arithmetic:
258             # the number of services returned by the function is
259             # available to be read from svcs_ret_ptr, and we iterate
260             # that many times moving the cursor pointer the length of
261             # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
262             # over the buffer and extract each struct.
263             services_returned = svcs_ret_ptr.read_dword
264             cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
265             0.upto(services_returned - 1) do |index|
266               service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
267               services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
268                 :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
269                 :service_status_process => service[:ServiceStatusProcess]
270               }
271             end
272           end # buffer_ptr
273         end # resume_ptr
274       end # scvs_ret_ptr
275     end # bytes_ptr
276   end # open_scm
277   services
278 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
169 def set_startup_configuration(service_name, options: {})
170   options[:startup_type] = SERVICE_START_TYPES.key(options[:startup_type]) || SERVICE_NO_CHANGE
171   options[:logon_account] = wide_string(options[:logon_account]) || FFI::Pointer::NULL
172   options[:logon_password] = wide_string(options[:logon_password]) || FFI::Pointer::NULL
173 
174   open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
175     success = ChangeServiceConfigW(
176       service,
177       SERVICE_NO_CHANGE,        # dwServiceType
178       options[:startup_type],   # dwStartType
179       SERVICE_NO_CHANGE,        # dwErrorControl
180       FFI::Pointer::NULL,       # lpBinaryPathName
181       FFI::Pointer::NULL,       # lpLoadOrderGroup
182       FFI::Pointer::NULL,       # lpdwTagId
183       FFI::Pointer::NULL,       # lpDependencies
184       options[:logon_account],  # lpServiceStartName
185       options[:logon_password], # lpPassword
186       FFI::Pointer::NULL        # lpDisplayName
187     )
188     if success == FFI::WIN32_FALSE
189       raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration"))
190     end
191   end
192 
193   if options[:startup_type]
194     options[:delayed] ||= false
195     set_startup_mode_delayed(service_name, options[:delayed])
196   end
197 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
39 def start(service_name, timeout: DEFAULT_TIMEOUT)
40   Puppet.debug _("Starting the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
41 
42   valid_initial_states = [
43     SERVICE_STOP_PENDING,
44     SERVICE_STOPPED,
45     SERVICE_START_PENDING
46   ]
47 
48   transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
49     if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
50       raise Puppet::Util::Windows::Error, _("Failed to start the service")
51     end
52   end
53 
54   Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
55 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
62 def stop(service_name, timeout: DEFAULT_TIMEOUT)
63   Puppet.debug _("Stopping the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }
64 
65   valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
66 
67   transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED, timeout) do |service|
68     send_service_control_signal(service, SERVICE_CONTROL_STOP)
69   end
70 
71   Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
72 end