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