class Chef::Provider::Service::Windows

Constants

AUTO_START

Win32::Service.get_start_type

CONTINUE_PENDING
DISABLED
MANUAL
PAUSED
PAUSE_PENDING
RUNNING

Win32::Service.get_current_state

SERVICE_RIGHT
START_PENDING
STOPPED
STOP_PENDING
TIMEOUT

Public Instance Methods

action_configure_startup() click to toggle source
# File lib/chef/provider/service/windows.rb, line 246
def action_configure_startup
  startup_type = @new_resource.startup_type
  if current_startup_type != startup_type
    converge_by("set service #{@new_resource} startup type to #{startup_type}") do
      set_startup_type(startup_type)
    end
  else
    logger.trace("#{@new_resource} startup_type already #{startup_type} - nothing to do")
  end

  converge_delayed_start

  # Avoid changing enabled from true/false for now
  @new_resource.enabled(nil)
end
action_disable() click to toggle source
# File lib/chef/provider/service/windows.rb, line 233
def action_disable
  if current_startup_type != :disabled
    converge_by("disable service #{@new_resource}") do
      disable_service
      logger.info("#{@new_resource} disabled")
    end
  else
    logger.trace("#{@new_resource} already disabled - nothing to do")
  end
  load_new_resource_state
  @new_resource.enabled(false)
end
action_enable() click to toggle source
# File lib/chef/provider/service/windows.rb, line 220
def action_enable
  if current_startup_type != :automatic
    converge_by("enable service #{@new_resource}") do
      enable_service
      logger.info("#{@new_resource} enabled")
    end
  else
    logger.trace("#{@new_resource} already enabled - nothing to do")
  end
  load_new_resource_state
  @new_resource.enabled(true)
end
disable_service() click to toggle source
# File lib/chef/provider/service/windows.rb, line 173
def disable_service
  if Win32::Service.exists?(@new_resource.service_name)
    set_startup_type(:disabled)
  else
    logger.trace "#{@new_resource} does not exist - nothing to do"
  end
end
enable_service() click to toggle source
# File lib/chef/provider/service/windows.rb, line 165
def enable_service
  if Win32::Service.exists?(@new_resource.service_name)
    set_startup_type(:automatic)
  else
    logger.trace "#{@new_resource} does not exist - nothing to do"
  end
end
load_current_resource() click to toggle source
# File lib/chef/provider/service/windows.rb, line 54
def load_current_resource
  @current_resource = Chef::Resource::WindowsService.new(new_resource.name)
  current_resource.service_name(new_resource.service_name)

  if Win32::Service.exists?(current_resource.service_name)
    current_resource.running(current_state == RUNNING)
    logger.trace "#{new_resource} running: #{current_resource.running}"
    case current_startup_type
    when :automatic
      current_resource.enabled(true)
    when :disabled
      current_resource.enabled(false)
    end
    logger.trace "#{new_resource} enabled: #{current_resource.enabled}"

    config_info = Win32::Service.config_info(current_resource.service_name)
    current_resource.service_type(get_service_type(config_info.service_type))    if config_info.service_type
    current_resource.startup_type(start_type_to_sym(config_info.start_type))     if config_info.start_type
    current_resource.error_control(get_error_control(config_info.error_control)) if config_info.error_control
    current_resource.binary_path_name(config_info.binary_path_name) if config_info.binary_path_name
    current_resource.load_order_group(config_info.load_order_group) if config_info.load_order_group
    current_resource.dependencies(config_info.dependencies)         if config_info.dependencies
    current_resource.run_as_user(config_info.service_start_name)    if config_info.service_start_name
    current_resource.display_name(config_info.display_name)         if config_info.display_name
    current_resource.delayed_start(current_delayed_start)           if current_delayed_start
  end

  current_resource
end
restart_service() click to toggle source
# File lib/chef/provider/service/windows.rb, line 150
def restart_service
  if Win32::Service.exists?(@new_resource.service_name)
    if @new_resource.restart_command
      logger.trace "#{@new_resource} restarting service using the given restart_command"
      shell_out!(@new_resource.restart_command)
    else
      stop_service
      start_service
    end
    @new_resource.updated_by_last_action(true)
  else
    logger.trace "#{@new_resource} does not exist - nothing to do"
  end
end
start_service() click to toggle source
# File lib/chef/provider/service/windows.rb, line 84
def start_service
  if Win32::Service.exists?(@new_resource.service_name)
    configure_service_run_as_properties

    state = current_state
    if state == RUNNING
      logger.trace "#{@new_resource} already started - nothing to do"
    elsif state == START_PENDING
      logger.trace "#{@new_resource} already sent start signal - waiting for start"
      wait_for_state(RUNNING)
    elsif state == STOPPED
      if @new_resource.start_command
        logger.trace "#{@new_resource} starting service using the given start_command"
        shell_out!(@new_resource.start_command)
      else
        spawn_command_thread do
          begin
            Win32::Service.start(@new_resource.service_name)
          rescue SystemCallError => ex
            if ex.errno == ERROR_SERVICE_LOGON_FAILED
              logger.error ex.message
              raise Chef::Exceptions::Service,
              "Service #{@new_resource} did not start due to a logon failure (error #{ERROR_SERVICE_LOGON_FAILED}): possibly the specified user '#{@new_resource.run_as_user}' does not have the 'log on as a service' privilege, or the password is incorrect."
            else
              raise ex
            end
          end
        end
        wait_for_state(RUNNING)
      end
      @new_resource.updated_by_last_action(true)
    else
      raise Chef::Exceptions::Service, "Service #{@new_resource} can't be started from state [#{state}]"
    end
  else
    logger.trace "#{@new_resource} does not exist - nothing to do"
  end
end
stop_service() click to toggle source
# File lib/chef/provider/service/windows.rb, line 123
def stop_service
  if Win32::Service.exists?(@new_resource.service_name)
    state = current_state
    if state == RUNNING
      if @new_resource.stop_command
        logger.trace "#{@new_resource} stopping service using the given stop_command"
        shell_out!(@new_resource.stop_command)
      else
        spawn_command_thread do
          Win32::Service.stop(@new_resource.service_name)
        end
        wait_for_state(STOPPED)
      end
      @new_resource.updated_by_last_action(true)
    elsif state == STOPPED
      logger.trace "#{@new_resource} already stopped - nothing to do"
    elsif state == STOP_PENDING
      logger.trace "#{@new_resource} already sent stop signal - waiting for stop"
      wait_for_state(STOPPED)
    else
      raise Chef::Exceptions::Service, "Service #{@new_resource} can't be stopped from state [#{state}]"
    end
  else
    logger.trace "#{@new_resource} does not exist - nothing to do"
  end
end

Private Instance Methods

canonicalize_username(username) click to toggle source
# File lib/chef/provider/service/windows.rb, line 313
def canonicalize_username(username)
  username.sub(/^\.?\\+/, "")
end
clean_username_for_path(username) click to toggle source

remove characters that make for broken or wonky filenames.

# File lib/chef/provider/service/windows.rb, line 309
def clean_username_for_path(username)
  username.gsub(/[\/\\. ]+/, "_")
end
configure_service_run_as_properties() click to toggle source
# File lib/chef/provider/service/windows.rb, line 264
def configure_service_run_as_properties
  return unless new_resource.property_is_set?(:run_as_user)

  new_config = {
    service_name: new_resource.service_name,
    service_start_name: new_resource.run_as_user,
    password: new_resource.run_as_password,
  }.reject { |k, v| v.nil? || v.length == 0 }

  Win32::Service.configure(new_config)
  logger.info "#{new_resource} configured."

  grant_service_logon(new_resource.run_as_user) if new_resource.run_as_user.casecmp("localsystem") != 0
end
converge_delayed_start() click to toggle source
# File lib/chef/provider/service/windows.rb, line 397
def converge_delayed_start
  converge_if_changed :delayed_start do
    config = {}
    config[:service_name]  = new_resource.service_name
    config[:delayed_start] = new_resource.delayed_start ? 1 : 0

    Win32::Service.configure(config)
  end
end
current_delayed_start() click to toggle source

Queries the delayed auto-start setting of the auto-start service. If the service is not auto-start, this will return nil.

@return [Boolean, nil]

# File lib/chef/provider/service/windows.rb, line 285
def current_delayed_start
  case Win32::Service.delayed_start(new_resource.service_name)
  when 0
    false
  when 1
    true
  end
end
current_startup_type() click to toggle source
# File lib/chef/provider/service/windows.rb, line 321
def current_startup_type
  start_type = Win32::Service.config_info(@new_resource.service_name).start_type
  start_type_to_sym(start_type)
end
current_state() click to toggle source
# File lib/chef/provider/service/windows.rb, line 317
def current_state
  Win32::Service.status(@new_resource.service_name).current_state
end
get_error_control(error_control) click to toggle source
# File lib/chef/provider/service/windows.rb, line 470
def get_error_control(error_control)
  case error_control
  when "critical"
    SERVICE_ERROR_CRITICAL
  when "ignore"
    SERVICE_ERROR_IGNORE
  when "normal"
    SERVICE_ERROR_NORMAL
  when "severe"
    SERVICE_ERROR_SEVERE
  else
    nil
  end
end
get_service_type(service_type) click to toggle source
# File lib/chef/provider/service/windows.rb, line 425
def get_service_type(service_type)
  case service_type
  when "file system driver"
    SERVICE_FILE_SYSTEM_DRIVER
  when "kernel driver"
    SERVICE_KERNEL_DRIVER
  when "own process"
    SERVICE_WIN32_OWN_PROCESS
  when "share process"
    SERVICE_WIN32_SHARE_PROCESS
  when "recognizer driver"
    SERVICE_RECOGNIZER_DRIVER
  when "driver"
    SERVICE_DRIVER
  when "win32"
    SERVICE_WIN32
  when "all"
    SERVICE_TYPE_ALL
  when "own process, interactive"
    SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
  when "share process, interactive"
    SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
  else
    raise("Unsupported service type, #{service_type}. Submit bug request to fix.")
  end
end
get_start_type(start_type) click to toggle source

@return [Integer]

# File lib/chef/provider/service/windows.rb, line 453
def get_start_type(start_type)
  case start_type
  when "auto start"
    SERVICE_AUTO_START
  when "boot start"
    SERVICE_BOOT_START
  when "demand start"
    SERVICE_DEMAND_START
  when "disabled"
    SERVICE_DISABLED
  when "system start"
    SERVICE_SYSTEM_START
  else
    raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
  end
end
grant_service_logon(username) click to toggle source
# File lib/chef/provider/service/windows.rb, line 294
def grant_service_logon(username)
  return if Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(username)).include?(SERVICE_RIGHT)

  begin
    Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT)
  rescue Chef::Exceptions::Win32APIError => err
    logger.fatal "Logon-as-service grant failed with output: #{err}"
    raise Chef::Exceptions::Service, "Logon-as-service grant failed for #{username}: #{err}"
  end

  logger.info "Grant logon-as-service to user '#{username}' successful."
  true
end
resource_timeout() click to toggle source
# File lib/chef/provider/service/windows.rb, line 337
def resource_timeout
  @resource_timeout ||= @new_resource.timeout || TIMEOUT
end
set_startup_type(type) click to toggle source

Takes Win32::Service start_types

# File lib/chef/provider/service/windows.rb, line 363
def set_startup_type(type)
  startup_type = startup_type_to_int(type)

  logger.trace "#{@new_resource.name} setting start_type to #{type}"
  Win32::Service.configure(
    service_name: @new_resource.service_name,
    start_type: startup_type
  )
  @new_resource.updated_by_last_action(true)
end
spawn_command_thread() { || ... } click to toggle source
# File lib/chef/provider/service/windows.rb, line 341
def spawn_command_thread
  worker = Thread.new do
    yield
  end

  Timeout.timeout(resource_timeout) do
    worker.join
  end
end
start_type_to_sym(start_type) click to toggle source

@return [Symbol]

# File lib/chef/provider/service/windows.rb, line 408
def start_type_to_sym(start_type)
  case start_type
  when "auto start"
    :automatic
  when "boot start"
    raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
  when "demand start"
    :manual
  when "disabled"
    :disabled
  when "system start"
    raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
  else
    raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
  end
end
startup_type_to_int(type) click to toggle source

@param type [Symbol] @return [Integer] @raise [Chef::Exceptions::ConfigurationError] if the startup type is

not supported.

@see Chef::Resource::WindowsService::ALLOWED_START_TYPES

# File lib/chef/provider/service/windows.rb, line 356
def startup_type_to_int(type)
  Chef::Resource::WindowsService::ALLOWED_START_TYPES.fetch(type) do
    raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported"
  end
end
wait_for_state(desired_state) click to toggle source

Helper method that waits for a status to change its state since state changes aren't usually instantaneous.

# File lib/chef/provider/service/windows.rb, line 328
def wait_for_state(desired_state)
  retries = 0
  loop do
    break if current_state == desired_state
    raise Timeout::Error if ( retries += 1 ) > resource_timeout
    sleep 1
  end
end
windows_service_config(action = :create) click to toggle source
# File lib/chef/provider/service/windows.rb, line 374
def windows_service_config(action = :create)
  config = {}

  config[:service_name]       = new_resource.service_name
  config[:display_name]       = new_resource.display_name                      if new_resource.display_name
  config[:service_type]       = new_resource.service_type                      if new_resource.service_type
  config[:start_type]         = startup_type_to_int(new_resource.startup_type) if new_resource.startup_type
  config[:error_control]      = new_resource.error_control                     if new_resource.error_control
  config[:binary_path_name]   = new_resource.binary_path_name                  if new_resource.binary_path_name
  config[:load_order_group]   = new_resource.load_order_group                  if new_resource.load_order_group
  config[:dependencies]       = new_resource.dependencies                      if new_resource.dependencies
  config[:service_start_name] = new_resource.run_as_user                       unless new_resource.run_as_user.empty?
  config[:password]           = new_resource.run_as_password                   unless new_resource.run_as_user.empty? || new_resource.run_as_password.empty?
  config[:description]        = new_resource.description                       if new_resource.description

  case action
  when :create
    config[:desired_access] = new_resource.desired_access if new_resource.desired_access
  end

  config
end