mixlib-shellout 2.2.6-universal-mingw32 → 2.2.7-universal-mingw32
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 +4 -4
- data/Gemfile +15 -12
- data/LICENSE +201 -201
- data/README.md +87 -54
- data/Rakefile +24 -24
- data/lib/mixlib/shellout.rb +357 -357
- data/lib/mixlib/shellout/exceptions.rb +7 -7
- data/lib/mixlib/shellout/unix.rb +415 -415
- data/lib/mixlib/shellout/version.rb +5 -5
- data/lib/mixlib/shellout/windows.rb +382 -362
- data/lib/mixlib/shellout/windows/core_ext.rb +371 -371
- data/mixlib-shellout-windows.gemspec +8 -8
- data/mixlib-shellout.gemspec +24 -24
- metadata +5 -5
@@ -1,371 +1,371 @@
|
|
1
|
-
#--
|
2
|
-
# Author:: Daniel DeLeo (<dan@
|
3
|
-
# Author:: John Keiser (<jkeiser@
|
4
|
-
# Copyright:: Copyright (c) 2011
|
5
|
-
# License:: Apache License, Version 2.0
|
6
|
-
#
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
-
# you may not use this file except in compliance with the License.
|
9
|
-
# You may obtain a copy of the License at
|
10
|
-
#
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
-
# See the License for the specific language governing permissions and
|
17
|
-
# limitations under the License.
|
18
|
-
#
|
19
|
-
|
20
|
-
require 'win32/process'
|
21
|
-
|
22
|
-
# Add new constants for Logon
|
23
|
-
module Process::Constants
|
24
|
-
private
|
25
|
-
|
26
|
-
LOGON32_LOGON_INTERACTIVE = 0x00000002
|
27
|
-
LOGON32_PROVIDER_DEFAULT = 0x00000000
|
28
|
-
UOI_NAME = 0x00000002
|
29
|
-
|
30
|
-
WAIT_OBJECT_0 = 0
|
31
|
-
WAIT_TIMEOUT = 0x102
|
32
|
-
WAIT_ABANDONED = 128
|
33
|
-
WAIT_ABANDONED_0 = WAIT_ABANDONED
|
34
|
-
WAIT_FAILED = 0xFFFFFFFF
|
35
|
-
end
|
36
|
-
|
37
|
-
# Define the functions needed to check with Service windows station
|
38
|
-
module Process::Functions
|
39
|
-
ffi_lib :advapi32
|
40
|
-
|
41
|
-
attach_pfunc :LogonUserW,
|
42
|
-
[:buffer_in, :buffer_in, :buffer_in, :ulong, :ulong, :pointer], :bool
|
43
|
-
|
44
|
-
attach_pfunc :CreateProcessAsUserW,
|
45
|
-
[:ulong, :buffer_in, :buffer_inout, :pointer, :pointer, :int,
|
46
|
-
:ulong, :buffer_in, :buffer_in, :pointer, :pointer], :bool
|
47
|
-
|
48
|
-
ffi_lib :user32
|
49
|
-
|
50
|
-
attach_pfunc :GetProcessWindowStation,
|
51
|
-
[], :ulong
|
52
|
-
|
53
|
-
attach_pfunc :GetUserObjectInformationA,
|
54
|
-
[:ulong, :uint, :buffer_out, :ulong, :pointer], :bool
|
55
|
-
end
|
56
|
-
|
57
|
-
# Override Process.create to check for running in the Service window station and doing
|
58
|
-
# a full logon with LogonUser, instead of a CreateProcessWithLogon
|
59
|
-
# Cloned from https://github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb
|
60
|
-
# as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
|
61
|
-
module Process
|
62
|
-
|
63
|
-
# Explicitly reopen singleton class so that class/constant declarations from
|
64
|
-
# extensions are visible in Modules.nesting.
|
65
|
-
class << self
|
66
|
-
def create(args)
|
67
|
-
unless args.kind_of?(Hash)
|
68
|
-
raise TypeError, 'hash keyword arguments expected'
|
69
|
-
end
|
70
|
-
|
71
|
-
valid_keys = %w[
|
72
|
-
app_name command_line inherit creation_flags cwd environment
|
73
|
-
startup_info thread_inherit process_inherit close_handles with_logon
|
74
|
-
domain password
|
75
|
-
]
|
76
|
-
|
77
|
-
valid_si_keys = %w[
|
78
|
-
startf_flags desktop title x y x_size y_size x_count_chars
|
79
|
-
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
80
|
-
]
|
81
|
-
|
82
|
-
# Set default values
|
83
|
-
hash = {
|
84
|
-
'app_name' => nil,
|
85
|
-
'creation_flags' => 0,
|
86
|
-
'close_handles' => true
|
87
|
-
}
|
88
|
-
|
89
|
-
# Validate the keys, and convert symbols and case to lowercase strings.
|
90
|
-
args.each{ |key, val|
|
91
|
-
key = key.to_s.downcase
|
92
|
-
unless valid_keys.include?(key)
|
93
|
-
raise ArgumentError, "invalid key '#{key}'"
|
94
|
-
end
|
95
|
-
hash[key] = val
|
96
|
-
}
|
97
|
-
|
98
|
-
si_hash = {}
|
99
|
-
|
100
|
-
# If the startup_info key is present, validate its subkeys
|
101
|
-
if hash['startup_info']
|
102
|
-
hash['startup_info'].each{ |key, val|
|
103
|
-
key = key.to_s.downcase
|
104
|
-
unless valid_si_keys.include?(key)
|
105
|
-
raise ArgumentError, "invalid startup_info key '#{key}'"
|
106
|
-
end
|
107
|
-
si_hash[key] = val
|
108
|
-
}
|
109
|
-
end
|
110
|
-
|
111
|
-
# The +command_line+ key is mandatory unless the +app_name+ key
|
112
|
-
# is specified.
|
113
|
-
unless hash['command_line']
|
114
|
-
if hash['app_name']
|
115
|
-
hash['command_line'] = hash['app_name']
|
116
|
-
hash['app_name'] = nil
|
117
|
-
else
|
118
|
-
raise ArgumentError, 'command_line or app_name must be specified'
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
env = nil
|
123
|
-
|
124
|
-
# The env string should be passed as a string of ';' separated paths.
|
125
|
-
if hash['environment']
|
126
|
-
env = hash['environment']
|
127
|
-
|
128
|
-
unless env.respond_to?(:join)
|
129
|
-
env = hash['environment'].split(File::PATH_SEPARATOR)
|
130
|
-
end
|
131
|
-
|
132
|
-
env = env.map{ |e| e + 0.chr }.join('') + 0.chr
|
133
|
-
env.to_wide_string! if hash['with_logon']
|
134
|
-
end
|
135
|
-
|
136
|
-
# Process SECURITY_ATTRIBUTE structure
|
137
|
-
process_security = nil
|
138
|
-
|
139
|
-
if hash['process_inherit']
|
140
|
-
process_security = SECURITY_ATTRIBUTES.new
|
141
|
-
process_security[:nLength] = 12
|
142
|
-
process_security[:bInheritHandle] = 1
|
143
|
-
end
|
144
|
-
|
145
|
-
# Thread SECURITY_ATTRIBUTE structure
|
146
|
-
thread_security = nil
|
147
|
-
|
148
|
-
if hash['thread_inherit']
|
149
|
-
thread_security = SECURITY_ATTRIBUTES.new
|
150
|
-
thread_security[:nLength] = 12
|
151
|
-
thread_security[:bInheritHandle] = 1
|
152
|
-
end
|
153
|
-
|
154
|
-
# Automatically handle stdin, stdout and stderr as either IO objects
|
155
|
-
# or file descriptors. This won't work for StringIO, however. It also
|
156
|
-
# will not work on JRuby because of the way it handles internal file
|
157
|
-
# descriptors.
|
158
|
-
#
|
159
|
-
['stdin', 'stdout', 'stderr'].each{ |io|
|
160
|
-
if si_hash[io]
|
161
|
-
if si_hash[io].respond_to?(:fileno)
|
162
|
-
handle = get_osfhandle(si_hash[io].fileno)
|
163
|
-
else
|
164
|
-
handle = get_osfhandle(si_hash[io])
|
165
|
-
end
|
166
|
-
|
167
|
-
if handle == INVALID_HANDLE_VALUE
|
168
|
-
ptr = FFI::MemoryPointer.new(:int)
|
169
|
-
|
170
|
-
if windows_version >= 6 && get_errno(ptr) == 0
|
171
|
-
errno = ptr.read_int
|
172
|
-
else
|
173
|
-
errno = FFI.errno
|
174
|
-
end
|
175
|
-
|
176
|
-
raise SystemCallError.new("get_osfhandle", errno)
|
177
|
-
end
|
178
|
-
|
179
|
-
# Most implementations of Ruby on Windows create inheritable
|
180
|
-
# handles by default, but some do not. RF bug #26988.
|
181
|
-
bool = SetHandleInformation(
|
182
|
-
handle,
|
183
|
-
HANDLE_FLAG_INHERIT,
|
184
|
-
HANDLE_FLAG_INHERIT
|
185
|
-
)
|
186
|
-
|
187
|
-
raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
|
188
|
-
|
189
|
-
si_hash[io] = handle
|
190
|
-
si_hash['startf_flags'] ||= 0
|
191
|
-
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
192
|
-
hash['inherit'] = true
|
193
|
-
end
|
194
|
-
}
|
195
|
-
|
196
|
-
procinfo = PROCESS_INFORMATION.new
|
197
|
-
startinfo = STARTUPINFO.new
|
198
|
-
|
199
|
-
unless si_hash.empty?
|
200
|
-
startinfo[:cb] = startinfo.size
|
201
|
-
startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
|
202
|
-
startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
|
203
|
-
startinfo[:dwX] = si_hash['x'] if si_hash['x']
|
204
|
-
startinfo[:dwY] = si_hash['y'] if si_hash['y']
|
205
|
-
startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
|
206
|
-
startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
|
207
|
-
startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
|
208
|
-
startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
|
209
|
-
startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
|
210
|
-
startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
|
211
|
-
startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
|
212
|
-
startinfo[:cbReserved2] = 0
|
213
|
-
startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
|
214
|
-
startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
|
215
|
-
startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
|
216
|
-
end
|
217
|
-
|
218
|
-
app = nil
|
219
|
-
cmd = nil
|
220
|
-
|
221
|
-
# Convert strings to wide character strings if present
|
222
|
-
if hash['app_name']
|
223
|
-
app = hash['app_name'].to_wide_string
|
224
|
-
end
|
225
|
-
|
226
|
-
if hash['command_line']
|
227
|
-
cmd = hash['command_line'].to_wide_string
|
228
|
-
end
|
229
|
-
|
230
|
-
if hash['cwd']
|
231
|
-
cwd = hash['cwd'].to_wide_string
|
232
|
-
end
|
233
|
-
|
234
|
-
inherit = hash['inherit'] ? 1 : 0
|
235
|
-
|
236
|
-
if hash['with_logon']
|
237
|
-
logon = hash['with_logon'].to_wide_string
|
238
|
-
|
239
|
-
if hash['password']
|
240
|
-
passwd = hash['password'].to_wide_string
|
241
|
-
else
|
242
|
-
raise ArgumentError, 'password must be specified if with_logon is used'
|
243
|
-
end
|
244
|
-
|
245
|
-
if hash['domain']
|
246
|
-
domain = hash['domain'].to_wide_string
|
247
|
-
end
|
248
|
-
|
249
|
-
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
250
|
-
|
251
|
-
winsta_name = FFI::MemoryPointer.new(:char, 256)
|
252
|
-
return_size = FFI::MemoryPointer.new(:ulong)
|
253
|
-
|
254
|
-
bool = GetUserObjectInformationA(
|
255
|
-
GetProcessWindowStation(), # Window station handle
|
256
|
-
UOI_NAME, # Information to get
|
257
|
-
winsta_name, # Buffer to receive information
|
258
|
-
winsta_name.size, # Size of buffer
|
259
|
-
return_size # Size filled into buffer
|
260
|
-
)
|
261
|
-
|
262
|
-
unless bool
|
263
|
-
raise SystemCallError.new("GetUserObjectInformationA", FFI.errno)
|
264
|
-
end
|
265
|
-
|
266
|
-
winsta_name = winsta_name.read_string(return_size.read_ulong)
|
267
|
-
|
268
|
-
# If running in the service windows station must do a log on to get
|
269
|
-
# to the interactive desktop. Running process user account must have
|
270
|
-
# the 'Replace a process level token' permission. This is necessary as
|
271
|
-
# the logon (which happens with CreateProcessWithLogon) must have an
|
272
|
-
# interactive windows station to attach to, which is created with the
|
273
|
-
# LogonUser cann with the LOGON32_LOGON_INTERACTIVE flag.
|
274
|
-
if winsta_name =~ /^Service-0x0-.*$/i
|
275
|
-
token = FFI::MemoryPointer.new(:ulong)
|
276
|
-
|
277
|
-
bool = LogonUserW(
|
278
|
-
logon, # User
|
279
|
-
domain, # Domain
|
280
|
-
passwd, # Password
|
281
|
-
LOGON32_LOGON_INTERACTIVE, # Logon Type
|
282
|
-
LOGON32_PROVIDER_DEFAULT, # Logon Provider
|
283
|
-
token # User token handle
|
284
|
-
)
|
285
|
-
|
286
|
-
unless bool
|
287
|
-
raise SystemCallError.new("LogonUserW", FFI.errno)
|
288
|
-
end
|
289
|
-
|
290
|
-
token = token.read_ulong
|
291
|
-
|
292
|
-
begin
|
293
|
-
bool = CreateProcessAsUserW(
|
294
|
-
token, # User token handle
|
295
|
-
app, # App name
|
296
|
-
cmd, # Command line
|
297
|
-
process_security, # Process attributes
|
298
|
-
thread_security, # Thread attributes
|
299
|
-
inherit, # Inherit handles
|
300
|
-
hash['creation_flags'], # Creation Flags
|
301
|
-
env, # Environment
|
302
|
-
cwd, # Working directory
|
303
|
-
startinfo, # Startup Info
|
304
|
-
procinfo # Process Info
|
305
|
-
)
|
306
|
-
ensure
|
307
|
-
CloseHandle(token)
|
308
|
-
end
|
309
|
-
|
310
|
-
unless bool
|
311
|
-
raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno)
|
312
|
-
end
|
313
|
-
else
|
314
|
-
bool = CreateProcessWithLogonW(
|
315
|
-
logon, # User
|
316
|
-
domain, # Domain
|
317
|
-
passwd, # Password
|
318
|
-
LOGON_WITH_PROFILE, # Logon flags
|
319
|
-
app, # App name
|
320
|
-
cmd, # Command line
|
321
|
-
hash['creation_flags'], # Creation flags
|
322
|
-
env, # Environment
|
323
|
-
cwd, # Working directory
|
324
|
-
startinfo, # Startup Info
|
325
|
-
procinfo # Process Info
|
326
|
-
)
|
327
|
-
|
328
|
-
unless bool
|
329
|
-
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
|
330
|
-
end
|
331
|
-
end
|
332
|
-
else
|
333
|
-
bool = CreateProcessW(
|
334
|
-
app, # App name
|
335
|
-
cmd, # Command line
|
336
|
-
process_security, # Process attributes
|
337
|
-
thread_security, # Thread attributes
|
338
|
-
inherit, # Inherit handles?
|
339
|
-
hash['creation_flags'], # Creation flags
|
340
|
-
env, # Environment
|
341
|
-
cwd, # Working directory
|
342
|
-
startinfo, # Startup Info
|
343
|
-
procinfo # Process Info
|
344
|
-
)
|
345
|
-
|
346
|
-
unless bool
|
347
|
-
raise SystemCallError.new("CreateProcessW", FFI.errno)
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
# Automatically close the process and thread handles in the
|
352
|
-
# PROCESS_INFORMATION struct unless explicitly told not to.
|
353
|
-
if hash['close_handles']
|
354
|
-
CloseHandle(procinfo[:hProcess])
|
355
|
-
CloseHandle(procinfo[:hThread])
|
356
|
-
# Clear these fields so callers don't attempt to close the handle
|
357
|
-
# which can result in the wrong handle being closed or an
|
358
|
-
# exception in some circumstances.
|
359
|
-
procinfo[:hProcess] = 0
|
360
|
-
procinfo[:hThread] = 0
|
361
|
-
end
|
362
|
-
|
363
|
-
ProcessInfo.new(
|
364
|
-
procinfo[:hProcess],
|
365
|
-
procinfo[:hThread],
|
366
|
-
procinfo[:dwProcessId],
|
367
|
-
procinfo[:dwThreadId]
|
368
|
-
)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
end
|
1
|
+
#--
|
2
|
+
# Author:: Daniel DeLeo (<dan@chef.io>)
|
3
|
+
# Author:: John Keiser (<jkeiser@chef.io>)
|
4
|
+
# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
|
5
|
+
# License:: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'win32/process'
|
21
|
+
|
22
|
+
# Add new constants for Logon
|
23
|
+
module Process::Constants
|
24
|
+
private
|
25
|
+
|
26
|
+
LOGON32_LOGON_INTERACTIVE = 0x00000002
|
27
|
+
LOGON32_PROVIDER_DEFAULT = 0x00000000
|
28
|
+
UOI_NAME = 0x00000002
|
29
|
+
|
30
|
+
WAIT_OBJECT_0 = 0
|
31
|
+
WAIT_TIMEOUT = 0x102
|
32
|
+
WAIT_ABANDONED = 128
|
33
|
+
WAIT_ABANDONED_0 = WAIT_ABANDONED
|
34
|
+
WAIT_FAILED = 0xFFFFFFFF
|
35
|
+
end
|
36
|
+
|
37
|
+
# Define the functions needed to check with Service windows station
|
38
|
+
module Process::Functions
|
39
|
+
ffi_lib :advapi32
|
40
|
+
|
41
|
+
attach_pfunc :LogonUserW,
|
42
|
+
[:buffer_in, :buffer_in, :buffer_in, :ulong, :ulong, :pointer], :bool
|
43
|
+
|
44
|
+
attach_pfunc :CreateProcessAsUserW,
|
45
|
+
[:ulong, :buffer_in, :buffer_inout, :pointer, :pointer, :int,
|
46
|
+
:ulong, :buffer_in, :buffer_in, :pointer, :pointer], :bool
|
47
|
+
|
48
|
+
ffi_lib :user32
|
49
|
+
|
50
|
+
attach_pfunc :GetProcessWindowStation,
|
51
|
+
[], :ulong
|
52
|
+
|
53
|
+
attach_pfunc :GetUserObjectInformationA,
|
54
|
+
[:ulong, :uint, :buffer_out, :ulong, :pointer], :bool
|
55
|
+
end
|
56
|
+
|
57
|
+
# Override Process.create to check for running in the Service window station and doing
|
58
|
+
# a full logon with LogonUser, instead of a CreateProcessWithLogon
|
59
|
+
# Cloned from https://github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb
|
60
|
+
# as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
|
61
|
+
module Process
|
62
|
+
|
63
|
+
# Explicitly reopen singleton class so that class/constant declarations from
|
64
|
+
# extensions are visible in Modules.nesting.
|
65
|
+
class << self
|
66
|
+
def create(args)
|
67
|
+
unless args.kind_of?(Hash)
|
68
|
+
raise TypeError, 'hash keyword arguments expected'
|
69
|
+
end
|
70
|
+
|
71
|
+
valid_keys = %w[
|
72
|
+
app_name command_line inherit creation_flags cwd environment
|
73
|
+
startup_info thread_inherit process_inherit close_handles with_logon
|
74
|
+
domain password
|
75
|
+
]
|
76
|
+
|
77
|
+
valid_si_keys = %w[
|
78
|
+
startf_flags desktop title x y x_size y_size x_count_chars
|
79
|
+
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
80
|
+
]
|
81
|
+
|
82
|
+
# Set default values
|
83
|
+
hash = {
|
84
|
+
'app_name' => nil,
|
85
|
+
'creation_flags' => 0,
|
86
|
+
'close_handles' => true
|
87
|
+
}
|
88
|
+
|
89
|
+
# Validate the keys, and convert symbols and case to lowercase strings.
|
90
|
+
args.each{ |key, val|
|
91
|
+
key = key.to_s.downcase
|
92
|
+
unless valid_keys.include?(key)
|
93
|
+
raise ArgumentError, "invalid key '#{key}'"
|
94
|
+
end
|
95
|
+
hash[key] = val
|
96
|
+
}
|
97
|
+
|
98
|
+
si_hash = {}
|
99
|
+
|
100
|
+
# If the startup_info key is present, validate its subkeys
|
101
|
+
if hash['startup_info']
|
102
|
+
hash['startup_info'].each{ |key, val|
|
103
|
+
key = key.to_s.downcase
|
104
|
+
unless valid_si_keys.include?(key)
|
105
|
+
raise ArgumentError, "invalid startup_info key '#{key}'"
|
106
|
+
end
|
107
|
+
si_hash[key] = val
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
# The +command_line+ key is mandatory unless the +app_name+ key
|
112
|
+
# is specified.
|
113
|
+
unless hash['command_line']
|
114
|
+
if hash['app_name']
|
115
|
+
hash['command_line'] = hash['app_name']
|
116
|
+
hash['app_name'] = nil
|
117
|
+
else
|
118
|
+
raise ArgumentError, 'command_line or app_name must be specified'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
env = nil
|
123
|
+
|
124
|
+
# The env string should be passed as a string of ';' separated paths.
|
125
|
+
if hash['environment']
|
126
|
+
env = hash['environment']
|
127
|
+
|
128
|
+
unless env.respond_to?(:join)
|
129
|
+
env = hash['environment'].split(File::PATH_SEPARATOR)
|
130
|
+
end
|
131
|
+
|
132
|
+
env = env.map{ |e| e + 0.chr }.join('') + 0.chr
|
133
|
+
env.to_wide_string! if hash['with_logon']
|
134
|
+
end
|
135
|
+
|
136
|
+
# Process SECURITY_ATTRIBUTE structure
|
137
|
+
process_security = nil
|
138
|
+
|
139
|
+
if hash['process_inherit']
|
140
|
+
process_security = SECURITY_ATTRIBUTES.new
|
141
|
+
process_security[:nLength] = 12
|
142
|
+
process_security[:bInheritHandle] = 1
|
143
|
+
end
|
144
|
+
|
145
|
+
# Thread SECURITY_ATTRIBUTE structure
|
146
|
+
thread_security = nil
|
147
|
+
|
148
|
+
if hash['thread_inherit']
|
149
|
+
thread_security = SECURITY_ATTRIBUTES.new
|
150
|
+
thread_security[:nLength] = 12
|
151
|
+
thread_security[:bInheritHandle] = 1
|
152
|
+
end
|
153
|
+
|
154
|
+
# Automatically handle stdin, stdout and stderr as either IO objects
|
155
|
+
# or file descriptors. This won't work for StringIO, however. It also
|
156
|
+
# will not work on JRuby because of the way it handles internal file
|
157
|
+
# descriptors.
|
158
|
+
#
|
159
|
+
['stdin', 'stdout', 'stderr'].each{ |io|
|
160
|
+
if si_hash[io]
|
161
|
+
if si_hash[io].respond_to?(:fileno)
|
162
|
+
handle = get_osfhandle(si_hash[io].fileno)
|
163
|
+
else
|
164
|
+
handle = get_osfhandle(si_hash[io])
|
165
|
+
end
|
166
|
+
|
167
|
+
if handle == INVALID_HANDLE_VALUE
|
168
|
+
ptr = FFI::MemoryPointer.new(:int)
|
169
|
+
|
170
|
+
if windows_version >= 6 && get_errno(ptr) == 0
|
171
|
+
errno = ptr.read_int
|
172
|
+
else
|
173
|
+
errno = FFI.errno
|
174
|
+
end
|
175
|
+
|
176
|
+
raise SystemCallError.new("get_osfhandle", errno)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Most implementations of Ruby on Windows create inheritable
|
180
|
+
# handles by default, but some do not. RF bug #26988.
|
181
|
+
bool = SetHandleInformation(
|
182
|
+
handle,
|
183
|
+
HANDLE_FLAG_INHERIT,
|
184
|
+
HANDLE_FLAG_INHERIT
|
185
|
+
)
|
186
|
+
|
187
|
+
raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
|
188
|
+
|
189
|
+
si_hash[io] = handle
|
190
|
+
si_hash['startf_flags'] ||= 0
|
191
|
+
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
192
|
+
hash['inherit'] = true
|
193
|
+
end
|
194
|
+
}
|
195
|
+
|
196
|
+
procinfo = PROCESS_INFORMATION.new
|
197
|
+
startinfo = STARTUPINFO.new
|
198
|
+
|
199
|
+
unless si_hash.empty?
|
200
|
+
startinfo[:cb] = startinfo.size
|
201
|
+
startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
|
202
|
+
startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
|
203
|
+
startinfo[:dwX] = si_hash['x'] if si_hash['x']
|
204
|
+
startinfo[:dwY] = si_hash['y'] if si_hash['y']
|
205
|
+
startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
|
206
|
+
startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
|
207
|
+
startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
|
208
|
+
startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
|
209
|
+
startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
|
210
|
+
startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
|
211
|
+
startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
|
212
|
+
startinfo[:cbReserved2] = 0
|
213
|
+
startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
|
214
|
+
startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
|
215
|
+
startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
|
216
|
+
end
|
217
|
+
|
218
|
+
app = nil
|
219
|
+
cmd = nil
|
220
|
+
|
221
|
+
# Convert strings to wide character strings if present
|
222
|
+
if hash['app_name']
|
223
|
+
app = hash['app_name'].to_wide_string
|
224
|
+
end
|
225
|
+
|
226
|
+
if hash['command_line']
|
227
|
+
cmd = hash['command_line'].to_wide_string
|
228
|
+
end
|
229
|
+
|
230
|
+
if hash['cwd']
|
231
|
+
cwd = hash['cwd'].to_wide_string
|
232
|
+
end
|
233
|
+
|
234
|
+
inherit = hash['inherit'] ? 1 : 0
|
235
|
+
|
236
|
+
if hash['with_logon']
|
237
|
+
logon = hash['with_logon'].to_wide_string
|
238
|
+
|
239
|
+
if hash['password']
|
240
|
+
passwd = hash['password'].to_wide_string
|
241
|
+
else
|
242
|
+
raise ArgumentError, 'password must be specified if with_logon is used'
|
243
|
+
end
|
244
|
+
|
245
|
+
if hash['domain']
|
246
|
+
domain = hash['domain'].to_wide_string
|
247
|
+
end
|
248
|
+
|
249
|
+
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
250
|
+
|
251
|
+
winsta_name = FFI::MemoryPointer.new(:char, 256)
|
252
|
+
return_size = FFI::MemoryPointer.new(:ulong)
|
253
|
+
|
254
|
+
bool = GetUserObjectInformationA(
|
255
|
+
GetProcessWindowStation(), # Window station handle
|
256
|
+
UOI_NAME, # Information to get
|
257
|
+
winsta_name, # Buffer to receive information
|
258
|
+
winsta_name.size, # Size of buffer
|
259
|
+
return_size # Size filled into buffer
|
260
|
+
)
|
261
|
+
|
262
|
+
unless bool
|
263
|
+
raise SystemCallError.new("GetUserObjectInformationA", FFI.errno)
|
264
|
+
end
|
265
|
+
|
266
|
+
winsta_name = winsta_name.read_string(return_size.read_ulong)
|
267
|
+
|
268
|
+
# If running in the service windows station must do a log on to get
|
269
|
+
# to the interactive desktop. Running process user account must have
|
270
|
+
# the 'Replace a process level token' permission. This is necessary as
|
271
|
+
# the logon (which happens with CreateProcessWithLogon) must have an
|
272
|
+
# interactive windows station to attach to, which is created with the
|
273
|
+
# LogonUser cann with the LOGON32_LOGON_INTERACTIVE flag.
|
274
|
+
if winsta_name =~ /^Service-0x0-.*$/i
|
275
|
+
token = FFI::MemoryPointer.new(:ulong)
|
276
|
+
|
277
|
+
bool = LogonUserW(
|
278
|
+
logon, # User
|
279
|
+
domain, # Domain
|
280
|
+
passwd, # Password
|
281
|
+
LOGON32_LOGON_INTERACTIVE, # Logon Type
|
282
|
+
LOGON32_PROVIDER_DEFAULT, # Logon Provider
|
283
|
+
token # User token handle
|
284
|
+
)
|
285
|
+
|
286
|
+
unless bool
|
287
|
+
raise SystemCallError.new("LogonUserW", FFI.errno)
|
288
|
+
end
|
289
|
+
|
290
|
+
token = token.read_ulong
|
291
|
+
|
292
|
+
begin
|
293
|
+
bool = CreateProcessAsUserW(
|
294
|
+
token, # User token handle
|
295
|
+
app, # App name
|
296
|
+
cmd, # Command line
|
297
|
+
process_security, # Process attributes
|
298
|
+
thread_security, # Thread attributes
|
299
|
+
inherit, # Inherit handles
|
300
|
+
hash['creation_flags'], # Creation Flags
|
301
|
+
env, # Environment
|
302
|
+
cwd, # Working directory
|
303
|
+
startinfo, # Startup Info
|
304
|
+
procinfo # Process Info
|
305
|
+
)
|
306
|
+
ensure
|
307
|
+
CloseHandle(token)
|
308
|
+
end
|
309
|
+
|
310
|
+
unless bool
|
311
|
+
raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno)
|
312
|
+
end
|
313
|
+
else
|
314
|
+
bool = CreateProcessWithLogonW(
|
315
|
+
logon, # User
|
316
|
+
domain, # Domain
|
317
|
+
passwd, # Password
|
318
|
+
LOGON_WITH_PROFILE, # Logon flags
|
319
|
+
app, # App name
|
320
|
+
cmd, # Command line
|
321
|
+
hash['creation_flags'], # Creation flags
|
322
|
+
env, # Environment
|
323
|
+
cwd, # Working directory
|
324
|
+
startinfo, # Startup Info
|
325
|
+
procinfo # Process Info
|
326
|
+
)
|
327
|
+
|
328
|
+
unless bool
|
329
|
+
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
else
|
333
|
+
bool = CreateProcessW(
|
334
|
+
app, # App name
|
335
|
+
cmd, # Command line
|
336
|
+
process_security, # Process attributes
|
337
|
+
thread_security, # Thread attributes
|
338
|
+
inherit, # Inherit handles?
|
339
|
+
hash['creation_flags'], # Creation flags
|
340
|
+
env, # Environment
|
341
|
+
cwd, # Working directory
|
342
|
+
startinfo, # Startup Info
|
343
|
+
procinfo # Process Info
|
344
|
+
)
|
345
|
+
|
346
|
+
unless bool
|
347
|
+
raise SystemCallError.new("CreateProcessW", FFI.errno)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# Automatically close the process and thread handles in the
|
352
|
+
# PROCESS_INFORMATION struct unless explicitly told not to.
|
353
|
+
if hash['close_handles']
|
354
|
+
CloseHandle(procinfo[:hProcess])
|
355
|
+
CloseHandle(procinfo[:hThread])
|
356
|
+
# Clear these fields so callers don't attempt to close the handle
|
357
|
+
# which can result in the wrong handle being closed or an
|
358
|
+
# exception in some circumstances.
|
359
|
+
procinfo[:hProcess] = 0
|
360
|
+
procinfo[:hThread] = 0
|
361
|
+
end
|
362
|
+
|
363
|
+
ProcessInfo.new(
|
364
|
+
procinfo[:hProcess],
|
365
|
+
procinfo[:hThread],
|
366
|
+
procinfo[:dwProcessId],
|
367
|
+
procinfo[:dwThreadId]
|
368
|
+
)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|