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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +109 -0
- data/.gitignore +4 -1
- data/CHANGELOG.md +55 -0
- data/Gemfile +5 -1
- data/README.md +12 -0
- data/Rakefile +34 -0
- data/lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb +342 -139
- data/lib/puppet/provider/dsc_base_provider/invoke_dsc_resource_functions.ps1 +5 -3
- data/lib/puppet/provider/dsc_base_provider/invoke_dsc_resource_postscript.ps1 +6 -0
- 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 +8 -9
- 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$_"
|