win32-service 0.5.2 → 0.6.0

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