win32-service 0.8.10 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,1577 +1,1684 @@
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
+ self.class.configure_failure_actions(handle_scs, opts)
401
+ end
402
+ ensure
403
+ self.class.close_service_handle(handle_scs)
404
+ self.class.close_service_handle(handle_scm)
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
+ close_service_handle(handle_scs)
584
+ close_service_handle(handle_scm)
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
+ close_service_handle(handle_scm)
609
+ close_service_handle(handle_scs)
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
+ close_service_handle(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
+ close_service_handle(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
+ close_service_handle(handle_scs)
739
+ close_service_handle(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
+ close_service_handle(handle_scs)
828
+ close_service_handle(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
+ close_service_handle(handle_scs)
877
+ close_service_handle(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
+ close_service_handle(handle_scs)
951
+ close_service_handle(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
+ delayed_start = delayed_start(service_name)
1091
+ else
1092
+ warn "WARNING: The registry entry for the #{service_name} service could not be found"
1093
+
1094
+ binary_path = nil
1095
+ load_order = nil
1096
+ start_name = nil
1097
+ start_type = nil
1098
+ error_ctrl = nil
1099
+ tag_id = nil
1100
+ deps = nil
1101
+ description = nil
1102
+ end
1103
+
1104
+ begin
1105
+ buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)
1106
+
1107
+ if buf2.is_a?(FFI::MemoryPointer)
1108
+ fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)
1109
+
1110
+ reset_period = fail_struct[:dwResetPeriod]
1111
+ num_actions = fail_struct[:cActions]
1112
+
1113
+ if fail_struct[:lpRebootMsg].null?
1114
+ reboot_msg = nil
1115
+ else
1116
+ reboot_msg = fail_struct[:lpRebootMsg].read_string
1117
+ end
1118
+
1119
+ if fail_struct[:lpCommand].null?
1120
+ command = nil
1121
+ else
1122
+ command = fail_struct[:lpCommand].read_string
1123
+ end
1124
+
1125
+ actions = nil
1126
+
1127
+ if num_actions > 0
1128
+ action_ptr = fail_struct[:lpsaActions]
1129
+
1130
+ actions = {}
1131
+
1132
+ num_actions.times{ |n|
1133
+ sc_action = SC_ACTION.new(action_ptr[n * SC_ACTION.size])
1134
+ delay = sc_action[:Delay]
1135
+ action_type = get_action_type(sc_action[:Type])
1136
+ actions[n+1] = {:action_type => action_type, :delay => delay}
1137
+ }
1138
+ end
1139
+ else
1140
+ reset_period = nil
1141
+ reboot_msg = nil
1142
+ command = nil
1143
+ actions = nil
1144
+ end
1145
+ rescue
1146
+ warn "WARNING: Unable to retrieve failure actions for the #{service_name} service"
1147
+ reset_period = nil
1148
+ reboot_msg = nil
1149
+ command = nil
1150
+ actions = nil
1151
+ end
1152
+ ensure
1153
+ close_service_handle(handle_scs)
1154
+ end
1155
+
1156
+ struct = ServiceStruct.new(
1157
+ service_name,
1158
+ display_name,
1159
+ service_type,
1160
+ current_state,
1161
+ controls,
1162
+ win_exit_code,
1163
+ ser_exit_code,
1164
+ check_point,
1165
+ wait_hint,
1166
+ binary_path,
1167
+ start_type,
1168
+ error_ctrl,
1169
+ load_order,
1170
+ tag_id,
1171
+ start_name,
1172
+ deps,
1173
+ description,
1174
+ interactive,
1175
+ pid,
1176
+ service_flags,
1177
+ reset_period,
1178
+ reboot_msg,
1179
+ command,
1180
+ num_actions,
1181
+ actions,
1182
+ delayed_start
1183
+ )
1184
+
1185
+ if block_given?
1186
+ yield struct
1187
+ else
1188
+ services_array << struct
1189
+ end
1190
+
1191
+ service_buf += ENUM_SERVICE_STATUS_PROCESS.size
1192
+ }
1193
+ ensure
1194
+ close_service_handle(handle_scm)
1195
+ end
1196
+
1197
+ block_given? ? nil : services_array
1198
+ end
1199
+
1200
+ #
1201
+ # Attempts to get the delayed_start attribute for the named +service+ on +host+,
1202
+ # or the local machine if no host is provided.
1203
+ #
1204
+ # @example Get 'SomeSvc' delayed start on the local machine
1205
+ # Service.delayed_start('SomeSvc') # => 1
1206
+ # @example Get 'SomeSvc' delayed start on host foo
1207
+ # Service.delayed_start('SomeSvc', 'foo') # => 1
1208
+ #
1209
+ # @param service [String] Service name (e.g. `"Dhcp"`)
1210
+ # @param host [String] Host of service (e.g. `"mymachine"`)
1211
+ # @return [Integer, false, nil] Returns `1` when delayed start is enabled
1212
+ # and `0` when it is not enabled. Returns nil or false when there is
1213
+ # a problem of some kind.
1214
+ #
1215
+ def self.delayed_start(service, host = nil)
1216
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_ENUMERATE_SERVICE)
1217
+
1218
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1219
+
1220
+ handle_scs = OpenService(
1221
+ handle_scm,
1222
+ service,
1223
+ SERVICE_QUERY_CONFIG
1224
+ )
1225
+
1226
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
1227
+
1228
+ delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
1229
+ if delayed_start_buf.is_a?(FFI::MemoryPointer)
1230
+ delayed_start_info = SERVICE_DELAYED_AUTO_START_INFO.new(delayed_start_buf)
1231
+ delayed_start = delayed_start_info[:fDelayedAutostart]
1232
+ else
1233
+ delayed_start = false
1234
+ end
1235
+ rescue SystemCallError
1236
+ delayed_start = nil
1237
+ ensure
1238
+ close_service_handle(handle_scs)
1239
+ close_service_handle(handle_scm)
1240
+ end
1241
+
1242
+ def self.close_service_handle(handle)
1243
+ case handle
1244
+ when NilClass
1245
+ false
1246
+ when Integer
1247
+ handle > 0 ? CloseServiceHandle(handle) : false
1248
+ else
1249
+ raise ArgumentError, "You must pass a valid handle to ::close_service_handle"
1250
+ end
1251
+ end
1252
+
1253
+ #
1254
+ # Opens an existing service.
1255
+ #
1256
+ # @example
1257
+ # open_sc_manager do |scm_handle|
1258
+ # open_service(scm_handle, 'Dhcp', SERVICE_ALL_ACCESS) do |service_handle|
1259
+ # p service_handle
1260
+ # end
1261
+ # end
1262
+ #
1263
+ # @param scm_handle [Integer] pointer to Service Control Manager
1264
+ # @param service_name [String] Name of the service
1265
+ # @param desired_access [Integer] The access to the service. For a list of
1266
+ # access rights, see `Windows::ServiceConstants`.
1267
+ #
1268
+ # @see Windows::ServiceConstants
1269
+ #
1270
+ def self.open_service(scm_handle, service_name, desired_access)
1271
+ service_handle = OpenService(
1272
+ scm_handle,
1273
+ service_name,
1274
+ desired_access
1275
+ )
1276
+ FFI.raise_windows_error('OpenService') if service_handle == 0
1277
+
1278
+ if block_given?
1279
+ yield service_handle
1280
+ else
1281
+ service_handle
1282
+ end
1283
+ ensure
1284
+ close_service_handle(service_handle) if block_given?
1285
+ end
1286
+
1287
+ #
1288
+ # Establishes a connection to the service control manager on the specified
1289
+ # host and opens the SERVICES_ACTIVE_DATABASE service control manager
1290
+ # database.
1291
+ #
1292
+ # @example
1293
+ # open_sc_manager(nil, SC_MANAGER_ENUMERATE_SERVICE) do |scm_handle|
1294
+ # p scm_handle
1295
+ # end
1296
+ #
1297
+ # @param host [String] Name of host you want to connect to. If `nil` it
1298
+ # will connect to the local host.
1299
+ # @param desired_access [Integer] The access to the service control
1300
+ # manager.
1301
+ # @return [Integer] If the function succeeds, the return value is a handle
1302
+ # to the specified service control manager database. If the function
1303
+ # fails, the return value is 0.
1304
+ #
1305
+ # @see Windows::ServiceConstants
1306
+ #
1307
+ def self.open_sc_manager(host = nil, desired_access = SC_MANAGER_CONNECT)
1308
+ scm_handle = OpenSCManager(host, nil, desired_access)
1309
+ FFI.raise_windows_error('OpenSCManager') if scm_handle == 0
1310
+
1311
+ if block_given?
1312
+ yield scm_handle
1313
+ else
1314
+ scm_handle
1315
+ end
1316
+ ensure
1317
+ close_service_handle(scm_handle) if block_given?
1318
+ end
1319
+
1320
+ private
1321
+
1322
+ # Configures failure actions for a given service.
1323
+ #
1324
+ def self.configure_failure_actions(handle_scs, opts)
1325
+ if opts['failure_actions']
1326
+ token_handle = FFI::MemoryPointer.new(:ulong)
1327
+
1328
+ bool = OpenProcessToken(
1329
+ GetCurrentProcess(),
1330
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1331
+ token_handle
1332
+ )
1333
+
1334
+ unless bool
1335
+ error = FFI.errno
1336
+ close_service_handle(handle_scs)
1337
+ raise SystemCallError.new('OpenProcessToken', error)
1338
+ end
1339
+
1340
+ token_handle = token_handle.read_ulong
1341
+
1342
+ # Get the LUID for shutdown privilege.
1343
+ luid = LUID.new
1344
+
1345
+ unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
1346
+ error = FFI.errno
1347
+ close_service_handle(handle_scs)
1348
+ raise SystemCallError.new('LookupPrivilegeValue', error)
1349
+ end
1350
+
1351
+ luid_and_attrs = LUID_AND_ATTRIBUTES.new
1352
+ luid_and_attrs[:Luid] = luid
1353
+ luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
1354
+
1355
+ tkp = TOKEN_PRIVILEGES.new
1356
+ tkp[:PrivilegeCount] = 1
1357
+ tkp[:Privileges][0] = luid_and_attrs
1358
+
1359
+ # Enable shutdown privilege in access token of this process
1360
+ bool = AdjustTokenPrivileges(
1361
+ token_handle,
1362
+ 0,
1363
+ tkp,
1364
+ tkp.size,
1365
+ nil,
1366
+ nil
1367
+ )
1368
+
1369
+ unless bool
1370
+ error = FFI.errno
1371
+ close_service_handle(handle_scs)
1372
+ raise SystemCallError.new('AdjustTokenPrivileges', error)
1373
+ end
1374
+ end
1375
+
1376
+ sfa = SERVICE_FAILURE_ACTIONS.new
1377
+
1378
+ if opts['failure_reset_period']
1379
+ sfa[:dwResetPeriod] = opts['failure_reset_period']
1380
+ end
1381
+
1382
+ if opts['failure_reboot_message']
1383
+ sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
1384
+ end
1385
+
1386
+ if opts['failure_command']
1387
+ sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
1388
+ end
1389
+
1390
+ if opts['failure_actions']
1391
+ action_size = opts['failure_actions'].size
1392
+ action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
1393
+
1394
+ actions = action_size.times.collect do |i|
1395
+ SC_ACTION.new(action_ptr + i * SC_ACTION.size)
1396
+ end
1397
+
1398
+ opts['failure_actions'].each_with_index{ |action, i|
1399
+ actions[i][:Type] = action
1400
+ actions[i][:Delay] = opts['failure_delay']
1401
+ }
1402
+
1403
+ sfa[:cActions] = action_size
1404
+ sfa[:lpsaActions] = action_ptr
1405
+ end
1406
+
1407
+ bool = ChangeServiceConfig2(
1408
+ handle_scs,
1409
+ SERVICE_CONFIG_FAILURE_ACTIONS,
1410
+ sfa
1411
+ )
1412
+
1413
+ unless bool
1414
+ error = FFI.errno
1415
+ close_service_handle(handle_scs)
1416
+ raise SystemCallError.new('ChangeServiceConfig2', error)
1417
+ end
1418
+ end
1419
+
1420
+ # Returns a human readable string indicating the action type.
1421
+ #
1422
+ def self.get_action_type(action_type)
1423
+ case action_type
1424
+ when SC_ACTION_NONE
1425
+ 'none'
1426
+ when SC_ACTION_REBOOT
1427
+ 'reboot'
1428
+ when SC_ACTION_RESTART
1429
+ 'restart'
1430
+ when SC_ACTION_RUN_COMMAND
1431
+ 'command'
1432
+ else
1433
+ 'unknown'
1434
+ end
1435
+ end
1436
+
1437
+ # Shortcut for QueryServiceConfig. Returns the buffer. In rare cases
1438
+ # the underlying registry entry may have been deleted, but the service
1439
+ # still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned
1440
+ # instead.
1441
+ #
1442
+ def self.get_config_info(handle)
1443
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1444
+
1445
+ # First attempt at QueryServiceConfig is to get size needed
1446
+ bool = QueryServiceConfig(handle, nil, 0, bytes_needed)
1447
+
1448
+ if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
1449
+ config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1450
+ elsif FFI.errno == ERROR_FILE_NOT_FOUND
1451
+ return FFI.errno
1452
+ else
1453
+ error = FFI.errno
1454
+ CloseServiceHandle(handle)
1455
+ FFI.raise_windows_error('QueryServiceConfig', error)
1456
+ end
1457
+
1458
+ bytes_needed.clear
1459
+
1460
+ # Second attempt at QueryServiceConfig gets the actual info
1461
+ begin
1462
+ bool = QueryServiceConfig(
1463
+ handle,
1464
+ config_buf,
1465
+ config_buf.size,
1466
+ bytes_needed
1467
+ )
1468
+
1469
+ FFI.raise_windows_error('QueryServiceConfig') unless bool
1470
+ ensure
1471
+ CloseServiceHandle(handle) unless bool
1472
+ end
1473
+
1474
+ QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
1475
+ end
1476
+
1477
+ # Shortcut for QueryServiceConfig2. Returns the buffer.
1478
+ #
1479
+ def self.get_config2_info(handle, info_level)
1480
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1481
+
1482
+ # First attempt at QueryServiceConfig2 is to get size needed
1483
+ bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)
1484
+
1485
+ err_num = FFI.errno
1486
+
1487
+ # This is a bit hacky since it means we have to check the type of value
1488
+ # we get back, but we don't always want to raise an error either,
1489
+ # depending on what we're trying to get at.
1490
+ #
1491
+ if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
1492
+ config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1493
+ elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
1494
+ return err_num
1495
+ else
1496
+ CloseServiceHandle(handle)
1497
+ FFI.raise_windows_error('QueryServiceConfig2', err_num)
1498
+ end
1499
+
1500
+ bytes_needed.clear
1501
+
1502
+ # Second attempt at QueryServiceConfig2 gets the actual info
1503
+ begin
1504
+ bool = QueryServiceConfig2(
1505
+ handle,
1506
+ info_level,
1507
+ config2_buf,
1508
+ config2_buf.size,
1509
+ bytes_needed
1510
+ )
1511
+
1512
+ FFI.raise_windows_error('QueryServiceConfig2') unless bool
1513
+ ensure
1514
+ CloseServiceHandle(handle) unless bool
1515
+ end
1516
+
1517
+ config2_buf
1518
+ end
1519
+
1520
+ # Returns a human readable string indicating the error control
1521
+ #
1522
+ def self.get_error_control(error_control)
1523
+ case error_control
1524
+ when SERVICE_ERROR_CRITICAL
1525
+ 'critical'
1526
+ when SERVICE_ERROR_IGNORE
1527
+ 'ignore'
1528
+ when SERVICE_ERROR_NORMAL
1529
+ 'normal'
1530
+ when SERVICE_ERROR_SEVERE
1531
+ 'severe'
1532
+ else
1533
+ nil
1534
+ end
1535
+ end
1536
+
1537
+ # Returns a human readable string indicating the start type.
1538
+ #
1539
+ def self.get_start_type(start_type)
1540
+ case start_type
1541
+ when SERVICE_AUTO_START
1542
+ 'auto start'
1543
+ when SERVICE_BOOT_START
1544
+ 'boot start'
1545
+ when SERVICE_DEMAND_START
1546
+ 'demand start'
1547
+ when SERVICE_DISABLED
1548
+ 'disabled'
1549
+ when SERVICE_SYSTEM_START
1550
+ 'system start'
1551
+ else
1552
+ nil
1553
+ end
1554
+ end
1555
+
1556
+ # Returns an array of human readable strings indicating the controls
1557
+ # that the service accepts.
1558
+ #
1559
+ def self.get_controls_accepted(controls)
1560
+ array = []
1561
+
1562
+ if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
1563
+ array << 'netbind change'
1564
+ end
1565
+
1566
+ if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
1567
+ array << 'param change'
1568
+ end
1569
+
1570
+ if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
1571
+ array << 'pause continue'
1572
+ end
1573
+
1574
+ if controls & SERVICE_ACCEPT_SHUTDOWN > 0
1575
+ array << 'shutdown'
1576
+ end
1577
+
1578
+ if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
1579
+ array << 'pre-shutdown'
1580
+ end
1581
+
1582
+ if controls & SERVICE_ACCEPT_STOP > 0
1583
+ array << 'stop'
1584
+ end
1585
+
1586
+ if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
1587
+ array << 'hardware profile change'
1588
+ end
1589
+
1590
+ if controls & SERVICE_ACCEPT_POWEREVENT > 0
1591
+ array << 'power event'
1592
+ end
1593
+
1594
+ if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
1595
+ array << 'session change'
1596
+ end
1597
+
1598
+ array
1599
+ end
1600
+
1601
+ # Converts a service state numeric constant into a readable string.
1602
+ #
1603
+ def self.get_current_state(state)
1604
+ case state
1605
+ when SERVICE_CONTINUE_PENDING
1606
+ 'continue pending'
1607
+ when SERVICE_PAUSE_PENDING
1608
+ 'pause pending'
1609
+ when SERVICE_PAUSED
1610
+ 'paused'
1611
+ when SERVICE_RUNNING
1612
+ 'running'
1613
+ when SERVICE_START_PENDING
1614
+ 'start pending'
1615
+ when SERVICE_STOP_PENDING
1616
+ 'stop pending'
1617
+ when SERVICE_STOPPED
1618
+ 'stopped'
1619
+ else
1620
+ nil
1621
+ end
1622
+ end
1623
+
1624
+ # Converts a service type numeric constant into a human readable string.
1625
+ #
1626
+ def self.get_service_type(service_type)
1627
+ case service_type
1628
+ when SERVICE_FILE_SYSTEM_DRIVER
1629
+ 'file system driver'
1630
+ when SERVICE_KERNEL_DRIVER
1631
+ 'kernel driver'
1632
+ when SERVICE_WIN32_OWN_PROCESS
1633
+ 'own process'
1634
+ when SERVICE_WIN32_SHARE_PROCESS
1635
+ 'share process'
1636
+ when SERVICE_RECOGNIZER_DRIVER
1637
+ 'recognizer driver'
1638
+ when SERVICE_DRIVER
1639
+ 'driver'
1640
+ when SERVICE_WIN32
1641
+ 'win32'
1642
+ when SERVICE_TYPE_ALL
1643
+ 'all'
1644
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
1645
+ 'own process, interactive'
1646
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
1647
+ 'share process, interactive'
1648
+ else
1649
+ nil
1650
+ end
1651
+ end
1652
+
1653
+ # A shortcut method that simplifies the various service control methods.
1654
+ #
1655
+ def self.send_signal(service, host, service_signal, control_signal)
1656
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
1657
+
1658
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1659
+
1660
+ begin
1661
+ handle_scs = OpenService(handle_scm, service, service_signal)
1662
+
1663
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
1664
+
1665
+ status = SERVICE_STATUS.new
1666
+
1667
+ unless ControlService(handle_scs, control_signal, status)
1668
+ FFI.raise_windows_error('ControlService')
1669
+ end
1670
+ ensure
1671
+ close_service_handle(handle_scs)
1672
+ close_service_handle(handle_scm)
1673
+ end
1674
+
1675
+ status
1676
+ end
1677
+
1678
+ class << self
1679
+ alias create new
1680
+ alias getdisplayname get_display_name
1681
+ alias getservicename get_service_name
1682
+ end
1683
+ end
1684
+ end