win32-service 0.6.1-x86-mswin32-60

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