win32-service 0.8.6 → 0.8.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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