module Process

Constants

ProcessInfo
VALID_KEYS

Process.create(key => value, …) => ProcessInfo

This is a wrapper for the CreateProcess() function. It executes a process, returning a ProcessInfo struct. It accepts a hash as an argument. There are several primary keys:

  • command_line (this or app_name must be present)

  • app_name (default: nil)

  • inherit (default: false)

  • process_inherit (default: false)

  • thread_inherit (default: false)

  • creation_flags (default: 0)

  • cwd (default: Dir.pwd)

  • startup_info (default: nil)

  • environment (default: nil)

  • close_handles (default: true)

  • with_logon (default: nil)

  • domain (default: nil)

  • password (default: nil, mandatory if with_logon)

Of these, the 'command_line' or 'app_name' must be specified or an error is raised. Both may be set individually, but 'command_line' should be preferred if only one of them is set because it does not (necessarily) require an explicit path or extension to work.

The 'domain' and 'password' options are only relevent in the context of 'with_logon'. If 'with_logon' is set, then the 'password' option is mandatory.

The startup_info key takes a hash. Its keys are attributes that are part of the StartupInfo struct, and are generally only meaningful for GUI or console processes. See the documentation on CreateProcess() and the StartupInfo struct on MSDN for more information.

  • desktop

  • title

  • x

  • y

  • x_size

  • y_size

  • x_count_chars

  • y_count_chars

  • fill_attribute

  • sw_flags

  • startf_flags

  • stdin

  • stdout

  • stderr

Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby IO objects or file descriptors (i.e. a fileno). However, StringIO objects are not currently supported. Unfortunately, setting these is not currently an option for JRuby.

If 'stdin', 'stdout' or 'stderr' are specified, then the inherit value is automatically set to true and the Process::STARTF_USESTDHANDLES flag is automatically OR'd to the startf_flags value.

The ProcessInfo struct contains the following members:

  • process_handle - The handle to the newly created process.

  • thread_handle - The handle to the primary thread of the process.

  • process_id - Process ID.

  • thread_id - Thread ID.

If the 'close_handles' option is set to true (the default) then the process_handle and the thread_handle are automatically closed for you before the ProcessInfo struct is returned.

If the 'with_logon' option is set, then the process runs the specified executable file in the security context of the specified credentials.

VALID_SI_KEYS

Public Class Methods

create(args) click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
121 def create(args)
122   # Validate that args is a Hash
123   validate_args(args)
124 
125   initialize_defaults
126 
127   # Validate the keys, and convert symbols and case to lowercase strings.
128   validate_keys(args)
129 
130   # If the startup_info key is present, validate its subkeys
131   validate_startup_info if hash[:startup_info]
132 
133   # validates that 'app_name' or 'command_line' is set
134   validate_command_line
135 
136   if hash[:app_name] && !hash[:command_line]
137     hash[:command_line] = hash[:app_name]
138     hash[:app_name] = nil
139   end
140 
141   # Setup stdin, stdout and stderr handlers
142   setup_std_handlers
143 
144   if logon
145     create_process_with_logon
146   else
147     create_process
148   end
149 
150   # Automatically close the process and thread handles in the
151   # PROCESS_INFORMATION struct unless explicitly told not to.
152   if hash[:close_handles]
153     FFI::WIN32.CloseHandle(procinfo[:hProcess])
154     FFI::WIN32.CloseHandle(procinfo[:hThread])
155   end
156 
157   ProcessInfo.new(
158     procinfo[:hProcess],
159     procinfo[:hThread],
160     procinfo[:dwProcessId],
161     procinfo[:dwThreadId]
162   )
163 end
setpriority(kind, int, int_priority) click to toggle source

Sets the priority class for the specified process id int.

The kind parameter is ignored but present for API compatibility. You can only retrieve process information, not process group or user information, so it is effectively always Process::PRIO_PROCESS.

Possible int_priority values are:

  • Process::NORMAL_PRIORITY_CLASS

  • Process::IDLE_PRIORITY_CLASS

  • Process::HIGH_PRIORITY_CLASS

  • Process::REALTIME_PRIORITY_CLASS

  • Process::BELOW_NORMAL_PRIORITY_CLASS

  • Process::ABOVE_NORMAL_PRIORITY_CLASS

    # File lib/puppet/util/windows/monkey_patches/process.rb
182 def setpriority(kind, int, int_priority)
183   raise TypeError unless kind.is_a?(Integer)
184   raise TypeError unless int.is_a?(Integer)
185   raise TypeError unless int_priority.is_a?(Integer)
186 
187   int = Process.pid if int == 0
188   handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
189 
190   if handle == 0
191     raise SystemCallError, FFI.errno, "OpenProcess"
192   end
193 
194   begin
195     result = SetPriorityClass(handle, int_priority)
196     raise SystemCallError, FFI.errno, "SetPriorityClass" unless result
197   ensure
198     FFI::WIN32.CloseHandle(handle)
199   end
200 
201   return 0
202 end

Private Class Methods

app() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
254 def app
255   wide_string(hash[:app_name])
256 end
cmd() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
258 def cmd
259   wide_string(hash[:command_line])
260 end
create_process() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
395 def create_process
396   inherit = hash[:inherit] ? 1 : 0
397 
398   bool = CreateProcessW(
399     app,                    # App name
400     cmd,                    # Command line
401     process_security,       # Process attributes
402     thread_security,        # Thread attributes
403     inherit,                # Inherit handles?
404     hash[:creation_flags],  # Creation flags
405     env,                    # Environment
406     cwd,                    # Working directory
407     startinfo,              # Startup Info
408     procinfo                # Process Info
409   )
410 
411   raise SystemCallError.new('CreateProcess', FFI.errno) unless bool
412 end
create_process_with_logon() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
373 def create_process_with_logon
374   raise ArgumentError, 'password must be specified if with_logon is used' unless password
375 
376   hash[:creation_flags] |= CREATE_UNICODE_ENVIRONMENT
377 
378   bool = CreateProcessWithLogonW(
379     logon,                  # User
380     domain,                 # Domain
381     password,               # Password
382     LOGON_WITH_PROFILE,     # Logon flags
383     app,                    # App name
384     cmd,                    # Command line
385     hash[:creation_flags],  # Creation flags
386     env,                    # Environment
387     cwd,                    # Working directory
388     startinfo,              # Startup Info
389     procinfo                # Process Info
390   )
391 
392   raise SystemCallError.new('CreateProcessWithLogonW', FFI.errno) unless bool
393 end
cwd() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
262 def cwd
263   wide_string(hash[:cwd])
264 end
domain() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
274 def domain
275   wide_string(hash[:domain])
276 end
env() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
278 def env
279   env = hash[:environment]
280   return unless env
281 
282   env = env.split(File::PATH_SEPARATOR) unless env.respond_to?(:join)
283   env = env.map { |e| e + 0.chr }.join('') + 0.chr
284   env = wide_string(env) if hash[:with_logon]
285   env
286 end
hash() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
246 def hash
247   @hash ||= {}
248 end
initialize_defaults() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
206 def initialize_defaults
207   @hash = {
208     app_name: nil,
209     creation_flags: 0,
210     close_handles: true
211   }
212   @si_hash = nil
213   @procinfo = nil
214 end
logon() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
270 def logon
271   wide_string(hash[:with_logon])
272 end
password() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
266 def password
267   wide_string(hash[:password])
268 end
process_security() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
288 def process_security
289   return unless hash[:process_inherit]
290 
291   process_security = SECURITY_ATTRIBUTES.new
292   process_security[:nLength] = SECURITY_ATTRIBUTES.size
293   process_security[:bInheritHandle] = 1
294   process_security
295 end
procinfo() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
242 def procinfo
243   @procinfo ||= PROCESS_INFORMATION.new
244 end
setup_std_handlers() click to toggle source

Automatically handle stdin, stdout and stderr as either IO objects or file descriptors. This won't work for StringIO, however. It also will not work on JRuby because of the way it handles internal file descriptors.

    # File lib/puppet/util/windows/monkey_patches/process.rb
310 def setup_std_handlers
311   %i[stdin stdout stderr].each do |io|
312     next unless si_hash[io]
313 
314     handle = if si_hash[io].respond_to?(:fileno)
315                get_osfhandle(si_hash[io].fileno)
316              else
317                get_osfhandle(si_hash[io])
318              end
319 
320     if handle == INVALID_HANDLE_VALUE
321       ptr = FFI::MemoryPointer.new(:int)
322 
323       errno = if get_errno(ptr).zero?
324                 ptr.read_int
325               else
326                 FFI.errno
327               end
328 
329       raise SystemCallError.new('get_osfhandle', errno)
330     end
331 
332     # Most implementations of Ruby on Windows create inheritable
333     # handles by default, but some do not. RF bug #26988.
334     bool = SetHandleInformation(
335       handle,
336       HANDLE_FLAG_INHERIT,
337       HANDLE_FLAG_INHERIT
338     )
339 
340     raise SystemCallError.new('SetHandleInformation', FFI.errno) unless bool
341 
342     si_hash[io] = handle
343     si_hash[:startf_flags] ||= 0
344     si_hash[:startf_flags] |= STARTF_USESTDHANDLES
345     hash[:inherit] = true
346   end
347 end
si_hash() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
250 def si_hash
251   @si_hash ||= {}
252 end
startinfo() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
349 def startinfo
350   startinfo = STARTUPINFO.new
351 
352   return startinfo if si_hash.empty?
353 
354   startinfo[:cb]              = startinfo.size
355   startinfo[:lpDesktop]       = si_hash[:desktop] if si_hash[:desktop]
356   startinfo[:lpTitle]         = si_hash[:title] if si_hash[:title]
357   startinfo[:dwX]             = si_hash[:x] if si_hash[:x]
358   startinfo[:dwY]             = si_hash[:y] if si_hash[:y]
359   startinfo[:dwXSize]         = si_hash[:x_size] if si_hash[:x_size]
360   startinfo[:dwYSize]         = si_hash[:y_size] if si_hash[:y_size]
361   startinfo[:dwXCountChars]   = si_hash[:x_count_chars] if si_hash[:x_count_chars]
362   startinfo[:dwYCountChars]   = si_hash[:y_count_chars] if si_hash[:y_count_chars]
363   startinfo[:dwFillAttribute] = si_hash[:fill_attribute] if si_hash[:fill_attribute]
364   startinfo[:dwFlags]         = si_hash[:startf_flags] if si_hash[:startf_flags]
365   startinfo[:wShowWindow]     = si_hash[:sw_flags] if si_hash[:sw_flags]
366   startinfo[:cbReserved2]     = 0
367   startinfo[:hStdInput]       = si_hash[:stdin] if si_hash[:stdin]
368   startinfo[:hStdOutput]      = si_hash[:stdout] if si_hash[:stdout]
369   startinfo[:hStdError]       = si_hash[:stderr] if si_hash[:stderr]
370   startinfo
371 end
thread_security() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
297 def thread_security
298   return unless hash[:thread_inherit]
299 
300   thread_security = SECURITY_ATTRIBUTES.new
301   thread_security[:nLength] = SECURITY_ATTRIBUTES.size
302   thread_security[:bInheritHandle] = 1
303   thread_security
304 end
validate_args(args) click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
216 def validate_args(args)
217   raise TypeError, 'hash keyword arguments expected' unless args.is_a?(Hash)
218 end
validate_command_line() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
238 def validate_command_line
239   raise ArgumentError, 'command_line or app_name must be specified' unless hash[:app_name] || hash[:command_line]
240 end
validate_keys(args) click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
220 def validate_keys(args)
221   args.each do |key, val|
222     key = key.to_s.to_sym
223     raise ArgumentError, "invalid key '#{key}'" unless VALID_KEYS.include?(key)
224 
225     hash[key] = val
226   end
227 end
validate_startup_info() click to toggle source
    # File lib/puppet/util/windows/monkey_patches/process.rb
229 def validate_startup_info
230   hash[:startup_info].each do |key, val|
231     key = key.to_s.to_sym
232     raise ArgumentError, "invalid startup_info key '#{key}'" unless VALID_SI_KEYS.include?(key)
233 
234     si_hash[key] = val
235   end
236 end