win32-service 0.8.6 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1567 +1,1567 @@
1
- require_relative 'windows/helper'
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::Structs
14
- include Windows::Functions
15
-
16
- extend Windows::Structs
17
- extend Windows::Functions
18
-
19
- # The version of the win32-service library
20
- VERSION = '0.8.6'
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[:lpDependencies].read_array_of_null_separated_strings,
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[:lpDependencies].read_array_of_null_separated_strings
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 retreive description for the #{service_name} service."
1089
- description = ''
1090
- end
1091
-
1092
- delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
1093
-
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
- else
1101
- msg = "WARNING: The registry entry for the #{service_name} "
1102
- msg += "service could not be found."
1103
- warn msg
1104
-
1105
- binary_path = nil
1106
- load_order = nil
1107
- start_name = nil
1108
- start_type = nil
1109
- error_ctrl = nil
1110
- tag_id = nil
1111
- deps = nil
1112
- description = nil
1113
- end
1114
-
1115
- buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)
1116
-
1117
- if buf2.is_a?(FFI::MemoryPointer)
1118
- fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)
1119
-
1120
- reset_period = fail_struct[:dwResetPeriod]
1121
- num_actions = fail_struct[:cActions]
1122
-
1123
- if fail_struct[:lpRebootMsg].null?
1124
- reboot_msg = nil
1125
- else
1126
- reboot_msg = fail_struct[:lpRebootMsg].read_string
1127
- end
1128
-
1129
- if fail_struct[:lpCommand].null?
1130
- command = nil
1131
- else
1132
- command = fail_struct[:lpCommand].read_string
1133
- end
1134
-
1135
- actions = nil
1136
-
1137
- if num_actions > 0
1138
- action_ptr = fail_struct[:lpsaActions]
1139
-
1140
- actions = {}
1141
-
1142
- num_actions.times{ |n|
1143
- sc_action = SC_ACTION.new(action_ptr[n])
1144
- delay = sc_action[:Delay]
1145
- action_type = get_action_type(sc_action[:Type])
1146
- actions[n+1] = {:action_type => action_type, :delay => delay}
1147
- }
1148
- end
1149
- else
1150
- reset_period = nil
1151
- reboot_msg = nil
1152
- command = nil
1153
- actions = nil
1154
- end
1155
- ensure
1156
- CloseServiceHandle(handle_scs) if handle_scs > 0
1157
- end
1158
-
1159
- struct = ServiceStruct.new(
1160
- service_name,
1161
- display_name,
1162
- service_type,
1163
- current_state,
1164
- controls,
1165
- win_exit_code,
1166
- ser_exit_code,
1167
- check_point,
1168
- wait_hint,
1169
- binary_path,
1170
- start_type,
1171
- error_ctrl,
1172
- load_order,
1173
- tag_id,
1174
- start_name,
1175
- deps,
1176
- description,
1177
- interactive,
1178
- pid,
1179
- service_flags,
1180
- reset_period,
1181
- reboot_msg,
1182
- command,
1183
- num_actions,
1184
- actions,
1185
- delayed_start
1186
- )
1187
-
1188
- if block_given?
1189
- yield struct
1190
- else
1191
- services_array << struct
1192
- end
1193
-
1194
- service_buf += ENUM_SERVICE_STATUS_PROCESS.size
1195
- }
1196
- ensure
1197
- CloseServiceHandle(handle_scm)
1198
- end
1199
-
1200
- block_given? ? nil : services_array
1201
- end
1202
-
1203
- private
1204
-
1205
- # Configures failure actions for a given service.
1206
- #
1207
- def self.configure_failure_actions(handle_scs, opts)
1208
- if opts['failure_actions']
1209
- token_handle = FFI::MemoryPointer.new(:ulong)
1210
-
1211
- bool = OpenProcessToken(
1212
- GetCurrentProcess(),
1213
- TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1214
- token_handle
1215
- )
1216
-
1217
- unless bool
1218
- error = FFI.errno
1219
- CloseServiceHandle(handle_scs)
1220
- raise SystemCallError.new('OpenProcessToken', error)
1221
- end
1222
-
1223
- token_handle = token_handle.read_ulong
1224
-
1225
- # Get the LUID for shutdown privilege.
1226
- luid = LUID.new
1227
-
1228
- unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
1229
- error = FFI.errno
1230
- CloseServiceHandle(handle_scs)
1231
- raise SystemCallError.new('LookupPrivilegeValue', error)
1232
- end
1233
-
1234
- luid_and_attrs = LUID_AND_ATTRIBUTES.new
1235
- luid_and_attrs[:Luid] = luid
1236
- luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
1237
-
1238
- tkp = TOKEN_PRIVILEGES.new
1239
- tkp[:PrivilegeCount] = 1
1240
- tkp[:Privileges][0] = luid_and_attrs
1241
-
1242
- # Enable shutdown privilege in access token of this process
1243
- bool = AdjustTokenPrivileges(
1244
- token_handle,
1245
- false,
1246
- tkp,
1247
- tkp.size,
1248
- nil,
1249
- nil
1250
- )
1251
-
1252
- unless bool
1253
- error = FFI.errno
1254
- CloseServiceHandle(handle_scs)
1255
- raise SystemCallError.new('AdjustTokenPrivileges', error)
1256
- end
1257
- end
1258
-
1259
- sfa = SERVICE_FAILURE_ACTIONS.new
1260
-
1261
- if opts['failure_reset_period']
1262
- sfa[:dwResetPeriod] = opts['failure_reset_period']
1263
- end
1264
-
1265
- if opts['failure_reboot_message']
1266
- sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
1267
- end
1268
-
1269
- if opts['failure_command']
1270
- sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
1271
- end
1272
-
1273
- if opts['failure_actions']
1274
- action_size = opts['failure_actions'].size
1275
- action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
1276
-
1277
- actions = action_size.times.collect do |i|
1278
- SC_ACTION.new(action_ptr + i * SC_ACTION.size)
1279
- end
1280
-
1281
- opts['failure_actions'].each_with_index{ |action, i|
1282
- actions[i][:Type] = action
1283
- actions[i][:Delay] = opts['failure_delay']
1284
- }
1285
-
1286
- sfa[:cActions] = action_size
1287
- sfa[:lpsaActions] = action_ptr
1288
- end
1289
-
1290
- bool = ChangeServiceConfig2(
1291
- handle_scs,
1292
- SERVICE_CONFIG_FAILURE_ACTIONS,
1293
- sfa
1294
- )
1295
-
1296
- unless bool
1297
- error = FFI.errno
1298
- CloseServiceHandle(handle_scs)
1299
- raise SystemCallError.new('ChangeServiceConfig2', error)
1300
- end
1301
- end
1302
-
1303
- # Returns a human readable string indicating the action type.
1304
- #
1305
- def self.get_action_type(action_type)
1306
- case action_type
1307
- when SC_ACTION_NONE
1308
- 'none'
1309
- when SC_ACTION_REBOOT
1310
- 'reboot'
1311
- when SC_ACTION_RESTART
1312
- 'restart'
1313
- when SC_ACTION_RUN_COMMAND
1314
- 'command'
1315
- else
1316
- 'unknown'
1317
- end
1318
- end
1319
-
1320
- # Shortcut for QueryServiceConfig. Returns the buffer. In rare cases
1321
- # the underlying registry entry may have been deleted, but the service
1322
- # still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned
1323
- # instead.
1324
- #
1325
- def self.get_config_info(handle)
1326
- bytes_needed = FFI::MemoryPointer.new(:ulong)
1327
-
1328
- # First attempt at QueryServiceConfig is to get size needed
1329
- bool = QueryServiceConfig(handle, nil, 0, bytes_needed)
1330
-
1331
- if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
1332
- config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1333
- elsif FFI.errno == ERROR_FILE_NOT_FOUND
1334
- return FFI.errno
1335
- else
1336
- error = FFI.errno
1337
- CloseServiceHandle(handle)
1338
- FFI.raise_windows_error('QueryServiceConfig', error)
1339
- end
1340
-
1341
- bytes_needed.clear
1342
-
1343
- # Second attempt at QueryServiceConfig gets the actual info
1344
- begin
1345
- bool = QueryServiceConfig(
1346
- handle,
1347
- config_buf,
1348
- config_buf.size,
1349
- bytes_needed
1350
- )
1351
-
1352
- FFI.raise_windows_error('QueryServiceConfig') unless bool
1353
- ensure
1354
- CloseServiceHandle(handle) unless bool
1355
- end
1356
-
1357
- QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
1358
- end
1359
-
1360
- # Shortcut for QueryServiceConfig2. Returns the buffer.
1361
- #
1362
- def self.get_config2_info(handle, info_level)
1363
- bytes_needed = FFI::MemoryPointer.new(:ulong)
1364
-
1365
- # First attempt at QueryServiceConfig2 is to get size needed
1366
- bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)
1367
-
1368
- err_num = FFI.errno
1369
-
1370
- # This is a bit hacky since it means we have to check the type of value
1371
- # we get back, but we don't always want to raise an error either,
1372
- # depending on what we're trying to get at.
1373
- #
1374
- if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
1375
- config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1376
- elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
1377
- return err_num
1378
- else
1379
- CloseServiceHandle(handle)
1380
- FFI.raise_windows_error('QueryServiceConfig2', err_num)
1381
- end
1382
-
1383
- bytes_needed.clear
1384
-
1385
- # Second attempt at QueryServiceConfig2 gets the actual info
1386
- begin
1387
- bool = QueryServiceConfig2(
1388
- handle,
1389
- info_level,
1390
- config2_buf,
1391
- config2_buf.size,
1392
- bytes_needed
1393
- )
1394
-
1395
- FFI.raise_windows_error('QueryServiceConfig2') unless bool
1396
- ensure
1397
- CloseServiceHandle(handle) unless bool
1398
- end
1399
-
1400
- config2_buf
1401
- end
1402
-
1403
- # Returns a human readable string indicating the error control
1404
- #
1405
- def self.get_error_control(error_control)
1406
- case error_control
1407
- when SERVICE_ERROR_CRITICAL
1408
- 'critical'
1409
- when SERVICE_ERROR_IGNORE
1410
- 'ignore'
1411
- when SERVICE_ERROR_NORMAL
1412
- 'normal'
1413
- when SERVICE_ERROR_SEVERE
1414
- 'severe'
1415
- else
1416
- nil
1417
- end
1418
- end
1419
-
1420
- # Returns a human readable string indicating the start type.
1421
- #
1422
- def self.get_start_type(start_type)
1423
- case start_type
1424
- when SERVICE_AUTO_START
1425
- 'auto start'
1426
- when SERVICE_BOOT_START
1427
- 'boot start'
1428
- when SERVICE_DEMAND_START
1429
- 'demand start'
1430
- when SERVICE_DISABLED
1431
- 'disabled'
1432
- when SERVICE_SYSTEM_START
1433
- 'system start'
1434
- else
1435
- nil
1436
- end
1437
- end
1438
-
1439
- # Returns an array of human readable strings indicating the controls
1440
- # that the service accepts.
1441
- #
1442
- def self.get_controls_accepted(controls)
1443
- array = []
1444
-
1445
- if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
1446
- array << 'netbind change'
1447
- end
1448
-
1449
- if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
1450
- array << 'param change'
1451
- end
1452
-
1453
- if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
1454
- array << 'pause continue'
1455
- end
1456
-
1457
- if controls & SERVICE_ACCEPT_SHUTDOWN > 0
1458
- array << 'shutdown'
1459
- end
1460
-
1461
- if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
1462
- array << 'pre-shutdown'
1463
- end
1464
-
1465
- if controls & SERVICE_ACCEPT_STOP > 0
1466
- array << 'stop'
1467
- end
1468
-
1469
- if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
1470
- array << 'hardware profile change'
1471
- end
1472
-
1473
- if controls & SERVICE_ACCEPT_POWEREVENT > 0
1474
- array << 'power event'
1475
- end
1476
-
1477
- if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
1478
- array << 'session change'
1479
- end
1480
-
1481
- array
1482
- end
1483
-
1484
- # Converts a service state numeric constant into a readable string.
1485
- #
1486
- def self.get_current_state(state)
1487
- case state
1488
- when SERVICE_CONTINUE_PENDING
1489
- 'continue pending'
1490
- when SERVICE_PAUSE_PENDING
1491
- 'pause pending'
1492
- when SERVICE_PAUSED
1493
- 'paused'
1494
- when SERVICE_RUNNING
1495
- 'running'
1496
- when SERVICE_START_PENDING
1497
- 'start pending'
1498
- when SERVICE_STOP_PENDING
1499
- 'stop pending'
1500
- when SERVICE_STOPPED
1501
- 'stopped'
1502
- else
1503
- nil
1504
- end
1505
- end
1506
-
1507
- # Converts a service type numeric constant into a human readable string.
1508
- #
1509
- def self.get_service_type(service_type)
1510
- case service_type
1511
- when SERVICE_FILE_SYSTEM_DRIVER
1512
- 'file system driver'
1513
- when SERVICE_KERNEL_DRIVER
1514
- 'kernel driver'
1515
- when SERVICE_WIN32_OWN_PROCESS
1516
- 'own process'
1517
- when SERVICE_WIN32_SHARE_PROCESS
1518
- 'share process'
1519
- when SERVICE_RECOGNIZER_DRIVER
1520
- 'recognizer driver'
1521
- when SERVICE_DRIVER
1522
- 'driver'
1523
- when SERVICE_WIN32
1524
- 'win32'
1525
- when SERVICE_TYPE_ALL
1526
- 'all'
1527
- when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
1528
- 'own process, interactive'
1529
- when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
1530
- 'share process, interactive'
1531
- else
1532
- nil
1533
- end
1534
- end
1535
-
1536
- # A shortcut method that simplifies the various service control methods.
1537
- #
1538
- def self.send_signal(service, host, service_signal, control_signal)
1539
- handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
1540
-
1541
- FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1542
-
1543
- begin
1544
- handle_scs = OpenService(handle_scm, service, service_signal)
1545
-
1546
- FFI.raise_windows_error('OpenService') if handle_scs == 0
1547
-
1548
- status = SERVICE_STATUS.new
1549
-
1550
- unless ControlService(handle_scs, control_signal, status)
1551
- FFI.raise_windows_error('ControlService')
1552
- end
1553
- ensure
1554
- CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
1555
- CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
1556
- end
1557
-
1558
- status
1559
- end
1560
-
1561
- class << self
1562
- alias create new
1563
- alias getdisplayname get_display_name
1564
- alias getservicename get_service_name
1565
- end
1566
- end
1567
- end
1
+ require_relative 'windows/helper'
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::Structs
14
+ include Windows::Functions
15
+
16
+ extend Windows::Structs
17
+ extend Windows::Functions
18
+
19
+ # The version of the win32-service library
20
+ VERSION = '0.8.7'
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[:lpDependencies].read_array_of_null_separated_strings,
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[:lpDependencies].read_array_of_null_separated_strings
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 retreive description for the #{service_name} service."
1089
+ description = ''
1090
+ end
1091
+
1092
+ delayed_start_buf = get_config2_info(handle_scs, SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
1093
+
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
+ else
1101
+ msg = "WARNING: The registry entry for the #{service_name} "
1102
+ msg += "service could not be found."
1103
+ warn msg
1104
+
1105
+ binary_path = nil
1106
+ load_order = nil
1107
+ start_name = nil
1108
+ start_type = nil
1109
+ error_ctrl = nil
1110
+ tag_id = nil
1111
+ deps = nil
1112
+ description = nil
1113
+ end
1114
+
1115
+ buf2 = get_config2_info(handle_scs, SERVICE_CONFIG_FAILURE_ACTIONS)
1116
+
1117
+ if buf2.is_a?(FFI::MemoryPointer)
1118
+ fail_struct = SERVICE_FAILURE_ACTIONS.new(buf2)
1119
+
1120
+ reset_period = fail_struct[:dwResetPeriod]
1121
+ num_actions = fail_struct[:cActions]
1122
+
1123
+ if fail_struct[:lpRebootMsg].null?
1124
+ reboot_msg = nil
1125
+ else
1126
+ reboot_msg = fail_struct[:lpRebootMsg].read_string
1127
+ end
1128
+
1129
+ if fail_struct[:lpCommand].null?
1130
+ command = nil
1131
+ else
1132
+ command = fail_struct[:lpCommand].read_string
1133
+ end
1134
+
1135
+ actions = nil
1136
+
1137
+ if num_actions > 0
1138
+ action_ptr = fail_struct[:lpsaActions]
1139
+
1140
+ actions = {}
1141
+
1142
+ num_actions.times{ |n|
1143
+ sc_action = SC_ACTION.new(action_ptr[n])
1144
+ delay = sc_action[:Delay]
1145
+ action_type = get_action_type(sc_action[:Type])
1146
+ actions[n+1] = {:action_type => action_type, :delay => delay}
1147
+ }
1148
+ end
1149
+ else
1150
+ reset_period = nil
1151
+ reboot_msg = nil
1152
+ command = nil
1153
+ actions = nil
1154
+ end
1155
+ ensure
1156
+ CloseServiceHandle(handle_scs) if handle_scs > 0
1157
+ end
1158
+
1159
+ struct = ServiceStruct.new(
1160
+ service_name,
1161
+ display_name,
1162
+ service_type,
1163
+ current_state,
1164
+ controls,
1165
+ win_exit_code,
1166
+ ser_exit_code,
1167
+ check_point,
1168
+ wait_hint,
1169
+ binary_path,
1170
+ start_type,
1171
+ error_ctrl,
1172
+ load_order,
1173
+ tag_id,
1174
+ start_name,
1175
+ deps,
1176
+ description,
1177
+ interactive,
1178
+ pid,
1179
+ service_flags,
1180
+ reset_period,
1181
+ reboot_msg,
1182
+ command,
1183
+ num_actions,
1184
+ actions,
1185
+ delayed_start
1186
+ )
1187
+
1188
+ if block_given?
1189
+ yield struct
1190
+ else
1191
+ services_array << struct
1192
+ end
1193
+
1194
+ service_buf += ENUM_SERVICE_STATUS_PROCESS.size
1195
+ }
1196
+ ensure
1197
+ CloseServiceHandle(handle_scm)
1198
+ end
1199
+
1200
+ block_given? ? nil : services_array
1201
+ end
1202
+
1203
+ private
1204
+
1205
+ # Configures failure actions for a given service.
1206
+ #
1207
+ def self.configure_failure_actions(handle_scs, opts)
1208
+ if opts['failure_actions']
1209
+ token_handle = FFI::MemoryPointer.new(:ulong)
1210
+
1211
+ bool = OpenProcessToken(
1212
+ GetCurrentProcess(),
1213
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
1214
+ token_handle
1215
+ )
1216
+
1217
+ unless bool
1218
+ error = FFI.errno
1219
+ CloseServiceHandle(handle_scs)
1220
+ raise SystemCallError.new('OpenProcessToken', error)
1221
+ end
1222
+
1223
+ token_handle = token_handle.read_ulong
1224
+
1225
+ # Get the LUID for shutdown privilege.
1226
+ luid = LUID.new
1227
+
1228
+ unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
1229
+ error = FFI.errno
1230
+ CloseServiceHandle(handle_scs)
1231
+ raise SystemCallError.new('LookupPrivilegeValue', error)
1232
+ end
1233
+
1234
+ luid_and_attrs = LUID_AND_ATTRIBUTES.new
1235
+ luid_and_attrs[:Luid] = luid
1236
+ luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
1237
+
1238
+ tkp = TOKEN_PRIVILEGES.new
1239
+ tkp[:PrivilegeCount] = 1
1240
+ tkp[:Privileges][0] = luid_and_attrs
1241
+
1242
+ # Enable shutdown privilege in access token of this process
1243
+ bool = AdjustTokenPrivileges(
1244
+ token_handle,
1245
+ false,
1246
+ tkp,
1247
+ tkp.size,
1248
+ nil,
1249
+ nil
1250
+ )
1251
+
1252
+ unless bool
1253
+ error = FFI.errno
1254
+ CloseServiceHandle(handle_scs)
1255
+ raise SystemCallError.new('AdjustTokenPrivileges', error)
1256
+ end
1257
+ end
1258
+
1259
+ sfa = SERVICE_FAILURE_ACTIONS.new
1260
+
1261
+ if opts['failure_reset_period']
1262
+ sfa[:dwResetPeriod] = opts['failure_reset_period']
1263
+ end
1264
+
1265
+ if opts['failure_reboot_message']
1266
+ sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
1267
+ end
1268
+
1269
+ if opts['failure_command']
1270
+ sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
1271
+ end
1272
+
1273
+ if opts['failure_actions']
1274
+ action_size = opts['failure_actions'].size
1275
+ action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
1276
+
1277
+ actions = action_size.times.collect do |i|
1278
+ SC_ACTION.new(action_ptr + i * SC_ACTION.size)
1279
+ end
1280
+
1281
+ opts['failure_actions'].each_with_index{ |action, i|
1282
+ actions[i][:Type] = action
1283
+ actions[i][:Delay] = opts['failure_delay']
1284
+ }
1285
+
1286
+ sfa[:cActions] = action_size
1287
+ sfa[:lpsaActions] = action_ptr
1288
+ end
1289
+
1290
+ bool = ChangeServiceConfig2(
1291
+ handle_scs,
1292
+ SERVICE_CONFIG_FAILURE_ACTIONS,
1293
+ sfa
1294
+ )
1295
+
1296
+ unless bool
1297
+ error = FFI.errno
1298
+ CloseServiceHandle(handle_scs)
1299
+ raise SystemCallError.new('ChangeServiceConfig2', error)
1300
+ end
1301
+ end
1302
+
1303
+ # Returns a human readable string indicating the action type.
1304
+ #
1305
+ def self.get_action_type(action_type)
1306
+ case action_type
1307
+ when SC_ACTION_NONE
1308
+ 'none'
1309
+ when SC_ACTION_REBOOT
1310
+ 'reboot'
1311
+ when SC_ACTION_RESTART
1312
+ 'restart'
1313
+ when SC_ACTION_RUN_COMMAND
1314
+ 'command'
1315
+ else
1316
+ 'unknown'
1317
+ end
1318
+ end
1319
+
1320
+ # Shortcut for QueryServiceConfig. Returns the buffer. In rare cases
1321
+ # the underlying registry entry may have been deleted, but the service
1322
+ # still exists. In that case, the ERROR_FILE_NOT_FOUND value is returned
1323
+ # instead.
1324
+ #
1325
+ def self.get_config_info(handle)
1326
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1327
+
1328
+ # First attempt at QueryServiceConfig is to get size needed
1329
+ bool = QueryServiceConfig(handle, nil, 0, bytes_needed)
1330
+
1331
+ if !bool && FFI.errno == ERROR_INSUFFICIENT_BUFFER
1332
+ config_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1333
+ elsif FFI.errno == ERROR_FILE_NOT_FOUND
1334
+ return FFI.errno
1335
+ else
1336
+ error = FFI.errno
1337
+ CloseServiceHandle(handle)
1338
+ FFI.raise_windows_error('QueryServiceConfig', error)
1339
+ end
1340
+
1341
+ bytes_needed.clear
1342
+
1343
+ # Second attempt at QueryServiceConfig gets the actual info
1344
+ begin
1345
+ bool = QueryServiceConfig(
1346
+ handle,
1347
+ config_buf,
1348
+ config_buf.size,
1349
+ bytes_needed
1350
+ )
1351
+
1352
+ FFI.raise_windows_error('QueryServiceConfig') unless bool
1353
+ ensure
1354
+ CloseServiceHandle(handle) unless bool
1355
+ end
1356
+
1357
+ QUERY_SERVICE_CONFIG.new(config_buf) # cast the buffer
1358
+ end
1359
+
1360
+ # Shortcut for QueryServiceConfig2. Returns the buffer.
1361
+ #
1362
+ def self.get_config2_info(handle, info_level)
1363
+ bytes_needed = FFI::MemoryPointer.new(:ulong)
1364
+
1365
+ # First attempt at QueryServiceConfig2 is to get size needed
1366
+ bool = QueryServiceConfig2(handle, info_level, nil, 0, bytes_needed)
1367
+
1368
+ err_num = FFI.errno
1369
+
1370
+ # This is a bit hacky since it means we have to check the type of value
1371
+ # we get back, but we don't always want to raise an error either,
1372
+ # depending on what we're trying to get at.
1373
+ #
1374
+ if !bool && err_num == ERROR_INSUFFICIENT_BUFFER
1375
+ config2_buf = FFI::MemoryPointer.new(:char, bytes_needed.read_ulong)
1376
+ elsif [ERROR_FILE_NOT_FOUND, ERROR_RESOURCE_TYPE_NOT_FOUND, ERROR_RESOURCE_NAME_NOT_FOUND].include?(err_num)
1377
+ return err_num
1378
+ else
1379
+ CloseServiceHandle(handle)
1380
+ FFI.raise_windows_error('QueryServiceConfig2', err_num)
1381
+ end
1382
+
1383
+ bytes_needed.clear
1384
+
1385
+ # Second attempt at QueryServiceConfig2 gets the actual info
1386
+ begin
1387
+ bool = QueryServiceConfig2(
1388
+ handle,
1389
+ info_level,
1390
+ config2_buf,
1391
+ config2_buf.size,
1392
+ bytes_needed
1393
+ )
1394
+
1395
+ FFI.raise_windows_error('QueryServiceConfig2') unless bool
1396
+ ensure
1397
+ CloseServiceHandle(handle) unless bool
1398
+ end
1399
+
1400
+ config2_buf
1401
+ end
1402
+
1403
+ # Returns a human readable string indicating the error control
1404
+ #
1405
+ def self.get_error_control(error_control)
1406
+ case error_control
1407
+ when SERVICE_ERROR_CRITICAL
1408
+ 'critical'
1409
+ when SERVICE_ERROR_IGNORE
1410
+ 'ignore'
1411
+ when SERVICE_ERROR_NORMAL
1412
+ 'normal'
1413
+ when SERVICE_ERROR_SEVERE
1414
+ 'severe'
1415
+ else
1416
+ nil
1417
+ end
1418
+ end
1419
+
1420
+ # Returns a human readable string indicating the start type.
1421
+ #
1422
+ def self.get_start_type(start_type)
1423
+ case start_type
1424
+ when SERVICE_AUTO_START
1425
+ 'auto start'
1426
+ when SERVICE_BOOT_START
1427
+ 'boot start'
1428
+ when SERVICE_DEMAND_START
1429
+ 'demand start'
1430
+ when SERVICE_DISABLED
1431
+ 'disabled'
1432
+ when SERVICE_SYSTEM_START
1433
+ 'system start'
1434
+ else
1435
+ nil
1436
+ end
1437
+ end
1438
+
1439
+ # Returns an array of human readable strings indicating the controls
1440
+ # that the service accepts.
1441
+ #
1442
+ def self.get_controls_accepted(controls)
1443
+ array = []
1444
+
1445
+ if controls & SERVICE_ACCEPT_NETBINDCHANGE > 0
1446
+ array << 'netbind change'
1447
+ end
1448
+
1449
+ if controls & SERVICE_ACCEPT_PARAMCHANGE > 0
1450
+ array << 'param change'
1451
+ end
1452
+
1453
+ if controls & SERVICE_ACCEPT_PAUSE_CONTINUE > 0
1454
+ array << 'pause continue'
1455
+ end
1456
+
1457
+ if controls & SERVICE_ACCEPT_SHUTDOWN > 0
1458
+ array << 'shutdown'
1459
+ end
1460
+
1461
+ if controls & SERVICE_ACCEPT_PRESHUTDOWN > 0
1462
+ array << 'pre-shutdown'
1463
+ end
1464
+
1465
+ if controls & SERVICE_ACCEPT_STOP > 0
1466
+ array << 'stop'
1467
+ end
1468
+
1469
+ if controls & SERVICE_ACCEPT_HARDWAREPROFILECHANGE > 0
1470
+ array << 'hardware profile change'
1471
+ end
1472
+
1473
+ if controls & SERVICE_ACCEPT_POWEREVENT > 0
1474
+ array << 'power event'
1475
+ end
1476
+
1477
+ if controls & SERVICE_ACCEPT_SESSIONCHANGE > 0
1478
+ array << 'session change'
1479
+ end
1480
+
1481
+ array
1482
+ end
1483
+
1484
+ # Converts a service state numeric constant into a readable string.
1485
+ #
1486
+ def self.get_current_state(state)
1487
+ case state
1488
+ when SERVICE_CONTINUE_PENDING
1489
+ 'continue pending'
1490
+ when SERVICE_PAUSE_PENDING
1491
+ 'pause pending'
1492
+ when SERVICE_PAUSED
1493
+ 'paused'
1494
+ when SERVICE_RUNNING
1495
+ 'running'
1496
+ when SERVICE_START_PENDING
1497
+ 'start pending'
1498
+ when SERVICE_STOP_PENDING
1499
+ 'stop pending'
1500
+ when SERVICE_STOPPED
1501
+ 'stopped'
1502
+ else
1503
+ nil
1504
+ end
1505
+ end
1506
+
1507
+ # Converts a service type numeric constant into a human readable string.
1508
+ #
1509
+ def self.get_service_type(service_type)
1510
+ case service_type
1511
+ when SERVICE_FILE_SYSTEM_DRIVER
1512
+ 'file system driver'
1513
+ when SERVICE_KERNEL_DRIVER
1514
+ 'kernel driver'
1515
+ when SERVICE_WIN32_OWN_PROCESS
1516
+ 'own process'
1517
+ when SERVICE_WIN32_SHARE_PROCESS
1518
+ 'share process'
1519
+ when SERVICE_RECOGNIZER_DRIVER
1520
+ 'recognizer driver'
1521
+ when SERVICE_DRIVER
1522
+ 'driver'
1523
+ when SERVICE_WIN32
1524
+ 'win32'
1525
+ when SERVICE_TYPE_ALL
1526
+ 'all'
1527
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
1528
+ 'own process, interactive'
1529
+ when SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
1530
+ 'share process, interactive'
1531
+ else
1532
+ nil
1533
+ end
1534
+ end
1535
+
1536
+ # A shortcut method that simplifies the various service control methods.
1537
+ #
1538
+ def self.send_signal(service, host, service_signal, control_signal)
1539
+ handle_scm = OpenSCManager(host, nil, SC_MANAGER_CONNECT)
1540
+
1541
+ FFI.raise_windows_error('OpenSCManager') if handle_scm == 0
1542
+
1543
+ begin
1544
+ handle_scs = OpenService(handle_scm, service, service_signal)
1545
+
1546
+ FFI.raise_windows_error('OpenService') if handle_scs == 0
1547
+
1548
+ status = SERVICE_STATUS.new
1549
+
1550
+ unless ControlService(handle_scs, control_signal, status)
1551
+ FFI.raise_windows_error('ControlService')
1552
+ end
1553
+ ensure
1554
+ CloseServiceHandle(handle_scs) if handle_scs && handle_scs > 0
1555
+ CloseServiceHandle(handle_scm) if handle_scm && handle_scm > 0
1556
+ end
1557
+
1558
+ status
1559
+ end
1560
+
1561
+ class << self
1562
+ alias create new
1563
+ alias getdisplayname get_display_name
1564
+ alias getservicename get_service_name
1565
+ end
1566
+ end
1567
+ end