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.
- checksums.yaml +7 -0
- data/lib/win32/daemon.rb +366 -0
- data/lib/win32/service.rb +1666 -0
- data/lib/win32/windows/constants.rb +143 -0
- data/lib/win32/windows/functions.rb +75 -0
- data/lib/win32/windows/structs.rb +121 -0
- data/lib/win32/windows/version.rb +6 -0
- data/lib/win32-daemon.rb +1 -0
- data/lib/win32-service.rb +1 -0
- metadata +151 -0
@@ -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
|