win32-srv 1.0.0

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