win32-service 0.7.0-x86-mswin32-60 → 0.7.1-x86-mswin32-60

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