class Win32::Service

The Service class encapsulates services controller actions, such as creating, starting, configuring or deleting services.

Constants

ACTION_NONE

No action

ACTION_REBOOT

Reboot the computer

ACTION_RESTART

Restart the service

ACTION_RUN_COMMAND

Run a command

ALL_ACCESS

Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights

AUTO_START

A service started automatically by the SCM during system startup

BOOT_START

A service started automatically by the SCM during system startup

CHANGE_CONFIG

Required to call functions that configure existing services

CONTINUE_PENDING

Service has received a signal to resume but is not yet running

CONTROL_CONTINUE

Notifies service that it should resume

CONTROL_INTERROGATE

Notifies service that it should return its current status information

CONTROL_NETBINDADD

Notifies a service that there is a new component for binding

CONTROL_NETBINDDISABLE

Notifies a service that a component for binding has been disabled

CONTROL_NETBINDENABLE

Notifies a service that a component for binding has been enabled

CONTROL_NETBINDREMOVE

Notifies a service that a component for binding has been removed

CONTROL_PARAMCHANGE

Notifies a service that its parameters have changed

CONTROL_PAUSE

Notifies service that it should pause

CONTROL_STOP

Notifies service that it should stop

DEMAND_START

A service started by the SCM when a process calls StartService

DISABLED

A service that cannot be started

DRIVER
ENUMERATE_DEPENDENTS

Required to enumerate all the services dependent on the service

ERROR_CRITICAL

Error logged, startup fails, system restarted last known good config

ERROR_IGNORE

Error logged, startup continues

ERROR_NORMAL

Error logged, pop up message, startup continues

ERROR_SEVERE

Error logged, startup continues, system restarted last known good config

FILE_SYSTEM_DRIVER

File system driver service

INTERACTIVE_PROCESS

The service can interact with the desktop

INTERROGATE

Required to make a service report its status immediately

KERNEL_DRIVER

Driver service

MANAGER_ALL_ACCESS

Includes STANDARD_RIGHTS_REQUIRED, in addition to all other rights

MANAGER_CONNECT

Required to connect to the service control manager.

MANAGER_CREATE_SERVICE

Required to call the CreateService function

MANAGER_ENUMERATE_SERVICE

Required to call the EnumServicesStatusEx function to list services

MANAGER_LOCK

Required to call the LockServiceDatabase function

MANAGER_MODIFY_BOOT_CONFIG

Required to call the NotifyBootConfigStatus function

MANAGER_QUERY_LOCK_STATUS

Required to call the QueryServiceLockStatus function

PAUSED

Service is paused

PAUSE_CONTINUE

Required to control a service with a pause or resume

PAUSE_PENDING

Service has received a signal to pause but is not yet paused

QUERY_CONFIG

Required to be able to gather configuration information about a service

QUERY_STATUS

Required to be ask the SCM about the status of a service

RUNNING

Service is running

START

Required to call the StartService function to start the service.

START_PENDING

Service has received a start signal but is not yet running

STOP

Required to call the ControlService function to stop the service.

STOPPED

Service is not running

STOP_PENDING

Service has received a stop signal but is not yet stopped

SYSTEM_START

A device driver started by the IoInitSystem function. Drivers only

TINY
TYPE_ALL
USER_DEFINED_CONTROL

Required to call ControlService with a user defined control code

VERSION
WIN32_OWN_PROCESS

Service that runs in its own process

WIN32_SHARE_PROCESS

Service that shares a process with one or more other services.

Public Class Methods

close_service_handle(handle) click to toggle source
# File lib/win32/service.rb, line 1226
def self.close_service_handle(handle)
  case handle
  when NilClass
    false
  when Integer
    handle > 0 ? CloseServiceHandle(handle) : false
  else
    raise ArgumentError, "You must pass a valid handle to ::close_service_handle"
  end
end
config_info(service, host = nil) click to toggle source

Returns a ServiceConfigInfo struct containing the configuration information about service on host, or the local host if no host is specified.

Example:

Service.config_info('W32Time') => <struct ServiceConfigInfo ...>
# File lib/win32/service.rb, line 848
def self.config_info(service, host = nil)
  raise TypeError if host && !host.is_a?(String)

  handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  begin
    handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_CONFIG)

    FFI.raise_windows_error("OpenService") if handle_scs == 0

    # First, get the buf size needed
    bytes = FFI::MemoryPointer.new(:ulong)

    bool = QueryServiceConfig(handle_scs, nil, 0, bytes)

    if !bool && FFI.errno != ERROR_INSUFFICIENT_BUFFER
      FFI.raise_windows_error("QueryServiceConfig")
    end

    buf = FFI::MemoryPointer.new(:char, bytes.read_ulong)
    bytes = FFI::MemoryPointer.new(:ulong)

    bool = QueryServiceConfig(handle_scs, buf, buf.size, bytes)

    struct = QUERY_SERVICE_CONFIG.new(buf) # cast the buffer

    FFI.raise_windows_error("QueryServiceConfig") unless bool
  ensure
    close_service_handle(handle_scs)
    close_service_handle(handle_scm)
  end

  ConfigStruct.new(
    get_service_type(struct[:dwServiceType]),
    get_start_type(struct[:dwStartType]),
    get_error_control(struct[:dwErrorControl]),
    struct[:lpBinaryPathName].read_string,
    struct[:lpLoadOrderGroup].read_string,
    struct[:dwTagId],
    struct.dependencies,
    struct[:lpServiceStartName].read_string,
    struct[:lpDisplayName].read_string
  )
end
configure(options = {}) click to toggle source

Configures the named service on host, or the local host if no host is specified. The options parameter is a hash that can contain any of the following parameters:

  • service_type

  • start_type

  • error_control

  • binary_path_name

  • load_order_group

  • dependencies

  • service_start_name

  • password (used with service_start_name)

  • display_name

  • description

  • failure_reset_period

  • failure_reboot_message

  • failure_command

  • failure_actions

  • failure_delay

Examples:

# Configure only the display name
Service.configure(
  :service_name => 'some_service',
  :display_name => 'Test 33'
)

# Configure everything
Service.configure(
   :service_name       => 'some_service'
   :service_type       => Service::WIN32_OWN_PROCESS,
   :start_type         => Service::AUTO_START,
   :error_control      => Service::ERROR_NORMAL,
   :binary_path_name   => 'C:\path\to\some_service.exe',
   :load_order_group   => 'Network',
   :dependencies       => ['W32Time','Schedule']
   :service_start_name => 'SomeDomain\\User',
   :password           => 'XXXXXXX',
   :display_name       => 'This is some service',
   :description        => 'A custom service I wrote just for fun'
)
# File lib/win32/service.rb, line 453
def self.configure(options = {})
  unless options.is_a?(Hash)
    raise ArgumentError, "options parameter must be a hash"
  end

  if options.empty?
    raise ArgumentError, "no options provided"
  end

  opts = {
    "service_type"           => SERVICE_NO_CHANGE,
    "start_type"             => SERVICE_NO_CHANGE,
    "error_control"          => SERVICE_NO_CHANGE,
    "binary_path_name"       => nil,
    "load_order_group"       => nil,
    "dependencies"           => nil,
    "service_start_name"     => nil,
    "password"               => nil,
    "display_name"           => nil,
    "description"            => nil,
    "failure_reset_period"   => nil,
    "failure_reboot_message" => nil,
    "failure_command"        => nil,
    "failure_actions"        => nil,
    "failure_delay"          => 0,
    "service_name"           => nil,
    "host"                   => nil,
    "delayed_start"          => false,
  }

  # Validate the hash options
  options.each { |key, value|
    key = key.to_s.downcase
    unless opts.include?(key)
      raise ArgumentError, "Invalid option '#{key}'"
    end

    opts[key] = value
  }

  unless opts["service_name"]
    raise ArgumentError, "No service_name specified"
  end

  service = opts.delete("service_name")
  host = opts.delete("host")

  raise TypeError unless service.is_a?(String)

  if host
    raise TypeError unless host.is_a?(String)
  end

  begin
    handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)

    FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

    desired_access = SERVICE_CHANGE_CONFIG

    if opts["failure_actions"]
      desired_access |= SERVICE_START
    end

    handle_scs = OpenService(
      handle_scm,
      service,
      desired_access
    )

    FFI.raise_windows_error("OpenService") if handle_scs == 0

    dependencies = opts["dependencies"]

    if dependencies && !dependencies.empty?
      unless dependencies.is_a?(Array) || dependencies.is_a?(String)
        raise TypeError, "dependencies must be a string or array"
      end

      if dependencies.is_a?(Array)
        dependencies = dependencies.join("\000")
      end

      dependencies += "\000"
    end

    bool = ChangeServiceConfig(
      handle_scs,
      opts["service_type"],
      opts["start_type"],
      opts["error_control"],
      opts["binary_path_name"],
      opts["load_order_group"],
      nil,
      dependencies,
      opts["service_start_name"],
      opts["password"],
      opts["display_name"]
    )

    FFI.raise_windows_error("ChangeServiceConfig") unless bool

    if opts["description"]
      description = SERVICE_DESCRIPTION.new
      description[:lpDescription] = FFI::MemoryPointer.from_string(opts["description"])

      bool = ChangeServiceConfig2(
        handle_scs,
        SERVICE_CONFIG_DESCRIPTION,
        description
      )

      FFI.raise_windows_error("ChangeServiceConfig2") unless bool
    end

    if opts["delayed_start"]
      delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
      delayed_start[:fDelayedAutostart] = opts["delayed_start"]

      bool = ChangeServiceConfig2(
        handle_scs,
        SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
        delayed_start
      )

      FFI.raise_windows_error("ChangeServiceConfig2") unless bool
    end

    if opts["failure_reset_period"] || opts["failure_reboot_message"] ||
        opts["failure_command"] || opts["failure_actions"]
      configure_failure_actions(handle_scs, opts)
    end
  ensure
    close_service_handle(handle_scs)
    close_service_handle(handle_scm)
  end

  self
end
configure_failure_actions(handle_scs, opts) click to toggle source

Configures failure actions for a given service.

# File lib/win32/service.rb, line 1306
def self.configure_failure_actions(handle_scs, opts)
  if opts["failure_actions"]
    token_handle = FFI::MemoryPointer.new(:ulong)

    bool = OpenProcessToken(
      GetCurrentProcess(),
      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
      token_handle
    )

    unless bool
      error = FFI.errno
      close_service_handle(handle_scs)
      raise SystemCallError.new("OpenProcessToken", error)
    end

    token_handle = token_handle.read_ulong

    # Get the LUID for shutdown privilege.
    luid = LUID.new

    unless LookupPrivilegeValue("", "SeShutdownPrivilege", luid)
      error = FFI.errno
      close_service_handle(handle_scs)
      raise SystemCallError.new("LookupPrivilegeValue", error)
    end

    luid_and_attrs = LUID_AND_ATTRIBUTES.new
    luid_and_attrs[:Luid] = luid
    luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED

    tkp = TOKEN_PRIVILEGES.new
    tkp[:PrivilegeCount] = 1
    tkp[:Privileges][0] = luid_and_attrs

    # Enable shutdown privilege in access token of this process
    bool = AdjustTokenPrivileges(
      token_handle,
      0,
      tkp,
      tkp.size,
      nil,
      nil
    )

    unless bool
      error = FFI.errno
      close_service_handle(handle_scs)
      raise SystemCallError.new("AdjustTokenPrivileges", error)
    end
  end

  sfa = SERVICE_FAILURE_ACTIONS.new

  if opts["failure_reset_period"]
    sfa[:dwResetPeriod] = opts["failure_reset_period"]
  end

  if opts["failure_reboot_message"]
    sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts["failure_reboot_message"])
  end

  if opts["failure_command"]
    sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts["failure_command"])
  end

  if opts["failure_actions"]
    action_size = opts["failure_actions"].size
    action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)

    actions = action_size.times.collect do |i|
      SC_ACTION.new(action_ptr + i * SC_ACTION.size)
    end

    opts["failure_actions"].each_with_index { |action, i|
      actions[i][:Type] = action
      actions[i][:Delay] = opts["failure_delay"]
    }

    sfa[:cActions] = action_size
    sfa[:lpsaActions] = action_ptr
  end

  bool = ChangeServiceConfig2(
    handle_scs,
    SERVICE_CONFIG_FAILURE_ACTIONS,
    sfa
  )

  unless bool
    error = FFI.errno
    close_service_handle(handle_scs)
    raise SystemCallError.new("ChangeServiceConfig2", error)
  end
end
create(options = {})
Alias for: new
delayed_start(service, host = nil) click to toggle source

Attempts to get the delayed_start attribute for the named service on host, or the local machine if no host is provided.

@example Get 'SomeSvc' delayed start on the local machine

Service.delayed_start('SomeSvc') # => 1

@example Get 'SomeSvc' delayed start on host foo

Service.delayed_start('SomeSvc', 'foo') # => 1

@param service [String] Service name (e.g. `“Dhcp”`) @param host [String] Host of service (e.g. `“mymachine”`) @return [Integer, false, nil] Returns `1` when delayed start is enabled

and `0` when it is not enabled. Returns nil or false when there is
a problem of some kind.
# File lib/win32/service.rb, line 1199
def self.delayed_start(service, host = nil)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  handle_scs = OpenService(
    handle_scm,
    service,
    SERVICE_QUERY_CONFIG
  )

  FFI.raise_windows_error("OpenService") if handle_scs == 0

  delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
  if delayed_start_buf.is_a?(FFI::MemoryPointer)
    delayed_start_info = SERVICE_DELAYED_AUTO_START_INFO.new(delayed_start_buf)
    delayed_start = delayed_start_info[:fDelayedAutostart]
  else
    delayed_start = false
  end
rescue SystemCallError
  delayed_start = nil
ensure
  close_service_handle(handle_scs)
  close_service_handle(handle_scm)
end
delete(service, host = nil) click to toggle source

Deletes the specified service from host, or the local host if no host is specified. Returns self.

Technical note. This method is not instantaneous. The service is first marked for deletion from the service control manager database. Then all handles to the service are closed. Then an attempt to stop the service is made. If the service cannot be stopped, the service control manager database entry is removed when the system is restarted.

Example:

Service.delete('SomeService') => self
# File lib/win32/service.rb, line 815
def self.delete(service, host = nil)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  begin
    handle_scs = OpenService(handle_scm, service, DELETE)

    FFI.raise_windows_error("OpenService") if handle_scs == 0

    unless DeleteService(handle_scs)
      FFI.raise_windows_error("DeleteService")
    end
  ensure
    close_service_handle(handle_scs)
    close_service_handle(handle_scm)
  end

  self
end
exists?(service, host = nil) click to toggle source

Returns whether or not service exists on host or localhost, if no host is specified.

Example:

Service.exists?('W32Time') => true

# File lib/win32/service.rb, line 600
def self.exists?(service, host = nil)
  bool = false

  begin
    handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)

    FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

    handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_STATUS)
    bool = true if handle_scs > 0
  ensure
    close_service_handle(handle_scm)
    close_service_handle(handle_scs)
  end

  bool
end
get_action_type(action_type) click to toggle source

Returns a human readable string indicating the action type.

# File lib/win32/service.rb, line 1404
def self.get_action_type(action_type)
  case action_type
    when SC_ACTION_NONE
      "none"
    when SC_ACTION_REBOOT
      "reboot"
    when SC_ACTION_RESTART
      "restart"
    when SC_ACTION_RUN_COMMAND
      "command"
    else
      "unknown"
  end
end
get_config2_info(handle, info_level) click to toggle source

Shortcut for QueryServiceConfig2. Returns the buffer.

# File lib/win32/service.rb, line 1461
def self.get_config2_info(handle, info_level)
  bytes_needed = FFI::MemoryPointer.new(:ulong)

  # First attempt at QueryServiceConfig2 is to get size needed
  bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)

  err_num = FFI.errno

  # This is a bit hacky since it means we have to check the type of value
  # we get back, but we don't always want to raise an error either,
  # depending on what we're trying to get at.
  #
  if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
    config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
  elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
    return err_num
  else
    CloseServiceHandle(handle)
    FFI.raise_windows_error("QueryServiceConfig2", err_num)
  end

  bytes_needed.clear

  # Second attempt at QueryServiceConfig2 gets the actual info
  begin
    bool = QueryServiceConfig2(
      handle,
      info_level,
      config2_buf,
      config2_buf.size,
      bytes_needed
    )

    FFI.raise_windows_error("QueryServiceConfig2") unless bool
  ensure
    CloseServiceHandle(handle) unless bool
  end

  config2_buf
end
get_config_info(handle) click to toggle source

Shortcut for QueryServiceConfig. Returns the buffer. In rare cases the underlying registry entry may have been deleted, but the service still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned instead.

# File lib/win32/service.rb, line 1424
def self.get_config_info(handle)
  bytes_needed = FFI::MemoryPointer.new(:ulong)

  # First attempt at QueryServiceConfig is to get size needed
  bool = QueryServiceConfig(handle, nil, 0, bytes_needed)

  if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
    config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
  elsif FFI.errno == ERROR_FILE_NOT_FOUND
    return FFI.errno
  else
    error = FFI.errno
    CloseServiceHandle(handle)
    FFI.raise_windows_error("QueryServiceConfig", error)
  end

  bytes_needed.clear

  # Second attempt at QueryServiceConfig gets the actual info
  begin
    bool = QueryServiceConfig(
      handle,
      config_buf,
      config_buf.size,
      bytes_needed
    )

    FFI.raise_windows_error("QueryServiceConfig") unless bool
  ensure
    CloseServiceHandle(handle) unless bool
  end

  QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
end
get_controls_accepted(controls) click to toggle source

Returns an array of human readable strings indicating the controls that the service accepts.

# File lib/win32/service.rb, line 1541
def self.get_controls_accepted(controls)
  array = []

  if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
    array << "netbind change"
  end

  if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
    array << "param change"
  end

  if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
    array << "pause continue"
  end

  if controls & SERVICE_ACCEPT_SHUTDOWN > 0
    array << "shutdown"
  end

  if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
    array << "pre-shutdown"
  end

  if controls & SERVICE_ACCEPT_STOP > 0
    array << "stop"
  end

  if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
    array << "hardware profile change"
  end

  if controls & SERVICE_ACCEPT_POWEREVENT > 0
    array << "power event"
  end

  if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
    array << "session change"
  end

  array
end
get_current_state(state) click to toggle source

Converts a service state numeric constant into a readable string.

# File lib/win32/service.rb, line 1585
def self.get_current_state(state)
  case state
    when SERVICE_CONTINUE_PENDING
      "continue pending"
    when SERVICE_PAUSE_PENDING
      "pause pending"
    when SERVICE_PAUSED
      "paused"
    when SERVICE_RUNNING
      "running"
    when SERVICE_START_PENDING
      "start pending"
    when SERVICE_STOP_PENDING
      "stop pending"
    when SERVICE_STOPPED
      "stopped"
    else
      nil
  end
end
get_display_name(service, host = nil) click to toggle source

Returns the display name of the specified service name, i.e. the string displayed in the Services GUI. Raises a Service::Error if the service name cannot be found.

If a host is provided, the information will be retrieved from that host. Otherwise, the information is pulled from the local host (the default behavior).

Example:

Service.get_display_name('W32Time') => 'Windows Time'

# File lib/win32/service.rb, line 630
def self.get_display_name(service, host = nil)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  display_name = FFI::MemoryPointer.new(260)
  display_size = FFI::MemoryPointer.new(:ulong)
  display_size.write_ulong(display_name.size)

  begin
    bool = GetServiceDisplayName(
      handle_scm,
      service,
      display_name,
      display_size
    )

    FFI.raise_windows_error("OpenSCManager") unless bool
  ensure
    close_service_handle(handle_scm)
  end

  display_name.read_string
end
Also aliased as: getdisplayname
get_error_control(error_control) click to toggle source

Returns a human readable string indicating the error control

# File lib/win32/service.rb, line 1504
def self.get_error_control(error_control)
  case error_control
    when SERVICE_ERROR_CRITICAL
      "critical"
    when SERVICE_ERROR_IGNORE
      "ignore"
    when SERVICE_ERROR_NORMAL
      "normal"
    when SERVICE_ERROR_SEVERE
      "severe"
    else
      nil
  end
end
get_service_name(display_name, host = nil) click to toggle source

Returns the service name of the specified service from the provided display_name. Raises a Service::Error if the display_name cannote be found.

If a host is provided, the information will be retrieved from that host. Otherwise, the information is pulled from the local host (the default behavior).

Example:

Service.get_service_name('Windows Time') => 'W32Time'

# File lib/win32/service.rb, line 667
def self.get_service_name(display_name, host = nil)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  service_name = FFI::MemoryPointer.new(260)
  service_size = FFI::MemoryPointer.new(:ulong)
  service_size.write_ulong(service_name.size)

  begin
    bool = GetServiceKeyName(
      handle_scm,
      display_name,
      service_name,
      service_size
    )

    FFI.raise_windows_error("GetServiceKeyName") unless bool
  ensure
    close_service_handle(handle_scm)
  end

  service_name.read_string
end
Also aliased as: getservicename
get_service_type(service_type) click to toggle source

Converts a service type numeric constant into a human readable string.

# File lib/win32/service.rb, line 1608
def self.get_service_type(service_type)
  case service_type
    when SERVICE_FILE_SYSTEM_DRIVER
      "file system driver"
    when SERVICE_KERNEL_DRIVER
      "kernel driver"
    when SERVICE_WIN32_OWN_PROCESS
      "own process"
    when SERVICE_WIN32_SHARE_PROCESS
      "share process"
    when SERVICE_RECOGNIZER_DRIVER
      "recognizer driver"
    when SERVICE_DRIVER
      "driver"
    when SERVICE_WIN32
      "win32"
    when SERVICE_TYPE_ALL
      "all"
    when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
      "own process, interactive"
    when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
      "share process, interactive"
    else
      nil
  end
end
get_start_type(start_type) click to toggle source

Returns a human readable string indicating the start type.

# File lib/win32/service.rb, line 1521
def self.get_start_type(start_type)
  case start_type
    when SERVICE_AUTO_START
      "auto start"
    when SERVICE_BOOT_START
      "boot start"
    when SERVICE_DEMAND_START
      "demand start"
    when SERVICE_DISABLED
      "disabled"
    when SERVICE_SYSTEM_START
      "system start"
    else
      nil
  end
end
getdisplayname(service, host = nil)
Alias for: get_display_name
getservicename(display_name, host = nil)
Alias for: get_service_name
new(options = {}) click to toggle source

Creates a new service with the specified options. A service_name must be specified or an ArgumentError is raised. A host option may be specified. If no host is specified the local machine is used.

Possible Options:

  • service_name => nil (you must specify)

  • host => nil (optional)

  • display_name => service_name

  • desired_access => Service::ALL_ACCESS

  • service_type => Service::WIN32_OWN_PROCESS

  • start_type => Service::DEMAND_START

  • error_control => Service::ERROR_NORMAL

  • binary_path_name => nil

  • load_order_group => nil

  • dependencies => nil

  • service_start_name => nil

  • password => nil

  • description => nil

  • failure_reset_period => nil,

  • failure_reboot_message => nil,

  • failure_command => nil,

  • failure_actions => nil,

  • failure_delay => 0

Example:

# Configure everything
Service.new(
  :service_name       => 'some_service',
  :host               => 'localhost',
  :service_type       => Service::WIN32_OWN_PROCESS,
  :description        => 'A custom service I wrote just for fun',
  :start_type         => Service::AUTO_START,
  :error_control      => Service::ERROR_NORMAL,
  :binary_path_name   => 'C:\path\to\some_service.exe',
  :load_order_group   => 'Network',
  :dependencies       => ['W32Time','Schedule'],
  :service_start_name => 'SomeDomain\\User',
  :password           => 'XXXXXXX',
  :display_name       => 'This is some service',
)
# File lib/win32/service.rb, line 294
def initialize(options = {})
  unless options.is_a?(Hash)
    raise ArgumentError, "options parameter must be a hash"
  end

  if options.empty?
    raise ArgumentError, "no options provided"
  end

  opts = {
    "display_name"           => nil,
    "desired_access"         => SERVICE_ALL_ACCESS,
    "service_type"           => SERVICE_WIN32_OWN_PROCESS,
    "start_type"             => SERVICE_DEMAND_START,
    "error_control"          => SERVICE_ERROR_NORMAL,
    "binary_path_name"       => nil,
    "load_order_group"       => nil,
    "dependencies"           => nil,
    "service_start_name"     => nil,
    "password"               => nil,
    "description"            => nil,
    "failure_reset_period"   => nil,
    "failure_reboot_message" => nil,
    "failure_command"        => nil,
    "failure_actions"        => nil,
    "failure_delay"          => 0,
    "host"                   => nil,
    "service_name"           => nil,
  }

  # Validate the hash options
  options.each { |key, value|
    key = key.to_s.downcase
    unless opts.include?(key)
      raise ArgumentError, "Invalid option '#{key}'"
    end

    opts[key] = value
  }

  unless opts["service_name"]
    raise ArgumentError, "No service_name specified"
  end

  service_name = opts.delete("service_name")
  host = opts.delete("host")

  raise TypeError unless service_name.is_a?(String)
  raise TypeError if host && !host.is_a?(String)

  begin
    handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)

    FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

    # Display name defaults to service_name
    opts["display_name"] ||= service_name

    dependencies = opts["dependencies"]

    if dependencies && !dependencies.empty?
      unless dependencies.is_a?(Array) || dependencies.is_a?(String)
        raise TypeError, "dependencies must be a string or array"
      end

      if dependencies.is_a?(Array)
        dependencies = dependencies.join("\000")
      end

      dependencies += "\000"
      dependencies = FFI::MemoryPointer.from_string(dependencies)
    end

    handle_scs = CreateService(
      handle_scm,
      service_name,
      opts["display_name"],
      opts["desired_access"],
      opts["service_type"],
      opts["start_type"],
      opts["error_control"],
      opts["binary_path_name"],
      opts["load_order_group"],
      nil,
      dependencies,
      opts["service_start_name"],
      opts["password"]
    )

    FFI.raise_windows_error("CreateService") if handle_scs == 0

    if opts["description"]
      description = SERVICE_DESCRIPTION.new
      description[:lpDescription] = FFI::MemoryPointer.from_string(opts["description"])

      bool = ChangeServiceConfig2(
        handle_scs,
        SERVICE_CONFIG_DESCRIPTION,
        description
      )

      FFI.raise_windows_error("ChangeServiceConfig2") unless bool
    end

    if opts["failure_reset_period"] || opts["failure_reboot_message"] ||
        opts["failure_command"] || opts["failure_actions"]
      self.class.configure_failure_actions(handle_scs, opts)
    end
  ensure
    self.class.close_service_handle(handle_scs)
    self.class.close_service_handle(handle_scm)
  end

  self
end
Also aliased as: create
open_sc_manager(host = nil, desired_access = SC_MANAGER_CONNECT) { |scm_handle| ... } click to toggle source

Establishes a connection to the service control manager on the specified host and opens the SERVICES_ACTIVE_DATABASE service control manager database.

@example

open_sc_manager(nil, SC_MANAGER_ENUMERATE_SERVICE) do |scm_handle|
  p scm_handle
end

@param host [String] Name of host you want to connect to. If `nil` it

will connect to the local host.

@param desired_access [Integer] The access to the service control

manager.

@return [Integer] If the function succeeds, the return value is a handle

to the specified service control manager database. If the function
fails, the return value is 0.

@see Windows::ServiceConstants

# File lib/win32/service.rb, line 1291
def self.open_sc_manager(host = nil, desired_access = SC_MANAGER_CONNECT)
  scm_handle = OpenSCManager(host, nil, desired_access)
  FFI.raise_windows_error("OpenSCManager") if scm_handle == 0

  if block_given?
    yield scm_handle
  else
    scm_handle
  end
ensure
  close_service_handle(scm_handle) if block_given?
end
open_service(scm_handle, service_name, desired_access) { |service_handle| ... } click to toggle source

Opens an existing service.

@example

open_sc_manager do |scm_handle|
  open_service(scm_handle, 'Dhcp', SERVICE_ALL_ACCESS) do |service_handle|
    p service_handle
  end
end

@param scm_handle [Integer] pointer to Service Control Manager @param service_name [String] Name of the service @param desired_access [Integer] The access to the service. For a list of

access rights, see `Windows::ServiceConstants`.

@see Windows::ServiceConstants

# File lib/win32/service.rb, line 1254
def self.open_service(scm_handle, service_name, desired_access)
   service_handle = OpenService(
    scm_handle,
    service_name,
    desired_access
  )
   FFI.raise_windows_error("OpenService") if service_handle == 0

   if block_given?
     yield service_handle
   else
     service_handle
   end
ensure
  close_service_handle(service_handle) if block_given?
end
pause(service, host = nil) click to toggle source

Pauses the given service on host, or the local host if no host is specified. Returns self

Note that pausing a service that is already paused will have no effect and it will not raise an error.

Be aware that not all services are configured to accept a pause command. Attempting to pause a service that isn't setup to receive a pause command will raise an error.

Example:

Service.pause('Schedule') => self
# File lib/win32/service.rb, line 778
def self.pause(service, host = nil)
  service_signal = SERVICE_PAUSE_CONTINUE
  control_signal = SERVICE_CONTROL_PAUSE
  send_signal(service, host, service_signal, control_signal)
  self
end
resume(service, host = nil) click to toggle source

Resume the given service on host, or the local host if no host is specified. Returns self.

Note that resuming a service that's already running will have no effect and it will not raise an error.

Example:

Service.resume('Schedule') => self
# File lib/win32/service.rb, line 795
def self.resume(service, host = nil)
  service_signal = SERVICE_PAUSE_CONTINUE
  control_signal = SERVICE_CONTROL_CONTINUE
  send_signal(service, host, service_signal, control_signal)
  self
end
send_signal(service, host, service_signal, control_signal) click to toggle source

A shortcut method that simplifies the various service control methods.

# File lib/win32/service.rb, line 1637
def self.send_signal(service, host, service_signal, control_signal)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  begin
    handle_scs = OpenService(handle_scm, service, service_signal)

    FFI.raise_windows_error("OpenService") if handle_scs == 0

    status = SERVICE_STATUS.new

    unless ControlService(handle_scs, control_signal, status)
      FFI.raise_windows_error("ControlService")
    end
  ensure
    close_service_handle(handle_scs)
    close_service_handle(handle_scm)
  end

  status
end
services(host = nil, group = nil) { |struct| ... } click to toggle source

Enumerates over a list of service types on host, or the local machine if no host is specified, yielding a ServiceInfo struct for each service.

If a group is specified, then only those services that belong to that load order group are enumerated. If an empty string is provided, then only services that do not belong to any group are enumerated. If this parameter is nil (the default), group membership is ignored and all services are enumerated. This value is not case sensitive.

Examples:

# Enumerate over all services on the localhost
Service.services{ |service| p service }

# Enumerate over all services on a remote host
Service.services('some_host'){ |service| p service }

# Enumerate over all 'network' services locally
Service.services(nil, 'network'){ |service| p service }
# File lib/win32/service.rb, line 962
def self.services(host = nil, group = nil)
  unless host.nil?
    raise TypeError unless host.is_a?(String) # Avoid strange errors
  end

  unless group.nil?
    raise TypeError unless group.is_a?(String) # Avoid strange errors
  end

  handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  bytes_needed      = FFI::MemoryPointer.new(:ulong)
  services_returned = FFI::MemoryPointer.new(:ulong)
  resume_handle     = FFI::MemoryPointer.new(:ulong)

  begin
    # The first call is used to determine the required buffer size
    bool = EnumServicesStatusEx(
      handle_scm,
      SC_ENUM_PROCESS_INFO,
      SERVICE_WIN32 | SERVICE_DRIVER,
      SERVICE_STATE_ALL,
      nil,
      0,
      bytes_needed,
      services_returned,
      resume_handle,
      group
    )

    if !bool && FFI.errno == ERROR_MORE_DATA
      service_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
    else
      FFI.raise_windows_error("EnumServiceStatusEx")
    end

    bool = EnumServicesStatusEx(
      handle_scm,
      SC_ENUM_PROCESS_INFO,
      SERVICE_WIN32 | SERVICE_DRIVER,
      SERVICE_STATE_ALL,
      service_buf,
      service_buf.size,
      bytes_needed,
      services_returned,
      resume_handle,
      group
    )

    FFI.raise_windows_error("EnumServiceStatusEx") unless bool

    num_services = services_returned.read_ulong

    services_array = [] unless block_given?

    1.upto(num_services) { |num|
      # Cast the buffer
      struct = ENUM_SERVICE_STATUS_PROCESS.new(service_buf)

      service_name = struct[:lpServiceName].read_string
      display_name = struct[:lpDisplayName].read_string

      service_type  = get_service_type(struct[:ServiceStatusProcess][:dwServiceType])
      current_state = get_current_state(struct[:ServiceStatusProcess][:dwCurrentState])
      controls      = get_controls_accepted(struct[:ServiceStatusProcess][:dwControlsAccepted])

      interactive   = struct[:ServiceStatusProcess][:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0
      win_exit_code = struct[:ServiceStatusProcess][:dwWin32ExitCode]
      ser_exit_code = struct[:ServiceStatusProcess][:dwServiceSpecificExitCode]
      check_point   = struct[:ServiceStatusProcess][:dwCheckPoint]
      wait_hint     = struct[:ServiceStatusProcess][:dwWaitHint]
      pid           = struct[:ServiceStatusProcess][:dwProcessId]
      service_flags = struct[:ServiceStatusProcess][:dwServiceFlags]

      begin
        handle_scs = OpenService(
          handle_scm,
          service_name,
          SERVICE_QUERY_CONFIG
        )

        FFI.raise_windows_error("OpenService") if handle_scs == 0

        config_struct = get_config_info(handle_scs)

        if config_struct != ERROR_FILE_NOT_FOUND
          binary_path = config_struct[:lpBinaryPathName].read_string
          load_order  = config_struct[:lpLoadOrderGroup].read_string
          start_name  = config_struct[:lpServiceStartName].read_string
          tag_id      = config_struct[:dwTagId]

          start_type = get_start_type(config_struct[:dwStartType])
          error_ctrl = get_error_control(config_struct[:dwErrorControl])

          deps = config_struct.dependencies

          begin
            buf = get_config2_info(handle_scs, SERVICE_CONFIG_DESCRIPTION)

            if buf.is_a?(Numeric) || buf.read_pointer.null?
              description = ""
            else
              description = buf.read_pointer.read_string
            end
          rescue
            # While being annoying, not being able to get a description is not exceptional
            warn "WARNING: Failed to retrieve description for the #{service_name} service."
            description = ""
          end

          delayed_start = delayed_start(service_name)
        else
          warn "WARNING: The registry entry for the #{service_name} service could not be found"

          binary_path = nil
          load_order  = nil
          start_name  = nil
          start_type  = nil
          error_ctrl  = nil
          tag_id      = nil
          deps        = nil
          description = nil
        end

        begin
          buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)

          if buf2.is_a?(FFI::MemoryPointer)
            fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)

            reset_period = fail_struct[:dwResetPeriod]
            num_actions  = fail_struct[:cActions]

            if fail_struct[:lpRebootMsg].null?
              reboot_msg = nil
            else
              reboot_msg = fail_struct[:lpRebootMsg].read_string
            end

            if fail_struct[:lpCommand].null?
              command = nil
            else
              command = fail_struct[:lpCommand].read_string
            end

            actions = nil

            if num_actions > 0
              action_ptr = fail_struct[:lpsaActions]

              actions = {}

              num_actions.times { |n|
                sc_action = SC_ACTION.new(action_ptr[n * SC_ACTION.size])
                delay = sc_action[:Delay]
                action_type = get_action_type(sc_action[:Type])
                actions[n + 1] = { action_type: action_type, delay: delay }
              }
            end
          else
            reset_period = nil
            reboot_msg   = nil
            command      = nil
            actions      = nil
          end
        rescue
          warn "WARNING: Unable to retrieve failure actions for the #{service_name} service"
          reset_period = nil
          reboot_msg   = nil
          command      = nil
          actions      = nil
        end
      ensure
        close_service_handle(handle_scs)
      end

      struct = ServiceStruct.new(
        service_name,
        display_name,
        service_type,
        current_state,
        controls,
        win_exit_code,
        ser_exit_code,
        check_point,
        wait_hint,
        binary_path,
        start_type,
        error_ctrl,
        load_order,
        tag_id,
        start_name,
        deps,
        description,
        interactive,
        pid,
        service_flags,
        reset_period,
        reboot_msg,
        command,
        num_actions,
        actions,
        delayed_start
      )

      if block_given?
        yield struct
      else
        services_array << struct
      end

      service_buf += ENUM_SERVICE_STATUS_PROCESS.size
    }
  ensure
    close_service_handle(handle_scm)
  end

  block_given? ? nil : services_array
end
start(service, host = nil, *args) click to toggle source

Attempts to start the named service on host, or the local machine if no host is provided. If args are provided, they are passed to the Daemon#service_main method.

Examples:

# Start 'SomeSvc' on the local machine
Service.start('SomeSvc', nil) => self

# Start 'SomeSvc' on host 'foo', passing 'hello' as an argument
Service.start('SomeSvc', 'foo', 'hello') => self
# File lib/win32/service.rb, line 704
def self.start(service, host = nil, *args)
  handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)

  FFI.raise_windows_error("OpenSCManager") if handle_scm == 0

  begin
    handle_scs = OpenService(handle_scm, service, SERVICE_START)

    FFI.raise_windows_error("OpenService") if handle_scs == 0

    num_args = 0

    if args.empty?
      args = nil
    else
      str_ptrs = []
      num_args = args.size

      args.each { |string|
        str_ptrs << FFI::MemoryPointer.from_string(string)
      }

      str_ptrs << nil

      vector = FFI::MemoryPointer.new(:pointer, str_ptrs.size)

      str_ptrs.each_with_index { |p, i|
        vector[i].put_pointer(0, p)
      }
    end

    unless StartService(handle_scs, num_args, vector)
      FFI.raise_windows_error("StartService")
    end

  ensure
    close_service_handle(handle_scs)
    close_service_handle(handle_scm)
  end

  self
end
status(service, host = nil) click to toggle source

Returns a ServiceStatus struct indicating the status of service name on host, or the localhost if none is provided.

Example:

Service.status('W32Time') => <struct Struct::ServiceStatus …>

# File lib/win32/service.rb, line 902
def self.status(service, host = nil)
  status = SERVICE_STATUS_PROCESS.new
  bytes  = FFI::MemoryPointer.new(:ulong)

  open_sc_manager(host) do |scm_handle|
    open_service(scm_handle, service, SERVICE_QUERY_STATUS) do |service_handle|
      bool = QueryServiceStatusEx(
        service_handle,
        SC_STATUS_PROCESS_INFO,
        status,
        status.size,
        bytes
      )

      FFI.raise_windows_error("QueryServiceStatusEx") unless bool
    end
  end

  service_type  = get_service_type(status[:dwServiceType])
  current_state = get_current_state(status[:dwCurrentState])
  controls      = get_controls_accepted(status[:dwControlsAccepted])
  interactive   = status[:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0

  status_struct = StatusStruct.new(
    service_type,
    current_state,
    controls,
    status[:dwWin32ExitCode],
    status[:dwServiceSpecificExitCode],
    status[:dwCheckPoint],
    status[:dwWaitHint],
    interactive,
    status[:dwProcessId],
    status[:dwServiceFlags]
  )

  status_struct
end
stop(service, host = nil) click to toggle source

Stops a the given service on host, or the local host if no host is specified. Returns self.

Note that attempting to stop an already stopped service raises Service::Error.

Example:

Service.stop('W32Time') => self
# File lib/win32/service.rb, line 757
def self.stop(service, host = nil)
  service_signal = SERVICE_STOP
  control_signal = SERVICE_CONTROL_STOP
  send_signal(service, host, service_signal, control_signal)
  self
end