mixlib-shellout 3.2.7-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,629 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Author:: John Keiser (<jkeiser@chef.io>)
4
+ # Copyright:: Copyright (c) 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
+ require "ffi/win32/extensions"
22
+
23
+ # Add new constants for Logon
24
+ module Process::Constants
25
+
26
+ LOGON32_LOGON_INTERACTIVE = 0x00000002
27
+ LOGON32_LOGON_BATCH = 0x00000004
28
+ LOGON32_PROVIDER_DEFAULT = 0x00000000
29
+ UOI_NAME = 0x00000002
30
+
31
+ WAIT_OBJECT_0 = 0
32
+ WAIT_TIMEOUT = 0x102
33
+ WAIT_ABANDONED = 128
34
+ WAIT_ABANDONED_0 = WAIT_ABANDONED
35
+ WAIT_FAILED = 0xFFFFFFFF
36
+
37
+ ERROR_PRIVILEGE_NOT_HELD = 1314
38
+ ERROR_LOGON_TYPE_NOT_GRANTED = 0x569
39
+
40
+ # Only documented in Userenv.h ???
41
+ # - ZERO (type Local) is assumed, no docs found
42
+ WIN32_PROFILETYPE_LOCAL = 0x00
43
+ WIN32_PROFILETYPE_PT_TEMPORARY = 0x01
44
+ WIN32_PROFILETYPE_PT_ROAMING = 0x02
45
+ WIN32_PROFILETYPE_PT_MANDATORY = 0x04
46
+ WIN32_PROFILETYPE_PT_ROAMING_PREEXISTING = 0x08
47
+
48
+ # The environment block list ends with two nulls (\0\0).
49
+ ENVIRONMENT_BLOCK_ENDS = "\0\0".freeze
50
+ end
51
+
52
+ # Structs required for data handling
53
+ module Process::Structs
54
+
55
+ class PROFILEINFO < FFI::Struct
56
+ layout(
57
+ :dwSize, :dword,
58
+ :dwFlags, :dword,
59
+ :lpUserName, :pointer,
60
+ :lpProfilePath, :pointer,
61
+ :lpDefaultPath, :pointer,
62
+ :lpServerName, :pointer,
63
+ :lpPolicyPath, :pointer,
64
+ :hProfile, :handle
65
+ )
66
+ end
67
+
68
+ end
69
+
70
+ # Define the functions needed to check with Service windows station
71
+ module Process::Functions
72
+ ffi_lib :userenv
73
+
74
+ attach_pfunc :GetProfileType,
75
+ [:pointer], :bool
76
+
77
+ attach_pfunc :LoadUserProfileW,
78
+ %i{handle pointer}, :bool
79
+
80
+ attach_pfunc :UnloadUserProfile,
81
+ %i{handle handle}, :bool
82
+
83
+ attach_pfunc :CreateEnvironmentBlock,
84
+ %i{pointer ulong bool}, :bool
85
+
86
+ attach_pfunc :DestroyEnvironmentBlock,
87
+ %i{pointer}, :bool
88
+
89
+ ffi_lib :advapi32
90
+
91
+ attach_pfunc :LogonUserW,
92
+ %i{buffer_in buffer_in buffer_in ulong ulong pointer}, :bool
93
+
94
+ attach_pfunc :CreateProcessAsUserW,
95
+ %i{ulong buffer_in buffer_inout pointer pointer int
96
+ ulong buffer_in buffer_in pointer pointer}, :bool
97
+
98
+ ffi_lib :user32
99
+
100
+ attach_pfunc :GetProcessWindowStation,
101
+ [], :ulong
102
+
103
+ attach_pfunc :GetUserObjectInformationA,
104
+ %i{ulong uint buffer_out ulong pointer}, :bool
105
+ end
106
+
107
+ # Override Process.create to check for running in the Service window station and doing
108
+ # a full logon with LogonUser, instead of a CreateProcessWithLogon
109
+ # Cloned from https://github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb
110
+ # as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
111
+ module Process
112
+
113
+ class UnsupportedFeature < StandardError; end
114
+
115
+ # Explicitly reopen singleton class so that class/constant declarations from
116
+ # extensions are visible in Modules.nesting.
117
+ class << self
118
+
119
+ def create(args)
120
+ create3(args).first
121
+ end
122
+
123
+ def create3(args)
124
+ unless args.is_a?(Hash)
125
+ raise TypeError, "hash keyword arguments expected"
126
+ end
127
+
128
+ valid_keys = %w{
129
+ app_name command_line inherit creation_flags cwd environment
130
+ startup_info thread_inherit process_inherit close_handles with_logon
131
+ domain password elevated
132
+ }
133
+
134
+ valid_si_keys = %w{
135
+ startf_flags desktop title x y x_size y_size x_count_chars
136
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
137
+ }
138
+
139
+ # Set default values
140
+ hash = {
141
+ "app_name" => nil,
142
+ "creation_flags" => 0,
143
+ "close_handles" => true,
144
+ }
145
+
146
+ # Validate the keys, and convert symbols and case to lowercase strings.
147
+ args.each do |key, val|
148
+ key = key.to_s.downcase
149
+ unless valid_keys.include?(key)
150
+ raise ArgumentError, "invalid key '#{key}'"
151
+ end
152
+
153
+ hash[key] = val
154
+ end
155
+
156
+ si_hash = {}
157
+
158
+ # If the startup_info key is present, validate its subkeys
159
+ hash["startup_info"]&.each do |key, val|
160
+ key = key.to_s.downcase
161
+ unless valid_si_keys.include?(key)
162
+ raise ArgumentError, "invalid startup_info key '#{key}'"
163
+ end
164
+
165
+ si_hash[key] = val
166
+ end
167
+
168
+ # The +command_line+ key is mandatory unless the +app_name+ key
169
+ # is specified.
170
+ unless hash["command_line"]
171
+ if hash["app_name"]
172
+ hash["command_line"] = hash["app_name"]
173
+ hash["app_name"] = nil
174
+ else
175
+ raise ArgumentError, "command_line or app_name must be specified"
176
+ end
177
+ end
178
+
179
+ env = nil
180
+
181
+ # Retrieve the environment variables for the specified user.
182
+ if hash["with_logon"]
183
+ logon, passwd, domain = format_creds_from_hash(hash)
184
+ logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
185
+ token = logon_user(logon, domain, passwd, logon_type)
186
+ logon_ptr = FFI::MemoryPointer.from_string(logon)
187
+ profile = PROFILEINFO.new.tap do |dat|
188
+ dat[:dwSize] = dat.size
189
+ dat[:dwFlags] = 1
190
+ dat[:lpUserName] = logon_ptr
191
+ end
192
+
193
+ load_user_profile(token, profile.pointer)
194
+ env_list = retrieve_environment_variables(token)
195
+ end
196
+
197
+ # The env string should be passed as a string of ';' separated paths.
198
+ if hash["environment"]
199
+ env = env_list.nil? ? hash["environment"] : merge_env_variables(env_list, hash["environment"])
200
+
201
+ unless env.respond_to?(:join)
202
+ env = hash["environment"].split(File::PATH_SEPARATOR)
203
+ end
204
+
205
+ env = env.map { |e| e + 0.chr }.join("") + 0.chr
206
+ env.to_wide_string! if hash["with_logon"]
207
+ end
208
+
209
+ # Process SECURITY_ATTRIBUTE structure
210
+ process_security = nil
211
+
212
+ if hash["process_inherit"]
213
+ process_security = SECURITY_ATTRIBUTES.new
214
+ process_security[:nLength] = 12
215
+ process_security[:bInheritHandle] = 1
216
+ end
217
+
218
+ # Thread SECURITY_ATTRIBUTE structure
219
+ thread_security = nil
220
+
221
+ if hash["thread_inherit"]
222
+ thread_security = SECURITY_ATTRIBUTES.new
223
+ thread_security[:nLength] = 12
224
+ thread_security[:bInheritHandle] = 1
225
+ end
226
+
227
+ # Automatically handle stdin, stdout and stderr as either IO objects
228
+ # or file descriptors. This won't work for StringIO, however. It also
229
+ # will not work on JRuby because of the way it handles internal file
230
+ # descriptors.
231
+ #
232
+ %w{stdin stdout stderr}.each do |io|
233
+ if si_hash[io]
234
+ if si_hash[io].respond_to?(:fileno)
235
+ handle = get_osfhandle(si_hash[io].fileno)
236
+ else
237
+ handle = get_osfhandle(si_hash[io])
238
+ end
239
+
240
+ if handle == INVALID_HANDLE_VALUE
241
+ ptr = FFI::MemoryPointer.new(:int)
242
+
243
+ if windows_version >= 6 && get_errno(ptr) == 0
244
+ errno = ptr.read_int
245
+ else
246
+ errno = FFI.errno
247
+ end
248
+
249
+ raise SystemCallError.new("get_osfhandle", errno)
250
+ end
251
+
252
+ # Most implementations of Ruby on Windows create inheritable
253
+ # handles by default, but some do not. RF bug #26988.
254
+ bool = SetHandleInformation(
255
+ handle,
256
+ HANDLE_FLAG_INHERIT,
257
+ HANDLE_FLAG_INHERIT
258
+ )
259
+
260
+ raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
261
+
262
+ si_hash[io] = handle
263
+ si_hash["startf_flags"] ||= 0
264
+ si_hash["startf_flags"] |= STARTF_USESTDHANDLES
265
+ hash["inherit"] = true
266
+ end
267
+ end
268
+
269
+ procinfo = PROCESS_INFORMATION.new
270
+ startinfo = STARTUPINFO.new
271
+
272
+ unless si_hash.empty?
273
+ startinfo[:cb] = startinfo.size
274
+ startinfo[:lpDesktop] = si_hash["desktop"] if si_hash["desktop"]
275
+ startinfo[:lpTitle] = si_hash["title"] if si_hash["title"]
276
+ startinfo[:dwX] = si_hash["x"] if si_hash["x"]
277
+ startinfo[:dwY] = si_hash["y"] if si_hash["y"]
278
+ startinfo[:dwXSize] = si_hash["x_size"] if si_hash["x_size"]
279
+ startinfo[:dwYSize] = si_hash["y_size"] if si_hash["y_size"]
280
+ startinfo[:dwXCountChars] = si_hash["x_count_chars"] if si_hash["x_count_chars"]
281
+ startinfo[:dwYCountChars] = si_hash["y_count_chars"] if si_hash["y_count_chars"]
282
+ startinfo[:dwFillAttribute] = si_hash["fill_attribute"] if si_hash["fill_attribute"]
283
+ startinfo[:dwFlags] = si_hash["startf_flags"] if si_hash["startf_flags"]
284
+ startinfo[:wShowWindow] = si_hash["sw_flags"] if si_hash["sw_flags"]
285
+ startinfo[:cbReserved2] = 0
286
+ startinfo[:hStdInput] = si_hash["stdin"] if si_hash["stdin"]
287
+ startinfo[:hStdOutput] = si_hash["stdout"] if si_hash["stdout"]
288
+ startinfo[:hStdError] = si_hash["stderr"] if si_hash["stderr"]
289
+ end
290
+
291
+ app = nil
292
+ cmd = nil
293
+
294
+ # Convert strings to wide character strings if present
295
+ if hash["app_name"]
296
+ app = hash["app_name"].to_wide_string
297
+ end
298
+
299
+ if hash["command_line"]
300
+ cmd = hash["command_line"].to_wide_string
301
+ end
302
+
303
+ if hash["cwd"]
304
+ cwd = hash["cwd"].to_wide_string
305
+ end
306
+
307
+ inherit = hash["inherit"] ? 1 : 0
308
+
309
+ if hash["with_logon"]
310
+
311
+ logon, passwd, domain = format_creds_from_hash(hash)
312
+
313
+ hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT
314
+
315
+ winsta_name = get_windows_station_name
316
+
317
+ # If running in the service windows station must do a log on to get
318
+ # to the interactive desktop. The running process user account must have
319
+ # the 'Replace a process level token' permission. This is necessary as
320
+ # the logon (which happens with CreateProcessWithLogon) must have an
321
+ # interactive windows station to attach to, which is created with the
322
+ # LogonUser call with the LOGON32_LOGON_INTERACTIVE flag.
323
+ #
324
+ # User Access Control (UAC) only applies to interactive logons, so we
325
+ # can simulate running a command 'elevated' by running it under a separate
326
+ # logon as a batch process.
327
+ if hash["elevated"] || winsta_name =~ /^Service-0x0-.*$/i
328
+
329
+ logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
330
+ token = logon_user(logon, domain, passwd, logon_type)
331
+ logon_ptr = FFI::MemoryPointer.from_string(logon)
332
+ profile = PROFILEINFO.new.tap do |dat|
333
+ dat[:dwSize] = dat.size
334
+ dat[:dwFlags] = 1
335
+ dat[:lpUserName] = logon_ptr
336
+ end
337
+
338
+ if logon_has_roaming_profile?
339
+ msg = %w{
340
+ Mixlib does not currently support executing commands as users
341
+ configured with Roaming Profiles. [%s]
342
+ }.join(" ") % logon.encode("UTF-8").unpack("A*")
343
+ raise UnsupportedFeature.new(msg)
344
+ end
345
+
346
+ load_user_profile(token, profile.pointer)
347
+
348
+ create_process_as_user(token, app, cmd, process_security,
349
+ thread_security, inherit, hash["creation_flags"], env,
350
+ cwd, startinfo, procinfo)
351
+
352
+ else
353
+
354
+ create_process_with_logon(logon, domain, passwd, LOGON_WITH_PROFILE,
355
+ app, cmd, hash["creation_flags"], env, cwd, startinfo, procinfo)
356
+
357
+ end
358
+
359
+ else
360
+
361
+ create_process(app, cmd, process_security, thread_security, inherit,
362
+ hash["creation_flags"], env, cwd, startinfo, procinfo)
363
+
364
+ end
365
+
366
+ # Automatically close the process and thread handles in the
367
+ # PROCESS_INFORMATION struct unless explicitly told not to.
368
+ if hash["close_handles"]
369
+ CloseHandle(procinfo[:hProcess])
370
+ CloseHandle(procinfo[:hThread])
371
+ # Clear these fields so callers don't attempt to close the handle
372
+ # which can result in the wrong handle being closed or an
373
+ # exception in some circumstances.
374
+ procinfo[:hProcess] = 0
375
+ procinfo[:hThread] = 0
376
+ end
377
+
378
+ process = ProcessInfo.new(
379
+ procinfo[:hProcess],
380
+ procinfo[:hThread],
381
+ procinfo[:dwProcessId],
382
+ procinfo[:dwThreadId]
383
+ )
384
+
385
+ [ process, profile, token ]
386
+ end
387
+
388
+ # See Process::Constants::WIN32_PROFILETYPE
389
+ def logon_has_roaming_profile?
390
+ get_profile_type >= 2
391
+ end
392
+
393
+ def get_profile_type
394
+ ptr = FFI::MemoryPointer.new(:uint)
395
+ unless GetProfileType(ptr)
396
+ raise SystemCallError.new("GetProfileType", FFI.errno)
397
+ end
398
+
399
+ ptr.read_uint
400
+ end
401
+
402
+ def load_user_profile(token, profile_ptr)
403
+ unless LoadUserProfileW(token, profile_ptr)
404
+ raise SystemCallError.new("LoadUserProfileW", FFI.errno)
405
+ end
406
+
407
+ true
408
+ end
409
+
410
+ def unload_user_profile(token, profile)
411
+ if profile[:hProfile] == 0
412
+ warn "\n\nWARNING: Profile not loaded\n"
413
+ else
414
+ unless UnloadUserProfile(token, profile[:hProfile])
415
+ raise SystemCallError.new("UnloadUserProfile", FFI.errno)
416
+ end
417
+ end
418
+ true
419
+ end
420
+
421
+ # Retrieves the environment variables for the specified user.
422
+ #
423
+ # @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
424
+ # @param token [Integer] User token handle.
425
+ # @return [Boolean] true if successfully retrieves the environment variables for the specified user.
426
+ #
427
+ def create_environment_block(env_pointer, token)
428
+ unless CreateEnvironmentBlock(env_pointer, token, false)
429
+ raise SystemCallError.new("CreateEnvironmentBlock", FFI.errno)
430
+ end
431
+
432
+ true
433
+ end
434
+
435
+ # Frees environment variables created by the CreateEnvironmentBlock function.
436
+ #
437
+ # @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
438
+ # @return [Boolean] true if successfully frees environment variables created by the CreateEnvironmentBlock function.
439
+ #
440
+ def destroy_environment_block(env_pointer)
441
+ unless DestroyEnvironmentBlock(env_pointer)
442
+ raise SystemCallError.new("DestroyEnvironmentBlock", FFI.errno)
443
+ end
444
+
445
+ true
446
+ end
447
+
448
+ def create_process_as_user(token, app, cmd, process_security,
449
+ thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo)
450
+
451
+ bool = CreateProcessAsUserW(
452
+ token, # User token handle
453
+ app, # App name
454
+ cmd, # Command line
455
+ process_security, # Process attributes
456
+ thread_security, # Thread attributes
457
+ inherit, # Inherit handles
458
+ creation_flags, # Creation Flags
459
+ env, # Environment
460
+ cwd, # Working directory
461
+ startinfo, # Startup Info
462
+ procinfo # Process Info
463
+ )
464
+
465
+ unless bool
466
+ msg = case FFI.errno
467
+ when ERROR_PRIVILEGE_NOT_HELD
468
+ [
469
+ %{CreateProcessAsUserW (User '%s' must hold the 'Replace a process},
470
+ %{level token' and 'Adjust Memory Quotas for a process' permissions.},
471
+ %{Logoff the user after adding this right to make it effective.)},
472
+ ].join(" ") % ::ENV["USERNAME"]
473
+ else
474
+ "CreateProcessAsUserW failed."
475
+ end
476
+ raise SystemCallError.new(msg, FFI.errno)
477
+ end
478
+ end
479
+
480
+ def create_process_with_logon(logon, domain, passwd, logon_flags, app, cmd,
481
+ creation_flags, env, cwd, startinfo, procinfo)
482
+
483
+ bool = CreateProcessWithLogonW(
484
+ logon, # User
485
+ domain, # Domain
486
+ passwd, # Password
487
+ logon_flags, # Logon flags
488
+ app, # App name
489
+ cmd, # Command line
490
+ creation_flags, # Creation flags
491
+ env, # Environment
492
+ cwd, # Working directory
493
+ startinfo, # Startup Info
494
+ procinfo # Process Info
495
+ )
496
+
497
+ unless bool
498
+ raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
499
+ end
500
+ end
501
+
502
+ def create_process(app, cmd, process_security, thread_security, inherit,
503
+ creation_flags, env, cwd, startinfo, procinfo)
504
+
505
+ bool = CreateProcessW(
506
+ app, # App name
507
+ cmd, # Command line
508
+ process_security, # Process attributes
509
+ thread_security, # Thread attributes
510
+ inherit, # Inherit handles?
511
+ creation_flags, # Creation flags
512
+ env, # Environment
513
+ cwd, # Working directory
514
+ startinfo, # Startup Info
515
+ procinfo # Process Info
516
+ )
517
+
518
+ unless bool
519
+ raise SystemCallError.new("CreateProcessW", FFI.errno)
520
+ end
521
+ end
522
+
523
+ def logon_user(user, domain, passwd, type, provider = LOGON32_PROVIDER_DEFAULT)
524
+ token = FFI::MemoryPointer.new(:ulong)
525
+
526
+ bool = LogonUserW(
527
+ user, # User
528
+ domain, # Domain
529
+ passwd, # Password
530
+ type, # Logon Type
531
+ provider, # Logon Provider
532
+ token # User token handle
533
+ )
534
+
535
+ unless bool
536
+ if (FFI.errno == ERROR_LOGON_TYPE_NOT_GRANTED) && (type == LOGON32_LOGON_BATCH)
537
+ user_utf8 = user.encode( "UTF-8", invalid: :replace, undef: :replace, replace: "" ).delete("\0")
538
+ raise SystemCallError.new("LogonUserW (User '#{user_utf8}' must hold 'Log on as a batch job' permissions.)", FFI.errno)
539
+ else
540
+ raise SystemCallError.new("LogonUserW", FFI.errno)
541
+ end
542
+ end
543
+
544
+ token.read_ulong
545
+ end
546
+
547
+ def get_windows_station_name
548
+ winsta_name = FFI::MemoryPointer.new(:char, 256)
549
+ return_size = FFI::MemoryPointer.new(:ulong)
550
+
551
+ bool = GetUserObjectInformationA(
552
+ GetProcessWindowStation(), # Window station handle
553
+ UOI_NAME, # Information to get
554
+ winsta_name, # Buffer to receive information
555
+ winsta_name.size, # Size of buffer
556
+ return_size # Size filled into buffer
557
+ )
558
+
559
+ unless bool
560
+ raise SystemCallError.new("GetUserObjectInformationA", FFI.errno)
561
+ end
562
+
563
+ winsta_name.read_string(return_size.read_ulong)
564
+ end
565
+
566
+ def format_creds_from_hash(hash)
567
+ logon = hash["with_logon"].to_wide_string
568
+
569
+ if hash["password"]
570
+ passwd = hash["password"].to_wide_string
571
+ else
572
+ raise ArgumentError, "password must be specified if with_logon is used"
573
+ end
574
+
575
+ if hash["domain"]
576
+ domain = hash["domain"].to_wide_string
577
+ end
578
+
579
+ [ logon, passwd, domain ]
580
+ end
581
+
582
+ # Retrieves the environment variables for the specified user.
583
+ #
584
+ # @param token [Integer] User token handle.
585
+ # @return env_list [Array<String>] Environment variables of specified user.
586
+ #
587
+ def retrieve_environment_variables(token)
588
+ env_list = []
589
+ env_pointer = FFI::MemoryPointer.new(:pointer)
590
+ create_environment_block(env_pointer, token)
591
+ str_ptr = env_pointer.read_pointer
592
+ offset = 0
593
+ loop do
594
+ new_str_pointer = str_ptr + offset
595
+ break if new_str_pointer.read_string(2) == ENVIRONMENT_BLOCK_ENDS
596
+
597
+ environment = new_str_pointer.read_wstring
598
+ env_list << environment
599
+ offset = offset + environment.length * 2 + 2
600
+ end
601
+
602
+ # To free the buffer when we have finished with the environment block
603
+ destroy_environment_block(str_ptr)
604
+ env_list
605
+ end
606
+
607
+ # Merge environment variables of specified user and current environment variables.
608
+ #
609
+ # @param fetched_env [Array<String>] environment variables of specified user.
610
+ # @param current_env [Array<String>] current environment variables.
611
+ # @return [Array<String>] Merged environment variables.
612
+ #
613
+ def merge_env_variables(fetched_env, current_env)
614
+ env_hash_1 = environment_list_to_hash(fetched_env)
615
+ env_hash_2 = environment_list_to_hash(current_env)
616
+ merged_env = env_hash_2.merge(env_hash_1)
617
+ merged_env.map { |k, v| "#{k}=#{v}" }
618
+ end
619
+
620
+ # Convert an array to a hash.
621
+ #
622
+ # @param env_var [Array<String>] Environment variables.
623
+ # @return [Hash] Converted an array to hash.
624
+ #
625
+ def environment_list_to_hash(env_var)
626
+ Hash[ env_var.map { |pair| pair.split("=", 2) } ]
627
+ end
628
+ end
629
+ end