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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +109 -0
- data/.gitignore +4 -1
- data/CHANGELOG.md +24 -2
- data/Gemfile +5 -1
- data/README.md +12 -0
- data/Rakefile +30 -0
- data/lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb +305 -137
- data/lib/puppet/provider/dsc_base_provider/invoke_dsc_resource_functions.ps1 +5 -3
- data/lib/pwsh.rb +19 -45
- data/lib/pwsh/version.rb +1 -1
- data/lib/templates/RubyPwsh.cs +302 -0
- data/lib/templates/init.ps1 +137 -447
- data/metadata.json +30 -33
- metadata +5 -5
- data/.travis.yml +0 -26
- data/appveyor.yml +0 -38
data/lib/templates/init.ps1
CHANGED
@@ -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
|
-
$
|
21
|
-
|
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
|
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
|
-
|
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
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
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
|
-
$
|
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:
|
407
|
-
$global:runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($global:
|
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:
|
415
|
-
$global:
|
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
|
-
|
434
|
-
|
435
|
-
|
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
|
-
|
439
|
-
|
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
|
-
|
453
|
-
|
454
|
-
|
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
|
-
#
|
462
|
-
# to
|
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
|
-
#
|
467
|
-
#
|
468
|
-
|
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
|
-
|
475
|
-
|
476
|
-
|
477
|
-
$ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::
|
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 (
|
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
|
-
|
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
|
-
[
|
199
|
+
[RubyPwsh.RubyPwshPSHostUserInterface]$ui = $global:RubyPwshPSHost.UI
|
503
200
|
return @{
|
504
|
-
exitcode = $global:
|
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
|
-
|
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:
|
521
|
-
$ec = $global:
|
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
|
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
|
-
|
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
|
-
|
539
|
-
try {
|
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
|
-
|
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
|
-
|
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 (
|
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 (
|
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
|
-
|
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$_"
|