ruby-pwsh 0.8.0 → 0.9.0

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.
@@ -15,371 +15,48 @@ param (
15
15
 
16
16
  $script:EmitDebugOutput = $EmitDebugOutput
17
17
  # Necessary for [System.Console]::Error.WriteLine to roundtrip with UTF-8
18
+ # Need to ensure we ignore encoding from other places and are consistent internally
18
19
  [System.Console]::OutputEncoding = $Encoding
19
20
 
20
- $hostSource = @"
21
- using System;
22
- using System.Collections.Generic;
23
- using System.Collections.ObjectModel;
24
- using System.Globalization;
25
- using System.IO;
26
- using System.Management.Automation;
27
- using System.Management.Automation.Host;
28
- using System.Security;
29
- using System.Text;
30
- using System.Threading;
31
-
32
- namespace Puppet
33
- {
34
- public class PuppetPSHostRawUserInterface : PSHostRawUserInterface
35
- {
36
- public PuppetPSHostRawUserInterface()
37
- {
38
- buffersize = new Size(120, 120);
39
- backgroundcolor = ConsoleColor.Black;
40
- foregroundcolor = ConsoleColor.White;
41
- cursorposition = new Coordinates(0, 0);
42
- cursorsize = 1;
43
- }
44
-
45
- private ConsoleColor backgroundcolor;
46
- public override ConsoleColor BackgroundColor
47
- {
48
- get { return backgroundcolor; }
49
- set { backgroundcolor = value; }
50
- }
51
-
52
- private Size buffersize;
53
- public override Size BufferSize
54
- {
55
- get { return buffersize; }
56
- set { buffersize = value; }
57
- }
58
-
59
- private Coordinates cursorposition;
60
- public override Coordinates CursorPosition
61
- {
62
- get { return cursorposition; }
63
- set { cursorposition = value; }
64
- }
65
-
66
- private int cursorsize;
67
- public override int CursorSize
68
- {
69
- get { return cursorsize; }
70
- set { cursorsize = value; }
71
- }
72
-
73
- private ConsoleColor foregroundcolor;
74
- public override ConsoleColor ForegroundColor
75
- {
76
- get { return foregroundcolor; }
77
- set { foregroundcolor = value; }
78
- }
79
-
80
- private Coordinates windowposition;
81
- public override Coordinates WindowPosition
82
- {
83
- get { return windowposition; }
84
- set { windowposition = value; }
85
- }
86
-
87
- private Size windowsize;
88
- public override Size WindowSize
89
- {
90
- get { return windowsize; }
91
- set { windowsize = value; }
92
- }
93
-
94
- private string windowtitle;
95
- public override string WindowTitle
96
- {
97
- get { return windowtitle; }
98
- set { windowtitle = value; }
99
- }
100
-
101
- public override bool KeyAvailable
102
- {
103
- get { return false; }
104
- }
105
-
106
- public override Size MaxPhysicalWindowSize
107
- {
108
- get { return new Size(165, 66); }
109
- }
110
-
111
- public override Size MaxWindowSize
112
- {
113
- get { return new Size(165, 66); }
114
- }
115
-
116
- public override void FlushInputBuffer()
117
- {
118
- throw new NotImplementedException();
119
- }
120
-
121
- public override BufferCell[,] GetBufferContents(Rectangle rectangle)
122
- {
123
- throw new NotImplementedException();
124
- }
125
-
126
- public override KeyInfo ReadKey(ReadKeyOptions options)
127
- {
128
- throw new NotImplementedException();
129
- }
130
-
131
- public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
132
- {
133
- throw new NotImplementedException();
134
- }
135
-
136
- public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
137
- {
138
- throw new NotImplementedException();
139
- }
140
-
141
- public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
142
- {
143
- throw new NotImplementedException();
144
- }
145
- }
146
-
147
- public class PuppetPSHostUserInterface : PSHostUserInterface
148
- {
149
- private PuppetPSHostRawUserInterface _rawui;
150
- private StringBuilder _sb;
151
- private StringWriter _errWriter;
152
- private StringWriter _outWriter;
153
-
154
- public PuppetPSHostUserInterface()
155
- {
156
- _sb = new StringBuilder();
157
- _errWriter = new StringWriter(new StringBuilder());
158
- // NOTE: StringWriter / StringBuilder are not technically thread-safe
159
- // but PowerShell Write-XXX cmdlets and System.Console.Out.WriteXXX
160
- // should not be executed concurrently within PowerShell, so should be safe
161
- _outWriter = new StringWriter(_sb);
162
- }
163
-
164
- public override PSHostRawUserInterface RawUI
165
- {
166
- get
167
- {
168
- if ( _rawui == null){
169
- _rawui = new PuppetPSHostRawUserInterface();
170
- }
171
- return _rawui;
172
- }
173
- }
174
-
175
- public void ResetConsoleStreams()
176
- {
177
- System.Console.SetError(_errWriter);
178
- System.Console.SetOut(_outWriter);
179
- }
180
-
181
- public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
182
- {
183
- _sb.Append(value);
184
- }
185
-
186
- public override void Write(string value)
187
- {
188
- _sb.Append(value);
189
- }
190
-
191
- public override void WriteDebugLine(string message)
192
- {
193
- _sb.AppendLine("DEBUG: " + message);
194
- }
195
-
196
- public override void WriteErrorLine(string value)
197
- {
198
- _sb.AppendLine(value);
199
- }
200
-
201
- public override void WriteLine(string value)
202
- {
203
- _sb.AppendLine(value);
204
- }
205
-
206
- public override void WriteVerboseLine(string message)
207
- {
208
- _sb.AppendLine("VERBOSE: " + message);
209
- }
210
-
211
- public override void WriteWarningLine(string message)
212
- {
213
- _sb.AppendLine("WARNING: " + message);
214
- }
215
-
216
- public override void WriteProgress(long sourceId, ProgressRecord record)
217
- {
218
- }
219
-
220
- public string Output
221
- {
222
- get
223
- {
224
- _outWriter.Flush();
225
- string text = _outWriter.GetStringBuilder().ToString();
226
- _outWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
227
- return text;
228
- }
229
- }
230
-
231
- public string StdErr
232
- {
233
- get
234
- {
235
- _errWriter.Flush();
236
- string text = _errWriter.GetStringBuilder().ToString();
237
- _errWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
238
- return text;
239
- }
240
- }
241
-
242
- public override Dictionary<string, PSObject> Prompt(string caption, string message, Collection<FieldDescription> descriptions)
243
- {
244
- throw new NotImplementedException();
245
- }
246
-
247
- public override int PromptForChoice(string caption, string message, Collection<ChoiceDescription> choices, int defaultChoice)
248
- {
249
- throw new NotImplementedException();
250
- }
251
-
252
- public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
253
- {
254
- throw new NotImplementedException();
255
- }
256
-
257
- public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
258
- {
259
- throw new NotImplementedException();
260
- }
261
-
262
- public override string ReadLine()
263
- {
264
- throw new NotImplementedException();
265
- }
266
-
267
- public override SecureString ReadLineAsSecureString()
268
- {
269
- throw new NotImplementedException();
270
- }
271
- }
272
-
273
- public class PuppetPSHost : PSHost
274
- {
275
- private Guid _hostId = Guid.NewGuid();
276
- private bool shouldExit;
277
- private int exitCode;
278
-
279
- private readonly PuppetPSHostUserInterface _ui = new PuppetPSHostUserInterface();
280
-
281
- public PuppetPSHost () {}
282
-
283
- public bool ShouldExit { get { return this.shouldExit; } }
284
- public int ExitCode { get { return this.exitCode; } }
285
- public void ResetExitStatus()
286
- {
287
- this.exitCode = 0;
288
- this.shouldExit = false;
289
- }
290
- public void ResetConsoleStreams()
291
- {
292
- _ui.ResetConsoleStreams();
293
- }
294
-
295
- public override Guid InstanceId { get { return _hostId; } }
296
- public override string Name { get { return "PuppetPSHost"; } }
297
- public override Version Version { get { return new Version(1, 1); } }
298
- public override PSHostUserInterface UI
299
- {
300
- get { return _ui; }
301
- }
302
- public override CultureInfo CurrentCulture
303
- {
304
- get { return Thread.CurrentThread.CurrentCulture; }
305
- }
306
- public override CultureInfo CurrentUICulture
307
- {
308
- get { return Thread.CurrentThread.CurrentUICulture; }
309
- }
310
-
311
- public override void EnterNestedPrompt() { throw new NotImplementedException(); }
312
- public override void ExitNestedPrompt() { throw new NotImplementedException(); }
313
- public override void NotifyBeginApplication() { return; }
314
- public override void NotifyEndApplication() { return; }
315
-
316
- public override void SetShouldExit(int exitCode)
317
- {
318
- this.shouldExit = true;
319
- this.exitCode = exitCode;
320
- }
321
- }
322
- }
323
- "@
21
+ $TemplateFolderPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Path
22
+ $hostSource = Get-Content -Path "$TemplateFolderPath/RubyPwsh.cs" -Raw
324
23
 
24
+ # Load the Custom PowerShell Host CSharp code
325
25
  Add-Type -TypeDefinition $hostSource -Language CSharp
26
+
27
+ # Cache the current directory as the working directory for the Dynamic PowerShell session
326
28
  $global:DefaultWorkingDirectory = (Get-Location -PSProvider FileSystem).Path
327
29
 
30
+ # Cache initial Environment Variables and values prior to any munging:
31
+ $global:CachedEnvironmentVariables = Get-ChildItem -Path Env:\
32
+
328
33
  #this is a string so we can import into our dynamic PS instance
329
34
  $global:ourFunctions = @'
330
- function Get-ProcessEnvironmentVariables
331
- {
332
- $processVars = [Environment]::GetEnvironmentVariables('Process').Keys |
333
- % -Begin { $h = @{} } -Process { $h.$_ = (Get-Item Env:\$_).Value } -End { $h }
334
-
335
- # eliminate Machine / User vars so that we have only process vars
336
- 'Machine', 'User' |
337
- % { [Environment]::GetEnvironmentVariables($_).GetEnumerator() } |
338
- ? { $processVars.ContainsKey($_.Name) -and ($processVars[$_.Name] -eq $_.Value) } |
339
- % { $processVars.Remove($_.Name) }
340
-
341
- $processVars.GetEnumerator() | Sort-Object Name
342
- }
343
-
344
- function Reset-ProcessEnvironmentVariables
345
- {
346
- param($processVars)
347
-
348
- # query Machine vars from registry, ensuring expansion EXCEPT for PATH
349
- $vars = [Environment]::GetEnvironmentVariables('Machine').GetEnumerator() |
350
- % -Begin { $h = @{} } -Process { $v = if ($_.Name -eq 'Path') { $_.Value } else { [Environment]::GetEnvironmentVariable($_.Name, 'Machine') }; $h."$($_.Name)" = $v } -End { $h }
351
-
352
- # query User vars from registry, ensuring expansion EXCEPT for PATH
353
- [Environment]::GetEnvironmentVariables('User').GetEnumerator() | % {
354
- if ($_.Name -eq 'Path') { $vars[$_.Name] += ';' + $_.Value }
355
- else
356
- {
357
- $value = [Environment]::GetEnvironmentVariable($_.Name, 'User')
358
- $vars[$_.Name] = $value
359
- }
360
- }
361
-
362
- $processVars.GetEnumerator() | % { $vars[$_.Name] = $_.Value }
35
+ function Reset-ProcessEnvironmentVariables {
36
+ param($CachedEnvironmentVariables)
363
37
 
38
+ # Delete existing environment variables
364
39
  Remove-Item -Path Env:\* -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Recurse
365
40
 
366
- $vars.GetEnumerator() | % { Set-Item -Path "Env:\$($_.Name)" -Value $_.Value }
41
+ # Re-add the cached environment variables
42
+ $CachedEnvironmentVariables |
43
+ ForEach-Object -Process { Set-Item -Path "Env:\$($_.Name)" -Value $_.Value }
367
44
  }
368
45
 
369
- function Reset-ProcessPowerShellVariables
370
- {
46
+ function Reset-ProcessPowerShellVariables {
371
47
  param($psVariables)
372
- $psVariables | %{
373
- $tempVar = $_
374
- if(-not(Get-Variable -Name $_.Name -ErrorAction SilentlyContinue)){
375
- New-Variable -Name $_.Name -Value $_.Value -Description $_.Description -Option $_.Options -Visibility $_.Visibility
48
+
49
+ $psVariables |
50
+ ForEach-Object -Process {
51
+ $tempVar = $_
52
+ if (-not(Get-Variable -Name $_.Name -ErrorAction SilentlyContinue)) {
53
+ New-Variable -Name $_.Name -Value $_.Value -Description $_.Description -Option $_.Options -Visibility $_.Visibility
54
+ }
376
55
  }
377
- }
378
56
  }
379
57
  '@
380
58
 
381
- function Invoke-PowerShellUserCode
382
- {
59
+ function Invoke-PowerShellUserCode {
383
60
  [CmdletBinding()]
384
61
  param(
385
62
  [String]
@@ -392,37 +69,47 @@ function Invoke-PowerShellUserCode
392
69
  $WorkingDirectory,
393
70
 
394
71
  [Hashtable]
395
- $ExecEnvironmentVariables
72
+ $AdditionalEnvironmentVariables
396
73
  )
397
74
 
75
+ # Instantiate the PowerShell Host and a new runspace to use if one is not already defined.
398
76
  if ($global:runspace -eq $null){
399
77
  # CreateDefault2 requires PS3
78
+ # Setup Initial Session State - can be modified later, defaults to only core PowerShell
79
+ # commands loaded/available. Everything else will dynamically load when needed.
400
80
  if ([System.Management.Automation.Runspaces.InitialSessionState].GetMethod('CreateDefault2')){
401
81
  $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2()
402
- }else{
82
+ } else {
403
83
  $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
404
84
  }
405
85
 
406
- $global:puppetPSHost = New-Object Puppet.PuppetPSHost
407
- $global:runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($global:puppetPSHost, $sessionState)
86
+ $global:RubyPwshPSHost = New-Object RubyPwsh.RubyPwshPSHost
87
+ $global:runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($global:RubyPwshPSHost, $sessionState)
408
88
  $global:runspace.Open()
409
89
  }
410
90
 
411
- try
412
- {
91
+ try {
92
+ # Reset the PowerShell handle, exit status, and streams.
413
93
  $ps = $null
414
- $global:puppetPSHost.ResetExitStatus()
415
- $global:puppetPSHost.ResetConsoleStreams()
94
+ $global:RubyPwshPSHost.ResetExitStatus()
95
+ $global:RubyPwshPSHost.ResetConsoleStreams()
416
96
 
97
+ # This resets the variables from prior runs, clearing them from memory.
417
98
  if ($PSVersionTable.PSVersion -ge [Version]'3.0') {
418
99
  $global:runspace.ResetRunspaceState()
419
100
  }
420
101
 
102
+ # Create a new instance of the PowerShell handle and drop into our reused runspace.
421
103
  $ps = [System.Management.Automation.PowerShell]::Create()
422
104
  $ps.Runspace = $global:runspace
105
+
106
+ # Preload our own functions; this could be moved into the array of startup scripts in the
107
+ # InitialSessionState, once implemented.
423
108
  [Void]$ps.AddScript($global:ourFunctions)
424
109
  $ps.Invoke()
425
110
 
111
+ # Set the working directory for the runspace; if not specified, use default; If it doesn't
112
+ # exist, terminate the execution and report the error back.
426
113
  if ([string]::IsNullOrEmpty($WorkingDirectory)) {
427
114
  [Void]$ps.Runspace.SessionStateProxy.Path.SetLocation($global:DefaultWorkingDirectory)
428
115
  } else {
@@ -430,13 +117,16 @@ function Invoke-PowerShellUserCode
430
117
  [Void]$ps.Runspace.SessionStateProxy.Path.SetLocation($WorkingDirectory)
431
118
  }
432
119
 
433
- if(!$global:environmentVariables){
434
- $ps.Commands.Clear()
435
- $global:environmentVariables = $ps.AddCommand('Get-ProcessEnvironmentVariables').Invoke()
436
- }
120
+ # Reset the environment variables to those cached at the instantiation of the PowerShell Host.
121
+ $ps.Commands.Clear()
122
+ [Void]$ps.AddCommand('Reset-ProcessEnvironmentVariables').AddParameter('CachedEnvironmentVariables', $global:CachedEnvironmentVariables)
123
+ $ps.Invoke()
437
124
 
438
- if($PSVersionTable.PSVersion -le [Version]'2.0'){
439
- if(!$global:psVariables){
125
+ # This code is the companion to the code at L403-405 and clears variables from prior runs.
126
+ # Because ResetRunspaceState does not work on v2 and earlier, it must be called here, after
127
+ # a new handle to PowerShell is created in prior steps.
128
+ if ($PSVersionTable.PSVersion -le [Version]'2.0'){
129
+ if (-not $global:psVariables){
440
130
  $global:psVariables = $ps.AddScript('Get-Variable').Invoke()
441
131
  }
442
132
 
@@ -449,110 +139,125 @@ function Invoke-PowerShellUserCode
449
139
  $ps.Invoke()
450
140
  }
451
141
 
452
- $ps.Commands.Clear()
453
- [Void]$ps.AddCommand('Reset-ProcessEnvironmentVariables').AddParameter('processVars', $global:environmentVariables)
454
- $ps.Invoke()
455
-
456
- # Set any exec level environment variables
457
- if ($ExecEnvironmentVariables -ne $null) {
458
- $ExecEnvironmentVariables.GetEnumerator() | % { Set-Item -Path "Env:\$($_.Name)" -Value $_.Value }
142
+ # Set any provided environment variables
143
+ if ($AdditionalEnvironmentVariables -ne $null) {
144
+ $AdditionalEnvironmentVariables.GetEnumerator() |
145
+ ForEach-Object -Process { Set-Item -Path "Env:\$($_.Name)" -Value $_.Value }
459
146
  }
460
147
 
461
- # we clear the commands before each new command
462
- # to avoid command pollution
148
+ # We clear the commands before each new command to avoid command pollution This does not need
149
+ # to be a single command, it works the same if you pass a string with multiple lines of
150
+ # PowerShell code. The user supplies a string and this gives it to the Host to execute.
463
151
  $ps.Commands.Clear()
464
152
  [Void]$ps.AddScript($Code)
465
153
 
466
- # out-default and MergeMyResults takes all output streams
467
- # and writes it to the PSHost we create
468
- # this needs to be the last thing executed
469
- [void]$ps.AddCommand("out-default");
154
+ # Out-Default and MergeMyResults takes all output streams and writes it to the PowerShell Host
155
+ # we create this needs to be the last thing executed.
156
+ [void]$ps.AddCommand("out-default")
470
157
 
471
- # if the call operator & established an exit code, exit with it
158
+ # if the call operator & established an exit code, exit with it; if this is NOT included, exit
159
+ # codes for scripts will not work; anything that does not throw will exit 0.
472
160
  [Void]$ps.AddScript('if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }')
473
161
 
474
- if($PSVersionTable.PSVersion -le [Version]'2.0'){
475
- $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::Error, [System.Management.Automation.Runspaces.PipelineResultTypes]::Output);
476
- }else{
477
- $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::All, [System.Management.Automation.Runspaces.PipelineResultTypes]::Output);
162
+ # This is the code that ensures the output from the Host is interleaved; this ensures
163
+ # everything written to the streams in the Host is returned.
164
+ if ($PSVersionTable.PSVersion -le [Version]'2.0') {
165
+ $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::Error,
166
+ [System.Management.Automation.Runspaces.PipelineResultTypes]::Output)
167
+ } else {
168
+ $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::All,
169
+ [System.Management.Automation.Runspaces.PipelineResultTypes]::Output)
478
170
  }
171
+
172
+ # The asynchronous execution enables a user to set a timeout for the execution of their
173
+ # provided code; this keeps the process from hanging eternally until an external caller
174
+ # times out or the user kills the process.
479
175
  $asyncResult = $ps.BeginInvoke()
480
176
 
481
- if (!$asyncResult.AsyncWaitHandle.WaitOne($TimeoutMilliseconds)){
177
+ if (-not $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMilliseconds)) {
482
178
  # forcibly terminate execution of pipeline
483
179
  $ps.Stop()
484
180
  throw "Catastrophic failure: PowerShell module timeout ($TimeoutMilliseconds ms) exceeded while executing"
485
181
  }
486
182
 
487
- try
488
- {
183
+ try {
489
184
  $ps.EndInvoke($asyncResult)
490
185
  } catch [System.Management.Automation.IncompleteParseException] {
186
+ # This surfaces an error for when syntactically incorrect code is passed
491
187
  # https://msdn.microsoft.com/en-us/library/system.management.automation.incompleteparseexception%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
492
188
  throw $_.Exception.Message
493
189
  } catch {
494
- if ($_.Exception.InnerException -ne $null)
495
- {
190
+ # This catches any execution errors from the passed code, drops out of execution here and
191
+ # throws the most specific exception available.
192
+ if ($null -ne $_.Exception.InnerException) {
496
193
  throw $_.Exception.InnerException
497
194
  } else {
498
195
  throw $_.Exception
499
196
  }
500
197
  }
501
198
 
502
- [Puppet.PuppetPSHostUserInterface]$ui = $global:puppetPSHost.UI
199
+ [RubyPwsh.RubyPwshPSHostUserInterface]$ui = $global:RubyPwshPSHost.UI
503
200
  return @{
504
- exitcode = $global:puppetPSHost.Exitcode;
201
+ exitcode = $global:RubyPwshPSHost.Exitcode;
505
202
  stdout = $ui.Output;
506
203
  stderr = $ui.StdErr;
507
204
  errormessage = $null;
508
205
  }
509
206
  }
510
- catch
511
- {
512
- try
513
- {
207
+ catch {
208
+ # if an execution or parse error is surfaced, dispose of the runspace and clear the global
209
+ # runspace; it will be rebuilt on the next execution.
210
+ try {
514
211
  if ($global:runspace) { $global:runspace.Dispose() }
515
- }
516
- finally
517
- {
212
+ } finally {
518
213
  $global:runspace = $null
519
214
  }
520
- if(($global:puppetPSHost -ne $null) -and $global:puppetPSHost.ExitCode){
521
- $ec = $global:puppetPSHost.ExitCode
522
- }else{
215
+ if (($global:RubyPwshPSHost -ne $null) -and $global:RubyPwshPSHost.ExitCode) {
216
+ $ec = $global:RubyPwshPSHost.ExitCode
217
+ } else {
523
218
  # This is technically not true at this point as we do not
524
219
  # know what exitcode we should return as an unexpected exception
525
220
  # happened and the user did not set an exitcode. Our best guess
526
- # is to return 1 so that we ensure Puppet reports this run as an error.
221
+ # is to return 1 so that we ensure ruby treats this run as an error.
527
222
  $ec = 1
528
223
  }
529
224
 
530
- if ($_.Exception.ErrorRecord.InvocationInfo -ne $null)
531
- {
225
+ # Format the exception message; this could be improved to surface more functional messaging
226
+ # to the user; right now it dumps the exception message as a string.
227
+ if ($_.Exception.ErrorRecord.InvocationInfo -ne $null) {
532
228
  $output = $_.Exception.Message + "`n`r" + $_.Exception.ErrorRecord.InvocationInfo.PositionMessage
533
229
  } else {
534
230
  $output = $_.Exception.Message | Out-String
535
231
  }
536
232
 
537
233
  # make an attempt to read Output / StdErr as it may contain partial output / info about failures
538
- try { $out = $global:puppetPSHost.UI.Output } catch { $out = $null }
539
- try { $err = $global:puppetPSHost.UI.StdErr } catch { $err = $null }
234
+ # The PowerShell Host could be entirely dead and broken at this stage.
235
+ try {
236
+ $out = $global:RubyPwshPSHost.UI.Output
237
+ } catch {
238
+ $out = $null
239
+ }
240
+ try {
241
+ $err = $global:RubyPwshPSHost.UI.StdErr
242
+ } catch {
243
+ $err = $null
244
+ }
540
245
 
246
+ # Make sure we return the expected data structure for what happened.
541
247
  return @{
542
248
  exitcode = $ec;
543
249
  stdout = $out;
544
250
  stderr = $err;
545
251
  errormessage = $output;
546
252
  }
547
- }
548
- finally
549
- {
253
+ } finally {
254
+ # Dispose of the shell regardless of success/failure. This clears state and memory both.
255
+ # To enable conditional keeping of state, this would need an additional condition.
550
256
  if ($ps -ne $null) { [Void]$ps.Dispose() }
551
257
  }
552
258
  }
553
259
 
554
- function Write-SystemDebugMessage
555
- {
260
+ function Write-SystemDebugMessage {
556
261
  [CmdletBinding()]
557
262
  param(
558
263
  [Parameter(Mandatory = $true)]
@@ -560,14 +265,16 @@ function Write-SystemDebugMessage
560
265
  $Message
561
266
  )
562
267
 
563
- if ($script:EmitDebugOutput -or ($DebugPreference -ne 'SilentlyContinue'))
564
- {
268
+ if ($script:EmitDebugOutput -or ($DebugPreference -ne 'SilentlyContinue')) {
269
+ # This writes to the console, not to the PowerShell streams.
270
+ # This is captured for communications with the pipe server.
565
271
  [System.Diagnostics.Debug]::WriteLine($Message)
566
272
  }
567
273
  }
568
274
 
569
- function Signal-Event
570
- {
275
+ # This is not called anywhere else in the project. It may be dead code for
276
+ # event handling used in an earlier implementation. Or magic?
277
+ function Signal-Event {
571
278
  [CmdletBinding()]
572
279
  param(
573
280
  [String]
@@ -585,8 +292,7 @@ function Signal-Event
585
292
  Write-SystemDebugMessage -Message "Signaled event $EventName"
586
293
  }
587
294
 
588
- function ConvertTo-LittleEndianBytes
589
- {
295
+ function ConvertTo-LittleEndianBytes {
590
296
  [CmdletBinding()]
591
297
  param (
592
298
  [Parameter(Mandatory = $true)]
@@ -595,13 +301,12 @@ function ConvertTo-LittleEndianBytes
595
301
  )
596
302
 
597
303
  $bytes = [BitConverter]::GetBytes($Value)
598
- if (![BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
304
+ if (-not [BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
599
305
 
600
306
  return $bytes
601
307
  }
602
308
 
603
- function ConvertTo-ByteArray
604
- {
309
+ function ConvertTo-ByteArray {
605
310
  [CmdletBinding()]
606
311
  param (
607
312
  [Parameter(Mandatory = $true)]
@@ -617,7 +322,7 @@ function ConvertTo-ByteArray
617
322
  $result = [Byte[]]@()
618
323
  # and add length / name / length / value from Hashtable
619
324
  $Hash.GetEnumerator() |
620
- % {
325
+ ForEach-Object -Process {
621
326
  $name = $Encoding.GetBytes($_.Name)
622
327
  $result += (ConvertTo-LittleEndianBytes $name.Length) + $name
623
328
 
@@ -629,8 +334,7 @@ function ConvertTo-ByteArray
629
334
  return $result
630
335
  }
631
336
 
632
- function Write-StreamResponse
633
- {
337
+ function Write-StreamResponse {
634
338
  [CmdletBinding()]
635
339
  param (
636
340
  [Parameter(Mandatory = $true)]
@@ -654,8 +358,7 @@ function Write-StreamResponse
654
358
  Write-SystemDebugMessage -Message "Wrote $($bytes.Length) bytes of data to Stream"
655
359
  }
656
360
 
657
- function Read-Int32FromStream
658
- {
361
+ function Read-Int32FromStream {
659
362
  [CmdletBinding()]
660
363
  param (
661
364
  [Parameter(Mandatory = $true)]
@@ -667,7 +370,7 @@ function Read-Int32FromStream
667
370
  # Read blocks until all 4 bytes available
668
371
  $Stream.Read($length, 0, 4) | Out-Null
669
372
  # value is sent in Little Endian, but if the CPU is not, in-place reverse the array
670
- if (![BitConverter]::IsLittleEndian) { [Array]::Reverse($length) }
373
+ if (-not [BitConverter]::IsLittleEndian) { [Array]::Reverse($length) }
671
374
  $value = [BitConverter]::ToInt32($length, 0)
672
375
 
673
376
  Write-SystemDebugMessage -Message "Read Byte[] $length from stream as Int32 $value"
@@ -683,8 +386,7 @@ function Read-Int32FromStream
683
386
  # [optional] 4 bytes - Little Endian encoded 32-bit code block length for execute
684
387
  # Intel CPUs are little endian, hence the .NET Framework typically is
685
388
  # [optional] variable length - code block
686
- function ConvertTo-PipeCommand
687
- {
389
+ function ConvertTo-PipeCommand {
688
390
  [CmdletBinding()]
689
391
  param (
690
392
  [Parameter(Mandatory = $true)]
@@ -705,8 +407,7 @@ function ConvertTo-PipeCommand
705
407
 
706
408
  Write-SystemDebugMessage -Message "Command id $command read from pipe"
707
409
 
708
- switch ($command)
709
- {
410
+ switch ($command) {
710
411
  # Exit
711
412
  # ReadByte returns a -1 when the pipe is closed on the other end
712
413
  { @(0, -1) -contains $_ } { return @{ Command = 'Exit' }}
@@ -728,8 +429,7 @@ function ConvertTo-PipeCommand
728
429
  $attempt = $attempt + 1
729
430
  # This will block if there's not enough data in the pipe
730
431
  $read = $Stream.Read($parsed.RawData, $readBytes, $parsed.Length - $readBytes)
731
- if ($read -eq 0)
732
- {
432
+ if ($read -eq 0) {
733
433
  throw "Catastrophic failure: Expected $($parsed.Length - $readBytesh) raw bytes, but the pipe reached an end of stream"
734
434
  }
735
435
 
@@ -737,8 +437,7 @@ function ConvertTo-PipeCommand
737
437
  Write-SystemDebugMessage -Message "Read $($read) bytes from the pipe"
738
438
  } while ($readBytes -lt $parsed.Length)
739
439
 
740
- if ($readBytes -lt $parsed.Length)
741
- {
440
+ if ($readBytes -lt $parsed.Length) {
742
441
  throw "Catastrophic failure: Expected $($parsed.Length) raw bytes, only received $readBytes"
743
442
  }
744
443
 
@@ -748,8 +447,7 @@ function ConvertTo-PipeCommand
748
447
  return $parsed
749
448
  }
750
449
 
751
- function Start-PipeServer
752
- {
450
+ function Start-PipeServer {
753
451
  [CmdletBinding()]
754
452
  param (
755
453
  [Parameter(Mandatory = $true)]
@@ -767,8 +465,7 @@ function Start-PipeServer
767
465
  $server = New-Object System.IO.Pipes.NamedPipeServerStream($CommandChannelPipeName,
768
466
  [System.IO.Pipes.PipeDirection]::InOut)
769
467
 
770
- try
771
- {
468
+ try {
772
469
  # block until Ruby process connects
773
470
  $server.WaitForConnection()
774
471
 
@@ -776,15 +473,13 @@ function Start-PipeServer
776
473
 
777
474
  # Infinite Loop to process commands until EXIT received
778
475
  $running = $true
779
- while ($running)
780
- {
476
+ while ($running) {
781
477
  # throws if an unxpected command id is read from pipe
782
478
  $response = ConvertTo-PipeCommand -Stream $server -Encoding $Encoding
783
479
 
784
480
  Write-SystemDebugMessage -Message "Received $($response.Command) command from client"
785
481
 
786
- switch ($response.Command)
787
- {
482
+ switch ($response.Command) {
788
483
  'Execute' {
789
484
  Write-SystemDebugMessage -Message "[Execute] Invoking user code:`n`n $($response.Code)"
790
485
 
@@ -799,26 +494,21 @@ function Start-PipeServer
799
494
  'Exit' { $running = $false }
800
495
  }
801
496
  }
802
- }
803
- catch [Exception]
804
- {
497
+ } catch [Exception] {
805
498
  Write-SystemDebugMessage -Message "PowerShell Pipe Server Failed!`n`n$_"
806
499
  throw
807
- }
808
- finally
809
- {
810
- if ($global:runspace -ne $null)
811
- {
500
+ } finally {
501
+ if ($global:runspace -ne $null) {
812
502
  $global:runspace.Dispose()
813
503
  Write-SystemDebugMessage -Message "PowerShell Runspace Disposed`n`n$_"
814
504
  }
815
- if ($server -ne $null)
816
- {
505
+ if ($server -ne $null) {
817
506
  $server.Dispose()
818
507
  Write-SystemDebugMessage -Message "NamedPipeServerStream Disposed`n`n$_"
819
508
  }
820
509
  }
821
510
  }
822
511
 
512
+ # Start the pipe server and wait for it to close.
823
513
  Start-PipeServer -CommandChannelPipeName $NamedPipeName -Encoding $Encoding
824
514
  Write-SystemDebugMessage -Message "Start-PipeServer Finished`n`n$_"