win32-service 0.8.0 → 0.8.1
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/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
|