win32-service 0.7.2-x86-mingw32

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