ruby-pwsh 0.7.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -51,8 +51,10 @@ Function ConvertTo-CanonicalResult {
51
51
  $Value = 'Present'
52
52
  }
53
53
  else {
54
- if ($Value -is [string] -or $value -is [string[]]) {
55
- $Value = $Value
54
+ if ([string]::IsNullOrEmpty($value)) {
55
+ # While PowerShell can happily treat empty strings as valid for returning
56
+ # an undefined enum, Puppet expects undefined values to be nil.
57
+ $Value = $null
56
58
  }
57
59
 
58
60
  if ($Value.Count -eq 1 -and $Property.Definition -match '\\[\\]') {
@@ -113,7 +115,7 @@ Function ConvertTo-CanonicalResult {
113
115
  }
114
116
 
115
117
  if ($Property.Definition -match 'InstanceArray') {
116
- if ($Value.Count -lt 2) { $Value = @($Value) }
118
+ If ($Value.GetType().Name -notmatch '\[\]') { $Value = @($Value) }
117
119
  }
118
120
 
119
121
  $ResultObject.$PropertyName = $Value
@@ -3,6 +3,12 @@ Try {
3
3
  } catch {
4
4
  $Response.errormessage = $_.Exception.Message
5
5
  return ($Response | ConvertTo-Json -Compress)
6
+ } Finally {
7
+ If (![string]::IsNullOrEmpty($UnmungedPSModulePath)) {
8
+ # Reset the PSModulePath
9
+ [System.Environment]::SetEnvironmentVariable('PSModulePath', $UnmungedPSModulePath, [System.EnvironmentVariableTarget]::Machine)
10
+ $env:PSModulePath = [System.Environment]::GetEnvironmentVariable('PSModulePath', 'machine')
11
+ }
6
12
  }
7
13
 
8
14
  # keep the switch for when Test passes back changed properties
data/lib/pwsh.rb CHANGED
@@ -136,14 +136,14 @@ module Pwsh
136
136
  stdin, @stdout, @stderr, @ps_process = Open3.popen3("#{native_cmd} #{ps_args.join(' ')}")
137
137
  stdin.close
138
138
 
139
- # Puppet.debug "#{Time.now} #{cmd} is running as pid: #{@ps_process[:pid]}"
139
+ # TODO: Log a debug for "#{Time.now} #{cmd} is running as pid: #{@ps_process[:pid]}"
140
140
 
141
141
  # Wait up to 180 seconds in 0.2 second intervals to be able to open the pipe.
142
142
  # If the pipe_timeout is ever specified as less than the sleep interval it will
143
143
  # never try to connect to a pipe and error out as if a timeout occurred.
144
144
  sleep_interval = 0.2
145
145
  (pipe_timeout / sleep_interval).to_int.times do
146
- begin
146
+ begin # rubocop:disable Style/RedundantBegin
147
147
  @pipe = if Pwsh::Util.on_windows?
148
148
  # Pipe is opened in binary mode and must always <- always what??
149
149
  File.open(pipe_path, 'r+b')
@@ -157,7 +157,7 @@ module Pwsh
157
157
  end
158
158
  if @pipe.nil?
159
159
  # Tear down and kill the process if unable to connect to the pipe; failure to do so
160
- # results in zombie processes being left after the puppet run. We discovered that
160
+ # results in zombie processes being left after a caller run. We discovered that
161
161
  # closing @ps_process via .kill instead of using this method actually kills the
162
162
  # watcher and leaves an orphaned process behind. Failing to close stdout and stderr
163
163
  # also leaves clutter behind, so explicitly close those too.
@@ -166,7 +166,8 @@ module Pwsh
166
166
  Process.kill('KILL', @ps_process[:pid]) if @ps_process.alive?
167
167
  raise "Failure waiting for PowerShell process #{@ps_process[:pid]} to start pipe server"
168
168
  end
169
- # Puppet.debug "#{Time.now} PowerShell initialization complete for pid: #{@ps_process[:pid]}"
169
+
170
+ # TODO: Log a debug for "#{Time.now} PowerShell initialization complete for pid: #{@ps_process[:pid]}"
170
171
 
171
172
  at_exit { exit }
172
173
  end
@@ -211,42 +212,13 @@ module Pwsh
211
212
  out
212
213
  end
213
214
 
214
- # TODO: Is this needed in the code manager? When brought into the module, should this be
215
- # added as helper code leveraging this gem?
216
- # Executes PowerShell code using the settings from a populated Puppet Exec Resource Type
217
- # def execute_resource(powershell_code, working_dir, timeout_ms, environment)
218
- # working_dir = resource[:cwd]
219
- # if (!working_dir.nil?)
220
- # fail "Working directory '#{working_dir}' does not exist" unless File.directory?(working_dir)
221
- # end
222
- # timeout_ms = resource[:timeout].nil? ? nil : resource[:timeout] * 1000
223
- # environment_variables = resource[:environment].nil? ? [] : resource[:environment]
224
-
225
- # result = execute(powershell_code, timeout_ms, working_dir, environment_variables)
226
-
227
- # stdout = result[:stdout]
228
- # native_out = result[:native_out]
229
- # stderr = result[:stderr]
230
- # exit_code = result[:exit_code]
231
-
232
- # # unless stderr.nil?
233
- # # stderr.each { |e| Puppet.debug "STDERR: #{e.chop}" unless e.empty? }
234
- # # end
235
-
236
- # # Puppet.debug "STDERR: #{result[:errormessage]}" unless result[:errormessage].nil?
237
-
238
- # output = Puppet::Util::Execution::ProcessOutput.new(stdout.to_s + native_out.to_s, exit_code)
239
-
240
- # return output, output
241
- # end
242
-
243
215
  # Tear down the instance of the manager, shutting down the pipe and process.
244
216
  #
245
217
  # @return nil
246
218
  def exit
247
219
  @usable = false
248
220
 
249
- # Puppet.debug "Pwsh exiting..."
221
+ # TODO: Log a debug for "Pwsh exiting..."
250
222
 
251
223
  # Ask PowerShell pipe server to shutdown if its still running
252
224
  # rather than expecting the pipe.close to terminate it
@@ -313,27 +285,26 @@ module Pwsh
313
285
  env_name = Regexp.last_match(1)
314
286
  value = Regexp.last_match(2)
315
287
  if environment.include?(env_name) || environment.include?(env_name.to_sym)
316
- # Puppet.warning("Overriding environment setting '#{env_name}' with '#{value}'")
288
+ # TODO: log a warning for "Overriding environment setting '#{env_name}' with '#{value}'"
317
289
  end
318
290
  environment[env_name] = value
319
291
  else # rubocop:disable Style/EmptyElse
320
- # TODO: Implement logging
321
- # Puppet.warning("Cannot understand environment setting #{setting.inspect}")
292
+ # TODO: log a warning for "Cannot understand environment setting #{setting.inspect}"
322
293
  end
323
294
  end
324
295
  end
325
296
  # Convert the Ruby Hashtable into PowerShell syntax
326
- exec_environment_variables = '@{'
297
+ additional_environment_variables = '@{'
327
298
  unless environment.empty?
328
299
  environment.each do |name, value|
329
300
  # PowerShell escapes single quotes inside a single quoted string by just adding
330
301
  # another single quote i.e. a value of foo'bar turns into 'foo''bar' when single quoted.
331
302
  ps_name = name.gsub('\'', '\'\'')
332
303
  ps_value = value.gsub('\'', '\'\'')
333
- exec_environment_variables += " '#{ps_name}' = '#{ps_value}';"
304
+ additional_environment_variables += " '#{ps_name}' = '#{ps_value}';"
334
305
  end
335
306
  end
336
- exec_environment_variables += '}'
307
+ additional_environment_variables += '}'
337
308
 
338
309
  # PS Side expects Invoke-PowerShellUserCode is always the return value here
339
310
  # TODO: Refactor to use <<~ as soon as we can :sob:
@@ -344,7 +315,7 @@ $params = @{
344
315
  '@
345
316
  TimeoutMilliseconds = #{timeout_ms}
346
317
  WorkingDirectory = "#{working_dir}"
347
- ExecEnvironmentVariables = #{exec_environment_variables}
318
+ AdditionalEnvironmentVariables = #{additional_environment_variables}
348
319
  }
349
320
 
350
321
  Invoke-PowerShellUserCode @params
@@ -391,12 +362,15 @@ Invoke-PowerShellUserCode @params
391
362
 
392
363
  # If we're on Windows, try the default installation locations as a last resort.
393
364
  # https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-6#msi
365
+ # https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-7.1
394
366
  if Pwsh::Util.on_windows?
395
- # TODO: What about PS 7? or 8?
367
+ # TODO: What about PS 8?
396
368
  # TODO: Need to check on French/Turkish windows if ENV['PROGRAMFILES'] parses UTF8 names correctly
397
369
  # TODO: Need to ensure ENV['PROGRAMFILES'] is case insensitive, i.e. ENV['PROGRAMFiles'] should also resolve on Windows
398
370
  search_paths += ";#{ENV['PROGRAMFILES']}\\PowerShell\\6" \
399
- ";#{ENV['PROGRAMFILES(X86)']}\\PowerShell\\6"
371
+ ";#{ENV['PROGRAMFILES(X86)']}\\PowerShell\\6" \
372
+ ";#{ENV['PROGRAMFILES']}\\PowerShell\\7" \
373
+ ";#{ENV['PROGRAMFILES(X86)']}\\PowerShell\\7"
400
374
  end
401
375
  raise 'No paths discovered to search for Powershell!' if search_paths.split(File::PATH_SEPARATOR).empty?
402
376
 
@@ -543,7 +517,7 @@ Invoke-PowerShellUserCode @params
543
517
  def read_from_pipe(pipe, timeout = 0.1, &_block)
544
518
  if self.class.readable?(pipe, timeout)
545
519
  l = pipe.readpartial(4096)
546
- # Puppet.debug "#{Time.now} PIPE> #{l}"
520
+ # TODO: Log a debug for "#{Time.now} PIPE> #{l}"
547
521
  # Since readpartial may return a nil at EOF, skip returning that value
548
522
  yield l unless l.nil?
549
523
  end
@@ -601,7 +575,7 @@ Invoke-PowerShellUserCode @params
601
575
  buffer
602
576
  end
603
577
 
604
- # Puppet.debug "Waited #{Time.now - start_time} total seconds."
578
+ # TODO: Log a debug for "Waited #{Time.now - start_time} total seconds."
605
579
 
606
580
  # Block until sysread has completed or errors
607
581
  begin
data/lib/pwsh/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pwsh
4
4
  # The version of the ruby-pwsh gem
5
- VERSION = '0.7.2'
5
+ VERSION = '0.10.0'
6
6
  end
@@ -0,0 +1,302 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Collections.ObjectModel;
4
+ using System.Globalization;
5
+ using System.IO;
6
+ using System.Management.Automation;
7
+ using System.Management.Automation.Host;
8
+ using System.Security;
9
+ using System.Text;
10
+ using System.Threading;
11
+
12
+ namespace RubyPwsh
13
+ {
14
+ public class RubyPwshPSHostRawUserInterface : PSHostRawUserInterface
15
+ {
16
+ public RubyPwshPSHostRawUserInterface()
17
+ {
18
+ buffersize = new Size(120, 120);
19
+ backgroundcolor = ConsoleColor.Black;
20
+ foregroundcolor = ConsoleColor.White;
21
+ cursorposition = new Coordinates(0, 0);
22
+ cursorsize = 1;
23
+ }
24
+
25
+ private ConsoleColor backgroundcolor;
26
+ public override ConsoleColor BackgroundColor
27
+ {
28
+ get { return backgroundcolor; }
29
+ set { backgroundcolor = value; }
30
+ }
31
+
32
+ private Size buffersize;
33
+ public override Size BufferSize
34
+ {
35
+ get { return buffersize; }
36
+ set { buffersize = value; }
37
+ }
38
+
39
+ private Coordinates cursorposition;
40
+ public override Coordinates CursorPosition
41
+ {
42
+ get { return cursorposition; }
43
+ set { cursorposition = value; }
44
+ }
45
+
46
+ private int cursorsize;
47
+ public override int CursorSize
48
+ {
49
+ get { return cursorsize; }
50
+ set { cursorsize = value; }
51
+ }
52
+
53
+ private ConsoleColor foregroundcolor;
54
+ public override ConsoleColor ForegroundColor
55
+ {
56
+ get { return foregroundcolor; }
57
+ set { foregroundcolor = value; }
58
+ }
59
+
60
+ private Coordinates windowposition;
61
+ public override Coordinates WindowPosition
62
+ {
63
+ get { return windowposition; }
64
+ set { windowposition = value; }
65
+ }
66
+
67
+ private Size windowsize;
68
+ public override Size WindowSize
69
+ {
70
+ get { return windowsize; }
71
+ set { windowsize = value; }
72
+ }
73
+
74
+ private string windowtitle;
75
+ public override string WindowTitle
76
+ {
77
+ get { return windowtitle; }
78
+ set { windowtitle = value; }
79
+ }
80
+
81
+ public override bool KeyAvailable
82
+ {
83
+ get { return false; }
84
+ }
85
+
86
+ public override Size MaxPhysicalWindowSize
87
+ {
88
+ get { return new Size(165, 66); }
89
+ }
90
+
91
+ public override Size MaxWindowSize
92
+ {
93
+ get { return new Size(165, 66); }
94
+ }
95
+
96
+ public override void FlushInputBuffer()
97
+ {
98
+ throw new NotImplementedException();
99
+ }
100
+
101
+ public override BufferCell[,] GetBufferContents(Rectangle rectangle)
102
+ {
103
+ throw new NotImplementedException();
104
+ }
105
+
106
+ public override KeyInfo ReadKey(ReadKeyOptions options)
107
+ {
108
+ throw new NotImplementedException();
109
+ }
110
+
111
+ public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
112
+ {
113
+ throw new NotImplementedException();
114
+ }
115
+
116
+ public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
117
+ {
118
+ throw new NotImplementedException();
119
+ }
120
+
121
+ public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
122
+ {
123
+ throw new NotImplementedException();
124
+ }
125
+ }
126
+
127
+ public class RubyPwshPSHostUserInterface : PSHostUserInterface
128
+ {
129
+ private RubyPwshPSHostRawUserInterface _rawui;
130
+ private StringBuilder _sb;
131
+ private StringWriter _errWriter;
132
+ private StringWriter _outWriter;
133
+
134
+ public RubyPwshPSHostUserInterface()
135
+ {
136
+ _sb = new StringBuilder();
137
+ _errWriter = new StringWriter(new StringBuilder());
138
+ // NOTE: StringWriter / StringBuilder are not technically thread-safe
139
+ // but PowerShell Write-XXX cmdlets and System.Console.Out.WriteXXX
140
+ // should not be executed concurrently within PowerShell, so should be safe
141
+ _outWriter = new StringWriter(_sb);
142
+ }
143
+
144
+ public override PSHostRawUserInterface RawUI
145
+ {
146
+ get
147
+ {
148
+ if ( _rawui == null){
149
+ _rawui = new RubyPwshPSHostRawUserInterface();
150
+ }
151
+ return _rawui;
152
+ }
153
+ }
154
+
155
+ public void ResetConsoleStreams()
156
+ {
157
+ System.Console.SetError(_errWriter);
158
+ System.Console.SetOut(_outWriter);
159
+ }
160
+
161
+ public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
162
+ {
163
+ _sb.Append(value);
164
+ }
165
+
166
+ public override void Write(string value)
167
+ {
168
+ _sb.Append(value);
169
+ }
170
+
171
+ public override void WriteDebugLine(string message)
172
+ {
173
+ _sb.AppendLine("DEBUG: " + message);
174
+ }
175
+
176
+ public override void WriteErrorLine(string value)
177
+ {
178
+ _sb.AppendLine(value);
179
+ }
180
+
181
+ public override void WriteLine(string value)
182
+ {
183
+ _sb.AppendLine(value);
184
+ }
185
+
186
+ public override void WriteVerboseLine(string message)
187
+ {
188
+ _sb.AppendLine("VERBOSE: " + message);
189
+ }
190
+
191
+ public override void WriteWarningLine(string message)
192
+ {
193
+ _sb.AppendLine("WARNING: " + message);
194
+ }
195
+
196
+ public override void WriteProgress(long sourceId, ProgressRecord record)
197
+ {
198
+ }
199
+
200
+ public string Output
201
+ {
202
+ get
203
+ {
204
+ _outWriter.Flush();
205
+ string text = _outWriter.GetStringBuilder().ToString();
206
+ _outWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
207
+ return text;
208
+ }
209
+ }
210
+
211
+ public string StdErr
212
+ {
213
+ get
214
+ {
215
+ _errWriter.Flush();
216
+ string text = _errWriter.GetStringBuilder().ToString();
217
+ _errWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
218
+ return text;
219
+ }
220
+ }
221
+
222
+ public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions)
223
+ {
224
+ throw new NotImplementedException();
225
+ }
226
+
227
+ public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice)
228
+ {
229
+ throw new NotImplementedException();
230
+ }
231
+
232
+ public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
233
+ {
234
+ throw new NotImplementedException();
235
+ }
236
+
237
+ public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
238
+ {
239
+ throw new NotImplementedException();
240
+ }
241
+
242
+ public override string ReadLine()
243
+ {
244
+ throw new NotImplementedException();
245
+ }
246
+
247
+ public override SecureString ReadLineAsSecureString()
248
+ {
249
+ throw new NotImplementedException();
250
+ }
251
+ }
252
+
253
+ public class RubyPwshPSHost : PSHost
254
+ {
255
+ private Guid _hostId = Guid.NewGuid();
256
+ private bool shouldExit;
257
+ private int exitCode;
258
+
259
+ private readonly RubyPwshPSHostUserInterface _ui = new RubyPwshPSHostUserInterface();
260
+
261
+ public RubyPwshPSHost () {}
262
+
263
+ public bool ShouldExit { get { return this.shouldExit; } }
264
+ public int ExitCode { get { return this.exitCode; } }
265
+ public void ResetExitStatus()
266
+ {
267
+ this.exitCode = 0;
268
+ this.shouldExit = false;
269
+ }
270
+ public void ResetConsoleStreams()
271
+ {
272
+ _ui.ResetConsoleStreams();
273
+ }
274
+
275
+ public override Guid InstanceId { get { return _hostId; } }
276
+ public override string Name { get { return "RubyPwshPSHost"; } }
277
+ public override Version Version { get { return new Version(1, 1); } }
278
+ public override PSHostUserInterface UI
279
+ {
280
+ get { return _ui; }
281
+ }
282
+ public override CultureInfo CurrentCulture
283
+ {
284
+ get { return Thread.CurrentThread.CurrentCulture; }
285
+ }
286
+ public override CultureInfo CurrentUICulture
287
+ {
288
+ get { return Thread.CurrentThread.CurrentUICulture; }
289
+ }
290
+
291
+ public override void EnterNestedPrompt() { throw new NotImplementedException(); }
292
+ public override void ExitNestedPrompt() { throw new NotImplementedException(); }
293
+ public override void NotifyBeginApplication() { return; }
294
+ public override void NotifyEndApplication() { return; }
295
+
296
+ public override void SetShouldExit(int exitCode)
297
+ {
298
+ this.shouldExit = true;
299
+ this.exitCode = exitCode;
300
+ }
301
+ }
302
+ }