Module: Puppet::Util::Windows::Service
- Extended by:
- FFI::Library, String
- Defined in:
- lib/puppet/util/windows/service.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
Defined Under Namespace
Classes: ENUM_SERVICE_STATUS_PROCESSW, QUERY_SERVICE_CONFIGW, SERVICE_DELAYED_AUTO_START_INFO, SERVICE_STATUS, SERVICE_STATUS_PROCESS
Constant Summary collapse
- FILE =
Puppet::Util::Windows::File
- DEFAULT_TIMEOUT =
integer value of the floor for timeouts when waiting for service pending states. puppet will wait the length of dwWaitHint if it is longer than this value, but no shorter
30
- ERROR_SERVICE_DOES_NOT_EXIST =
Service error codes docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes–1000-1299-
0x00000424
- SERVICE_CONTROL_STOP =
Service control codes docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-controlserviceexw
0x00000001
- SERVICE_CONTROL_PAUSE =
0x00000002
- SERVICE_CONTROL_CONTINUE =
0x00000003
- SERVICE_CONTROL_INTERROGATE =
0x00000004
- SERVICE_CONTROL_SHUTDOWN =
0x00000005
- SERVICE_CONTROL_PARAMCHANGE =
0x00000006
- SERVICE_CONTROL_NETBINDADD =
0x00000007
- SERVICE_CONTROL_NETBINDREMOVE =
0x00000008
- SERVICE_CONTROL_NETBINDENABLE =
0x00000009
- SERVICE_CONTROL_NETBINDDISABLE =
0x0000000A
- SERVICE_CONTROL_DEVICEEVENT =
0x0000000B
- SERVICE_CONTROL_HARDWAREPROFILECHANGE =
0x0000000C
- SERVICE_CONTROL_POWEREVENT =
0x0000000D
- SERVICE_CONTROL_SESSIONCHANGE =
0x0000000E
- SERVICE_CONTROL_PRESHUTDOWN =
0x0000000F
- SERVICE_CONTROL_TIMECHANGE =
0x00000010
- SERVICE_CONTROL_TRIGGEREVENT =
0x00000020
- SERVICE_CONTROL_SIGNALS =
{ SERVICE_CONTROL_STOP => :SERVICE_CONTROL_STOP, SERVICE_CONTROL_PAUSE => :SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE => :SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE => :SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_SHUTDOWN => :SERVICE_CONTROL_SHUTDOWN, SERVICE_CONTROL_PARAMCHANGE => :SERVICE_CONTROL_PARAMCHANGE, SERVICE_CONTROL_NETBINDADD => :SERVICE_CONTROL_NETBINDADD, SERVICE_CONTROL_NETBINDREMOVE => :SERVICE_CONTROL_NETBINDREMOVE, SERVICE_CONTROL_NETBINDENABLE => :SERVICE_CONTROL_NETBINDENABLE, SERVICE_CONTROL_NETBINDDISABLE => :SERVICE_CONTROL_NETBINDDISABLE, SERVICE_CONTROL_DEVICEEVENT => :SERVICE_CONTROL_DEVICEEVENT, SERVICE_CONTROL_HARDWAREPROFILECHANGE => :SERVICE_CONTROL_HARDWAREPROFILECHANGE, SERVICE_CONTROL_POWEREVENT => :SERVICE_CONTROL_POWEREVENT, SERVICE_CONTROL_SESSIONCHANGE => :SERVICE_CONTROL_SESSIONCHANGE, SERVICE_CONTROL_PRESHUTDOWN => :SERVICE_CONTROL_PRESHUTDOWN, SERVICE_CONTROL_TIMECHANGE => :SERVICE_CONTROL_TIMECHANGE, SERVICE_CONTROL_TRIGGEREVENT => :SERVICE_CONTROL_TRIGGEREVENT }
- SERVICE_AUTO_START =
Service start type codes docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw
0x00000002
- SERVICE_BOOT_START =
0x00000000
- SERVICE_DEMAND_START =
0x00000003
- SERVICE_DISABLED =
0x00000004
- SERVICE_SYSTEM_START =
0x00000001
- SERVICE_START_TYPES =
{ SERVICE_AUTO_START => :SERVICE_AUTO_START, SERVICE_BOOT_START => :SERVICE_BOOT_START, SERVICE_DEMAND_START => :SERVICE_DEMAND_START, SERVICE_DISABLED => :SERVICE_DISABLED, SERVICE_SYSTEM_START => :SERVICE_SYSTEM_START, }
- SERVICE_FILE_SYSTEM_DRIVER =
0x00000002
- SERVICE_KERNEL_DRIVER =
0x00000001
- SERVICE_WIN32_OWN_PROCESS =
0x00000010
- SERVICE_WIN32_SHARE_PROCESS =
0x00000020
- SERVICE_USER_OWN_PROCESS =
0x00000050
- SERVICE_USER_SHARE_PROCESS =
0x00000060
- SERVICE_INTERACTIVE_PROCESS =
Available only if service is also SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS
0x00000100
- ALL_SERVICE_TYPES =
SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
- SERVICE_CONTINUE_PENDING =
0x00000005
- SERVICE_PAUSE_PENDING =
0x00000006
- SERVICE_PAUSED =
0x00000007
- SERVICE_RUNNING =
0x00000004
- SERVICE_START_PENDING =
0x00000002
- SERVICE_STOP_PENDING =
0x00000003
- SERVICE_STOPPED =
0x00000001
- UNSAFE_PENDING_STATES =
[SERVICE_START_PENDING, SERVICE_STOP_PENDING]
- FINAL_STATES =
{ SERVICE_CONTINUE_PENDING => SERVICE_RUNNING, SERVICE_PAUSE_PENDING => SERVICE_PAUSED, SERVICE_START_PENDING => SERVICE_RUNNING, SERVICE_STOP_PENDING => SERVICE_STOPPED }
- SERVICE_STATES =
{ SERVICE_CONTINUE_PENDING => :SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING => :SERVICE_PAUSE_PENDING, SERVICE_PAUSED => :SERVICE_PAUSED, SERVICE_RUNNING => :SERVICE_RUNNING, SERVICE_START_PENDING => :SERVICE_START_PENDING, SERVICE_STOP_PENDING => :SERVICE_STOP_PENDING, SERVICE_STOPPED => :SERVICE_STOPPED, }
- SERVICE_ACCEPT_STOP =
Service accepts control codes docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process
0x00000001
- SERVICE_ACCEPT_PAUSE_CONTINUE =
0x00000002
- SERVICE_ACCEPT_SHUTDOWN =
0x00000004
- SERVICE_ACCEPT_PARAMCHANGE =
0x00000008
- SERVICE_ACCEPT_NETBINDCHANGE =
0x00000010
- SERVICE_ACCEPT_HARDWAREPROFILECHANGE =
0x00000020
- SERVICE_ACCEPT_POWEREVENT =
0x00000040
- SERVICE_ACCEPT_SESSIONCHANGE =
0x00000080
- SERVICE_ACCEPT_PRESHUTDOWN =
0x00000100
- SERVICE_ACCEPT_TIMECHANGE =
0x00000200
- SERVICE_ACCEPT_TRIGGEREVENT =
0x00000400
- SERVICE_ACCEPT_USER_LOGOFF =
0x00000800
- SC_MANAGER_CREATE_SERVICE =
Service manager access codes docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights
0x00000002
- SC_MANAGER_CONNECT =
0x00000001
- SC_MANAGER_ENUMERATE_SERVICE =
0x00000004
- SC_MANAGER_LOCK =
0x00000008
- SC_MANAGER_MODIFY_BOOT_CONFIG =
0x00000020
- SC_MANAGER_QUERY_LOCK_STATUS =
0x00000010
- SC_MANAGER_ALL_ACCESS =
FILE::STANDARD_RIGHTS_REQUIRED | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_LOCK | SC_MANAGER_MODIFY_BOOT_CONFIG | SC_MANAGER_QUERY_LOCK_STATUS
- SERVICE_CHANGE_CONFIG =
Service access codes docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights
0x0002
- SERVICE_ENUMERATE_DEPENDENTS =
0x0008
- SERVICE_INTERROGATE =
0x0080
- SERVICE_PAUSE_CONTINUE =
0x0040
- SERVICE_QUERY_STATUS =
0x0004
- SERVICE_QUERY_CONFIG =
0x0001
- SERVICE_START =
0x0010
- SERVICE_STOP =
0x0020
- SERVICE_USER_DEFINED_CONTROL =
0x0100
- SERVICE_ALL_ACCESS =
FILE::STANDARD_RIGHTS_REQUIRED | SERVICE_CHANGE_CONFIG | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_INTERROGATE | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_START | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL
- SERVICE_CONFIG_DESCRIPTION =
Service config codes From the windows 10 SDK: // // Value to indicate no change to an optional parameter // #define SERVICE_NO_CHANGE 0xffffffff docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-changeserviceconfig2w
0x00000001
- SERVICE_CONFIG_FAILURE_ACTIONS =
0x00000002
- SERVICE_CONFIG_DELAYED_AUTO_START_INFO =
0x00000003
- SERVICE_CONFIG_FAILURE_ACTIONS_FLAG =
0x00000004
- SERVICE_CONFIG_SERVICE_SID_INFO =
0x00000005
- SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO =
0x00000006
- SERVICE_CONFIG_PRESHUTDOWN_INFO =
0x00000007
- SERVICE_CONFIG_TRIGGER_INFO =
0x00000008
- SERVICE_CONFIG_PREFERRED_NODE =
0x00000009
- SERVICE_CONFIG_LAUNCH_PROTECTED =
0x00000012
- SERVICE_NO_CHANGE =
0xffffffff
- SERVICE_CONFIG_TYPES =
{ SERVICE_CONFIG_DESCRIPTION => :SERVICE_CONFIG_DESCRIPTION, SERVICE_CONFIG_FAILURE_ACTIONS => :SERVICE_CONFIG_FAILURE_ACTIONS, SERVICE_CONFIG_DELAYED_AUTO_START_INFO => :SERVICE_CONFIG_DELAYED_AUTO_START_INFO, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG => :SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, SERVICE_CONFIG_SERVICE_SID_INFO => :SERVICE_CONFIG_SERVICE_SID_INFO, SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO => :SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO, SERVICE_CONFIG_PRESHUTDOWN_INFO => :SERVICE_CONFIG_PRESHUTDOWN_INFO, SERVICE_CONFIG_TRIGGER_INFO => :SERVICE_CONFIG_TRIGGER_INFO, SERVICE_CONFIG_PREFERRED_NODE => :SERVICE_CONFIG_PREFERRED_NODE, SERVICE_CONFIG_LAUNCH_PROTECTED => :SERVICE_CONFIG_LAUNCH_PROTECTED, }
- SERVICE_ACTIVE =
0x00000001
- SERVICE_INACTIVE =
0x00000002
- SERVICE_STATE_ALL =
SERVICE_ACTIVE | SERVICE_INACTIVE
- SERVICENAME_MAX =
256
- SC_STATUS_TYPE =
docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicestatusex BOOL QueryServiceStatusEx(
SC_HANDLE hService, SC_STATUS_TYPE InfoLevel, LPBYTE lpBuffer, DWORD cbBufSize, LPDWORD pcbBytesNeeded
);
enum( :SC_STATUS_PROCESS_INFO, 0, )
- SC_ENUM_TYPE =
docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexw BOOL EnumServicesStatusExW(
SC_HANDLE hSCManager, SC_ENUM_TYPE InfoLevel, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle, LPCWSTR pszGroupName
);
enum( :SC_ENUM_PROCESS_INFO, 0, )
Class Method Summary collapse
-
.exists?(service_name) ⇒ Boolean
Returns true if the service exists, false otherwise.
-
.milliseconds_to_seconds(wait_hint) ⇒ Integer
private
process the wait hint listed by a service to something usable by ruby sleep.
-
.open_scm(scm_access, &block) ⇒ Object
private
Opens a handle to the service control manager.
-
.open_service(service_name, scm_access, service_access) {|service| ... } ⇒ Object
private
Opens a connection to the SCManager on windows then uses that handle to create a handle to a specific service in windows corresponding to service_name.
-
.query_config(service, &block) ⇒ QUERY_SERVICE_CONFIGW struct
private
perform QueryServiceConfigW on a windows service and return the result.
-
.query_config2(service, info_level, &block) ⇒ QUERY_SERVICE_CONFIG2W struct
private
perform QueryServiceConfig2W on a windows service and return the result.
-
.query_status(service) ⇒ SERVICE_STATUS_PROCESS struct
private
perform QueryServiceStatusEx on a windows service and return the result.
-
.resume(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object
Resume a paused windows service.
-
.send_service_control_signal(service, signal) ⇒ Object
private
Sends a service control signal to a service.
-
.service_start_type(service_name) ⇒ QUERY_SERVICE_CONFIGW.struct
Query the configuration of a service using QueryServiceConfigW or QueryServiceConfig2W.
-
.service_state(service_name) ⇒ string
Query the state of a service using QueryServiceStatusEx.
-
.services ⇒ Hash
enumerate over all services in all states and return them as a hash.
-
.set_optional_parameter(service_name, change, value) ⇒ Object
private
Sets an optional parameter on a service by calling ChangeServiceConfig2W.
-
.set_startup_mode(service_name, startup_type, delayed = false) ⇒ Object
Change the startup mode of a windows service.
-
.set_startup_mode_delayed(service_name, delayed) ⇒ Object
private
Controls the delayed auto-start setting of a service.
-
.start(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object
Start a windows service.
-
.stop(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object
Stop a windows service.
-
.transition_service_state(service_name, valid_initial_states, final_state, timeout, &block) ⇒ Object
private
Transition the service to the specified state.
-
.wait_hint_to_wait_time(wait_hint) ⇒ Integer
private
create a usable wait time to wait between querying the service.
-
.wait_on_pending_state(service, pending_state, timeout) ⇒ Object
private
Waits for a service to finish transitioning from a pending state.
-
.wait_on_state_transition(service, initial_state, final_state, timeout) ⇒ Object
private
Waits for a service to transition from one state to another state.
Methods included from FFI::Library
Methods included from String
Class Method Details
.exists?(service_name) ⇒ Boolean
Returns true if the service exists, false otherwise.
323 324 325 326 327 328 329 330 |
# File 'lib/puppet/util/windows/service.rb', line 323 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 |
.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
972 973 974 |
# File 'lib/puppet/util/windows/service.rb', line 972 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
599 600 601 602 603 604 605 |
# File 'lib/puppet/util/windows/service.rb', line 599 def open_scm(scm_access, &block) scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access) raise Puppet::Util::Windows::Error.new(_("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
578 579 580 581 582 583 584 585 586 587 588 589 590 591 |
# File 'lib/puppet/util/windows/service.rb', line 578 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.new(_("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
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 |
# File 'lib/puppet/util/windows/service.rb', line 730 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.new(_("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
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
# File 'lib/puppet/util/windows/service.rb', line 767 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.new(_("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
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 |
# File 'lib/puppet/util/windows/service.rb', line 687 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.new(_("Service query failed")) end yield status end end end |
.resume(service_name, timeout: DEFAULT_TIMEOUT) ⇒ Object
Resume a paused windows service
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/puppet/util/windows/service.rb', line 377 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
841 842 843 844 845 846 847 848 |
# File 'lib/puppet/util/windows/service.rb', line 841 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
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/puppet/util/windows/service.rb', line 421 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.new(_("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
402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/puppet/util/windows/service.rb', line 402 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.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name}) end state end |
.services ⇒ Hash
enumerate over all services in all states and return them as a hash
489 490 491 492 493 494 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 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/puppet/util/windows/service.rb', line 489 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.new(_("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
810 811 812 813 814 815 816 817 818 819 820 821 |
# File 'lib/puppet/util/windows/service.rb', line 810 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.new(_("Failed to update service %{change} configuration") % { change: change } ) end end end |
.set_startup_mode(service_name, startup_type, delayed = false) ⇒ Object
Change the startup mode of a windows service
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 |
# File 'lib/puppet/util/windows/service.rb', line 450 def set_startup_mode(service_name, startup_type, delayed=false) startup_code = SERVICE_START_TYPES.key(startup_type) if startup_code.nil? raise Puppet::Error.new(_("Unknown start type %{start_type}") % {startup_type: startup_type.to_s}) end open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service| # Currently the only thing puppet's API can really manage # in this list is dwStartType (the third param). Thus no # generic function was written to make use of all the params # since the API as-is couldn't use them anyway success = ChangeServiceConfigW( service, SERVICE_NO_CHANGE, # dwServiceType startup_code, # dwStartType SERVICE_NO_CHANGE, # dwErrorControl FFI::Pointer::NULL, # lpBinaryPathName FFI::Pointer::NULL, # lpLoadOrderGroup FFI::Pointer::NULL, # lpdwTagId FFI::Pointer::NULL, # lpDependencies FFI::Pointer::NULL, # lpServiceStartName FFI::Pointer::NULL, # lpPassword FFI::Pointer::NULL # lpDisplayName ) if success == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration")) end end set_startup_mode_delayed(service_name, delayed) 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
829 830 831 832 833 |
# File 'lib/puppet/util/windows/service.rb', line 829 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
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/puppet/util/windows/service.rb', line 337 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
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/puppet/util/windows/service.rb', line 360 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.
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 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 677 678 |
# File 'lib/puppet/util/windows/service.rb', line 616 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.
955 956 957 958 959 960 961 962 |
# File 'lib/puppet/util/windows/service.rb', line 955 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.
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 |
# File 'lib/puppet/util/windows/service.rb', line 906 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.
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 |
# File 'lib/puppet/util/windows/service.rb', line 858 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 |