win32-service 0.8.10 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1577 +1,1575 @@
1
- require 'ffi/win32/extensions'
2
- require_relative 'windows/constants'
3
- require_relative 'windows/structs'
4
- require_relative 'windows/functions'
5
-
6
- # The Win32 module serves as a namespace only.
7
- module Win32
8
-
9
- # The Service class encapsulates services controller actions, such as
10
- # creating, starting, configuring or deleting services.
11
- class Service
12
- include Windows::ServiceConstants
13
- include Windows::ServiceStructs
14
- include Windows::ServiceFunctions
15
-
16
- extend Windows::ServiceStructs
17
- extend Windows::ServiceFunctions
18
-
19
- # The version of the win32-service library
20
- VERSION = '0.8.10'.freeze
21
-
22
- # SCM security and access rights
23
-
24
- # Includes STANDARD_RIGHTS_REQUIRED, in addition to all other rights
25
- MANAGER_ALL_ACCESS = SC_MANAGER_ALL_ACCESS
26
-
27
- # Required to call the CreateService function
28
- MANAGER_CREATE_SERVICE = SC_MANAGER_CREATE_SERVICE
29
-
30
- # Required to connect to the service control manager.
31
- MANAGER_CONNECT = SC_MANAGER_CONNECT
32
-
33
- # Required to call the EnumServicesStatusEx function to list services
34
- MANAGER_ENUMERATE_SERVICE = SC_MANAGER_ENUMERATE_SERVICE
35
-
36
- # Required to call the LockServiceDatabase function
37
- MANAGER_LOCK = SC_MANAGER_LOCK
38
-
39
- # Required to call the NotifyBootConfigStatus function
40
- MANAGER_MODIFY_BOOT_CONFIG = SC_MANAGER_MODIFY_BOOT_CONFIG
41
-
42
- # Required to call the QueryServiceLockStatus function
43
- MANAGER_QUERY_LOCK_STATUS = SC_MANAGER_QUERY_LOCK_STATUS
44
-
45
- # Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights
46
- ALL_ACCESS = SERVICE_ALL_ACCESS
47
-
48
- # Required to call functions that configure existing services
49
- CHANGE_CONFIG = SERVICE_CHANGE_CONFIG
50
-
51
- # Required to enumerate all the services dependent on the service
52
- ENUMERATE_DEPENDENTS = SERVICE_ENUMERATE_DEPENDENTS
53
-
54
- # Required to make a service report its status immediately
55
- INTERROGATE = SERVICE_INTERROGATE
56
-
57
- # Required to control a service with a pause or resume
58
- PAUSE_CONTINUE = SERVICE_PAUSE_CONTINUE
59
-
60
- # Required to be able to gather configuration information about a service
61
- QUERY_CONFIG = SERVICE_QUERY_CONFIG
62
-
63
- # Required to be ask the SCM about the status of a service
64
- QUERY_STATUS = SERVICE_QUERY_STATUS
65
-
66
- # Required to call the StartService function to start the service.
67
- START = SERVICE_START
68
-
69
- # Required to call the ControlService function to stop the service.
70
- STOP = SERVICE_STOP
71
-
72
- # Required to call ControlService with a user defined control code
73
- USER_DEFINED_CONTROL = SERVICE_USER_DEFINED_CONTROL
74
-
75
- # Service Types
76
-
77
- # Driver service
78
- KERNEL_DRIVER = SERVICE_KERNEL_DRIVER
79
-
80
- # File system driver service
81
- FILE_SYSTEM_DRIVER = SERVICE_FILE_SYSTEM_DRIVER
82
-
83
- # Service that runs in its own process
84
- WIN32_OWN_PROCESS = SERVICE_WIN32_OWN_PROCESS
85
-
86
- # Service that shares a process with one or more other services.
87
- WIN32_SHARE_PROCESS = SERVICE_WIN32_SHARE_PROCESS
88
-
89
- # The service can interact with the desktop
90
- INTERACTIVE_PROCESS = SERVICE_INTERACTIVE_PROCESS
91
-
92
- DRIVER = SERVICE_DRIVER
93
- TYPE_ALL = SERVICE_TYPE_ALL
94
-
95
- # Service start options
96
-
97
- # A service started automatically by the SCM during system startup
98
- BOOT_START = SERVICE_BOOT_START
99
-
100
- # A device driver started by the IoInitSystem function. Drivers only
101
- SYSTEM_START = SERVICE_SYSTEM_START
102
-
103
- # A service started automatically by the SCM during system startup
104
- AUTO_START = SERVICE_AUTO_START
105
-
106
- # A service started by the SCM when a process calls StartService
107
- DEMAND_START = SERVICE_DEMAND_START
108
-
109
- # A service that cannot be started
110
- DISABLED = SERVICE_DISABLED
111
-
112
- # Error control
113
-
114
- # Error logged, startup continues
115
- ERROR_IGNORE = SERVICE_ERROR_IGNORE
116
-
117
- # Error logged, pop up message, startup continues
118
- ERROR_NORMAL = SERVICE_ERROR_NORMAL
119
-
120
- # Error logged, startup continues, system restarted last known good config
121
- ERROR_SEVERE = SERVICE_ERROR_SEVERE
122
-
123
- # Error logged, startup fails, system restarted last known good config
124
- ERROR_CRITICAL = SERVICE_ERROR_CRITICAL
125
-
126
- # Current state
127
-
128
- # Service is not running
129
- STOPPED = SERVICE_STOPPED
130
-
131
- # Service has received a start signal but is not yet running
132
- START_PENDING = SERVICE_START_PENDING
133
-
134
- # Service has received a stop signal but is not yet stopped
135
- STOP_PENDING = SERVICE_STOP_PENDING
136
-
137
- # Service is running
138
- RUNNING = SERVICE_RUNNING
139
-
140
- # Service has received a signal to resume but is not yet running
141
- CONTINUE_PENDING = SERVICE_CONTINUE_PENDING
142
-
143
- # Service has received a signal to pause but is not yet paused
144
- PAUSE_PENDING = SERVICE_PAUSE_PENDING
145
-
146
- # Service is paused
147
- PAUSED = SERVICE_PAUSED
148
-
149
- # Service controls
150
-
151
- # Notifies service that it should stop
152
- CONTROL_STOP = SERVICE_CONTROL_STOP
153
-
154
- # Notifies service that it should pause
155
- CONTROL_PAUSE = SERVICE_CONTROL_PAUSE
156
-
157
- # Notifies service that it should resume
158
- CONTROL_CONTINUE = SERVICE_CONTROL_CONTINUE
159
-
160
- # Notifies service that it should return its current status information
161
- CONTROL_INTERROGATE = SERVICE_CONTROL_INTERROGATE
162
-
163
- # Notifies a service that its parameters have changed
164
- CONTROL_PARAMCHANGE = SERVICE_CONTROL_PARAMCHANGE
165
-
166
- # Notifies a service that there is a new component for binding
167
- CONTROL_NETBINDADD = SERVICE_CONTROL_NETBINDADD
168
-
169
- # Notifies a service that a component for binding has been removed
170
- CONTROL_NETBINDREMOVE = SERVICE_CONTROL_NETBINDREMOVE
171
-
172
- # Notifies a service that a component for binding has been enabled
173
- CONTROL_NETBINDENABLE = SERVICE_CONTROL_NETBINDENABLE
174
-
175
- # Notifies a service that a component for binding has been disabled
176
- CONTROL_NETBINDDISABLE = SERVICE_CONTROL_NETBINDDISABLE
177
-
178
- # Failure actions
179
-
180
- # No action
181
- ACTION_NONE = SC_ACTION_NONE
182
-
183
- # Reboot the computer
184
- ACTION_REBOOT = SC_ACTION_REBOOT
185
-
186
- # Restart the service
187
- ACTION_RESTART = SC_ACTION_RESTART
188
-
189
- # Run a command
190
- ACTION_RUN_COMMAND = SC_ACTION_RUN_COMMAND
191
-
192
- # :stopdoc: #
193
-
194
- StatusStruct = Struct.new(
195
- 'ServiceStatus',
196
- :service_type,
197
- :current_state,
198
- :controls_accepted,
199
- :win32_exit_code,
200
- :service_specific_exit_code,
201
- :check_point,
202
- :wait_hint,
203
- :interactive,
204
- :pid,
205
- :service_flags
206
- )
207
-
208
- ConfigStruct = Struct.new(
209
- 'ServiceConfigInfo',
210
- :service_type,
211
- :start_type,
212
- :error_control,
213
- :binary_path_name,
214
- :load_order_group,
215
- :tag_id,
216
- :dependencies,
217
- :service_start_name,
218
- :display_name
219
- )
220
-
221
- ServiceStruct = Struct.new(
222
- 'ServiceInfo',
223
- :service_name,
224
- :display_name,
225
- :service_type,
226
- :current_state,
227
- :controls_accepted,
228
- :win32_exit_code,
229
- :service_specific_exit_code,
230
- :check_point,
231
- :wait_hint,
232
- :binary_path_name,
233
- :start_type,
234
- :error_control,
235
- :load_order_group,
236
- :tag_id,
237
- :start_name,
238
- :dependencies,
239
- :description,
240
- :interactive,
241
- :pid,
242
- :service_flags,
243
- :reset_period,
244
- :reboot_message,
245
- :command,
246
- :num_actions,
247
- :actions,
248
- :delayed_start
249
- )
250
-
251
- # :startdoc: #
252
-
253
- # Creates a new service with the specified +options+. A +service_name+
254
- # must be specified or an ArgumentError is raised. A +host+ option may
255
- # be specified. If no host is specified the local machine is used.
256
- #
257
- # Possible Options:
258
- #
259
- # * service_name => nil (you must specify)
260
- # * host => nil (optional)
261
- # * display_name => service_name
262
- # * desired_access => Service::ALL_ACCESS
263
- # * service_type => Service::WIN32_OWN_PROCESS
264
- # * start_type => Service::DEMAND_START
265
- # * error_control => Service::ERROR_NORMAL
266
- # * binary_path_name => nil
267
- # * load_order_group => nil
268
- # * dependencies => nil
269
- # * service_start_name => nil
270
- # * password => nil
271
- # * description => nil
272
- # * failure_reset_period => nil,
273
- # * failure_reboot_message => nil,
274
- # * failure_command => nil,
275
- # * failure_actions => nil,
276
- # * failure_delay => 0
277
- #
278
- # Example:
279
- #
280
- # # Configure everything
281
- # Service.new(
282
- # :service_name => 'some_service',
283
- # :host => 'localhost',
284
- # :service_type => Service::WIN32_OWN_PROCESS,
285
- # :description => 'A custom service I wrote just for fun',
286
- # :start_type => Service::AUTO_START,
287
- # :error_control => Service::ERROR_NORMAL,
288
- # :binary_path_name => 'C:\path\to\some_service.exe',
289
- # :load_order_group => 'Network',
290
- # :dependencies => ['W32Time','Schedule'],
291
- # :service_start_name => 'SomeDomain\\User',
292
- # :password => 'XXXXXXX',
293
- # :display_name => 'This is some service',
294
- # )
295
- #
296
- def initialize(options={})
297
- unless options.is_a?(Hash)
298
- raise ArgumentError, 'options parameter must be a hash'
299
- end
300
-
301
- if options.empty?
302
- raise ArgumentError, 'no options provided'
303
- end
304
-
305
- opts = {
306
- 'display_name' => nil,
307
- 'desired_access' => SERVICE_ALL_ACCESS,
308
- 'service_type' => SERVICE_WIN32_OWN_PROCESS,
309
- 'start_type' => SERVICE_DEMAND_START,
310
- 'error_control' => SERVICE_ERROR_NORMAL,
311
- 'binary_path_name' => nil,
312
- 'load_order_group' => nil,
313
- 'dependencies' => nil,
314
- 'service_start_name' => nil,
315
- 'password' => nil,
316
- 'description' => nil,
317
- 'failure_reset_period' => nil,
318
- 'failure_reboot_message' => nil,
319
- 'failure_command' => nil,
320
- 'failure_actions' => nil,
321
- 'failure_delay' => 0,
322
- 'host' => nil,
323
- 'service_name' => nil
324
- }
325
-
326
- # Validate the hash options
327
- options.each{ |key, value|
328
- key = key.to_s.downcase
329
- unless opts.include?(key)
330
- raise ArgumentError, "Invalid option '#{key}'"
331
- end
332
- opts[key] = value
333
- }
334
-
335
- unless opts['service_name']
336
- raise ArgumentError, 'No service_name specified'
337
- end
338
-
339
- service_name = opts.delete('service_name')
340
- host = opts.delete('host')
341
-
342
- raise TypeError unless service_name.is_a?(String)
343
- raise TypeError if host && !host.is_a?(String)
344
-
345
- begin
346
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)
347
-
348
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
349
-
350
- # Display name defaults to service_name
351
- opts['display_name'] ||= service_name
352
-
353
- dependencies = opts['dependencies']
354
-
355
- if dependencies && !dependencies.empty?
356
- unless dependencies.is_a?(Array) || dependencies.is_a?(String)
357
- raise TypeError, 'dependencies must be a string or array'
358
- end
359
-
360
- if dependencies.is_a?(Array)
361
- dependencies = dependencies.join("\000")
362
- end
363
-
364
- dependencies += "\000"
365
- dependencies = FFI::MemoryPointer.from_string(dependencies)
366
- end
367
-
368
- handle_scs = CreateService(
369
- handle_scm,
370
- service_name,
371
- opts['display_name'],
372
- opts['desired_access'],
373
- opts['service_type'],
374
- opts['start_type'],
375
- opts['error_control'],
376
- opts['binary_path_name'],
377
- opts['load_order_group'],
378
- nil,
379
- dependencies,
380
- opts['service_start_name'],
381
- opts['password']
382
- )
383
-
384
- FFI.raise_windows_error('CreateService') if handle_scs == 0
385
-
386
- if opts['description']
387
- description = SERVICE_DESCRIPTION.new
388
- description[:lpDescription] = FFI::MemoryPointer.from_string(opts['description'])
389
-
390
- bool = ChangeServiceConfig2(
391
- handle_scs,
392
- SERVICE_CONFIG_DESCRIPTION,
393
- description
394
- )
395
-
396
- FFI.raise_windows_error('ChangeServiceConfig2') unless bool
397
- end
398
-
399
- if opts['failure_reset_period'] || opts['failure_reboot_message'] ||
400
- opts['failure_command'] || opts['failure_actions']
401
- then
402
- Service.configure_failure_actions(handle_scs, opts)
403
- end
404
- ensure
405
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
406
- CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
407
- end
408
-
409
- self
410
- end
411
-
412
- # Configures the named +service+ on +host+, or the local host if no host
413
- # is specified. The +options+ parameter is a hash that can contain any
414
- # of the following parameters:
415
- #
416
- # * service_type
417
- # * start_type
418
- # * error_control
419
- # * binary_path_name
420
- # * load_order_group
421
- # * dependencies
422
- # * service_start_name
423
- # * password (used with service_start_name)
424
- # * display_name
425
- # * description
426
- # * failure_reset_period
427
- # * failure_reboot_message
428
- # * failure_command
429
- # * failure_actions
430
- # * failure_delay
431
- #
432
- # Examples:
433
- #
434
- # # Configure only the display name
435
- # Service.configure(
436
- # :service_name => 'some_service',
437
- # :display_name => 'Test 33'
438
- # )
439
- #
440
- # # Configure everything
441
- # Service.configure(
442
- # :service_name => 'some_service'
443
- # :service_type => Service::WIN32_OWN_PROCESS,
444
- # :start_type => Service::AUTO_START,
445
- # :error_control => Service::ERROR_NORMAL,
446
- # :binary_path_name => 'C:\path\to\some_service.exe',
447
- # :load_order_group => 'Network',
448
- # :dependencies => ['W32Time','Schedule']
449
- # :service_start_name => 'SomeDomain\\User',
450
- # :password => 'XXXXXXX',
451
- # :display_name => 'This is some service',
452
- # :description => 'A custom service I wrote just for fun'
453
- # )
454
- #
455
- def self.configure(options={})
456
- unless options.is_a?(Hash)
457
- raise ArgumentError, 'options parameter must be a hash'
458
- end
459
-
460
- if options.empty?
461
- raise ArgumentError, 'no options provided'
462
- end
463
-
464
- opts = {
465
- 'service_type' => SERVICE_NO_CHANGE,
466
- 'start_type' => SERVICE_NO_CHANGE,
467
- 'error_control' => SERVICE_NO_CHANGE,
468
- 'binary_path_name' => nil,
469
- 'load_order_group' => nil,
470
- 'dependencies' => nil,
471
- 'service_start_name' => nil,
472
- 'password' => nil,
473
- 'display_name' => nil,
474
- 'description' => nil,
475
- 'failure_reset_period' => nil,
476
- 'failure_reboot_message' => nil,
477
- 'failure_command' => nil,
478
- 'failure_actions' => nil,
479
- 'failure_delay' => 0,
480
- 'service_name' => nil,
481
- 'host' => nil,
482
- 'delayed_start' => false
483
- }
484
-
485
- # Validate the hash options
486
- options.each{ |key, value|
487
- key = key.to_s.downcase
488
- unless opts.include?(key)
489
- raise ArgumentError, "Invalid option '#{key}'"
490
- end
491
- opts[key] = value
492
- }
493
-
494
- unless opts['service_name']
495
- raise ArgumentError, 'No service_name specified'
496
- end
497
-
498
- service = opts.delete('service_name')
499
- host = opts.delete('host')
500
-
501
- raise TypeError unless service.is_a?(String)
502
- raise TypeError unless host.is_a?(String) if host
503
-
504
- begin
505
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
506
-
507
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
508
-
509
- desired_access = SERVICE_CHANGE_CONFIG
510
-
511
- if opts['failure_actions']
512
- desired_access |= SERVICE_START
513
- end
514
-
515
- handle_scs = OpenService(
516
- handle_scm,
517
- service,
518
- desired_access
519
- )
520
-
521
- FFI.raise_windows_error('OpenService') if handle_scs == 0
522
-
523
- dependencies = opts['dependencies']
524
-
525
- if dependencies && !dependencies.empty?
526
- unless dependencies.is_a?(Array) || dependencies.is_a?(String)
527
- raise TypeError, 'dependencies must be a string or array'
528
- end
529
-
530
- if dependencies.is_a?(Array)
531
- dependencies = dependencies.join("\000")
532
- end
533
-
534
- dependencies += "\000"
535
- end
536
-
537
- bool = ChangeServiceConfig(
538
- handle_scs,
539
- opts['service_type'],
540
- opts['start_type'],
541
- opts['error_control'],
542
- opts['binary_path_name'],
543
- opts['load_order_group'],
544
- nil,
545
- dependencies,
546
- opts['service_start_name'],
547
- opts['password'],
548
- opts['display_name']
549
- )
550
-
551
- FFI.raise_windows_error('ChangeServiceConfig') unless bool
552
-
553
- if opts['description']
554
- description = SERVICE_DESCRIPTION.new
555
- description[:lpDescription] = FFI::MemoryPointer.from_string(opts['description'])
556
-
557
- bool = ChangeServiceConfig2(
558
- handle_scs,
559
- SERVICE_CONFIG_DESCRIPTION,
560
- description
561
- )
562
-
563
- FFI.raise_windows_error('ChangeServiceConfig2') unless bool
564
- end
565
-
566
- if opts['delayed_start']
567
- delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
568
- delayed_start[:fDelayedAutostart] = opts['delayed_start']
569
-
570
- bool = ChangeServiceConfig2(
571
- handle_scs,
572
- SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
573
- delayed_start
574
- )
575
-
576
- FFI.raise_windows_error('ChangeServiceConfig2') unless bool
577
- end
578
-
579
- if opts['failure_reset_period'] || opts['failure_reboot_message'] ||
580
- opts['failure_command'] || opts['failure_actions']
581
- then
582
- configure_failure_actions(handle_scs, opts)
583
- end
584
- ensure
585
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
586
- CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
587
- end
588
-
589
- self
590
- end
591
-
592
- # Returns whether or not +service+ exists on +host+ or localhost, if
593
- # no host is specified.
594
- #
595
- # Example:
596
- #
597
- # Service.exists?('W32Time') => true
598
- #
599
- def self.exists?(service, host=nil)
600
- bool = false
601
-
602
- begin
603
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
604
-
605
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
606
-
607
- handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_STATUS)
608
- bool = true if handle_scs > 0
609
- ensure
610
- CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
611
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
612
- end
613
-
614
- bool
615
- end
616
-
617
- # Returns the display name of the specified service name, i.e. the string
618
- # displayed in the Services GUI. Raises a Service::Error if the service
619
- # name cannot be found.
620
- #
621
- # If a +host+ is provided, the information will be retrieved from that
622
- # host. Otherwise, the information is pulled from the local host (the
623
- # default behavior).
624
- #
625
- # Example:
626
- #
627
- # Service.get_display_name('W32Time') => 'Windows Time'
628
- #
629
- def self.get_display_name(service, host=nil)
630
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
631
-
632
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
633
-
634
- display_name = FFI::MemoryPointer.new(260)
635
- display_size = FFI::MemoryPointer.new(:ulong)
636
- display_size.write_ulong(display_name.size)
637
-
638
- begin
639
- bool = GetServiceDisplayName(
640
- handle_scm,
641
- service,
642
- display_name,
643
- display_size
644
- )
645
-
646
-
647
- FFI.raise_windows_error('OpenSCManager') unless bool
648
- ensure
649
- CloseServiceHandle(handle_scm)
650
- end
651
-
652
- display_name.read_string
653
- end
654
-
655
- # Returns the service name of the specified service from the provided
656
- # +display_name+. Raises a Service::Error if the +display_name+ cannote
657
- # be found.
658
- #
659
- # If a +host+ is provided, the information will be retrieved from that
660
- # host. Otherwise, the information is pulled from the local host (the
661
- # default behavior).
662
- #
663
- # Example:
664
- #
665
- # Service.get_service_name('Windows Time') => 'W32Time'
666
- #
667
- def self.get_service_name(display_name, host=nil)
668
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
669
-
670
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
671
-
672
- service_name = FFI::MemoryPointer.new(260)
673
- service_size = FFI::MemoryPointer.new(:ulong)
674
- service_size.write_ulong(service_name.size)
675
-
676
- begin
677
- bool = GetServiceKeyName(
678
- handle_scm,
679
- display_name,
680
- service_name,
681
- service_size
682
- )
683
-
684
- FFI.raise_windows_error('GetServiceKeyName') unless bool
685
- ensure
686
- CloseServiceHandle(handle_scm)
687
- end
688
-
689
- service_name.read_string
690
- end
691
-
692
- # Attempts to start the named +service+ on +host+, or the local machine
693
- # if no host is provided. If +args+ are provided, they are passed to the
694
- # Daemon#service_main method.
695
- #
696
- # Examples:
697
- #
698
- # # Start 'SomeSvc' on the local machine
699
- # Service.start('SomeSvc', nil) => self
700
- #
701
- # # Start 'SomeSvc' on host 'foo', passing 'hello' as an argument
702
- # Service.start('SomeSvc', 'foo', 'hello') => self
703
- #
704
- def self.start(service, host=nil, *args)
705
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
706
-
707
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
708
-
709
- begin
710
- handle_scs = OpenService(handle_scm, service, SERVICE_START)
711
-
712
- FFI.raise_windows_error('OpenService') if handle_scs == 0
713
-
714
- num_args = 0
715
-
716
- if args.empty?
717
- args = nil
718
- else
719
- str_ptrs = []
720
- num_args = args.size
721
-
722
- args.each{ |string|
723
- str_ptrs << FFI::MemoryPointer.from_string(string)
724
- }
725
-
726
- str_ptrs << nil
727
-
728
- vector = FFI::MemoryPointer.new(:pointer, str_ptrs.size)
729
-
730
- str_ptrs.each_with_index{ |p, i|
731
- vector[i].put_pointer(0, p)
732
- }
733
- end
734
-
735
- unless StartService(handle_scs, num_args, vector)
736
- FFI.raise_windows_error('StartService')
737
- end
738
-
739
- ensure
740
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
741
- CloseServiceHandle(handle_scm)
742
- end
743
-
744
- self
745
- end
746
-
747
- # Stops a the given +service+ on +host+, or the local host if no host
748
- # is specified. Returns self.
749
- #
750
- # Note that attempting to stop an already stopped service raises
751
- # Service::Error.
752
- #
753
- # Example:
754
- #
755
- # Service.stop('W32Time') => self
756
- #
757
- def self.stop(service, host=nil)
758
- service_signal = SERVICE_STOP
759
- control_signal = SERVICE_CONTROL_STOP
760
- send_signal(service, host, service_signal, control_signal)
761
- self
762
- end
763
-
764
- # Pauses the given +service+ on +host+, or the local host if no host
765
- # is specified. Returns self
766
- #
767
- # Note that pausing a service that is already paused will have
768
- # no effect and it will not raise an error.
769
- #
770
- # Be aware that not all services are configured to accept a pause
771
- # command. Attempting to pause a service that isn't setup to receive
772
- # a pause command will raise an error.
773
- #
774
- # Example:
775
- #
776
- # Service.pause('Schedule') => self
777
- #
778
- def self.pause(service, host=nil)
779
- service_signal = SERVICE_PAUSE_CONTINUE
780
- control_signal = SERVICE_CONTROL_PAUSE
781
- send_signal(service, host, service_signal, control_signal)
782
- self
783
- end
784
-
785
- # Resume the given +service+ on +host+, or the local host if no host
786
- # is specified. Returns self.
787
- #
788
- # Note that resuming a service that's already running will have no
789
- # effect and it will not raise an error.
790
- #
791
- # Example:
792
- #
793
- # Service.resume('Schedule') => self
794
- #
795
- def self.resume(service, host=nil)
796
- service_signal = SERVICE_PAUSE_CONTINUE
797
- control_signal = SERVICE_CONTROL_CONTINUE
798
- send_signal(service, host, service_signal, control_signal)
799
- self
800
- end
801
-
802
- # Deletes the specified +service+ from +host+, or the local host if
803
- # no host is specified. Returns self.
804
- #
805
- # Technical note. This method is not instantaneous. The service is first
806
- # marked for deletion from the service control manager database. Then all
807
- # handles to the service are closed. Then an attempt to stop the service
808
- # is made. If the service cannot be stopped, the service control manager
809
- # database entry is removed when the system is restarted.
810
- #
811
- # Example:
812
- #
813
- # Service.delete('SomeService') => self
814
- #
815
- def self.delete(service, host=nil)
816
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)
817
-
818
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
819
-
820
- begin
821
- handle_scs = OpenService(handle_scm, service, DELETE)
822
-
823
- FFI.raise_windows_error('OpenService') if handle_scs == 0
824
-
825
- unless DeleteService(handle_scs)
826
- FFI.raise_windows_error('DeleteService')
827
- end
828
- ensure
829
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
830
- CloseServiceHandle(handle_scm)
831
- end
832
-
833
- self
834
- end
835
-
836
- # Returns a ServiceConfigInfo struct containing the configuration
837
- # information about +service+ on +host+, or the local host if no
838
- # host is specified.
839
- #
840
- # Example:
841
- #
842
- # Service.config_info('W32Time') => <struct ServiceConfigInfo ...>
843
- #--
844
- # This contains less information that the ServiceInfo struct that
845
- # is returned with the Service.services method, but is faster for
846
- # looking up basic information for a single service.
847
- #
848
- def self.config_info(service, host=nil)
849
- raise TypeError if host && !host.is_a?(String)
850
-
851
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
852
-
853
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
854
-
855
- begin
856
- handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_CONFIG)
857
-
858
- FFI.raise_windows_error('OpenService') if handle_scs == 0
859
-
860
- # First, get the buf size needed
861
- bytes = FFI::MemoryPointer.new(:ulong)
862
-
863
- bool = QueryServiceConfig(handle_scs, nil, 0, bytes)
864
-
865
- if !bool && FFI.errno != ERROR_INSUFFICIENT_BUFFER
866
- FFI.raise_windows_error('QueryServiceConfig')
867
- end
868
-
869
- buf = FFI::MemoryPointer.new(:char, bytes.read_ulong)
870
- bytes = FFI::MemoryPointer.new(:ulong)
871
-
872
- bool = QueryServiceConfig(handle_scs, buf, buf.size, bytes)
873
-
874
- struct = QUERY_SERVICE_CONFIG.new(buf) # cast the buffer
875
-
876
- FFI.raise_windows_error('QueryServiceConfig') unless bool
877
- ensure
878
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
879
- CloseServiceHandle(handle_scm)
880
- end
881
-
882
- ConfigStruct.new(
883
- get_service_type(struct[:dwServiceType]),
884
- get_start_type(struct[:dwStartType]),
885
- get_error_control(struct[:dwErrorControl]),
886
- struct[:lpBinaryPathName].read_string,
887
- struct[:lpLoadOrderGroup].read_string,
888
- struct[:dwTagId],
889
- struct.dependencies,
890
- struct[:lpServiceStartName].read_string,
891
- struct[:lpDisplayName].read_string
892
- )
893
- end
894
-
895
- # Returns a ServiceStatus struct indicating the status of service +name+
896
- # on +host+, or the localhost if none is provided.
897
- #
898
- # Example:
899
- #
900
- # Service.status('W32Time') => <struct Struct::ServiceStatus ...>
901
- #
902
- def self.status(service, host=nil)
903
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
904
-
905
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
906
-
907
- begin
908
- handle_scs = OpenService(
909
- handle_scm,
910
- service,
911
- SERVICE_QUERY_STATUS
912
- )
913
-
914
- FFI.raise_windows_error('OpenService') if handle_scs == 0
915
-
916
- # SERVICE_STATUS_PROCESS struct
917
- status = SERVICE_STATUS_PROCESS.new
918
- bytes = FFI::MemoryPointer.new(:ulong)
919
-
920
- bool = QueryServiceStatusEx(
921
- handle_scs,
922
- SC_STATUS_PROCESS_INFO,
923
- status,
924
- status.size,
925
- bytes
926
- )
927
-
928
- FFI.raise_windows_error('QueryServiceStatusEx') unless bool
929
-
930
- service_type = get_service_type(status[:dwServiceType])
931
- current_state = get_current_state(status[:dwCurrentState])
932
- controls = get_controls_accepted(status[:dwControlsAccepted])
933
- interactive = status[:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0
934
-
935
- # Note that the pid and service flags will always return 0 if you're
936
- # on Windows NT 4 or using a version of Ruby compiled with VC++ 6
937
- # or earlier.
938
- #
939
- status_struct = StatusStruct.new(
940
- service_type,
941
- current_state,
942
- controls,
943
- status[:dwWin32ExitCode],
944
- status[:dwServiceSpecificExitCode],
945
- status[:dwCheckPoint],
946
- status[:dwWaitHint],
947
- interactive,
948
- status[:dwProcessId],
949
- status[:dwServiceFlags]
950
- )
951
- ensure
952
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
953
- CloseServiceHandle(handle_scm)
954
- end
955
-
956
- status_struct
957
- end
958
-
959
- # Enumerates over a list of service types on +host+, or the local
960
- # machine if no host is specified, yielding a ServiceInfo struct for
961
- # each service.
962
- #
963
- # If a +group+ is specified, then only those services that belong to
964
- # that load order group are enumerated. If an empty string is provided,
965
- # then only services that do not belong to any group are enumerated. If
966
- # this parameter is nil (the default), group membership is ignored and
967
- # all services are enumerated. This value is not case sensitive.
968
- #
969
- # Examples:
970
- #
971
- # # Enumerate over all services on the localhost
972
- # Service.services{ |service| p service }
973
- #
974
- # # Enumerate over all services on a remote host
975
- # Service.services('some_host'){ |service| p service }
976
- #
977
- # # Enumerate over all 'network' services locally
978
- # Service.services(nil, 'network'){ |service| p service }
979
- #
980
- def self.services(host=nil, group=nil)
981
- unless host.nil?
982
- raise TypeError unless host.is_a?(String) # Avoid strange errors
983
- end
984
-
985
- unless group.nil?
986
- raise TypeError unless group.is_a?(String) # Avoid strange errors
987
- end
988
-
989
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
990
-
991
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
992
-
993
- bytes_needed = FFI::MemoryPointer.new(:ulong)
994
- services_returned = FFI::MemoryPointer.new(:ulong)
995
- resume_handle = FFI::MemoryPointer.new(:ulong)
996
-
997
- begin
998
- # The first call is used to determine the required buffer size
999
- bool = EnumServicesStatusEx(
1000
- handle_scm,
1001
- SC_ENUM_PROCESS_INFO,
1002
- SERVICE_WIN32 | SERVICE_DRIVER,
1003
- SERVICE_STATE_ALL,
1004
- nil,
1005
- 0,
1006
- bytes_needed,
1007
- services_returned,
1008
- resume_handle,
1009
- group
1010
- )
1011
-
1012
- if !bool && FFI.errno == ERROR_MORE_DATA
1013
- service_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1014
- else
1015
- FFI.raise_windows_error('EnumServiceStatusEx')
1016
- end
1017
-
1018
- bool = EnumServicesStatusEx(
1019
- handle_scm,
1020
- SC_ENUM_PROCESS_INFO,
1021
- SERVICE_WIN32 | SERVICE_DRIVER,
1022
- SERVICE_STATE_ALL,
1023
- service_buf,
1024
- service_buf.size,
1025
- bytes_needed,
1026
- services_returned,
1027
- resume_handle,
1028
- group
1029
- )
1030
-
1031
- FFI.raise_windows_error('EnumServiceStatusEx') unless bool
1032
-
1033
- num_services = services_returned.read_ulong
1034
-
1035
- services_array = [] unless block_given?
1036
-
1037
- 1.upto(num_services){ |num|
1038
- # Cast the buffer
1039
- struct = ENUM_SERVICE_STATUS_PROCESS.new(service_buf)
1040
-
1041
- service_name = struct[:lpServiceName].read_string
1042
- display_name = struct[:lpDisplayName].read_string
1043
-
1044
- service_type = get_service_type(struct[:ServiceStatusProcess][:dwServiceType])
1045
- current_state = get_current_state(struct[:ServiceStatusProcess][:dwCurrentState])
1046
- controls = get_controls_accepted(struct[:ServiceStatusProcess][:dwControlsAccepted])
1047
-
1048
- interactive = struct[:ServiceStatusProcess][:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0
1049
- win_exit_code = struct[:ServiceStatusProcess][:dwWin32ExitCode]
1050
- ser_exit_code = struct[:ServiceStatusProcess][:dwServiceSpecificExitCode]
1051
- check_point = struct[:ServiceStatusProcess][:dwCheckPoint]
1052
- wait_hint = struct[:ServiceStatusProcess][:dwWaitHint]
1053
- pid = struct[:ServiceStatusProcess][:dwProcessId]
1054
- service_flags = struct[:ServiceStatusProcess][:dwServiceFlags]
1055
-
1056
- begin
1057
- handle_scs = OpenService(
1058
- handle_scm,
1059
- service_name,
1060
- SERVICE_QUERY_CONFIG
1061
- )
1062
-
1063
- FFI.raise_windows_error('OpenService') if handle_scs == 0
1064
-
1065
- config_struct = get_config_info(handle_scs)
1066
-
1067
- if config_struct != ERROR_FILE_NOT_FOUND
1068
- binary_path = config_struct[:lpBinaryPathName].read_string
1069
- load_order = config_struct[:lpLoadOrderGroup].read_string
1070
- start_name = config_struct[:lpServiceStartName].read_string
1071
- tag_id = config_struct[:dwTagId]
1072
-
1073
- start_type = get_start_type(config_struct[:dwStartType])
1074
- error_ctrl = get_error_control(config_struct[:dwErrorControl])
1075
-
1076
- deps = config_struct.dependencies
1077
-
1078
- begin
1079
- buf = get_config2_info(handle_scs, SERVICE_CONFIG_DESCRIPTION)
1080
-
1081
- if buf.is_a?(Fixnum) || buf.read_pointer.null?
1082
- description = ''
1083
- else
1084
- description = buf.read_pointer.read_string
1085
- end
1086
- rescue
1087
- # While being annoying, not being able to get a description is not exceptional
1088
- warn "WARNING: Failed to retrieve description for the #{service_name} service."
1089
- description = ''
1090
- end
1091
-
1092
- begin
1093
- delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
1094
- if delayed_start_buf.is_a?(FFI::MemoryPointer)
1095
- delayed_start_info = SERVICE_DELAYED_AUTO_START_INFO.new(delayed_start_buf)
1096
- delayed_start = delayed_start_info[:fDelayedAutostart]
1097
- else
1098
- delayed_start = false
1099
- end
1100
- rescue
1101
- warn "WARNING: Unable to get delayed auto start information for the #{service_name} service"
1102
- delayed_start = nil
1103
- end
1104
- else
1105
- warn "WARNING: The registry entry for the #{service_name} service could not be found"
1106
-
1107
- binary_path = nil
1108
- load_order = nil
1109
- start_name = nil
1110
- start_type = nil
1111
- error_ctrl = nil
1112
- tag_id = nil
1113
- deps = nil
1114
- description = nil
1115
- end
1116
-
1117
- begin
1118
- buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)
1119
-
1120
- if buf2.is_a?(FFI::MemoryPointer)
1121
- fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)
1122
-
1123
- reset_period = fail_struct[:dwResetPeriod]
1124
- num_actions = fail_struct[:cActions]
1125
-
1126
- if fail_struct[:lpRebootMsg].null?
1127
- reboot_msg = nil
1128
- else
1129
- reboot_msg = fail_struct[:lpRebootMsg].read_string
1130
- end
1131
-
1132
- if fail_struct[:lpCommand].null?
1133
- command = nil
1134
- else
1135
- command = fail_struct[:lpCommand].read_string
1136
- end
1137
-
1138
- actions = nil
1139
-
1140
- if num_actions > 0
1141
- action_ptr = fail_struct[:lpsaActions]
1142
-
1143
- actions = {}
1144
-
1145
- num_actions.times{ |n|
1146
- sc_action = SC_ACTION.new(action_ptr[n * SC_ACTION.size])
1147
- delay = sc_action[:Delay]
1148
- action_type = get_action_type(sc_action[:Type])
1149
- actions[n+1] = {:action_type => action_type, :delay => delay}
1150
- }
1151
- end
1152
- else
1153
- reset_period = nil
1154
- reboot_msg = nil
1155
- command = nil
1156
- actions = nil
1157
- end
1158
- rescue
1159
- warn "WARNING: Unable to retrieve failure actions for the #{service_name} service"
1160
- reset_period = nil
1161
- reboot_msg = nil
1162
- command = nil
1163
- actions = nil
1164
- end
1165
- ensure
1166
- CloseServiceHandle(handle_scs) if handle_scs > 0
1167
- end
1168
-
1169
- struct = ServiceStruct.new(
1170
- service_name,
1171
- display_name,
1172
- service_type,
1173
- current_state,
1174
- controls,
1175
- win_exit_code,
1176
- ser_exit_code,
1177
- check_point,
1178
- wait_hint,
1179
- binary_path,
1180
- start_type,
1181
- error_ctrl,
1182
- load_order,
1183
- tag_id,
1184
- start_name,
1185
- deps,
1186
- description,
1187
- interactive,
1188
- pid,
1189
- service_flags,
1190
- reset_period,
1191
- reboot_msg,
1192
- command,
1193
- num_actions,
1194
- actions,
1195
- delayed_start
1196
- )
1197
-
1198
- if block_given?
1199
- yield struct
1200
- else
1201
- services_array << struct
1202
- end
1203
-
1204
- service_buf += ENUM_SERVICE_STATUS_PROCESS.size
1205
- }
1206
- ensure
1207
- CloseServiceHandle(handle_scm)
1208
- end
1209
-
1210
- block_given? ? nil : services_array
1211
- end
1212
-
1213
- private
1214
-
1215
- # Configures failure actions for a given service.
1216
- #
1217
- def self.configure_failure_actions(handle_scs, opts)
1218
- if opts['failure_actions']
1219
- token_handle = FFI::MemoryPointer.new(:ulong)
1220
-
1221
- bool = OpenProcessToken(
1222
- GetCurrentProcess(),
1223
- TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1224
- token_handle
1225
- )
1226
-
1227
- unless bool
1228
- error = FFI.errno
1229
- CloseServiceHandle(handle_scs)
1230
- raise SystemCallError.new('OpenProcessToken', error)
1231
- end
1232
-
1233
- token_handle = token_handle.read_ulong
1234
-
1235
- # Get the LUID for shutdown privilege.
1236
- luid = LUID.new
1237
-
1238
- unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
1239
- error = FFI.errno
1240
- CloseServiceHandle(handle_scs)
1241
- raise SystemCallError.new('LookupPrivilegeValue', error)
1242
- end
1243
-
1244
- luid_and_attrs = LUID_AND_ATTRIBUTES.new
1245
- luid_and_attrs[:Luid] = luid
1246
- luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
1247
-
1248
- tkp = TOKEN_PRIVILEGES.new
1249
- tkp[:PrivilegeCount] = 1
1250
- tkp[:Privileges][0] = luid_and_attrs
1251
-
1252
- # Enable shutdown privilege in access token of this process
1253
- bool = AdjustTokenPrivileges(
1254
- token_handle,
1255
- 0,
1256
- tkp,
1257
- tkp.size,
1258
- nil,
1259
- nil
1260
- )
1261
-
1262
- unless bool
1263
- error = FFI.errno
1264
- CloseServiceHandle(handle_scs)
1265
- raise SystemCallError.new('AdjustTokenPrivileges', error)
1266
- end
1267
- end
1268
-
1269
- sfa = SERVICE_FAILURE_ACTIONS.new
1270
-
1271
- if opts['failure_reset_period']
1272
- sfa[:dwResetPeriod] = opts['failure_reset_period']
1273
- end
1274
-
1275
- if opts['failure_reboot_message']
1276
- sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
1277
- end
1278
-
1279
- if opts['failure_command']
1280
- sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
1281
- end
1282
-
1283
- if opts['failure_actions']
1284
- action_size = opts['failure_actions'].size
1285
- action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
1286
-
1287
- actions = action_size.times.collect do |i|
1288
- SC_ACTION.new(action_ptr + i * SC_ACTION.size)
1289
- end
1290
-
1291
- opts['failure_actions'].each_with_index{ |action, i|
1292
- actions[i][:Type] = action
1293
- actions[i][:Delay] = opts['failure_delay']
1294
- }
1295
-
1296
- sfa[:cActions] = action_size
1297
- sfa[:lpsaActions] = action_ptr
1298
- end
1299
-
1300
- bool = ChangeServiceConfig2(
1301
- handle_scs,
1302
- SERVICE_CONFIG_FAILURE_ACTIONS,
1303
- sfa
1304
- )
1305
-
1306
- unless bool
1307
- error = FFI.errno
1308
- CloseServiceHandle(handle_scs)
1309
- raise SystemCallError.new('ChangeServiceConfig2', error)
1310
- end
1311
- end
1312
-
1313
- # Returns a human readable string indicating the action type.
1314
- #
1315
- def self.get_action_type(action_type)
1316
- case action_type
1317
- when SC_ACTION_NONE
1318
- 'none'
1319
- when SC_ACTION_REBOOT
1320
- 'reboot'
1321
- when SC_ACTION_RESTART
1322
- 'restart'
1323
- when SC_ACTION_RUN_COMMAND
1324
- 'command'
1325
- else
1326
- 'unknown'
1327
- end
1328
- end
1329
-
1330
- # Shortcut for QueryServiceConfig. Returns the buffer. In rare cases
1331
- # the underlying registry entry may have been deleted, but the service
1332
- # still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned
1333
- # instead.
1334
- #
1335
- def self.get_config_info(handle)
1336
- bytes_needed = FFI::MemoryPointer.new(:ulong)
1337
-
1338
- # First attempt at QueryServiceConfig is to get size needed
1339
- bool = QueryServiceConfig(handle, nil, 0, bytes_needed)
1340
-
1341
- if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
1342
- config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1343
- elsif FFI.errno == ERROR_FILE_NOT_FOUND
1344
- return FFI.errno
1345
- else
1346
- error = FFI.errno
1347
- CloseServiceHandle(handle)
1348
- FFI.raise_windows_error('QueryServiceConfig', error)
1349
- end
1350
-
1351
- bytes_needed.clear
1352
-
1353
- # Second attempt at QueryServiceConfig gets the actual info
1354
- begin
1355
- bool = QueryServiceConfig(
1356
- handle,
1357
- config_buf,
1358
- config_buf.size,
1359
- bytes_needed
1360
- )
1361
-
1362
- FFI.raise_windows_error('QueryServiceConfig') unless bool
1363
- ensure
1364
- CloseServiceHandle(handle) unless bool
1365
- end
1366
-
1367
- QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
1368
- end
1369
-
1370
- # Shortcut for QueryServiceConfig2. Returns the buffer.
1371
- #
1372
- def self.get_config2_info(handle, info_level)
1373
- bytes_needed = FFI::MemoryPointer.new(:ulong)
1374
-
1375
- # First attempt at QueryServiceConfig2 is to get size needed
1376
- bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)
1377
-
1378
- err_num = FFI.errno
1379
-
1380
- # This is a bit hacky since it means we have to check the type of value
1381
- # we get back, but we don't always want to raise an error either,
1382
- # depending on what we're trying to get at.
1383
- #
1384
- if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
1385
- config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1386
- elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
1387
- return err_num
1388
- else
1389
- CloseServiceHandle(handle)
1390
- FFI.raise_windows_error('QueryServiceConfig2', err_num)
1391
- end
1392
-
1393
- bytes_needed.clear
1394
-
1395
- # Second attempt at QueryServiceConfig2 gets the actual info
1396
- begin
1397
- bool = QueryServiceConfig2(
1398
- handle,
1399
- info_level,
1400
- config2_buf,
1401
- config2_buf.size,
1402
- bytes_needed
1403
- )
1404
-
1405
- FFI.raise_windows_error('QueryServiceConfig2') unless bool
1406
- ensure
1407
- CloseServiceHandle(handle) unless bool
1408
- end
1409
-
1410
- config2_buf
1411
- end
1412
-
1413
- # Returns a human readable string indicating the error control
1414
- #
1415
- def self.get_error_control(error_control)
1416
- case error_control
1417
- when SERVICE_ERROR_CRITICAL
1418
- 'critical'
1419
- when SERVICE_ERROR_IGNORE
1420
- 'ignore'
1421
- when SERVICE_ERROR_NORMAL
1422
- 'normal'
1423
- when SERVICE_ERROR_SEVERE
1424
- 'severe'
1425
- else
1426
- nil
1427
- end
1428
- end
1429
-
1430
- # Returns a human readable string indicating the start type.
1431
- #
1432
- def self.get_start_type(start_type)
1433
- case start_type
1434
- when SERVICE_AUTO_START
1435
- 'auto start'
1436
- when SERVICE_BOOT_START
1437
- 'boot start'
1438
- when SERVICE_DEMAND_START
1439
- 'demand start'
1440
- when SERVICE_DISABLED
1441
- 'disabled'
1442
- when SERVICE_SYSTEM_START
1443
- 'system start'
1444
- else
1445
- nil
1446
- end
1447
- end
1448
-
1449
- # Returns an array of human readable strings indicating the controls
1450
- # that the service accepts.
1451
- #
1452
- def self.get_controls_accepted(controls)
1453
- array = []
1454
-
1455
- if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
1456
- array << 'netbind change'
1457
- end
1458
-
1459
- if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
1460
- array << 'param change'
1461
- end
1462
-
1463
- if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
1464
- array << 'pause continue'
1465
- end
1466
-
1467
- if controls & SERVICE_ACCEPT_SHUTDOWN > 0
1468
- array << 'shutdown'
1469
- end
1470
-
1471
- if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
1472
- array << 'pre-shutdown'
1473
- end
1474
-
1475
- if controls & SERVICE_ACCEPT_STOP > 0
1476
- array << 'stop'
1477
- end
1478
-
1479
- if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
1480
- array << 'hardware profile change'
1481
- end
1482
-
1483
- if controls & SERVICE_ACCEPT_POWEREVENT > 0
1484
- array << 'power event'
1485
- end
1486
-
1487
- if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
1488
- array << 'session change'
1489
- end
1490
-
1491
- array
1492
- end
1493
-
1494
- # Converts a service state numeric constant into a readable string.
1495
- #
1496
- def self.get_current_state(state)
1497
- case state
1498
- when SERVICE_CONTINUE_PENDING
1499
- 'continue pending'
1500
- when SERVICE_PAUSE_PENDING
1501
- 'pause pending'
1502
- when SERVICE_PAUSED
1503
- 'paused'
1504
- when SERVICE_RUNNING
1505
- 'running'
1506
- when SERVICE_START_PENDING
1507
- 'start pending'
1508
- when SERVICE_STOP_PENDING
1509
- 'stop pending'
1510
- when SERVICE_STOPPED
1511
- 'stopped'
1512
- else
1513
- nil
1514
- end
1515
- end
1516
-
1517
- # Converts a service type numeric constant into a human readable string.
1518
- #
1519
- def self.get_service_type(service_type)
1520
- case service_type
1521
- when SERVICE_FILE_SYSTEM_DRIVER
1522
- 'file system driver'
1523
- when SERVICE_KERNEL_DRIVER
1524
- 'kernel driver'
1525
- when SERVICE_WIN32_OWN_PROCESS
1526
- 'own process'
1527
- when SERVICE_WIN32_SHARE_PROCESS
1528
- 'share process'
1529
- when SERVICE_RECOGNIZER_DRIVER
1530
- 'recognizer driver'
1531
- when SERVICE_DRIVER
1532
- 'driver'
1533
- when SERVICE_WIN32
1534
- 'win32'
1535
- when SERVICE_TYPE_ALL
1536
- 'all'
1537
- when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
1538
- 'own process, interactive'
1539
- when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
1540
- 'share process, interactive'
1541
- else
1542
- nil
1543
- end
1544
- end
1545
-
1546
- # A shortcut method that simplifies the various service control methods.
1547
- #
1548
- def self.send_signal(service, host, service_signal, control_signal)
1549
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
1550
-
1551
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1552
-
1553
- begin
1554
- handle_scs = OpenService(handle_scm, service, service_signal)
1555
-
1556
- FFI.raise_windows_error('OpenService') if handle_scs == 0
1557
-
1558
- status = SERVICE_STATUS.new
1559
-
1560
- unless ControlService(handle_scs, control_signal, status)
1561
- FFI.raise_windows_error('ControlService')
1562
- end
1563
- ensure
1564
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
1565
- CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
1566
- end
1567
-
1568
- status
1569
- end
1570
-
1571
- class << self
1572
- alias create new
1573
- alias getdisplayname get_display_name
1574
- alias getservicename get_service_name
1575
- end
1576
- end
1577
- end
1
+ require 'ffi/win32/extensions'
2
+ require_relative 'windows/version'
3
+ require_relative 'windows/constants'
4
+ require_relative 'windows/structs'
5
+ require_relative 'windows/functions'
6
+
7
+ # The Win32 module serves as a namespace only.
8
+ module Win32
9
+
10
+ # The Service class encapsulates services controller actions, such as
11
+ # creating, starting, configuring or deleting services.
12
+ class Service
13
+ include Windows::ServiceConstants
14
+ include Windows::ServiceStructs
15
+ include Windows::ServiceFunctions
16
+
17
+ extend Windows::ServiceStructs
18
+ extend Windows::ServiceFunctions
19
+
20
+ # SCM security and access rights
21
+
22
+ # Includes STANDARD_RIGHTS_REQUIRED, in addition to all other rights
23
+ MANAGER_ALL_ACCESS = SC_MANAGER_ALL_ACCESS
24
+
25
+ # Required to call the CreateService function
26
+ MANAGER_CREATE_SERVICE = SC_MANAGER_CREATE_SERVICE
27
+
28
+ # Required to connect to the service control manager.
29
+ MANAGER_CONNECT = SC_MANAGER_CONNECT
30
+
31
+ # Required to call the EnumServicesStatusEx function to list services
32
+ MANAGER_ENUMERATE_SERVICE = SC_MANAGER_ENUMERATE_SERVICE
33
+
34
+ # Required to call the LockServiceDatabase function
35
+ MANAGER_LOCK = SC_MANAGER_LOCK
36
+
37
+ # Required to call the NotifyBootConfigStatus function
38
+ MANAGER_MODIFY_BOOT_CONFIG = SC_MANAGER_MODIFY_BOOT_CONFIG
39
+
40
+ # Required to call the QueryServiceLockStatus function
41
+ MANAGER_QUERY_LOCK_STATUS = SC_MANAGER_QUERY_LOCK_STATUS
42
+
43
+ # Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights
44
+ ALL_ACCESS = SERVICE_ALL_ACCESS
45
+
46
+ # Required to call functions that configure existing services
47
+ CHANGE_CONFIG = SERVICE_CHANGE_CONFIG
48
+
49
+ # Required to enumerate all the services dependent on the service
50
+ ENUMERATE_DEPENDENTS = SERVICE_ENUMERATE_DEPENDENTS
51
+
52
+ # Required to make a service report its status immediately
53
+ INTERROGATE = SERVICE_INTERROGATE
54
+
55
+ # Required to control a service with a pause or resume
56
+ PAUSE_CONTINUE = SERVICE_PAUSE_CONTINUE
57
+
58
+ # Required to be able to gather configuration information about a service
59
+ QUERY_CONFIG = SERVICE_QUERY_CONFIG
60
+
61
+ # Required to be ask the SCM about the status of a service
62
+ QUERY_STATUS = SERVICE_QUERY_STATUS
63
+
64
+ # Required to call the StartService function to start the service.
65
+ START = SERVICE_START
66
+
67
+ # Required to call the ControlService function to stop the service.
68
+ STOP = SERVICE_STOP
69
+
70
+ # Required to call ControlService with a user defined control code
71
+ USER_DEFINED_CONTROL = SERVICE_USER_DEFINED_CONTROL
72
+
73
+ # Service Types
74
+
75
+ # Driver service
76
+ KERNEL_DRIVER = SERVICE_KERNEL_DRIVER
77
+
78
+ # File system driver service
79
+ FILE_SYSTEM_DRIVER = SERVICE_FILE_SYSTEM_DRIVER
80
+
81
+ # Service that runs in its own process
82
+ WIN32_OWN_PROCESS = SERVICE_WIN32_OWN_PROCESS
83
+
84
+ # Service that shares a process with one or more other services.
85
+ WIN32_SHARE_PROCESS = SERVICE_WIN32_SHARE_PROCESS
86
+
87
+ # The service can interact with the desktop
88
+ INTERACTIVE_PROCESS = SERVICE_INTERACTIVE_PROCESS
89
+
90
+ DRIVER = SERVICE_DRIVER
91
+ TYPE_ALL = SERVICE_TYPE_ALL
92
+
93
+ # Service start options
94
+
95
+ # A service started automatically by the SCM during system startup
96
+ BOOT_START = SERVICE_BOOT_START
97
+
98
+ # A device driver started by the IoInitSystem function. Drivers only
99
+ SYSTEM_START = SERVICE_SYSTEM_START
100
+
101
+ # A service started automatically by the SCM during system startup
102
+ AUTO_START = SERVICE_AUTO_START
103
+
104
+ # A service started by the SCM when a process calls StartService
105
+ DEMAND_START = SERVICE_DEMAND_START
106
+
107
+ # A service that cannot be started
108
+ DISABLED = SERVICE_DISABLED
109
+
110
+ # Error control
111
+
112
+ # Error logged, startup continues
113
+ ERROR_IGNORE = SERVICE_ERROR_IGNORE
114
+
115
+ # Error logged, pop up message, startup continues
116
+ ERROR_NORMAL = SERVICE_ERROR_NORMAL
117
+
118
+ # Error logged, startup continues, system restarted last known good config
119
+ ERROR_SEVERE = SERVICE_ERROR_SEVERE
120
+
121
+ # Error logged, startup fails, system restarted last known good config
122
+ ERROR_CRITICAL = SERVICE_ERROR_CRITICAL
123
+
124
+ # Current state
125
+
126
+ # Service is not running
127
+ STOPPED = SERVICE_STOPPED
128
+
129
+ # Service has received a start signal but is not yet running
130
+ START_PENDING = SERVICE_START_PENDING
131
+
132
+ # Service has received a stop signal but is not yet stopped
133
+ STOP_PENDING = SERVICE_STOP_PENDING
134
+
135
+ # Service is running
136
+ RUNNING = SERVICE_RUNNING
137
+
138
+ # Service has received a signal to resume but is not yet running
139
+ CONTINUE_PENDING = SERVICE_CONTINUE_PENDING
140
+
141
+ # Service has received a signal to pause but is not yet paused
142
+ PAUSE_PENDING = SERVICE_PAUSE_PENDING
143
+
144
+ # Service is paused
145
+ PAUSED = SERVICE_PAUSED
146
+
147
+ # Service controls
148
+
149
+ # Notifies service that it should stop
150
+ CONTROL_STOP = SERVICE_CONTROL_STOP
151
+
152
+ # Notifies service that it should pause
153
+ CONTROL_PAUSE = SERVICE_CONTROL_PAUSE
154
+
155
+ # Notifies service that it should resume
156
+ CONTROL_CONTINUE = SERVICE_CONTROL_CONTINUE
157
+
158
+ # Notifies service that it should return its current status information
159
+ CONTROL_INTERROGATE = SERVICE_CONTROL_INTERROGATE
160
+
161
+ # Notifies a service that its parameters have changed
162
+ CONTROL_PARAMCHANGE = SERVICE_CONTROL_PARAMCHANGE
163
+
164
+ # Notifies a service that there is a new component for binding
165
+ CONTROL_NETBINDADD = SERVICE_CONTROL_NETBINDADD
166
+
167
+ # Notifies a service that a component for binding has been removed
168
+ CONTROL_NETBINDREMOVE = SERVICE_CONTROL_NETBINDREMOVE
169
+
170
+ # Notifies a service that a component for binding has been enabled
171
+ CONTROL_NETBINDENABLE = SERVICE_CONTROL_NETBINDENABLE
172
+
173
+ # Notifies a service that a component for binding has been disabled
174
+ CONTROL_NETBINDDISABLE = SERVICE_CONTROL_NETBINDDISABLE
175
+
176
+ # Failure actions
177
+
178
+ # No action
179
+ ACTION_NONE = SC_ACTION_NONE
180
+
181
+ # Reboot the computer
182
+ ACTION_REBOOT = SC_ACTION_REBOOT
183
+
184
+ # Restart the service
185
+ ACTION_RESTART = SC_ACTION_RESTART
186
+
187
+ # Run a command
188
+ ACTION_RUN_COMMAND = SC_ACTION_RUN_COMMAND
189
+
190
+ # :stopdoc: #
191
+
192
+ StatusStruct = Struct.new(
193
+ 'ServiceStatus',
194
+ :service_type,
195
+ :current_state,
196
+ :controls_accepted,
197
+ :win32_exit_code,
198
+ :service_specific_exit_code,
199
+ :check_point,
200
+ :wait_hint,
201
+ :interactive,
202
+ :pid,
203
+ :service_flags
204
+ )
205
+
206
+ ConfigStruct = Struct.new(
207
+ 'ServiceConfigInfo',
208
+ :service_type,
209
+ :start_type,
210
+ :error_control,
211
+ :binary_path_name,
212
+ :load_order_group,
213
+ :tag_id,
214
+ :dependencies,
215
+ :service_start_name,
216
+ :display_name
217
+ )
218
+
219
+ ServiceStruct = Struct.new(
220
+ 'ServiceInfo',
221
+ :service_name,
222
+ :display_name,
223
+ :service_type,
224
+ :current_state,
225
+ :controls_accepted,
226
+ :win32_exit_code,
227
+ :service_specific_exit_code,
228
+ :check_point,
229
+ :wait_hint,
230
+ :binary_path_name,
231
+ :start_type,
232
+ :error_control,
233
+ :load_order_group,
234
+ :tag_id,
235
+ :start_name,
236
+ :dependencies,
237
+ :description,
238
+ :interactive,
239
+ :pid,
240
+ :service_flags,
241
+ :reset_period,
242
+ :reboot_message,
243
+ :command,
244
+ :num_actions,
245
+ :actions,
246
+ :delayed_start
247
+ )
248
+
249
+ # :startdoc: #
250
+
251
+ # Creates a new service with the specified +options+. A +service_name+
252
+ # must be specified or an ArgumentError is raised. A +host+ option may
253
+ # be specified. If no host is specified the local machine is used.
254
+ #
255
+ # Possible Options:
256
+ #
257
+ # * service_name => nil (you must specify)
258
+ # * host => nil (optional)
259
+ # * display_name => service_name
260
+ # * desired_access => Service::ALL_ACCESS
261
+ # * service_type => Service::WIN32_OWN_PROCESS
262
+ # * start_type => Service::DEMAND_START
263
+ # * error_control => Service::ERROR_NORMAL
264
+ # * binary_path_name => nil
265
+ # * load_order_group => nil
266
+ # * dependencies => nil
267
+ # * service_start_name => nil
268
+ # * password => nil
269
+ # * description => nil
270
+ # * failure_reset_period => nil,
271
+ # * failure_reboot_message => nil,
272
+ # * failure_command => nil,
273
+ # * failure_actions => nil,
274
+ # * failure_delay => 0
275
+ #
276
+ # Example:
277
+ #
278
+ # # Configure everything
279
+ # Service.new(
280
+ # :service_name => 'some_service',
281
+ # :host => 'localhost',
282
+ # :service_type => Service::WIN32_OWN_PROCESS,
283
+ # :description => 'A custom service I wrote just for fun',
284
+ # :start_type => Service::AUTO_START,
285
+ # :error_control => Service::ERROR_NORMAL,
286
+ # :binary_path_name => 'C:\path\to\some_service.exe',
287
+ # :load_order_group => 'Network',
288
+ # :dependencies => ['W32Time','Schedule'],
289
+ # :service_start_name => 'SomeDomain\\User',
290
+ # :password => 'XXXXXXX',
291
+ # :display_name => 'This is some service',
292
+ # )
293
+ #
294
+ def initialize(options={})
295
+ unless options.is_a?(Hash)
296
+ raise ArgumentError, 'options parameter must be a hash'
297
+ end
298
+
299
+ if options.empty?
300
+ raise ArgumentError, 'no options provided'
301
+ end
302
+
303
+ opts = {
304
+ 'display_name' => nil,
305
+ 'desired_access' => SERVICE_ALL_ACCESS,
306
+ 'service_type' => SERVICE_WIN32_OWN_PROCESS,
307
+ 'start_type' => SERVICE_DEMAND_START,
308
+ 'error_control' => SERVICE_ERROR_NORMAL,
309
+ 'binary_path_name' => nil,
310
+ 'load_order_group' => nil,
311
+ 'dependencies' => nil,
312
+ 'service_start_name' => nil,
313
+ 'password' => nil,
314
+ 'description' => nil,
315
+ 'failure_reset_period' => nil,
316
+ 'failure_reboot_message' => nil,
317
+ 'failure_command' => nil,
318
+ 'failure_actions' => nil,
319
+ 'failure_delay' => 0,
320
+ 'host' => nil,
321
+ 'service_name' => nil
322
+ }
323
+
324
+ # Validate the hash options
325
+ options.each{ |key, value|
326
+ key = key.to_s.downcase
327
+ unless opts.include?(key)
328
+ raise ArgumentError, "Invalid option '#{key}'"
329
+ end
330
+ opts[key] = value
331
+ }
332
+
333
+ unless opts['service_name']
334
+ raise ArgumentError, 'No service_name specified'
335
+ end
336
+
337
+ service_name = opts.delete('service_name')
338
+ host = opts.delete('host')
339
+
340
+ raise TypeError unless service_name.is_a?(String)
341
+ raise TypeError if host && !host.is_a?(String)
342
+
343
+ begin
344
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)
345
+
346
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
347
+
348
+ # Display name defaults to service_name
349
+ opts['display_name'] ||= service_name
350
+
351
+ dependencies = opts['dependencies']
352
+
353
+ if dependencies && !dependencies.empty?
354
+ unless dependencies.is_a?(Array) || dependencies.is_a?(String)
355
+ raise TypeError, 'dependencies must be a string or array'
356
+ end
357
+
358
+ if dependencies.is_a?(Array)
359
+ dependencies = dependencies.join("\000")
360
+ end
361
+
362
+ dependencies += "\000"
363
+ dependencies = FFI::MemoryPointer.from_string(dependencies)
364
+ end
365
+
366
+ handle_scs = CreateService(
367
+ handle_scm,
368
+ service_name,
369
+ opts['display_name'],
370
+ opts['desired_access'],
371
+ opts['service_type'],
372
+ opts['start_type'],
373
+ opts['error_control'],
374
+ opts['binary_path_name'],
375
+ opts['load_order_group'],
376
+ nil,
377
+ dependencies,
378
+ opts['service_start_name'],
379
+ opts['password']
380
+ )
381
+
382
+ FFI.raise_windows_error('CreateService') if handle_scs == 0
383
+
384
+ if opts['description']
385
+ description = SERVICE_DESCRIPTION.new
386
+ description[:lpDescription] = FFI::MemoryPointer.from_string(opts['description'])
387
+
388
+ bool = ChangeServiceConfig2(
389
+ handle_scs,
390
+ SERVICE_CONFIG_DESCRIPTION,
391
+ description
392
+ )
393
+
394
+ FFI.raise_windows_error('ChangeServiceConfig2') unless bool
395
+ end
396
+
397
+ if opts['failure_reset_period'] || opts['failure_reboot_message'] ||
398
+ opts['failure_command'] || opts['failure_actions']
399
+ then
400
+ Service.configure_failure_actions(handle_scs, opts)
401
+ end
402
+ ensure
403
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
404
+ CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
405
+ end
406
+
407
+ self
408
+ end
409
+
410
+ # Configures the named +service+ on +host+, or the local host if no host
411
+ # is specified. The +options+ parameter is a hash that can contain any
412
+ # of the following parameters:
413
+ #
414
+ # * service_type
415
+ # * start_type
416
+ # * error_control
417
+ # * binary_path_name
418
+ # * load_order_group
419
+ # * dependencies
420
+ # * service_start_name
421
+ # * password (used with service_start_name)
422
+ # * display_name
423
+ # * description
424
+ # * failure_reset_period
425
+ # * failure_reboot_message
426
+ # * failure_command
427
+ # * failure_actions
428
+ # * failure_delay
429
+ #
430
+ # Examples:
431
+ #
432
+ # # Configure only the display name
433
+ # Service.configure(
434
+ # :service_name => 'some_service',
435
+ # :display_name => 'Test 33'
436
+ # )
437
+ #
438
+ # # Configure everything
439
+ # Service.configure(
440
+ # :service_name => 'some_service'
441
+ # :service_type => Service::WIN32_OWN_PROCESS,
442
+ # :start_type => Service::AUTO_START,
443
+ # :error_control => Service::ERROR_NORMAL,
444
+ # :binary_path_name => 'C:\path\to\some_service.exe',
445
+ # :load_order_group => 'Network',
446
+ # :dependencies => ['W32Time','Schedule']
447
+ # :service_start_name => 'SomeDomain\\User',
448
+ # :password => 'XXXXXXX',
449
+ # :display_name => 'This is some service',
450
+ # :description => 'A custom service I wrote just for fun'
451
+ # )
452
+ #
453
+ def self.configure(options={})
454
+ unless options.is_a?(Hash)
455
+ raise ArgumentError, 'options parameter must be a hash'
456
+ end
457
+
458
+ if options.empty?
459
+ raise ArgumentError, 'no options provided'
460
+ end
461
+
462
+ opts = {
463
+ 'service_type' => SERVICE_NO_CHANGE,
464
+ 'start_type' => SERVICE_NO_CHANGE,
465
+ 'error_control' => SERVICE_NO_CHANGE,
466
+ 'binary_path_name' => nil,
467
+ 'load_order_group' => nil,
468
+ 'dependencies' => nil,
469
+ 'service_start_name' => nil,
470
+ 'password' => nil,
471
+ 'display_name' => nil,
472
+ 'description' => nil,
473
+ 'failure_reset_period' => nil,
474
+ 'failure_reboot_message' => nil,
475
+ 'failure_command' => nil,
476
+ 'failure_actions' => nil,
477
+ 'failure_delay' => 0,
478
+ 'service_name' => nil,
479
+ 'host' => nil,
480
+ 'delayed_start' => false
481
+ }
482
+
483
+ # Validate the hash options
484
+ options.each{ |key, value|
485
+ key = key.to_s.downcase
486
+ unless opts.include?(key)
487
+ raise ArgumentError, "Invalid option '#{key}'"
488
+ end
489
+ opts[key] = value
490
+ }
491
+
492
+ unless opts['service_name']
493
+ raise ArgumentError, 'No service_name specified'
494
+ end
495
+
496
+ service = opts.delete('service_name')
497
+ host = opts.delete('host')
498
+
499
+ raise TypeError unless service.is_a?(String)
500
+ raise TypeError unless host.is_a?(String) if host
501
+
502
+ begin
503
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
504
+
505
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
506
+
507
+ desired_access = SERVICE_CHANGE_CONFIG
508
+
509
+ if opts['failure_actions']
510
+ desired_access |= SERVICE_START
511
+ end
512
+
513
+ handle_scs = OpenService(
514
+ handle_scm,
515
+ service,
516
+ desired_access
517
+ )
518
+
519
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
520
+
521
+ dependencies = opts['dependencies']
522
+
523
+ if dependencies && !dependencies.empty?
524
+ unless dependencies.is_a?(Array) || dependencies.is_a?(String)
525
+ raise TypeError, 'dependencies must be a string or array'
526
+ end
527
+
528
+ if dependencies.is_a?(Array)
529
+ dependencies = dependencies.join("\000")
530
+ end
531
+
532
+ dependencies += "\000"
533
+ end
534
+
535
+ bool = ChangeServiceConfig(
536
+ handle_scs,
537
+ opts['service_type'],
538
+ opts['start_type'],
539
+ opts['error_control'],
540
+ opts['binary_path_name'],
541
+ opts['load_order_group'],
542
+ nil,
543
+ dependencies,
544
+ opts['service_start_name'],
545
+ opts['password'],
546
+ opts['display_name']
547
+ )
548
+
549
+ FFI.raise_windows_error('ChangeServiceConfig') unless bool
550
+
551
+ if opts['description']
552
+ description = SERVICE_DESCRIPTION.new
553
+ description[:lpDescription] = FFI::MemoryPointer.from_string(opts['description'])
554
+
555
+ bool = ChangeServiceConfig2(
556
+ handle_scs,
557
+ SERVICE_CONFIG_DESCRIPTION,
558
+ description
559
+ )
560
+
561
+ FFI.raise_windows_error('ChangeServiceConfig2') unless bool
562
+ end
563
+
564
+ if opts['delayed_start']
565
+ delayed_start = SERVICE_DELAYED_AUTO_START_INFO.new
566
+ delayed_start[:fDelayedAutostart] = opts['delayed_start']
567
+
568
+ bool = ChangeServiceConfig2(
569
+ handle_scs,
570
+ SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
571
+ delayed_start
572
+ )
573
+
574
+ FFI.raise_windows_error('ChangeServiceConfig2') unless bool
575
+ end
576
+
577
+ if opts['failure_reset_period'] || opts['failure_reboot_message'] ||
578
+ opts['failure_command'] || opts['failure_actions']
579
+ then
580
+ configure_failure_actions(handle_scs, opts)
581
+ end
582
+ ensure
583
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
584
+ CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
585
+ end
586
+
587
+ self
588
+ end
589
+
590
+ # Returns whether or not +service+ exists on +host+ or localhost, if
591
+ # no host is specified.
592
+ #
593
+ # Example:
594
+ #
595
+ # Service.exists?('W32Time') => true
596
+ #
597
+ def self.exists?(service, host=nil)
598
+ bool = false
599
+
600
+ begin
601
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
602
+
603
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
604
+
605
+ handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_STATUS)
606
+ bool = true if handle_scs > 0
607
+ ensure
608
+ CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
609
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
610
+ end
611
+
612
+ bool
613
+ end
614
+
615
+ # Returns the display name of the specified service name, i.e. the string
616
+ # displayed in the Services GUI. Raises a Service::Error if the service
617
+ # name cannot be found.
618
+ #
619
+ # If a +host+ is provided, the information will be retrieved from that
620
+ # host. Otherwise, the information is pulled from the local host (the
621
+ # default behavior).
622
+ #
623
+ # Example:
624
+ #
625
+ # Service.get_display_name('W32Time') => 'Windows Time'
626
+ #
627
+ def self.get_display_name(service, host=nil)
628
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
629
+
630
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
631
+
632
+ display_name = FFI::MemoryPointer.new(260)
633
+ display_size = FFI::MemoryPointer.new(:ulong)
634
+ display_size.write_ulong(display_name.size)
635
+
636
+ begin
637
+ bool = GetServiceDisplayName(
638
+ handle_scm,
639
+ service,
640
+ display_name,
641
+ display_size
642
+ )
643
+
644
+
645
+ FFI.raise_windows_error('OpenSCManager') unless bool
646
+ ensure
647
+ CloseServiceHandle(handle_scm)
648
+ end
649
+
650
+ display_name.read_string
651
+ end
652
+
653
+ # Returns the service name of the specified service from the provided
654
+ # +display_name+. Raises a Service::Error if the +display_name+ cannote
655
+ # be found.
656
+ #
657
+ # If a +host+ is provided, the information will be retrieved from that
658
+ # host. Otherwise, the information is pulled from the local host (the
659
+ # default behavior).
660
+ #
661
+ # Example:
662
+ #
663
+ # Service.get_service_name('Windows Time') => 'W32Time'
664
+ #
665
+ def self.get_service_name(display_name, host=nil)
666
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
667
+
668
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
669
+
670
+ service_name = FFI::MemoryPointer.new(260)
671
+ service_size = FFI::MemoryPointer.new(:ulong)
672
+ service_size.write_ulong(service_name.size)
673
+
674
+ begin
675
+ bool = GetServiceKeyName(
676
+ handle_scm,
677
+ display_name,
678
+ service_name,
679
+ service_size
680
+ )
681
+
682
+ FFI.raise_windows_error('GetServiceKeyName') unless bool
683
+ ensure
684
+ CloseServiceHandle(handle_scm)
685
+ end
686
+
687
+ service_name.read_string
688
+ end
689
+
690
+ # Attempts to start the named +service+ on +host+, or the local machine
691
+ # if no host is provided. If +args+ are provided, they are passed to the
692
+ # Daemon#service_main method.
693
+ #
694
+ # Examples:
695
+ #
696
+ # # Start 'SomeSvc' on the local machine
697
+ # Service.start('SomeSvc', nil) => self
698
+ #
699
+ # # Start 'SomeSvc' on host 'foo', passing 'hello' as an argument
700
+ # Service.start('SomeSvc', 'foo', 'hello') => self
701
+ #
702
+ def self.start(service, host=nil, *args)
703
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
704
+
705
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
706
+
707
+ begin
708
+ handle_scs = OpenService(handle_scm, service, SERVICE_START)
709
+
710
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
711
+
712
+ num_args = 0
713
+
714
+ if args.empty?
715
+ args = nil
716
+ else
717
+ str_ptrs = []
718
+ num_args = args.size
719
+
720
+ args.each{ |string|
721
+ str_ptrs << FFI::MemoryPointer.from_string(string)
722
+ }
723
+
724
+ str_ptrs << nil
725
+
726
+ vector = FFI::MemoryPointer.new(:pointer, str_ptrs.size)
727
+
728
+ str_ptrs.each_with_index{ |p, i|
729
+ vector[i].put_pointer(0, p)
730
+ }
731
+ end
732
+
733
+ unless StartService(handle_scs, num_args, vector)
734
+ FFI.raise_windows_error('StartService')
735
+ end
736
+
737
+ ensure
738
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
739
+ CloseServiceHandle(handle_scm)
740
+ end
741
+
742
+ self
743
+ end
744
+
745
+ # Stops a the given +service+ on +host+, or the local host if no host
746
+ # is specified. Returns self.
747
+ #
748
+ # Note that attempting to stop an already stopped service raises
749
+ # Service::Error.
750
+ #
751
+ # Example:
752
+ #
753
+ # Service.stop('W32Time') => self
754
+ #
755
+ def self.stop(service, host=nil)
756
+ service_signal = SERVICE_STOP
757
+ control_signal = SERVICE_CONTROL_STOP
758
+ send_signal(service, host, service_signal, control_signal)
759
+ self
760
+ end
761
+
762
+ # Pauses the given +service+ on +host+, or the local host if no host
763
+ # is specified. Returns self
764
+ #
765
+ # Note that pausing a service that is already paused will have
766
+ # no effect and it will not raise an error.
767
+ #
768
+ # Be aware that not all services are configured to accept a pause
769
+ # command. Attempting to pause a service that isn't setup to receive
770
+ # a pause command will raise an error.
771
+ #
772
+ # Example:
773
+ #
774
+ # Service.pause('Schedule') => self
775
+ #
776
+ def self.pause(service, host=nil)
777
+ service_signal = SERVICE_PAUSE_CONTINUE
778
+ control_signal = SERVICE_CONTROL_PAUSE
779
+ send_signal(service, host, service_signal, control_signal)
780
+ self
781
+ end
782
+
783
+ # Resume the given +service+ on +host+, or the local host if no host
784
+ # is specified. Returns self.
785
+ #
786
+ # Note that resuming a service that's already running will have no
787
+ # effect and it will not raise an error.
788
+ #
789
+ # Example:
790
+ #
791
+ # Service.resume('Schedule') => self
792
+ #
793
+ def self.resume(service, host=nil)
794
+ service_signal = SERVICE_PAUSE_CONTINUE
795
+ control_signal = SERVICE_CONTROL_CONTINUE
796
+ send_signal(service, host, service_signal, control_signal)
797
+ self
798
+ end
799
+
800
+ # Deletes the specified +service+ from +host+, or the local host if
801
+ # no host is specified. Returns self.
802
+ #
803
+ # Technical note. This method is not instantaneous. The service is first
804
+ # marked for deletion from the service control manager database. Then all
805
+ # handles to the service are closed. Then an attempt to stop the service
806
+ # is made. If the service cannot be stopped, the service control manager
807
+ # database entry is removed when the system is restarted.
808
+ #
809
+ # Example:
810
+ #
811
+ # Service.delete('SomeService') => self
812
+ #
813
+ def self.delete(service, host=nil)
814
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CREATE_SERVICE)
815
+
816
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
817
+
818
+ begin
819
+ handle_scs = OpenService(handle_scm, service, DELETE)
820
+
821
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
822
+
823
+ unless DeleteService(handle_scs)
824
+ FFI.raise_windows_error('DeleteService')
825
+ end
826
+ ensure
827
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
828
+ CloseServiceHandle(handle_scm)
829
+ end
830
+
831
+ self
832
+ end
833
+
834
+ # Returns a ServiceConfigInfo struct containing the configuration
835
+ # information about +service+ on +host+, or the local host if no
836
+ # host is specified.
837
+ #
838
+ # Example:
839
+ #
840
+ # Service.config_info('W32Time') => <struct ServiceConfigInfo ...>
841
+ #--
842
+ # This contains less information that the ServiceInfo struct that
843
+ # is returned with the Service.services method, but is faster for
844
+ # looking up basic information for a single service.
845
+ #
846
+ def self.config_info(service, host=nil)
847
+ raise TypeError if host && !host.is_a?(String)
848
+
849
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
850
+
851
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
852
+
853
+ begin
854
+ handle_scs = OpenService(handle_scm, service, SERVICE_QUERY_CONFIG)
855
+
856
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
857
+
858
+ # First, get the buf size needed
859
+ bytes = FFI::MemoryPointer.new(:ulong)
860
+
861
+ bool = QueryServiceConfig(handle_scs, nil, 0, bytes)
862
+
863
+ if !bool && FFI.errno != ERROR_INSUFFICIENT_BUFFER
864
+ FFI.raise_windows_error('QueryServiceConfig')
865
+ end
866
+
867
+ buf = FFI::MemoryPointer.new(:char, bytes.read_ulong)
868
+ bytes = FFI::MemoryPointer.new(:ulong)
869
+
870
+ bool = QueryServiceConfig(handle_scs, buf, buf.size, bytes)
871
+
872
+ struct = QUERY_SERVICE_CONFIG.new(buf) # cast the buffer
873
+
874
+ FFI.raise_windows_error('QueryServiceConfig') unless bool
875
+ ensure
876
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
877
+ CloseServiceHandle(handle_scm)
878
+ end
879
+
880
+ ConfigStruct.new(
881
+ get_service_type(struct[:dwServiceType]),
882
+ get_start_type(struct[:dwStartType]),
883
+ get_error_control(struct[:dwErrorControl]),
884
+ struct[:lpBinaryPathName].read_string,
885
+ struct[:lpLoadOrderGroup].read_string,
886
+ struct[:dwTagId],
887
+ struct.dependencies,
888
+ struct[:lpServiceStartName].read_string,
889
+ struct[:lpDisplayName].read_string
890
+ )
891
+ end
892
+
893
+ # Returns a ServiceStatus struct indicating the status of service +name+
894
+ # on +host+, or the localhost if none is provided.
895
+ #
896
+ # Example:
897
+ #
898
+ # Service.status('W32Time') => <struct Struct::ServiceStatus ...>
899
+ #
900
+ def self.status(service, host=nil)
901
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
902
+
903
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
904
+
905
+ begin
906
+ handle_scs = OpenService(
907
+ handle_scm,
908
+ service,
909
+ SERVICE_QUERY_STATUS
910
+ )
911
+
912
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
913
+
914
+ # SERVICE_STATUS_PROCESS struct
915
+ status = SERVICE_STATUS_PROCESS.new
916
+ bytes = FFI::MemoryPointer.new(:ulong)
917
+
918
+ bool = QueryServiceStatusEx(
919
+ handle_scs,
920
+ SC_STATUS_PROCESS_INFO,
921
+ status,
922
+ status.size,
923
+ bytes
924
+ )
925
+
926
+ FFI.raise_windows_error('QueryServiceStatusEx') unless bool
927
+
928
+ service_type = get_service_type(status[:dwServiceType])
929
+ current_state = get_current_state(status[:dwCurrentState])
930
+ controls = get_controls_accepted(status[:dwControlsAccepted])
931
+ interactive = status[:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0
932
+
933
+ # Note that the pid and service flags will always return 0 if you're
934
+ # on Windows NT 4 or using a version of Ruby compiled with VC++ 6
935
+ # or earlier.
936
+ #
937
+ status_struct = StatusStruct.new(
938
+ service_type,
939
+ current_state,
940
+ controls,
941
+ status[:dwWin32ExitCode],
942
+ status[:dwServiceSpecificExitCode],
943
+ status[:dwCheckPoint],
944
+ status[:dwWaitHint],
945
+ interactive,
946
+ status[:dwProcessId],
947
+ status[:dwServiceFlags]
948
+ )
949
+ ensure
950
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
951
+ CloseServiceHandle(handle_scm)
952
+ end
953
+
954
+ status_struct
955
+ end
956
+
957
+ # Enumerates over a list of service types on +host+, or the local
958
+ # machine if no host is specified, yielding a ServiceInfo struct for
959
+ # each service.
960
+ #
961
+ # If a +group+ is specified, then only those services that belong to
962
+ # that load order group are enumerated. If an empty string is provided,
963
+ # then only services that do not belong to any group are enumerated. If
964
+ # this parameter is nil (the default), group membership is ignored and
965
+ # all services are enumerated. This value is not case sensitive.
966
+ #
967
+ # Examples:
968
+ #
969
+ # # Enumerate over all services on the localhost
970
+ # Service.services{ |service| p service }
971
+ #
972
+ # # Enumerate over all services on a remote host
973
+ # Service.services('some_host'){ |service| p service }
974
+ #
975
+ # # Enumerate over all 'network' services locally
976
+ # Service.services(nil, 'network'){ |service| p service }
977
+ #
978
+ def self.services(host=nil, group=nil)
979
+ unless host.nil?
980
+ raise TypeError unless host.is_a?(String) # Avoid strange errors
981
+ end
982
+
983
+ unless group.nil?
984
+ raise TypeError unless group.is_a?(String) # Avoid strange errors
985
+ end
986
+
987
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
988
+
989
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
990
+
991
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
992
+ services_returned = FFI::MemoryPointer.new(:ulong)
993
+ resume_handle = FFI::MemoryPointer.new(:ulong)
994
+
995
+ begin
996
+ # The first call is used to determine the required buffer size
997
+ bool = EnumServicesStatusEx(
998
+ handle_scm,
999
+ SC_ENUM_PROCESS_INFO,
1000
+ SERVICE_WIN32 | SERVICE_DRIVER,
1001
+ SERVICE_STATE_ALL,
1002
+ nil,
1003
+ 0,
1004
+ bytes_needed,
1005
+ services_returned,
1006
+ resume_handle,
1007
+ group
1008
+ )
1009
+
1010
+ if !bool && FFI.errno == ERROR_MORE_DATA
1011
+ service_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1012
+ else
1013
+ FFI.raise_windows_error('EnumServiceStatusEx')
1014
+ end
1015
+
1016
+ bool = EnumServicesStatusEx(
1017
+ handle_scm,
1018
+ SC_ENUM_PROCESS_INFO,
1019
+ SERVICE_WIN32 | SERVICE_DRIVER,
1020
+ SERVICE_STATE_ALL,
1021
+ service_buf,
1022
+ service_buf.size,
1023
+ bytes_needed,
1024
+ services_returned,
1025
+ resume_handle,
1026
+ group
1027
+ )
1028
+
1029
+ FFI.raise_windows_error('EnumServiceStatusEx') unless bool
1030
+
1031
+ num_services = services_returned.read_ulong
1032
+
1033
+ services_array = [] unless block_given?
1034
+
1035
+ 1.upto(num_services){ |num|
1036
+ # Cast the buffer
1037
+ struct = ENUM_SERVICE_STATUS_PROCESS.new(service_buf)
1038
+
1039
+ service_name = struct[:lpServiceName].read_string
1040
+ display_name = struct[:lpDisplayName].read_string
1041
+
1042
+ service_type = get_service_type(struct[:ServiceStatusProcess][:dwServiceType])
1043
+ current_state = get_current_state(struct[:ServiceStatusProcess][:dwCurrentState])
1044
+ controls = get_controls_accepted(struct[:ServiceStatusProcess][:dwControlsAccepted])
1045
+
1046
+ interactive = struct[:ServiceStatusProcess][:dwServiceType] & SERVICE_INTERACTIVE_PROCESS > 0
1047
+ win_exit_code = struct[:ServiceStatusProcess][:dwWin32ExitCode]
1048
+ ser_exit_code = struct[:ServiceStatusProcess][:dwServiceSpecificExitCode]
1049
+ check_point = struct[:ServiceStatusProcess][:dwCheckPoint]
1050
+ wait_hint = struct[:ServiceStatusProcess][:dwWaitHint]
1051
+ pid = struct[:ServiceStatusProcess][:dwProcessId]
1052
+ service_flags = struct[:ServiceStatusProcess][:dwServiceFlags]
1053
+
1054
+ begin
1055
+ handle_scs = OpenService(
1056
+ handle_scm,
1057
+ service_name,
1058
+ SERVICE_QUERY_CONFIG
1059
+ )
1060
+
1061
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
1062
+
1063
+ config_struct = get_config_info(handle_scs)
1064
+
1065
+ if config_struct != ERROR_FILE_NOT_FOUND
1066
+ binary_path = config_struct[:lpBinaryPathName].read_string
1067
+ load_order = config_struct[:lpLoadOrderGroup].read_string
1068
+ start_name = config_struct[:lpServiceStartName].read_string
1069
+ tag_id = config_struct[:dwTagId]
1070
+
1071
+ start_type = get_start_type(config_struct[:dwStartType])
1072
+ error_ctrl = get_error_control(config_struct[:dwErrorControl])
1073
+
1074
+ deps = config_struct.dependencies
1075
+
1076
+ begin
1077
+ buf = get_config2_info(handle_scs, SERVICE_CONFIG_DESCRIPTION)
1078
+
1079
+ if buf.is_a?(Numeric) || buf.read_pointer.null?
1080
+ description = ''
1081
+ else
1082
+ description = buf.read_pointer.read_string
1083
+ end
1084
+ rescue
1085
+ # While being annoying, not being able to get a description is not exceptional
1086
+ warn "WARNING: Failed to retrieve description for the #{service_name} service."
1087
+ description = ''
1088
+ end
1089
+
1090
+ begin
1091
+ delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
1092
+ if delayed_start_buf.is_a?(FFI::MemoryPointer)
1093
+ delayed_start_info = SERVICE_DELAYED_AUTO_START_INFO.new(delayed_start_buf)
1094
+ delayed_start = delayed_start_info[:fDelayedAutostart]
1095
+ else
1096
+ delayed_start = false
1097
+ end
1098
+ rescue
1099
+ warn "WARNING: Unable to get delayed auto start information for the #{service_name} service"
1100
+ delayed_start = nil
1101
+ end
1102
+ else
1103
+ warn "WARNING: The registry entry for the #{service_name} service could not be found"
1104
+
1105
+ binary_path = nil
1106
+ load_order = nil
1107
+ start_name = nil
1108
+ start_type = nil
1109
+ error_ctrl = nil
1110
+ tag_id = nil
1111
+ deps = nil
1112
+ description = nil
1113
+ end
1114
+
1115
+ begin
1116
+ buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)
1117
+
1118
+ if buf2.is_a?(FFI::MemoryPointer)
1119
+ fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)
1120
+
1121
+ reset_period = fail_struct[:dwResetPeriod]
1122
+ num_actions = fail_struct[:cActions]
1123
+
1124
+ if fail_struct[:lpRebootMsg].null?
1125
+ reboot_msg = nil
1126
+ else
1127
+ reboot_msg = fail_struct[:lpRebootMsg].read_string
1128
+ end
1129
+
1130
+ if fail_struct[:lpCommand].null?
1131
+ command = nil
1132
+ else
1133
+ command = fail_struct[:lpCommand].read_string
1134
+ end
1135
+
1136
+ actions = nil
1137
+
1138
+ if num_actions > 0
1139
+ action_ptr = fail_struct[:lpsaActions]
1140
+
1141
+ actions = {}
1142
+
1143
+ num_actions.times{ |n|
1144
+ sc_action = SC_ACTION.new(action_ptr[n * SC_ACTION.size])
1145
+ delay = sc_action[:Delay]
1146
+ action_type = get_action_type(sc_action[:Type])
1147
+ actions[n+1] = {:action_type => action_type, :delay => delay}
1148
+ }
1149
+ end
1150
+ else
1151
+ reset_period = nil
1152
+ reboot_msg = nil
1153
+ command = nil
1154
+ actions = nil
1155
+ end
1156
+ rescue
1157
+ warn "WARNING: Unable to retrieve failure actions for the #{service_name} service"
1158
+ reset_period = nil
1159
+ reboot_msg = nil
1160
+ command = nil
1161
+ actions = nil
1162
+ end
1163
+ ensure
1164
+ CloseServiceHandle(handle_scs) if handle_scs > 0
1165
+ end
1166
+
1167
+ struct = ServiceStruct.new(
1168
+ service_name,
1169
+ display_name,
1170
+ service_type,
1171
+ current_state,
1172
+ controls,
1173
+ win_exit_code,
1174
+ ser_exit_code,
1175
+ check_point,
1176
+ wait_hint,
1177
+ binary_path,
1178
+ start_type,
1179
+ error_ctrl,
1180
+ load_order,
1181
+ tag_id,
1182
+ start_name,
1183
+ deps,
1184
+ description,
1185
+ interactive,
1186
+ pid,
1187
+ service_flags,
1188
+ reset_period,
1189
+ reboot_msg,
1190
+ command,
1191
+ num_actions,
1192
+ actions,
1193
+ delayed_start
1194
+ )
1195
+
1196
+ if block_given?
1197
+ yield struct
1198
+ else
1199
+ services_array << struct
1200
+ end
1201
+
1202
+ service_buf += ENUM_SERVICE_STATUS_PROCESS.size
1203
+ }
1204
+ ensure
1205
+ CloseServiceHandle(handle_scm)
1206
+ end
1207
+
1208
+ block_given? ? nil : services_array
1209
+ end
1210
+
1211
+ private
1212
+
1213
+ # Configures failure actions for a given service.
1214
+ #
1215
+ def self.configure_failure_actions(handle_scs, opts)
1216
+ if opts['failure_actions']
1217
+ token_handle = FFI::MemoryPointer.new(:ulong)
1218
+
1219
+ bool = OpenProcessToken(
1220
+ GetCurrentProcess(),
1221
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1222
+ token_handle
1223
+ )
1224
+
1225
+ unless bool
1226
+ error = FFI.errno
1227
+ CloseServiceHandle(handle_scs)
1228
+ raise SystemCallError.new('OpenProcessToken', error)
1229
+ end
1230
+
1231
+ token_handle = token_handle.read_ulong
1232
+
1233
+ # Get the LUID for shutdown privilege.
1234
+ luid = LUID.new
1235
+
1236
+ unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
1237
+ error = FFI.errno
1238
+ CloseServiceHandle(handle_scs)
1239
+ raise SystemCallError.new('LookupPrivilegeValue', error)
1240
+ end
1241
+
1242
+ luid_and_attrs = LUID_AND_ATTRIBUTES.new
1243
+ luid_and_attrs[:Luid] = luid
1244
+ luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
1245
+
1246
+ tkp = TOKEN_PRIVILEGES.new
1247
+ tkp[:PrivilegeCount] = 1
1248
+ tkp[:Privileges][0] = luid_and_attrs
1249
+
1250
+ # Enable shutdown privilege in access token of this process
1251
+ bool = AdjustTokenPrivileges(
1252
+ token_handle,
1253
+ 0,
1254
+ tkp,
1255
+ tkp.size,
1256
+ nil,
1257
+ nil
1258
+ )
1259
+
1260
+ unless bool
1261
+ error = FFI.errno
1262
+ CloseServiceHandle(handle_scs)
1263
+ raise SystemCallError.new('AdjustTokenPrivileges', error)
1264
+ end
1265
+ end
1266
+
1267
+ sfa = SERVICE_FAILURE_ACTIONS.new
1268
+
1269
+ if opts['failure_reset_period']
1270
+ sfa[:dwResetPeriod] = opts['failure_reset_period']
1271
+ end
1272
+
1273
+ if opts['failure_reboot_message']
1274
+ sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
1275
+ end
1276
+
1277
+ if opts['failure_command']
1278
+ sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
1279
+ end
1280
+
1281
+ if opts['failure_actions']
1282
+ action_size = opts['failure_actions'].size
1283
+ action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
1284
+
1285
+ actions = action_size.times.collect do |i|
1286
+ SC_ACTION.new(action_ptr + i * SC_ACTION.size)
1287
+ end
1288
+
1289
+ opts['failure_actions'].each_with_index{ |action, i|
1290
+ actions[i][:Type] = action
1291
+ actions[i][:Delay] = opts['failure_delay']
1292
+ }
1293
+
1294
+ sfa[:cActions] = action_size
1295
+ sfa[:lpsaActions] = action_ptr
1296
+ end
1297
+
1298
+ bool = ChangeServiceConfig2(
1299
+ handle_scs,
1300
+ SERVICE_CONFIG_FAILURE_ACTIONS,
1301
+ sfa
1302
+ )
1303
+
1304
+ unless bool
1305
+ error = FFI.errno
1306
+ CloseServiceHandle(handle_scs)
1307
+ raise SystemCallError.new('ChangeServiceConfig2', error)
1308
+ end
1309
+ end
1310
+
1311
+ # Returns a human readable string indicating the action type.
1312
+ #
1313
+ def self.get_action_type(action_type)
1314
+ case action_type
1315
+ when SC_ACTION_NONE
1316
+ 'none'
1317
+ when SC_ACTION_REBOOT
1318
+ 'reboot'
1319
+ when SC_ACTION_RESTART
1320
+ 'restart'
1321
+ when SC_ACTION_RUN_COMMAND
1322
+ 'command'
1323
+ else
1324
+ 'unknown'
1325
+ end
1326
+ end
1327
+
1328
+ # Shortcut for QueryServiceConfig. Returns the buffer. In rare cases
1329
+ # the underlying registry entry may have been deleted, but the service
1330
+ # still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned
1331
+ # instead.
1332
+ #
1333
+ def self.get_config_info(handle)
1334
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1335
+
1336
+ # First attempt at QueryServiceConfig is to get size needed
1337
+ bool = QueryServiceConfig(handle, nil, 0, bytes_needed)
1338
+
1339
+ if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
1340
+ config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1341
+ elsif FFI.errno == ERROR_FILE_NOT_FOUND
1342
+ return FFI.errno
1343
+ else
1344
+ error = FFI.errno
1345
+ CloseServiceHandle(handle)
1346
+ FFI.raise_windows_error('QueryServiceConfig', error)
1347
+ end
1348
+
1349
+ bytes_needed.clear
1350
+
1351
+ # Second attempt at QueryServiceConfig gets the actual info
1352
+ begin
1353
+ bool = QueryServiceConfig(
1354
+ handle,
1355
+ config_buf,
1356
+ config_buf.size,
1357
+ bytes_needed
1358
+ )
1359
+
1360
+ FFI.raise_windows_error('QueryServiceConfig') unless bool
1361
+ ensure
1362
+ CloseServiceHandle(handle) unless bool
1363
+ end
1364
+
1365
+ QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
1366
+ end
1367
+
1368
+ # Shortcut for QueryServiceConfig2. Returns the buffer.
1369
+ #
1370
+ def self.get_config2_info(handle, info_level)
1371
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1372
+
1373
+ # First attempt at QueryServiceConfig2 is to get size needed
1374
+ bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)
1375
+
1376
+ err_num = FFI.errno
1377
+
1378
+ # This is a bit hacky since it means we have to check the type of value
1379
+ # we get back, but we don't always want to raise an error either,
1380
+ # depending on what we're trying to get at.
1381
+ #
1382
+ if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
1383
+ config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1384
+ elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
1385
+ return err_num
1386
+ else
1387
+ CloseServiceHandle(handle)
1388
+ FFI.raise_windows_error('QueryServiceConfig2', err_num)
1389
+ end
1390
+
1391
+ bytes_needed.clear
1392
+
1393
+ # Second attempt at QueryServiceConfig2 gets the actual info
1394
+ begin
1395
+ bool = QueryServiceConfig2(
1396
+ handle,
1397
+ info_level,
1398
+ config2_buf,
1399
+ config2_buf.size,
1400
+ bytes_needed
1401
+ )
1402
+
1403
+ FFI.raise_windows_error('QueryServiceConfig2') unless bool
1404
+ ensure
1405
+ CloseServiceHandle(handle) unless bool
1406
+ end
1407
+
1408
+ config2_buf
1409
+ end
1410
+
1411
+ # Returns a human readable string indicating the error control
1412
+ #
1413
+ def self.get_error_control(error_control)
1414
+ case error_control
1415
+ when SERVICE_ERROR_CRITICAL
1416
+ 'critical'
1417
+ when SERVICE_ERROR_IGNORE
1418
+ 'ignore'
1419
+ when SERVICE_ERROR_NORMAL
1420
+ 'normal'
1421
+ when SERVICE_ERROR_SEVERE
1422
+ 'severe'
1423
+ else
1424
+ nil
1425
+ end
1426
+ end
1427
+
1428
+ # Returns a human readable string indicating the start type.
1429
+ #
1430
+ def self.get_start_type(start_type)
1431
+ case start_type
1432
+ when SERVICE_AUTO_START
1433
+ 'auto start'
1434
+ when SERVICE_BOOT_START
1435
+ 'boot start'
1436
+ when SERVICE_DEMAND_START
1437
+ 'demand start'
1438
+ when SERVICE_DISABLED
1439
+ 'disabled'
1440
+ when SERVICE_SYSTEM_START
1441
+ 'system start'
1442
+ else
1443
+ nil
1444
+ end
1445
+ end
1446
+
1447
+ # Returns an array of human readable strings indicating the controls
1448
+ # that the service accepts.
1449
+ #
1450
+ def self.get_controls_accepted(controls)
1451
+ array = []
1452
+
1453
+ if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
1454
+ array << 'netbind change'
1455
+ end
1456
+
1457
+ if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
1458
+ array << 'param change'
1459
+ end
1460
+
1461
+ if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
1462
+ array << 'pause continue'
1463
+ end
1464
+
1465
+ if controls & SERVICE_ACCEPT_SHUTDOWN > 0
1466
+ array << 'shutdown'
1467
+ end
1468
+
1469
+ if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
1470
+ array << 'pre-shutdown'
1471
+ end
1472
+
1473
+ if controls & SERVICE_ACCEPT_STOP > 0
1474
+ array << 'stop'
1475
+ end
1476
+
1477
+ if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
1478
+ array << 'hardware profile change'
1479
+ end
1480
+
1481
+ if controls & SERVICE_ACCEPT_POWEREVENT > 0
1482
+ array << 'power event'
1483
+ end
1484
+
1485
+ if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
1486
+ array << 'session change'
1487
+ end
1488
+
1489
+ array
1490
+ end
1491
+
1492
+ # Converts a service state numeric constant into a readable string.
1493
+ #
1494
+ def self.get_current_state(state)
1495
+ case state
1496
+ when SERVICE_CONTINUE_PENDING
1497
+ 'continue pending'
1498
+ when SERVICE_PAUSE_PENDING
1499
+ 'pause pending'
1500
+ when SERVICE_PAUSED
1501
+ 'paused'
1502
+ when SERVICE_RUNNING
1503
+ 'running'
1504
+ when SERVICE_START_PENDING
1505
+ 'start pending'
1506
+ when SERVICE_STOP_PENDING
1507
+ 'stop pending'
1508
+ when SERVICE_STOPPED
1509
+ 'stopped'
1510
+ else
1511
+ nil
1512
+ end
1513
+ end
1514
+
1515
+ # Converts a service type numeric constant into a human readable string.
1516
+ #
1517
+ def self.get_service_type(service_type)
1518
+ case service_type
1519
+ when SERVICE_FILE_SYSTEM_DRIVER
1520
+ 'file system driver'
1521
+ when SERVICE_KERNEL_DRIVER
1522
+ 'kernel driver'
1523
+ when SERVICE_WIN32_OWN_PROCESS
1524
+ 'own process'
1525
+ when SERVICE_WIN32_SHARE_PROCESS
1526
+ 'share process'
1527
+ when SERVICE_RECOGNIZER_DRIVER
1528
+ 'recognizer driver'
1529
+ when SERVICE_DRIVER
1530
+ 'driver'
1531
+ when SERVICE_WIN32
1532
+ 'win32'
1533
+ when SERVICE_TYPE_ALL
1534
+ 'all'
1535
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
1536
+ 'own process, interactive'
1537
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
1538
+ 'share process, interactive'
1539
+ else
1540
+ nil
1541
+ end
1542
+ end
1543
+
1544
+ # A shortcut method that simplifies the various service control methods.
1545
+ #
1546
+ def self.send_signal(service, host, service_signal, control_signal)
1547
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
1548
+
1549
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1550
+
1551
+ begin
1552
+ handle_scs = OpenService(handle_scm, service, service_signal)
1553
+
1554
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
1555
+
1556
+ status = SERVICE_STATUS.new
1557
+
1558
+ unless ControlService(handle_scs, control_signal, status)
1559
+ FFI.raise_windows_error('ControlService')
1560
+ end
1561
+ ensure
1562
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
1563
+ CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
1564
+ end
1565
+
1566
+ status
1567
+ end
1568
+
1569
+ class << self
1570
+ alias create new
1571
+ alias getdisplayname get_display_name
1572
+ alias getservicename get_service_name
1573
+ end
1574
+ end
1575
+ end