win32-service 0.5.2 → 0.6.0

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