win32-service 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +11 -1
- data/README +75 -75
- data/Rakefile +6 -1
- data/examples/demo_daemon.rb +5 -5
- data/examples/demo_services.rb +30 -30
- data/lib/win32/daemon.rb +2 -1
- data/lib/win32/service.rb +36 -52
- data/lib/win32/windows/constants.rb +4 -0
- data/lib/win32/windows/functions.rb +40 -28
- data/lib/win32/windows/structs.rb +21 -0
- data/test/test_win32_daemon.rb +1 -1
- data/test/test_win32_service.rb +17 -2
- data/test/test_win32_service_create.rb +129 -129
- data/win32-service.gemspec +2 -1
- metadata +32 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a9f9dd48b778b4dbe8d17581a339f8a6a15069e
|
4
|
+
data.tar.gz: cd6fff85777b0440d645f9714462a6ab6e0fd2b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 709dedda4dee8f0b78985723b48ed5a059c183337c16ea2fd8145b4a8eedf4fb28561e8f2bc0d0317e1ac78b2b9cbc4b72ca0cdf6897dfa7cd045195b84e3868
|
7
|
+
data.tar.gz: c3024db96b9db1493eca72ecfe5f02730cacdf0c22c29482ee5bed062edb667e34e556d2694c541c4e98a8776d6c284da1ff3d47267a6dd9ab8ea78bd7c306d6
|
data/CHANGES
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
== 0.8.
|
1
|
+
== 0.8.1 - 2-Aug-2013
|
2
|
+
* Fixed the failure actions code for the Service#configure method
|
3
|
+
so that it is also using FFI.
|
4
|
+
* An internal fix for the Daemon class was added. In short, you no longer
|
5
|
+
need to, nor should you, call exit! in a service_stop method. Thanks go
|
6
|
+
to Adrian Candaten for pointing out the issue in general.
|
7
|
+
* Made FFI functions private.
|
8
|
+
* Added Rake as a development dependency.
|
9
|
+
* Removed an internal helper function that was no longer being used.
|
10
|
+
|
11
|
+
== 0.8.0 - 19-Jun-2013
|
2
12
|
* Converted all code to use FFI. This includes the Daemon code, which means
|
3
13
|
that it also now works with JRuby.
|
4
14
|
|
data/README
CHANGED
@@ -1,75 +1,75 @@
|
|
1
|
-
== Description
|
2
|
-
The win32-service library allows you to control or create MS Windows services.
|
3
|
-
|
4
|
-
== Installation
|
5
|
-
gem install win32-service
|
6
|
-
|
7
|
-
== Synopsis
|
8
|
-
require 'win32/service'
|
9
|
-
|
10
|
-
# Iterate over the available services
|
11
|
-
Service.services do |service|
|
12
|
-
p service
|
13
|
-
end
|
14
|
-
|
15
|
-
== More Documentation
|
16
|
-
Please see the documentation in the 'doc' directory, or the gem documentation
|
17
|
-
that was installed when you installed this library as a gem.
|
18
|
-
|
19
|
-
== Known Issues
|
20
|
-
=== Problem:
|
21
|
-
Service.delete causes "Unable to delete: The specified service has been
|
22
|
-
marked for deletion."
|
23
|
-
|
24
|
-
=== Troubleshooting:
|
25
|
-
This can be caused by one of two things. Either you attempted to delete a
|
26
|
-
running service without stopping it first, or you have the Services
|
27
|
-
Administrative Tool (GUI) open. In the former case, the solution is to first
|
28
|
-
stop the service if it's running. In the latter, close the Services GUI
|
29
|
-
admin tool before deleting.
|
30
|
-
|
31
|
-
=== Problem:
|
32
|
-
Service.start causes, "The service did not respond to the start or control
|
33
|
-
request in a timely fashion."
|
34
|
-
|
35
|
-
=== Troubleshooting:
|
36
|
-
The best way to debug your services is to wrap your entire Daemon subclass
|
37
|
-
in a begin/end block and send error messages to a file. That should give a
|
38
|
-
good clue as to the nature of the problem. The most probable culprits are:
|
39
|
-
|
40
|
-
* You've tried to require a library that's not in your $LOAD_PATH. Make sure
|
41
|
-
that your require statements are inside the begin/rescue block so that you can
|
42
|
-
easily find those mistakes.
|
43
|
-
|
44
|
-
* Your have a bad binary path name. Be sure to use an absolute path name for
|
45
|
-
the binary path name, including the full path to the Ruby interpreter, e.g.
|
46
|
-
'c:\ruby\bin\ruby' instead of just 'ruby'.
|
47
|
-
|
48
|
-
* You've got a syntax error in your code somewhere.
|
49
|
-
|
50
|
-
== See Also
|
51
|
-
ruby-wmi
|
52
|
-
|
53
|
-
== Future Plans
|
54
|
-
Add service_session_change hook
|
55
|
-
|
56
|
-
== Copyright
|
57
|
-
(C) 2003-2013, Daniel J. Berger, All Rights Reserved
|
58
|
-
|
59
|
-
== License
|
60
|
-
Artistic 2.0
|
61
|
-
|
62
|
-
== Contributions
|
63
|
-
Although this library is free, please consider having your company
|
64
|
-
setup a gittip if used by your company professionally.
|
65
|
-
|
66
|
-
http://www.gittip.com/djberg96/
|
67
|
-
|
68
|
-
== Warranty
|
69
|
-
This package is provided "as is" and without any express or
|
70
|
-
implied warranties, including, without limitation, the implied
|
71
|
-
warranties of merchantability and fitness for a particular purpose.
|
72
|
-
|
73
|
-
== Authors
|
74
|
-
Daniel J. Berger
|
75
|
-
Park Heesob
|
1
|
+
== Description
|
2
|
+
The win32-service library allows you to control or create MS Windows services.
|
3
|
+
|
4
|
+
== Installation
|
5
|
+
gem install win32-service
|
6
|
+
|
7
|
+
== Synopsis
|
8
|
+
require 'win32/service'
|
9
|
+
|
10
|
+
# Iterate over the available services
|
11
|
+
Service.services do |service|
|
12
|
+
p service
|
13
|
+
end
|
14
|
+
|
15
|
+
== More Documentation
|
16
|
+
Please see the documentation in the 'doc' directory, or the gem documentation
|
17
|
+
that was installed when you installed this library as a gem.
|
18
|
+
|
19
|
+
== Known Issues
|
20
|
+
=== Problem:
|
21
|
+
Service.delete causes "Unable to delete: The specified service has been
|
22
|
+
marked for deletion."
|
23
|
+
|
24
|
+
=== Troubleshooting:
|
25
|
+
This can be caused by one of two things. Either you attempted to delete a
|
26
|
+
running service without stopping it first, or you have the Services
|
27
|
+
Administrative Tool (GUI) open. In the former case, the solution is to first
|
28
|
+
stop the service if it's running. In the latter, close the Services GUI
|
29
|
+
admin tool before deleting.
|
30
|
+
|
31
|
+
=== Problem:
|
32
|
+
Service.start causes, "The service did not respond to the start or control
|
33
|
+
request in a timely fashion."
|
34
|
+
|
35
|
+
=== Troubleshooting:
|
36
|
+
The best way to debug your services is to wrap your entire Daemon subclass
|
37
|
+
in a begin/end block and send error messages to a file. That should give a
|
38
|
+
good clue as to the nature of the problem. The most probable culprits are:
|
39
|
+
|
40
|
+
* You've tried to require a library that's not in your $LOAD_PATH. Make sure
|
41
|
+
that your require statements are inside the begin/rescue block so that you can
|
42
|
+
easily find those mistakes.
|
43
|
+
|
44
|
+
* Your have a bad binary path name. Be sure to use an absolute path name for
|
45
|
+
the binary path name, including the full path to the Ruby interpreter, e.g.
|
46
|
+
'c:\ruby\bin\ruby' instead of just 'ruby'.
|
47
|
+
|
48
|
+
* You've got a syntax error in your code somewhere.
|
49
|
+
|
50
|
+
== See Also
|
51
|
+
ruby-wmi
|
52
|
+
|
53
|
+
== Future Plans
|
54
|
+
Add service_session_change hook
|
55
|
+
|
56
|
+
== Copyright
|
57
|
+
(C) 2003-2013, Daniel J. Berger, All Rights Reserved
|
58
|
+
|
59
|
+
== License
|
60
|
+
Artistic 2.0
|
61
|
+
|
62
|
+
== Contributions
|
63
|
+
Although this library is free, please consider having your company
|
64
|
+
setup a gittip if used by your company professionally.
|
65
|
+
|
66
|
+
http://www.gittip.com/djberg96/
|
67
|
+
|
68
|
+
== Warranty
|
69
|
+
This package is provided "as is" and without any express or
|
70
|
+
implied warranties, including, without limitation, the implied
|
71
|
+
warranties of merchantability and fitness for a particular purpose.
|
72
|
+
|
73
|
+
== Authors
|
74
|
+
Daniel J. Berger
|
75
|
+
Park Heesob
|
data/Rakefile
CHANGED
@@ -13,7 +13,12 @@ namespace 'gem' do
|
|
13
13
|
desc "Create the win32-service gem"
|
14
14
|
task :create => [:clean] do
|
15
15
|
spec = eval(IO.read('win32-service.gemspec'))
|
16
|
-
Gem::
|
16
|
+
if Gem::VERSION.to_f < 2.0
|
17
|
+
Gem::Builder.new(spec).build
|
18
|
+
else
|
19
|
+
require 'rubygems/package'
|
20
|
+
Gem::Package.build(spec)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
desc "Install the win32-service gem"
|
data/examples/demo_daemon.rb
CHANGED
@@ -58,15 +58,15 @@ begin
|
|
58
58
|
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
59
59
|
end
|
60
60
|
|
61
|
-
# This event triggers when the service receives a signal to stop.
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
61
|
+
# This event triggers when the service receives a signal to stop.
|
62
|
+
#
|
63
|
+
# NOTE: Older versions of this code used an explicit exit! call
|
64
|
+
# to force the Ruby interpreter to exit. Don't do that. It is no
|
65
|
+
# longer required and, in fact, may cause issues.
|
65
66
|
#
|
66
67
|
def service_stop
|
67
68
|
msg = 'Received stop signal at: ' + Time.now.to_s
|
68
69
|
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
69
|
-
exit!
|
70
70
|
end
|
71
71
|
|
72
72
|
# This event triggers when the service receives a signal to pause.
|
data/examples/demo_services.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
|
-
#######################################################################
|
2
|
-
# demo_services.rb
|
3
|
-
#
|
4
|
-
# Test script for general futzing that shows off the basic
|
5
|
-
# capabilities of this library. Modify as you see fit.
|
6
|
-
#
|
7
|
-
# You can run this sample program via the "example:services" task.
|
8
|
-
#######################################################################
|
9
|
-
require 'win32/service'
|
10
|
-
include Win32
|
11
|
-
|
12
|
-
puts "VERSION: " + Service::VERSION
|
13
|
-
|
14
|
-
p Service.exists?('Schedule')
|
15
|
-
p Service.exists?('bogusxxx')
|
16
|
-
|
17
|
-
status = Service.status('Schedule')
|
18
|
-
p status
|
19
|
-
|
20
|
-
info = Service.config_info('Schedule')
|
21
|
-
|
22
|
-
print "\n\nShowing config info for Schedule service\n\n"
|
23
|
-
p info
|
24
|
-
|
25
|
-
print "\n\nAbout to show all services\n\n"
|
26
|
-
sleep 10
|
27
|
-
|
28
|
-
Service.services{ |struct|
|
29
|
-
p struct
|
30
|
-
}
|
1
|
+
#######################################################################
|
2
|
+
# demo_services.rb
|
3
|
+
#
|
4
|
+
# Test script for general futzing that shows off the basic
|
5
|
+
# capabilities of this library. Modify as you see fit.
|
6
|
+
#
|
7
|
+
# You can run this sample program via the "example:services" task.
|
8
|
+
#######################################################################
|
9
|
+
require 'win32/service'
|
10
|
+
include Win32
|
11
|
+
|
12
|
+
puts "VERSION: " + Service::VERSION
|
13
|
+
|
14
|
+
p Service.exists?('Schedule')
|
15
|
+
p Service.exists?('bogusxxx')
|
16
|
+
|
17
|
+
status = Service.status('Schedule')
|
18
|
+
p status
|
19
|
+
|
20
|
+
info = Service.config_info('Schedule')
|
21
|
+
|
22
|
+
print "\n\nShowing config info for Schedule service\n\n"
|
23
|
+
p info
|
24
|
+
|
25
|
+
print "\n\nAbout to show all services\n\n"
|
26
|
+
sleep 10
|
27
|
+
|
28
|
+
Service.services{ |struct|
|
29
|
+
p struct
|
30
|
+
}
|
data/lib/win32/daemon.rb
CHANGED
@@ -18,7 +18,7 @@ module Win32
|
|
18
18
|
extend Windows::Functions
|
19
19
|
|
20
20
|
# The version of this library
|
21
|
-
VERSION = '0.8.
|
21
|
+
VERSION = '0.8.1'
|
22
22
|
|
23
23
|
private
|
24
24
|
|
@@ -294,6 +294,7 @@ module Win32
|
|
294
294
|
end
|
295
295
|
|
296
296
|
service_stop() if respond_to?('service_stop')
|
297
|
+
SetEvent(@@hStopCompletedEvent)
|
297
298
|
end
|
298
299
|
|
299
300
|
if respond_to?('service_main')
|
data/lib/win32/service.rb
CHANGED
@@ -17,7 +17,7 @@ module Win32
|
|
17
17
|
extend Windows::Functions
|
18
18
|
|
19
19
|
# The version of the win32-service library
|
20
|
-
VERSION = '0.8.
|
20
|
+
VERSION = '0.8.1'
|
21
21
|
|
22
22
|
# SCM security and access rights
|
23
23
|
|
@@ -1180,7 +1180,7 @@ module Win32
|
|
1180
1180
|
#
|
1181
1181
|
def self.configure_failure_actions(handle_scs, opts)
|
1182
1182
|
if opts['failure_actions']
|
1183
|
-
token_handle =
|
1183
|
+
token_handle = FFI::MemoryPointer.new(:ulong)
|
1184
1184
|
|
1185
1185
|
bool = OpenProcessToken(
|
1186
1186
|
GetCurrentProcess(),
|
@@ -1189,28 +1189,34 @@ module Win32
|
|
1189
1189
|
)
|
1190
1190
|
|
1191
1191
|
unless bool
|
1192
|
-
error =
|
1192
|
+
error = FFI.errno
|
1193
1193
|
CloseServiceHandle(handle_scs)
|
1194
|
-
raise
|
1194
|
+
raise SystemCallError.new('OpenProcessToken', error)
|
1195
1195
|
end
|
1196
1196
|
|
1197
|
-
token_handle = token_handle.
|
1197
|
+
token_handle = token_handle.read_ulong
|
1198
1198
|
|
1199
1199
|
# Get the LUID for shutdown privilege.
|
1200
|
-
luid =
|
1200
|
+
luid = LUID.new
|
1201
1201
|
|
1202
1202
|
unless LookupPrivilegeValue('', 'SeShutdownPrivilege', luid)
|
1203
|
-
error =
|
1203
|
+
error = FFI.errno
|
1204
1204
|
CloseServiceHandle(handle_scs)
|
1205
|
-
raise
|
1205
|
+
raise SystemCallError.new('LookupPrivilegeValue', error)
|
1206
1206
|
end
|
1207
1207
|
|
1208
|
-
|
1208
|
+
luid_and_attrs = LUID_AND_ATTRIBUTES.new
|
1209
|
+
luid_and_attrs[:Luid] = luid
|
1210
|
+
luid_and_attrs[:Attributes] = SE_PRIVILEGE_ENABLED
|
1211
|
+
|
1212
|
+
tkp = TOKEN_PRIVILEGES.new
|
1213
|
+
tkp[:PrivilegeCount] = 1
|
1214
|
+
tkp[:Privileges][0] = luid_and_attrs
|
1209
1215
|
|
1210
1216
|
# Enable shutdown privilege in access token of this process
|
1211
1217
|
bool = AdjustTokenPrivileges(
|
1212
1218
|
token_handle,
|
1213
|
-
|
1219
|
+
false,
|
1214
1220
|
tkp,
|
1215
1221
|
tkp.size,
|
1216
1222
|
nil,
|
@@ -1218,78 +1224,56 @@ module Win32
|
|
1218
1224
|
)
|
1219
1225
|
|
1220
1226
|
unless bool
|
1221
|
-
error =
|
1227
|
+
error = FFI.errno
|
1222
1228
|
CloseServiceHandle(handle_scs)
|
1223
|
-
raise
|
1229
|
+
raise SystemCallError.new('AdjustTokenPrivileges', error)
|
1224
1230
|
end
|
1225
1231
|
end
|
1226
1232
|
|
1227
|
-
|
1233
|
+
sfa = SERVICE_FAILURE_ACTIONS.new
|
1228
1234
|
|
1229
1235
|
if opts['failure_reset_period']
|
1230
|
-
|
1236
|
+
sfa[:dwResetPeriod] = opts['failure_reset_period']
|
1231
1237
|
end
|
1232
1238
|
|
1233
1239
|
if opts['failure_reboot_message']
|
1234
|
-
|
1240
|
+
sfa[:lpRebootMsg] = FFI::MemoryPointer.from_string(opts['failure_reboot_message'])
|
1235
1241
|
end
|
1236
1242
|
|
1237
1243
|
if opts['failure_command']
|
1238
|
-
|
1244
|
+
sfa[:lpCommand] = FFI::MemoryPointer.from_string(opts['failure_command'])
|
1239
1245
|
end
|
1240
1246
|
|
1241
1247
|
if opts['failure_actions']
|
1242
|
-
|
1248
|
+
action_size = opts['failure_actions'].size
|
1249
|
+
action_ptr = FFI::MemoryPointer.new(SC_ACTION, action_size)
|
1243
1250
|
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
action_buf[4, 4] = [opts['failure_delay']].pack('L')
|
1248
|
-
actions << action_buf
|
1249
|
-
}
|
1251
|
+
actions = action_size.times.collect do |i|
|
1252
|
+
SC_ACTION.new(action_ptr + i * SC_ACTION.size)
|
1253
|
+
end
|
1250
1254
|
|
1251
|
-
|
1255
|
+
opts['failure_actions'].each_with_index{ |action, i|
|
1256
|
+
actions[i][:Type] = action
|
1257
|
+
actions[i][:Delay] = opts['failure_delay']
|
1258
|
+
}
|
1252
1259
|
|
1253
|
-
|
1254
|
-
|
1260
|
+
sfa[:cActions] = action_size
|
1261
|
+
sfa[:lpsaActions] = action_ptr
|
1255
1262
|
end
|
1256
1263
|
|
1257
1264
|
bool = ChangeServiceConfig2(
|
1258
1265
|
handle_scs,
|
1259
1266
|
SERVICE_CONFIG_FAILURE_ACTIONS,
|
1260
|
-
|
1267
|
+
sfa
|
1261
1268
|
)
|
1262
1269
|
|
1263
1270
|
unless bool
|
1264
|
-
error =
|
1271
|
+
error = FFI.errno
|
1265
1272
|
CloseServiceHandle(handle_scs)
|
1266
|
-
raise
|
1273
|
+
raise SystemCallError.new('ChangeServiceConfig2', error)
|
1267
1274
|
end
|
1268
1275
|
end
|
1269
1276
|
|
1270
|
-
# Unravels a pointer to an array of dependencies. Takes the address
|
1271
|
-
# that points the array as an argument.
|
1272
|
-
#
|
1273
|
-
def self.get_dependencies(address)
|
1274
|
-
dep_buf = ""
|
1275
|
-
|
1276
|
-
while address != 0
|
1277
|
-
char_buf = 0.chr
|
1278
|
-
memcpy(char_buf, address, 1)
|
1279
|
-
address += 1
|
1280
|
-
dep_buf += char_buf
|
1281
|
-
break if dep_buf[-2,2] == "\0\0"
|
1282
|
-
end
|
1283
|
-
|
1284
|
-
dependencies = []
|
1285
|
-
|
1286
|
-
if dep_buf != "\0\0"
|
1287
|
-
dependencies = dep_buf.split("\000\000").first.split(0.chr)
|
1288
|
-
end
|
1289
|
-
|
1290
|
-
dependencies
|
1291
|
-
end
|
1292
|
-
|
1293
1277
|
# Returns a human readable string indicating the action type.
|
1294
1278
|
#
|
1295
1279
|
def self.get_action_type(action_type)
|
@@ -4,6 +4,14 @@ module Windows
|
|
4
4
|
module Functions
|
5
5
|
extend FFI::Library
|
6
6
|
|
7
|
+
# Make FFI functions private
|
8
|
+
module FFI::Library
|
9
|
+
def attach_pfunc(*args)
|
10
|
+
attach_function(*args)
|
11
|
+
private args[0]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
7
15
|
typedef :ulong, :dword
|
8
16
|
typedef :uintptr_t, :handle
|
9
17
|
typedef :pointer, :ptr
|
@@ -13,51 +21,55 @@ module Windows
|
|
13
21
|
|
14
22
|
ffi_lib :kernel32
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
attach_pfunc :CloseHandle, [:handle], :bool
|
25
|
+
attach_pfunc :CreateEvent, :CreateEventA, [:ptr, :bool, :bool, :str], :handle
|
26
|
+
attach_pfunc :CreateThread, [:ptr, :size_t, :ptr, :ptr, :dword, :ptr], :handle, :blocking => true
|
27
|
+
attach_pfunc :EnterCriticalSection, [:ptr], :void
|
28
|
+
attach_pfunc :FormatMessage, :FormatMessageA, [:ulong, :ptr, :ulong, :ulong, :str, :ulong, :ptr], :ulong
|
29
|
+
attach_pfunc :GetCurrentProcess, [], :handle
|
30
|
+
attach_pfunc :InitializeCriticalSection, [:ptr], :void
|
31
|
+
attach_pfunc :LeaveCriticalSection, [:ptr], :void
|
32
|
+
attach_pfunc :SetEvent, [:handle], :bool
|
33
|
+
attach_pfunc :WaitForSingleObject, [:handle, :dword], :dword, :blocking => true
|
34
|
+
attach_pfunc :WaitForMultipleObjects, [:dword, :ptr, :bool, :dword], :dword
|
26
35
|
|
27
36
|
ffi_lib :advapi32
|
28
37
|
|
29
38
|
callback :handler_ex, [:ulong, :ulong, :ptr, :ptr], :void
|
30
39
|
|
31
|
-
|
40
|
+
attach_pfunc :AdjustTokenPrivileges, [:handle, :bool, :ptr, :dword, :ptr, :ptr], :bool
|
41
|
+
attach_pfunc :CloseServiceHandle, [:handle], :bool
|
32
42
|
|
33
|
-
|
43
|
+
attach_pfunc :ChangeServiceConfig, :ChangeServiceConfigA,
|
34
44
|
[:handle, :dword, :dword, :dword, :str, :str, :ptr, :str, :str, :str, :str],
|
35
45
|
:bool
|
36
46
|
|
37
|
-
|
47
|
+
attach_pfunc :ChangeServiceConfig2, :ChangeServiceConfig2A, [:handle, :dword, :ptr], :bool
|
38
48
|
|
39
|
-
|
49
|
+
attach_pfunc :CreateService, :CreateServiceA,
|
40
50
|
[:handle, :string, :string, :dword, :dword, :dword, :dword,
|
41
51
|
:string, :string, :ptr, :pointer, :string, :string],
|
42
52
|
:handle
|
43
53
|
|
44
|
-
|
45
|
-
|
54
|
+
attach_pfunc :ControlService, [:handle, :dword, :ptr], :bool
|
55
|
+
attach_pfunc :DeleteService, [:handle], :bool
|
46
56
|
|
47
|
-
|
57
|
+
attach_pfunc :EnumServicesStatusEx, :EnumServicesStatusExA,
|
48
58
|
[:handle, :int, :dword, :dword, :ptr, :dword, :ptr, :ptr, :ptr, :string],
|
49
59
|
:bool
|
50
60
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
attach_pfunc :GetServiceDisplayName, :GetServiceDisplayNameA, [:handle, :string, :ptr, :ptr], :bool
|
62
|
+
attach_pfunc :GetServiceKeyName, :GetServiceKeyNameA, [:handle, :string, :ptr, :ptr], :bool
|
63
|
+
attach_pfunc :LookupPrivilegeValue, :LookupPrivilegeValueA, [:string, :string, :ptr], :bool
|
64
|
+
attach_pfunc :OpenSCManager, :OpenSCManagerA, [:ptr, :ptr, :dword], :handle
|
65
|
+
attach_pfunc :OpenProcessToken, [:handle, :dword, :ptr], :bool
|
66
|
+
attach_pfunc :OpenService, :OpenServiceA, [:handle, :string, :dword], :handle
|
67
|
+
attach_pfunc :QueryServiceConfig, :QueryServiceConfigA, [:handle, :ptr, :dword, :ptr], :bool
|
68
|
+
attach_pfunc :QueryServiceConfig2, :QueryServiceConfig2A, [:handle, :dword, :ptr, :dword, :ptr], :bool
|
69
|
+
attach_pfunc :QueryServiceStatusEx, [:handle, :int, :ptr, :dword, :ptr], :bool
|
70
|
+
attach_pfunc :RegisterServiceCtrlHandlerEx, :RegisterServiceCtrlHandlerExA, [:str, :handler_ex, :ptr], :handle
|
71
|
+
attach_pfunc :SetServiceStatus, [:handle, :ptr], :bool
|
72
|
+
attach_pfunc :StartService, :StartServiceA, [:handle, :dword, :ptr], :bool
|
73
|
+
attach_pfunc :StartServiceCtrlDispatcher, :StartServiceCtrlDispatcherA, [:ptr], :bool, :blocking => true
|
62
74
|
end
|
63
75
|
end
|
@@ -92,5 +92,26 @@ module Windows
|
|
92
92
|
:lpServiceProc, :pointer
|
93
93
|
)
|
94
94
|
end
|
95
|
+
|
96
|
+
class LUID < FFI::Struct
|
97
|
+
layout(
|
98
|
+
:LowPart, :ulong,
|
99
|
+
:HighPart, :long
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
class LUID_AND_ATTRIBUTES < FFI::Struct
|
104
|
+
layout(
|
105
|
+
:Luid, LUID,
|
106
|
+
:Attributes, :ulong
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
class TOKEN_PRIVILEGES < FFI::Struct
|
111
|
+
layout(
|
112
|
+
:PrivilegeCount, :dword,
|
113
|
+
:Privileges, [LUID_AND_ATTRIBUTES, 1]
|
114
|
+
)
|
115
|
+
end
|
95
116
|
end
|
96
117
|
end
|
data/test/test_win32_daemon.rb
CHANGED
data/test/test_win32_service.rb
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Tests for the Win32::Service class.
|
5
5
|
##########################################################################
|
6
6
|
require 'test-unit'
|
7
|
-
require 'win32/service'
|
8
7
|
require 'win32/security'
|
8
|
+
require 'win32/service'
|
9
9
|
require 'socket'
|
10
10
|
|
11
11
|
class TC_Win32_Service < Test::Unit::TestCase
|
@@ -20,6 +20,9 @@ class TC_Win32_Service < Test::Unit::TestCase
|
|
20
20
|
@service_stat = nil
|
21
21
|
@services = []
|
22
22
|
@elevated = Win32::Security.elevated_security?
|
23
|
+
|
24
|
+
@singleton_methods = Win32::Service.methods.map{ |m| m.to_s }
|
25
|
+
@instance_methods = Win32::Service.instance_methods.map{ |m| m.to_s }
|
23
26
|
end
|
24
27
|
|
25
28
|
def start_service(service)
|
@@ -49,7 +52,7 @@ class TC_Win32_Service < Test::Unit::TestCase
|
|
49
52
|
end
|
50
53
|
|
51
54
|
test "version number is expected value" do
|
52
|
-
assert_equal('0.8.
|
55
|
+
assert_equal('0.8.1', Win32::Service::VERSION)
|
53
56
|
end
|
54
57
|
|
55
58
|
test "services basic functionality" do
|
@@ -392,6 +395,18 @@ class TC_Win32_Service < Test::Unit::TestCase
|
|
392
395
|
assert_not_nil(Win32::Service::STOPPED)
|
393
396
|
end
|
394
397
|
|
398
|
+
test "internal ffi functions are not public as singleton methods" do
|
399
|
+
assert_false(@singleton_methods.include?('CloseHandle'))
|
400
|
+
assert_false(@singleton_methods.include?('ControlService'))
|
401
|
+
assert_false(@singleton_methods.include?('DeleteService'))
|
402
|
+
end
|
403
|
+
|
404
|
+
test "internal ffi functions are not public as instance methods" do
|
405
|
+
assert_false(@instance_methods.include?('CloseHandle'))
|
406
|
+
assert_false(@instance_methods.include?('ControlService'))
|
407
|
+
assert_false(@instance_methods.include?('DeleteService'))
|
408
|
+
end
|
409
|
+
|
395
410
|
def teardown
|
396
411
|
@display_name = nil
|
397
412
|
@service_name = nil
|
@@ -1,129 +1,129 @@
|
|
1
|
-
########################################################################
|
2
|
-
# test_win32_service_create.rb
|
3
|
-
#
|
4
|
-
# Test case for the Service.create method. This test case will create
|
5
|
-
# a dummy (notepad) service. It won't actually run of course.
|
6
|
-
########################################################################
|
7
|
-
require 'test-unit'
|
8
|
-
require 'win32/service'
|
9
|
-
|
10
|
-
class TC_Win32_Service_Create < Test::Unit::TestCase
|
11
|
-
def self.startup
|
12
|
-
@@service1 = "notepad_service1"
|
13
|
-
@@service2 = "notepad_service2"
|
14
|
-
@@command = "C:\\windows\\system32\\notepad.exe"
|
15
|
-
|
16
|
-
Win32::Service.new(
|
17
|
-
:service_name => @@service1,
|
18
|
-
:binary_path_name => @@command
|
19
|
-
)
|
20
|
-
|
21
|
-
Win32::Service.new(
|
22
|
-
:service_name => @@service2,
|
23
|
-
:display_name => 'Notepad Test',
|
24
|
-
:desired_access => Win32::Service::ALL_ACCESS,
|
25
|
-
:service_type => Win32::Service::WIN32_OWN_PROCESS,
|
26
|
-
:start_type => Win32::Service::DISABLED,
|
27
|
-
:error_control => Win32::Service::ERROR_IGNORE,
|
28
|
-
:binary_path_name => @@command,
|
29
|
-
:load_order_group => 'Network',
|
30
|
-
:dependencies => 'W32Time',
|
31
|
-
:description => 'Test service. Please delete me'
|
32
|
-
)
|
33
|
-
end
|
34
|
-
|
35
|
-
def setup
|
36
|
-
@info1 = Win32::Service.config_info(@@service1)
|
37
|
-
@info2 = Win32::Service.config_info(@@service2)
|
38
|
-
end
|
39
|
-
|
40
|
-
test "constructor basic functionality" do
|
41
|
-
assert_respond_to(Win32::Service, :new)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Service.create works, but the test fails. Might be a bug in test-unit.
|
45
|
-
test "create is an alias for new" do
|
46
|
-
assert_respond_to(Win32::Service, :create)
|
47
|
-
#assert_alias_method(Win32::Service, :create, :new)
|
48
|
-
end
|
49
|
-
|
50
|
-
test "ensure services were created in startup method" do
|
51
|
-
notify "If this test fails then remaining results are meaningless."
|
52
|
-
assert_true(Win32::Service.exists?(@@service1))
|
53
|
-
assert_true(Win32::Service.exists?(@@service2))
|
54
|
-
end
|
55
|
-
|
56
|
-
test "expected service type configuration information" do
|
57
|
-
assert_equal('own process, interactive', @info1.service_type)
|
58
|
-
end
|
59
|
-
|
60
|
-
test "expected start type configuration information" do
|
61
|
-
assert_equal('demand start', @info1.start_type)
|
62
|
-
end
|
63
|
-
|
64
|
-
test "expected error control configuration information" do
|
65
|
-
assert_equal('normal', @info1.error_control)
|
66
|
-
end
|
67
|
-
|
68
|
-
test "expected binary path name configuration information" do
|
69
|
-
assert_equal(@@command, @info1.binary_path_name)
|
70
|
-
end
|
71
|
-
|
72
|
-
test "expected load order group configuration information" do
|
73
|
-
assert_equal('', @info1.load_order_group)
|
74
|
-
end
|
75
|
-
|
76
|
-
test "expected tag id configuration information" do
|
77
|
-
assert_equal(0, @info1.tag_id)
|
78
|
-
end
|
79
|
-
|
80
|
-
test "expected dependency configuration information" do
|
81
|
-
assert_equal([], @info1.dependencies)
|
82
|
-
end
|
83
|
-
|
84
|
-
test "expected service start time configuration information" do
|
85
|
-
assert_equal('LocalSystem', @info1.service_start_name)
|
86
|
-
end
|
87
|
-
|
88
|
-
test "expected display name configuration information" do
|
89
|
-
assert_equal('notepad_service1', @info1.display_name)
|
90
|
-
end
|
91
|
-
|
92
|
-
test "configuration information options are set properly for service 2" do
|
93
|
-
assert_equal('own process', @info2.service_type)
|
94
|
-
assert_equal('disabled', @info2.start_type)
|
95
|
-
assert_equal('ignore', @info2.error_control)
|
96
|
-
assert_equal(@@command, @info2.binary_path_name)
|
97
|
-
assert_equal('Network', @info2.load_order_group)
|
98
|
-
assert_equal(0, @info2.tag_id)
|
99
|
-
assert_equal(['W32Time'], @info2.dependencies)
|
100
|
-
assert_equal('LocalSystem', @info2.service_start_name)
|
101
|
-
assert_equal('Notepad Test', @info2.display_name)
|
102
|
-
end
|
103
|
-
|
104
|
-
test "at least one argument is required or an error is raised" do
|
105
|
-
assert_raise(ArgumentError){ Win32::Service.new }
|
106
|
-
end
|
107
|
-
|
108
|
-
test "passing a bogus option to the constructor will cause an error" do
|
109
|
-
assert_raise(ArgumentError){ Win32::Service.new(:bogus => 'test.exe') }
|
110
|
-
end
|
111
|
-
|
112
|
-
test "the service name must be provided or an error is raised" do
|
113
|
-
assert_raise(ArgumentError){ Win32::Service.new(:binary_path_name => 'test.exe') }
|
114
|
-
end
|
115
|
-
|
116
|
-
def teardown
|
117
|
-
@info1 = nil
|
118
|
-
@info2 = nil
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.shutdown
|
122
|
-
Win32::Service.delete(@@service1) if Win32::Service.exists?(@@service1)
|
123
|
-
Win32::Service.delete(@@service2) if Win32::Service.exists?(@@service2)
|
124
|
-
|
125
|
-
@@service1 = nil
|
126
|
-
@@service2 = nil
|
127
|
-
@@command = nil
|
128
|
-
end
|
129
|
-
end
|
1
|
+
########################################################################
|
2
|
+
# test_win32_service_create.rb
|
3
|
+
#
|
4
|
+
# Test case for the Service.create method. This test case will create
|
5
|
+
# a dummy (notepad) service. It won't actually run of course.
|
6
|
+
########################################################################
|
7
|
+
require 'test-unit'
|
8
|
+
require 'win32/service'
|
9
|
+
|
10
|
+
class TC_Win32_Service_Create < Test::Unit::TestCase
|
11
|
+
def self.startup
|
12
|
+
@@service1 = "notepad_service1"
|
13
|
+
@@service2 = "notepad_service2"
|
14
|
+
@@command = "C:\\windows\\system32\\notepad.exe"
|
15
|
+
|
16
|
+
Win32::Service.new(
|
17
|
+
:service_name => @@service1,
|
18
|
+
:binary_path_name => @@command
|
19
|
+
)
|
20
|
+
|
21
|
+
Win32::Service.new(
|
22
|
+
:service_name => @@service2,
|
23
|
+
:display_name => 'Notepad Test',
|
24
|
+
:desired_access => Win32::Service::ALL_ACCESS,
|
25
|
+
:service_type => Win32::Service::WIN32_OWN_PROCESS,
|
26
|
+
:start_type => Win32::Service::DISABLED,
|
27
|
+
:error_control => Win32::Service::ERROR_IGNORE,
|
28
|
+
:binary_path_name => @@command,
|
29
|
+
:load_order_group => 'Network',
|
30
|
+
:dependencies => 'W32Time',
|
31
|
+
:description => 'Test service. Please delete me'
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup
|
36
|
+
@info1 = Win32::Service.config_info(@@service1)
|
37
|
+
@info2 = Win32::Service.config_info(@@service2)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "constructor basic functionality" do
|
41
|
+
assert_respond_to(Win32::Service, :new)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Service.create works, but the test fails. Might be a bug in test-unit.
|
45
|
+
test "create is an alias for new" do
|
46
|
+
assert_respond_to(Win32::Service, :create)
|
47
|
+
#assert_alias_method(Win32::Service, :create, :new)
|
48
|
+
end
|
49
|
+
|
50
|
+
test "ensure services were created in startup method" do
|
51
|
+
notify "If this test fails then remaining results are meaningless."
|
52
|
+
assert_true(Win32::Service.exists?(@@service1))
|
53
|
+
assert_true(Win32::Service.exists?(@@service2))
|
54
|
+
end
|
55
|
+
|
56
|
+
test "expected service type configuration information" do
|
57
|
+
assert_equal('own process, interactive', @info1.service_type)
|
58
|
+
end
|
59
|
+
|
60
|
+
test "expected start type configuration information" do
|
61
|
+
assert_equal('demand start', @info1.start_type)
|
62
|
+
end
|
63
|
+
|
64
|
+
test "expected error control configuration information" do
|
65
|
+
assert_equal('normal', @info1.error_control)
|
66
|
+
end
|
67
|
+
|
68
|
+
test "expected binary path name configuration information" do
|
69
|
+
assert_equal(@@command, @info1.binary_path_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
test "expected load order group configuration information" do
|
73
|
+
assert_equal('', @info1.load_order_group)
|
74
|
+
end
|
75
|
+
|
76
|
+
test "expected tag id configuration information" do
|
77
|
+
assert_equal(0, @info1.tag_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
test "expected dependency configuration information" do
|
81
|
+
assert_equal([], @info1.dependencies)
|
82
|
+
end
|
83
|
+
|
84
|
+
test "expected service start time configuration information" do
|
85
|
+
assert_equal('LocalSystem', @info1.service_start_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
test "expected display name configuration information" do
|
89
|
+
assert_equal('notepad_service1', @info1.display_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
test "configuration information options are set properly for service 2" do
|
93
|
+
assert_equal('own process', @info2.service_type)
|
94
|
+
assert_equal('disabled', @info2.start_type)
|
95
|
+
assert_equal('ignore', @info2.error_control)
|
96
|
+
assert_equal(@@command, @info2.binary_path_name)
|
97
|
+
assert_equal('Network', @info2.load_order_group)
|
98
|
+
assert_equal(0, @info2.tag_id)
|
99
|
+
assert_equal(['W32Time'], @info2.dependencies)
|
100
|
+
assert_equal('LocalSystem', @info2.service_start_name)
|
101
|
+
assert_equal('Notepad Test', @info2.display_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
test "at least one argument is required or an error is raised" do
|
105
|
+
assert_raise(ArgumentError){ Win32::Service.new }
|
106
|
+
end
|
107
|
+
|
108
|
+
test "passing a bogus option to the constructor will cause an error" do
|
109
|
+
assert_raise(ArgumentError){ Win32::Service.new(:bogus => 'test.exe') }
|
110
|
+
end
|
111
|
+
|
112
|
+
test "the service name must be provided or an error is raised" do
|
113
|
+
assert_raise(ArgumentError){ Win32::Service.new(:binary_path_name => 'test.exe') }
|
114
|
+
end
|
115
|
+
|
116
|
+
def teardown
|
117
|
+
@info1 = nil
|
118
|
+
@info2 = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.shutdown
|
122
|
+
Win32::Service.delete(@@service1) if Win32::Service.exists?(@@service1)
|
123
|
+
Win32::Service.delete(@@service2) if Win32::Service.exists?(@@service2)
|
124
|
+
|
125
|
+
@@service1 = nil
|
126
|
+
@@service2 = nil
|
127
|
+
@@command = nil
|
128
|
+
end
|
129
|
+
end
|
data/win32-service.gemspec
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = 'win32-service'
|
5
|
-
spec.version = '0.8.
|
5
|
+
spec.version = '0.8.1'
|
6
6
|
spec.authors = ['Daniel J. Berger', 'Park Heesob']
|
7
7
|
spec.license = 'Artistic 2.0'
|
8
8
|
spec.email = 'djberg96@gmail.com'
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_dependency('ffi')
|
24
24
|
spec.add_development_dependency('test-unit', '>= 2.4.0')
|
25
|
+
spec.add_development_dependency('rake')
|
25
26
|
|
26
27
|
spec.description = <<-EOF
|
27
28
|
The win32-service library provides a Ruby interface to services on
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: win32-service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
5
|
-
prerelease:
|
4
|
+
version: 0.8.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Daniel J. Berger
|
@@ -10,44 +9,57 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2013-
|
12
|
+
date: 2013-08-02 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: ffi
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - '>='
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: test-unit
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
|
-
- -
|
32
|
+
- - '>='
|
37
33
|
- !ruby/object:Gem::Version
|
38
34
|
version: 2.4.0
|
39
35
|
type: :development
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
|
-
- -
|
39
|
+
- - '>='
|
45
40
|
- !ruby/object:Gem::Version
|
46
41
|
version: 2.4.0
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: |2
|
57
|
+
The win32-service library provides a Ruby interface to services on
|
58
|
+
MS Windows. You can create new services, or control, configure and
|
59
|
+
inspect existing services.
|
60
|
+
|
61
|
+
In addition, you can create a pure Ruby service by using the Daemon
|
62
|
+
class that is included as part of the library.
|
51
63
|
email: djberg96@gmail.com
|
52
64
|
executables: []
|
53
65
|
extensions: []
|
@@ -83,27 +95,26 @@ files:
|
|
83
95
|
homepage: http://github.com/djberg96/win32-service
|
84
96
|
licenses:
|
85
97
|
- Artistic 2.0
|
98
|
+
metadata: {}
|
86
99
|
post_install_message:
|
87
100
|
rdoc_options: []
|
88
101
|
require_paths:
|
89
102
|
- lib
|
90
103
|
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
-
none: false
|
92
104
|
requirements:
|
93
|
-
- -
|
105
|
+
- - '>='
|
94
106
|
- !ruby/object:Gem::Version
|
95
107
|
version: '0'
|
96
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
109
|
requirements:
|
99
|
-
- -
|
110
|
+
- - '>='
|
100
111
|
- !ruby/object:Gem::Version
|
101
112
|
version: '0'
|
102
113
|
requirements: []
|
103
114
|
rubyforge_project:
|
104
|
-
rubygems_version:
|
115
|
+
rubygems_version: 2.0.3
|
105
116
|
signing_key:
|
106
|
-
specification_version:
|
117
|
+
specification_version: 4
|
107
118
|
summary: An interface for MS Windows services
|
108
119
|
test_files:
|
109
120
|
- test/test_win32_daemon.rb
|