Module: Puppet::Util::Windows::Service

Extended by:
FFI::Windows::Constants, FFI::Windows::Functions, FFI::Windows::Structs, String
Includes:
FFI::Windows::Constants, FFI::Windows::Functions, FFI::Windows::Structs
Defined in:
lib/puppet/util/windows/service.rb,
lib/puppet/util/windows.rb

Overview

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

Constant Summary collapse

DEFAULT_TIMEOUT =
30

Constants included from FFI::Windows::Constants

FFI::Windows::Constants::ABOVE_NORMAL_PRIORITY_CLASS, FFI::Windows::Constants::ALL_SERVICE_TYPES, FFI::Windows::Constants::BELOW_NORMAL_PRIORITY_CLASS, FFI::Windows::Constants::CREATE_BREAKAWAY_FROM_JOB, FFI::Windows::Constants::CREATE_DEFAULT_ERROR_MODE, FFI::Windows::Constants::CREATE_NEW_CONSOLE, FFI::Windows::Constants::CREATE_NEW_PROCESS_GROUP, FFI::Windows::Constants::CREATE_NO_WINDOW, FFI::Windows::Constants::CREATE_PRESERVE_CODE_AUTHZ_LEVEL, FFI::Windows::Constants::CREATE_PROTECTED_PROCESS, FFI::Windows::Constants::CREATE_SEPARATE_WOW_VDM, FFI::Windows::Constants::CREATE_SHARED_WOW_VDM, FFI::Windows::Constants::CREATE_SUSPENDED, FFI::Windows::Constants::CREATE_UNICODE_ENVIRONMENT, FFI::Windows::Constants::DEBUG_ONLY_THIS_PROCESS, FFI::Windows::Constants::DEBUG_PROCESS, FFI::Windows::Constants::DELETE, FFI::Windows::Constants::DETACHED_PROCESS, FFI::Windows::Constants::ERROR_ALREADY_EXISTS, FFI::Windows::Constants::ERROR_FILE_NOT_FOUND, FFI::Windows::Constants::ERROR_PATH_NOT_FOUND, FFI::Windows::Constants::ERROR_SERVICE_DOES_NOT_EXIST, FFI::Windows::Constants::FILE_ALL_ACCESS, FFI::Windows::Constants::FILE_APPEND_DATA, FFI::Windows::Constants::FILE_ATTRIBUTE_DIRECTORY, FFI::Windows::Constants::FILE_ATTRIBUTE_READONLY, FFI::Windows::Constants::FILE_ATTRIBUTE_REPARSE_POINT, FFI::Windows::Constants::FILE_DELETE_CHILD, FFI::Windows::Constants::FILE_DEVICE_FILE_SYSTEM, FFI::Windows::Constants::FILE_EXECUTE, FFI::Windows::Constants::FILE_FLAG_BACKUP_SEMANTICS, FFI::Windows::Constants::FILE_FLAG_OPEN_REPARSE_POINT, FFI::Windows::Constants::FILE_GENERIC_EXECUTE, FFI::Windows::Constants::FILE_GENERIC_READ, FFI::Windows::Constants::FILE_GENERIC_WRITE, FFI::Windows::Constants::FILE_READ_ATTRIBUTES, FFI::Windows::Constants::FILE_READ_DATA, FFI::Windows::Constants::FILE_READ_EA, FFI::Windows::Constants::FILE_SHARE_READ, FFI::Windows::Constants::FILE_SHARE_WRITE, FFI::Windows::Constants::FILE_WRITE_ATTRIBUTES, FFI::Windows::Constants::FILE_WRITE_DATA, FFI::Windows::Constants::FILE_WRITE_EA, FFI::Windows::Constants::FINAL_STATES, FFI::Windows::Constants::FSCTL_GET_REPARSE_POINT, FFI::Windows::Constants::GENERIC_ALL, FFI::Windows::Constants::GENERIC_EXECUTE, FFI::Windows::Constants::GENERIC_READ, FFI::Windows::Constants::GENERIC_WRITE, FFI::Windows::Constants::HANDLE_FLAG_INHERIT, FFI::Windows::Constants::HIGH_PRIORITY_CLASS, FFI::Windows::Constants::IDLE_PRIORITY_CLASS, FFI::Windows::Constants::INHERIT_PARENT_AFFINITY, FFI::Windows::Constants::INVALID_FILE_ATTRIBUTES, FFI::Windows::Constants::INVALID_HANDLE_VALUE, FFI::Windows::Constants::IO_REPARSE_TAG_CSV, FFI::Windows::Constants::IO_REPARSE_TAG_DEDUP, FFI::Windows::Constants::IO_REPARSE_TAG_DFS, FFI::Windows::Constants::IO_REPARSE_TAG_DFSR, FFI::Windows::Constants::IO_REPARSE_TAG_HSM, FFI::Windows::Constants::IO_REPARSE_TAG_HSM2, FFI::Windows::Constants::IO_REPARSE_TAG_MOUNT_POINT, FFI::Windows::Constants::IO_REPARSE_TAG_NFS, FFI::Windows::Constants::IO_REPARSE_TAG_SIS, FFI::Windows::Constants::IO_REPARSE_TAG_SYMLINK, FFI::Windows::Constants::IO_REPARSE_TAG_WIM, FFI::Windows::Constants::LOGON_WITH_PROFILE, FFI::Windows::Constants::MAXIMUM_REPARSE_DATA_BUFFER_SIZE, FFI::Windows::Constants::METHOD_BUFFERED, FFI::Windows::Constants::NORMAL_PRIORITY_CLASS, FFI::Windows::Constants::OPEN_EXISTING, FFI::Windows::Constants::PROCESS_ALL_ACCESS, FFI::Windows::Constants::PROCESS_QUERY_INFORMATION, FFI::Windows::Constants::PROCESS_SET_INFORMATION, FFI::Windows::Constants::PROCESS_TERMINATE, FFI::Windows::Constants::PROCESS_VM_READ, FFI::Windows::Constants::REALTIME_PRIORITY_CLASS, FFI::Windows::Constants::REPLACEFILE_IGNORE_ACL_ERRORS, FFI::Windows::Constants::REPLACEFILE_IGNORE_MERGE_ERRORS, FFI::Windows::Constants::REPLACEFILE_WRITE_THROUGH, FFI::Windows::Constants::SC_MANAGER_ALL_ACCESS, FFI::Windows::Constants::SC_MANAGER_CONNECT, FFI::Windows::Constants::SC_MANAGER_CREATE_SERVICE, FFI::Windows::Constants::SC_MANAGER_ENUMERATE_SERVICE, FFI::Windows::Constants::SC_MANAGER_LOCK, FFI::Windows::Constants::SC_MANAGER_MODIFY_BOOT_CONFIG, FFI::Windows::Constants::SC_MANAGER_QUERY_LOCK_STATUS, FFI::Windows::Constants::SEM_FAILCRITICALERRORS, FFI::Windows::Constants::SEM_NOGPFAULTERRORBOX, FFI::Windows::Constants::SERVICENAME_MAX, FFI::Windows::Constants::SERVICE_ACCEPT_HARDWAREPROFILECHANGE, FFI::Windows::Constants::SERVICE_ACCEPT_NETBINDCHANGE, FFI::Windows::Constants::SERVICE_ACCEPT_PARAMCHANGE, FFI::Windows::Constants::SERVICE_ACCEPT_PAUSE_CONTINUE, FFI::Windows::Constants::SERVICE_ACCEPT_POWEREVENT, FFI::Windows::Constants::SERVICE_ACCEPT_PRESHUTDOWN, FFI::Windows::Constants::SERVICE_ACCEPT_SESSIONCHANGE, FFI::Windows::Constants::SERVICE_ACCEPT_SHUTDOWN, FFI::Windows::Constants::SERVICE_ACCEPT_STOP, FFI::Windows::Constants::SERVICE_ACCEPT_TIMECHANGE, FFI::Windows::Constants::SERVICE_ACCEPT_TRIGGEREVENT, FFI::Windows::Constants::SERVICE_ACCEPT_USER_LOGOFF, FFI::Windows::Constants::SERVICE_ACTIVE, FFI::Windows::Constants::SERVICE_ALL_ACCESS, FFI::Windows::Constants::SERVICE_AUTO_START, FFI::Windows::Constants::SERVICE_BOOT_START, FFI::Windows::Constants::SERVICE_CHANGE_CONFIG, FFI::Windows::Constants::SERVICE_CONFIG_DELAYED_AUTO_START_INFO, FFI::Windows::Constants::SERVICE_CONFIG_DESCRIPTION, FFI::Windows::Constants::SERVICE_CONFIG_FAILURE_ACTIONS, FFI::Windows::Constants::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, FFI::Windows::Constants::SERVICE_CONFIG_LAUNCH_PROTECTED, FFI::Windows::Constants::SERVICE_CONFIG_PREFERRED_NODE, FFI::Windows::Constants::SERVICE_CONFIG_PRESHUTDOWN_INFO, FFI::Windows::Constants::SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, FFI::Windows::Constants::SERVICE_CONFIG_SERVICE_SID_INFO, FFI::Windows::Constants::SERVICE_CONFIG_TRIGGER_INFO, FFI::Windows::Constants::SERVICE_CONFIG_TYPES, FFI::Windows::Constants::SERVICE_CONTINUE_PENDING, FFI::Windows::Constants::SERVICE_CONTROL_CONTINUE, FFI::Windows::Constants::SERVICE_CONTROL_DEVICEEVENT, FFI::Windows::Constants::SERVICE_CONTROL_HARDWAREPROFILECHANGE, FFI::Windows::Constants::SERVICE_CONTROL_INTERROGATE, FFI::Windows::Constants::SERVICE_CONTROL_NETBINDADD, FFI::Windows::Constants::SERVICE_CONTROL_NETBINDDISABLE, FFI::Windows::Constants::SERVICE_CONTROL_NETBINDENABLE, FFI::Windows::Constants::SERVICE_CONTROL_NETBINDREMOVE, FFI::Windows::Constants::SERVICE_CONTROL_PARAMCHANGE, FFI::Windows::Constants::SERVICE_CONTROL_PAUSE, FFI::Windows::Constants::SERVICE_CONTROL_POWEREVENT, FFI::Windows::Constants::SERVICE_CONTROL_PRESHUTDOWN, FFI::Windows::Constants::SERVICE_CONTROL_SESSIONCHANGE, FFI::Windows::Constants::SERVICE_CONTROL_SHUTDOWN, FFI::Windows::Constants::SERVICE_CONTROL_SIGNALS, FFI::Windows::Constants::SERVICE_CONTROL_STOP, FFI::Windows::Constants::SERVICE_CONTROL_TIMECHANGE, FFI::Windows::Constants::SERVICE_CONTROL_TRIGGEREVENT, FFI::Windows::Constants::SERVICE_DEMAND_START, FFI::Windows::Constants::SERVICE_DISABLED, FFI::Windows::Constants::SERVICE_ENUMERATE_DEPENDENTS, FFI::Windows::Constants::SERVICE_FILE_SYSTEM_DRIVER, FFI::Windows::Constants::SERVICE_INACTIVE, FFI::Windows::Constants::SERVICE_INTERACTIVE_PROCESS, FFI::Windows::Constants::SERVICE_INTERROGATE, FFI::Windows::Constants::SERVICE_KERNEL_DRIVER, FFI::Windows::Constants::SERVICE_NO_CHANGE, FFI::Windows::Constants::SERVICE_PAUSED, FFI::Windows::Constants::SERVICE_PAUSE_CONTINUE, FFI::Windows::Constants::SERVICE_PAUSE_PENDING, FFI::Windows::Constants::SERVICE_QUERY_CONFIG, FFI::Windows::Constants::SERVICE_QUERY_STATUS, FFI::Windows::Constants::SERVICE_RUNNING, FFI::Windows::Constants::SERVICE_START, FFI::Windows::Constants::SERVICE_START_PENDING, FFI::Windows::Constants::SERVICE_START_TYPES, FFI::Windows::Constants::SERVICE_STATES, FFI::Windows::Constants::SERVICE_STATE_ALL, FFI::Windows::Constants::SERVICE_STOP, FFI::Windows::Constants::SERVICE_STOPPED, FFI::Windows::Constants::SERVICE_STOP_PENDING, FFI::Windows::Constants::SERVICE_SYSTEM_START, FFI::Windows::Constants::SERVICE_USER_DEFINED_CONTROL, FFI::Windows::Constants::SERVICE_USER_OWN_PROCESS, FFI::Windows::Constants::SERVICE_USER_SHARE_PROCESS, FFI::Windows::Constants::SERVICE_WIN32_OWN_PROCESS, FFI::Windows::Constants::SERVICE_WIN32_SHARE_PROCESS, FFI::Windows::Constants::SHGFI_DISPLAYNAME, FFI::Windows::Constants::SHGFI_PIDL, FFI::Windows::Constants::SPECIFIC_RIGHTS_ALL, FFI::Windows::Constants::STANDARD_RIGHTS_ALL, FFI::Windows::Constants::STANDARD_RIGHTS_EXECUTE, FFI::Windows::Constants::STANDARD_RIGHTS_READ, FFI::Windows::Constants::STANDARD_RIGHTS_REQUIRED, FFI::Windows::Constants::STANDARD_RIGHTS_WRITE, FFI::Windows::Constants::STARTF_USESTDHANDLES, FFI::Windows::Constants::SYNCHRONIZE, FFI::Windows::Constants::TOKEN_INFORMATION_CLASS, FFI::Windows::Constants::UNSAFE_PENDING_STATES, FFI::Windows::Constants::WRITE_DAC, FFI::Windows::Constants::WRITE_OWNER

Constants included from FFI::Windows::Structs

FFI::Windows::Structs::MAXIMUM_REPARSE_DATA_BUFFER_SIZE

Constants included from FFI::Windows::Functions

FFI::Windows::Functions::SC_ENUM_TYPE, FFI::Windows::Functions::SC_STATUS_TYPE

Class Method Summary collapse

Methods included from String

wide_string

Class Method Details

.exists?(service_name) ⇒ Boolean

Returns true if the service exists, false otherwise.

Parameters:

  • service_name (String)

    name of the service

Returns:

  • (Boolean)


26
27
28
29
30
31
32
33
34
# File 'lib/puppet/util/windows/service.rb', line 26

def exists?(service_name)
  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
    true
  end
rescue Puppet::Util::Windows::Error => e
  return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST

  raise e
end

.logon_account(service_name) ⇒ String

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

Returns:

  • (String)

    logon_account account currently set for the service’s logon in the format “DOMAINAccount” or “.Account” if it’s a local account



154
155
156
157
158
159
160
# File 'lib/puppet/util/windows/service.rb', line 154

def (service_name)
  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
    query_config(service) do |config|
      return config[:lpServiceStartName].read_arbitrary_wide_string_up_to(Puppet::Util::Windows::ADSI::User::MAX_USERNAME_LENGTH)
    end
  end
end

.milliseconds_to_seconds(wait_hint) ⇒ Integer

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:

  • wait_hint (Integer)

    the wait hint of a service in milliseconds

Returns:

  • (Integer)

    wait_hint in seconds



702
703
704
# File 'lib/puppet/util/windows/service.rb', line 702

def milliseconds_to_seconds(wait_hint)
  wait_hint / 1000;
end

.open_scm(scm_access, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Opens a handle to the service control manager

Parameters:

  • scm_access (Integer)

    code corresponding to the access type requested for the scm



324
325
326
327
328
329
330
331
# File 'lib/puppet/util/windows/service.rb', line 324

def open_scm(scm_access, &block)
  scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access)
  raise Puppet::Util::Windows::Error, _("Failed to open a handle to the service control manager") if scm == FFI::Pointer::NULL_HANDLE

  yield scm
ensure
  CloseServiceHandle(scm)
end

.open_service(service_name, scm_access, service_access) {|service| ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:

  • service_name (string)

    the name of the service to open

  • scm_access (Integer)

    code corresponding to the access type requested for the scm

  • service_access (Integer)

    code corresponding to the access type requested for the service

Yield Parameters:

  • service (:handle)

    the windows native handle used to access the service

Returns:

  • the result of the block



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/puppet/util/windows/service.rb', line 302

def open_service(service_name, scm_access, service_access, &block)
  service = FFI::Pointer::NULL_HANDLE

  result = nil
  open_scm(scm_access) do |scm|
    service = OpenServiceW(scm, wide_string(service_name), service_access)
    raise Puppet::Util::Windows::Error, _("Failed to open a handle to the service") if service == FFI::Pointer::NULL_HANDLE

    result = yield service
  end

  result
ensure
  CloseServiceHandle(service)
end

.query_config(service, &block) ⇒ QUERY_SERVICE_CONFIGW struct

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

perform QueryServiceConfigW on a windows service and return the result

Parameters:

  • service (:handle)

    handle of the service to query

Returns:



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/puppet/util/windows/service.rb', line 457

def query_config(service, &block)
  config = nil
  size_required = nil
  # Fetch the bytes of memory required to be allocated
  # for QueryServiceConfigW to return succesfully. This
  # is done by sending NULL and 0 for the pointer and size
  # respectively, letting the command fail, then reading the
  # value of pcbBytesNeeded
  FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
    # return value will be false from this call, since it's designed
    # to fail. Just ignore it
    QueryServiceConfigW(service, FFI::Pointer::NULL, 0, bytes_pointer)
    size_required = bytes_pointer.read_dword
    FFI::MemoryPointer.new(size_required) do |ssp_ptr|
      config = QUERY_SERVICE_CONFIGW.new(ssp_ptr)
      success = QueryServiceConfigW(
        service,
        ssp_ptr,
        size_required,
        bytes_pointer
      )
      if success == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error, _("Service query failed")
      end

      yield config
    end
  end
end

.query_config2(service, info_level, &block) ⇒ QUERY_SERVICE_CONFIG2W struct

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

perform QueryServiceConfig2W on a windows service and return the result

Parameters:

  • service (:handle)

    handle of the service to query

  • info_level (Integer)

    the configuration information to be queried

Returns:

  • (QUERY_SERVICE_CONFIG2W struct)

    the result of the query



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/puppet/util/windows/service.rb', line 495

def query_config2(service, info_level, &block)
  config = nil
  size_required = nil
  # Fetch the bytes of memory required to be allocated
  # for QueryServiceConfig2W to return succesfully. This
  # is done by sending NULL and 0 for the pointer and size
  # respectively, letting the command fail, then reading the
  # value of pcbBytesNeeded
  FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
    # return value will be false from this call, since it's designed
    # to fail. Just ignore it
    QueryServiceConfig2W(service, info_level, FFI::Pointer::NULL, 0, bytes_pointer)
    size_required = bytes_pointer.read_dword
    FFI::MemoryPointer.new(size_required) do |ssp_ptr|
      # We need to supply the appropriate struct to be created based on
      # the info_level
      case info_level
      when SERVICE_CONFIG_DELAYED_AUTO_START_INFO
        config = SERVICE_DELAYED_AUTO_START_INFO.new(ssp_ptr)
      end
      success = QueryServiceConfig2W(
        service,
        info_level,
        ssp_ptr,
        size_required,
        bytes_pointer
      )
      if success == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error, _("Service query for %{parameter_name} failed") % { parameter_name: SERVICE_CONFIG_TYPES[info_level] }
      end

      yield config
    end
  end
end

.query_status(service) ⇒ SERVICE_STATUS_PROCESS struct

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

perform QueryServiceStatusEx on a windows service and return the result

Parameters:

  • service (:handle)

    handle of the service to query

Returns:



413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/puppet/util/windows/service.rb', line 413

def query_status(service)
  size_required = nil
  status = nil
  # Fetch the bytes of memory required to be allocated
  # for QueryServiceConfigW to return succesfully. This
  # is done by sending NULL and 0 for the pointer and size
  # respectively, letting the command fail, then reading the
  # value of pcbBytesNeeded
  FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
    # return value will be false from this call, since it's designed
    # to fail. Just ignore it
    QueryServiceStatusEx(
      service,
      :SC_STATUS_PROCESS_INFO,
      FFI::Pointer::NULL,
      0,
      bytes_pointer
    )
    size_required = bytes_pointer.read_dword
    FFI::MemoryPointer.new(size_required) do |ssp_ptr|
      status = SERVICE_STATUS_PROCESS.new(ssp_ptr)
      success = QueryServiceStatusEx(
        service,
        :SC_STATUS_PROCESS_INFO,
        ssp_ptr,
        size_required,
        bytes_pointer
      )
      if success == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error, _("Service query failed")
      end

      yield status
    end
  end
end

.resume(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object

Resume a paused windows service

Parameters:

  • service_name (String)

    name of the service to resume

  • optional (Integer)

    :timeout the minumum number of seconds to wait before timing out



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/puppet/util/windows/service.rb', line 81

def resume(service_name, timeout: DEFAULT_TIMEOUT)
  Puppet.debug _("Resuming the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }

  valid_initial_states = [
    SERVICE_PAUSE_PENDING,
    SERVICE_PAUSED,
    SERVICE_CONTINUE_PENDING
  ]

  transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
    # The SERVICE_CONTROL_CONTINUE signal can only be sent when
    # the service is in the SERVICE_PAUSED state
    wait_on_pending_state(service, SERVICE_PAUSE_PENDING, timeout)

    send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
  end

  Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
end

.send_service_control_signal(service, signal) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sends a service control signal to a service

Parameters:

  • service (:handle)

    handle to the service

  • signal (Integer)

    the service control signal to send



570
571
572
573
574
575
576
577
# File 'lib/puppet/util/windows/service.rb', line 570

def send_service_control_signal(service, signal)
  FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
    status = SERVICE_STATUS.new(status_ptr)
    if ControlService(service, signal, status) == FFI::WIN32_FALSE
      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]] }
    end
  end
end

.service_start_type(service_name) ⇒ QUERY_SERVICE_CONFIGW.struct

Query the configuration of a service using QueryServiceConfigW or QueryServiceConfig2W

Parameters:

  • service_name (String)

    name of the service to query

Returns:



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/puppet/util/windows/service.rb', line 126

def service_start_type(service_name)
  start_type = nil
  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
    query_config(service) do |config|
      start_type = SERVICE_START_TYPES[config[:dwStartType]]
    end
  end
  # if the service has type AUTO_START, check if it's a delayed service
  if start_type == :SERVICE_AUTO_START
    open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
      query_config2(service, SERVICE_CONFIG_DELAYED_AUTO_START_INFO) do |config|
        return :SERVICE_DELAYED_AUTO_START if config[:fDelayedAutostart] == 1
      end
    end
  end
  if start_type.nil?
    raise Puppet::Error, _("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name }
  end

  start_type
end

.service_state(service_name) ⇒ string

Query the state of a service using QueryServiceStatusEx

Parameters:

  • service_name (string)

    name of the service to query

Returns:

  • (string)

    the status of the service



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/puppet/util/windows/service.rb', line 106

def service_state(service_name)
  state = nil
  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
    query_status(service) do |status|
      state = SERVICE_STATES[status[:dwCurrentState]]
    end
  end
  if state.nil?
    raise Puppet::Error, _("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name }
  end

  state
end

.servicesHash

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

Returns:

  • (Hash)

    a hash containing services: { ‘service name’ => {

      'display_name' => 'display name',
      'service_status_process' => SERVICE_STATUS_PROCESS struct
    }
    

    }



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/puppet/util/windows/service.rb', line 212

def services
  services = {}
  open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
    size_required = 0
    services_returned = 0
    FFI::MemoryPointer.new(:dword) do |bytes_pointer|
      FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
        FFI::MemoryPointer.new(:dword) do |resume_ptr|
          resume_ptr.write_dword(0)
          # Fetch the bytes of memory required to be allocated
          # for QueryServiceConfigW to return succesfully. This
          # is done by sending NULL and 0 for the pointer and size
          # respectively, letting the command fail, then reading the
          # value of pcbBytesNeeded
          #
          # return value will be false from this call, since it's designed
          # to fail. Just ignore it
          EnumServicesStatusExW(
            scm,
            :SC_ENUM_PROCESS_INFO,
            ALL_SERVICE_TYPES,
            SERVICE_STATE_ALL,
            FFI::Pointer::NULL,
            0,
            bytes_pointer,
            svcs_ret_ptr,
            resume_ptr,
            FFI::Pointer::NULL
          )
          size_required = bytes_pointer.read_dword
          FFI::MemoryPointer.new(size_required) do |buffer_ptr|
            resume_ptr.write_dword(0)
            svcs_ret_ptr.write_dword(0)
            success = EnumServicesStatusExW(
              scm,
              :SC_ENUM_PROCESS_INFO,
              ALL_SERVICE_TYPES,
              SERVICE_STATE_ALL,
              buffer_ptr,
              buffer_ptr.size,
              bytes_pointer,
              svcs_ret_ptr,
              resume_ptr,
              FFI::Pointer::NULL
            )
            if success == FFI::WIN32_FALSE
              raise Puppet::Util::Windows::Error, _("Failed to fetch services")
            end

            # Now that the buffer is populated with services
            # we pull the data from memory using pointer arithmetic:
            # the number of services returned by the function is
            # available to be read from svcs_ret_ptr, and we iterate
            # that many times moving the cursor pointer the length of
            # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
            # over the buffer and extract each struct.
            services_returned = svcs_ret_ptr.read_dword
            cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
            0.upto(services_returned - 1) do |index|
              service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
              services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
                :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
                :service_status_process => service[:ServiceStatusProcess]
              }
            end
          end # buffer_ptr
        end # resume_ptr
      end # scvs_ret_ptr
    end # bytes_ptr
  end # open_scm
  services
end

.set_optional_parameter(service_name, change, value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets an optional parameter on a service by calling ChangeServiceConfig2W

Parameters:

  • service_name (String)

    name of service

  • change (Integer)

    parameter to change

  • value (struct)

    appropriate struct based on the parameter to change



539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/puppet/util/windows/service.rb', line 539

def set_optional_parameter(service_name, change, value)
  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
    success = ChangeServiceConfig2W(
      service,
      change, # dwInfoLevel
      value # lpInfo
    )
    if success == FFI::WIN32_FALSE
      raise Puppet::Util.windows::Error, _("Failed to update service %{change} configuration") % { change: change }
    end
  end
end

.set_startup_configuration(service_name, options: {}) ⇒ Object

Set the startup configuration of a windows service

Parameters:

  • service_name (String)

    the name of the service to modify

  • options (Hash) (defaults to: {})

    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



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/puppet/util/windows/service.rb', line 173

def set_startup_configuration(service_name, options: {})
  options[:startup_type] = SERVICE_START_TYPES.key(options[:startup_type]) || SERVICE_NO_CHANGE
  options[:logon_account] = wide_string(options[:logon_account]) || FFI::Pointer::NULL
  options[:logon_password] = wide_string(options[:logon_password]) || FFI::Pointer::NULL

  open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
    success = ChangeServiceConfigW(
      service,
      SERVICE_NO_CHANGE,        # dwServiceType
      options[:startup_type],   # dwStartType
      SERVICE_NO_CHANGE,        # dwErrorControl
      FFI::Pointer::NULL,       # lpBinaryPathName
      FFI::Pointer::NULL,       # lpLoadOrderGroup
      FFI::Pointer::NULL,       # lpdwTagId
      FFI::Pointer::NULL,       # lpDependencies
      options[:logon_account],  # lpServiceStartName
      options[:logon_password], # lpPassword
      FFI::Pointer::NULL        # lpDisplayName
    )
    if success == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error, _("Failed to update service configuration")
    end
  end

  if options[:startup_type]
    options[:delayed] ||= false
    set_startup_mode_delayed(service_name, options[:delayed])
  end
end

.set_startup_mode_delayed(service_name, delayed) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Controls the delayed auto-start setting of a service

Parameters:

  • service_name (String)

    name of service

  • delayed (Bool)

    whether the service should be started with a delay or not



558
559
560
561
562
# File 'lib/puppet/util/windows/service.rb', line 558

def set_startup_mode_delayed(service_name, delayed)
  delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
  delayed_start[:fDelayedAutostart] = delayed
  set_optional_parameter(service_name, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayed_start)
end

.start(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object

Start a windows service

Parameters:

  • service_name (String)

    name of the service to start

  • optional (Integer)

    timeout the minumum number of seconds to wait before timing out



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/puppet/util/windows/service.rb', line 41

def start(service_name, timeout: DEFAULT_TIMEOUT)
  Puppet.debug _("Starting the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }

  valid_initial_states = [
    SERVICE_STOP_PENDING,
    SERVICE_STOPPED,
    SERVICE_START_PENDING
  ]

  transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING, timeout) do |service|
    if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error, _("Failed to start the service")
    end
  end

  Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
end

.stop(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object

Stop a windows service

Parameters:

  • service_name (String)

    name of the service to stop

  • optional (Integer)

    timeout the minumum number of seconds to wait before timing out



64
65
66
67
68
69
70
71
72
73
74
# File 'lib/puppet/util/windows/service.rb', line 64

def stop(service_name, timeout: DEFAULT_TIMEOUT)
  Puppet.debug _("Stopping the %{service_name} service. Timeout set to: %{timeout} seconds") % { service_name: service_name, timeout: timeout }

  valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]

  transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED, timeout) do |service|
    send_service_control_signal(service, SERVICE_CONTROL_STOP)
  end

  Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
end

.transition_service_state(service_name, valid_initial_states, final_state, timeout, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:

  • service_name (String)

    the name of the service to transition

  • valid_initial_states ([Integer])

    an array of valid states that the service can transition from

  • final_state (Integer)

    the state that the service will transition to

  • timeout (Integer)

    the minumum number of seconds to wait before timing out



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/puppet/util/windows/service.rb', line 342

def transition_service_state(service_name, valid_initial_states, final_state, timeout, &block)
  service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS
  open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service|
    query_status(service) do |status|
      initial_state = status[:dwCurrentState]
      # If the service is already in the final_state, then
      # no further work needs to be done
      if initial_state == final_state
        Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] }

        next
      end

      # Check that initial_state corresponds to a valid
      # initial state
      unless valid_initial_states.include?(initial_state)
        valid_initial_states_str = valid_initial_states.map do |state|
          SERVICE_STATES[state]
        end.join(", ")

        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] }
      end

      # Check if there's a pending transition to the final_state. If so, then wait for
      # that transition to finish.
      possible_pending_states = FINAL_STATES.keys.select do |pending_state|
        # SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and
        # SERVICE_CONTINUE_PENDING. That is why we need the #select here
        FINAL_STATES[pending_state] == final_state
      end
      if possible_pending_states.include?(initial_state)
        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 }
        wait_on_pending_state(service, initial_state, timeout)

        next
      end

      # If we are in an unsafe pending state like SERVICE_START_PENDING
      # or SERVICE_STOP_PENDING, then we want to wait for that pending
      # transition to finish before transitioning the service state.
      # The reason we do this is because SERVICE_START_PENDING is when
      # the service thread is being created and initialized, while
      # SERVICE_STOP_PENDING is when the service thread is being cleaned
      # up and destroyed. Thus there is a chance that when the service is
      # in either of these states, its service thread may not yet be ready
      # to perform the state transition (it may not even exist).
      if UNSAFE_PENDING_STATES.include?(initial_state)
        Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] }
        wait_on_pending_state(service, initial_state, timeout)
        initial_state = FINAL_STATES[initial_state]
      end

      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] }

      yield service

      Puppet.debug _("Waiting for the transition to finish")
      wait_on_state_transition(service, initial_state, final_state, timeout)
    end
  end
rescue => detail
  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
end

.wait_hint_to_wait_time(wait_hint) ⇒ Integer

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:

  • wait_hint (Integer)

    the wait hint of a service in milliseconds

Returns:

  • (Integer)

    the time to wait in seconds between querying the service



685
686
687
688
689
690
691
692
# File 'lib/puppet/util/windows/service.rb', line 685

def wait_hint_to_wait_time(wait_hint)
  # Wait 1/10th the wait_hint, but no less than 1 and
  # no more than 10 seconds
  wait_time = milliseconds_to_seconds(wait_hint) / 10;
  wait_time = 1 if wait_time < 1
  wait_time = 10 if wait_time > 10
  wait_time
end

.wait_on_pending_state(service, pending_state, timeout) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

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

Parameters:

  • service (:handle)

    handle to the service to wait on

  • pending_state (Integer)

    the pending state

  • timeout (Integer)

    the minumum number of seconds to wait before timing out



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/puppet/util/windows/service.rb', line 636

def wait_on_pending_state(service, pending_state, timeout)
  final_state = FINAL_STATES[pending_state]

  Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] }

  elapsed_time = 0
  last_checkpoint = -1
  loop do
    query_status(service) do |status|
      state = status[:dwCurrentState]
      checkpoint = status[:dwCheckPoint]
      wait_hint = status[:dwWaitHint]
      # Check if our service has finished transitioning to
      # the final_state OR if an unexpected transition
      # has occurred
      return if state == final_state
      unless state == pending_state
        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] }
      end

      # Check if any progress has been made since our last sleep
      # using the dwCheckPoint. If no progress has been made then
      # check if we've timed out, and raise an error if so
      if checkpoint > last_checkpoint
        elapsed_time = 0
        last_checkpoint = checkpoint
      else
        wait_hint = milliseconds_to_seconds(status[:dwWaitHint])
        timeout = wait_hint < timeout ? timeout : wait_hint

        if elapsed_time >= timeout
          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] }
        end
      end
      wait_time = wait_hint_to_wait_time(wait_hint)
      # Wait a bit before rechecking the service's state
      sleep(wait_time)
      elapsed_time += wait_time
    end
  end
end

.wait_on_state_transition(service, initial_state, final_state, timeout) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Waits for a service to transition from one state to another state.

Parameters:

  • service (:handle)

    handle to the service to wait on

  • initial_state (Integer)

    the state that the service is transitioning from.

  • final_state (Integer)

    the state that the service is transitioning to

  • timeout (Integer)

    the minumum number of seconds to wait before timing out

Raises:



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'lib/puppet/util/windows/service.rb', line 587

def wait_on_state_transition(service, initial_state, final_state, timeout)
  # Get the pending state for this transition. Note that SERVICE_RUNNING
  # has two possible pending states, which is why we need this logic.
  if final_state != SERVICE_RUNNING
    pending_state = FINAL_STATES.key(final_state)
  elsif initial_state == SERVICE_STOPPED
    # SERVICE_STOPPED => SERVICE_RUNNING
    pending_state = SERVICE_START_PENDING
  else
    # SERVICE_PAUSED => SERVICE_RUNNING
    pending_state = SERVICE_CONTINUE_PENDING
  end

  # Wait for the transition to finish
  state = nil
  elapsed_time = 0
  while elapsed_time <= timeout

    query_status(service) do |status|
      state = status[:dwCurrentState]
      return if state == final_state

      if state == pending_state
        Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] }
        wait_on_pending_state(service, pending_state, timeout)
        return
      end
      sleep(1)
      elapsed_time += 1
    end
  end
  # Timed out while waiting for the transition to finish. Raise an error
  # We can still use the state variable read from the FFI struct because
  # FFI creates new Integer objects during an assignment of an integer value
  # stored in an FFI struct. We verified that the '=' operater is safe
  # from the freed memory since the new ruby object created during the
  # assignment will remain in ruby memory and remain immutable and constant.
  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] }
end