NOTE: This is based on the code at the branch https://github.com/puppetlabs/puppet/commits/a28fc17fb and requires changing https://github.com/puppetlabs/puppet/blob/22638660973d96eacfad20d4d525ef100643030d/lib/puppet/functions/run_script.rb#L49 from executor.run_script(executor.from_uris(hosts), found)
to executor.run_script(executor.from_uris(hosts), found, [])
to match Bolts executor method signature for run_script
A PR is up at puppetlabs/puppet#6339
bundle exec bolt plan run sample::bleed_script_test nodes=winrm://vagrant:vagrant@localhost:55985 --modulepath ./spec/fixtures/modules --debug --verbose
spec/fixtures/modules/sample/plans/bleed_script_test.pp
plan sample::bleed_script_test(String $nodes ) {
$node_list = split($nodes , ' ,' )
$script_file = " sample/uploads/bleed.ps1"
run_script($script_file , $node_list )
run_script($script_file , $node_list )
}
spec/fixtures/modules/sample/files/uploads/bleed.ps1
" InstanceId is " + $Host.InstanceId
" RunspaceId is " + $Host.Runspace.InstanceId
Write-Host " Foo: $ENV: Foo / MyValue: $MyValue "
$ENV: Foo = " bar"
$MyValue = " baz"
Write-Host " Foo: $ENV: Foo / MyValue: $MyValue "
2017-11-07T14:04:14.210702 DEBUG executor: Started with 1 thread(s)
2017-11-07T14:04:21.225904 DEBUG localhost: Opened session
2017-11-07T14:04:21.225950 INFO localhost: Running script '/Users/Iristyle/source/bolt/spec/fixtures/modules/sample/files/uploads/bleed.ps1'
2017-11-07T14:04:21.225988 DEBUG localhost: Executing command: $parent = [System.IO.Path]::GetTempPath()
$name = [System.IO.Path]::GetRandomFileName()
$path = Join-Path $parent $name
New-Item -ItemType Directory -Path $path | Out-Null
$path
2017-11-07T14:04:21.576239 DEBUG localhost: stdout: C:\Users\vagrant\AppData\Local\Temp\zwrsvvme.mui
2017-11-07T14:04:21.576294 DEBUG localhost: stderr:
2017-11-07T14:04:21.622373 DEBUG localhost: Command returned successfully
2017-11-07T14:04:21.622482 DEBUG localhost: Uploading /Users/Iristyle/source/bolt/spec/fixtures/modules/sample/files/uploads/bleed.ps1 to C:\Users\vagrant\AppData\Local\Temp\zwrsvvme.mui\bleed.ps1
2017-11-07T14:04:29.529575 DEBUG localhost: Executing command:
$ENV:PATH += ";${ENV:ProgramFiles}\Puppet Labs\Puppet\bin\;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\sys\ruby\bin\"
$ENV:RUBYLIB = "${ENV:ProgramFiles}\Puppet Labs\Puppet\puppet\lib;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\facter\lib;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\hiera\lib;" +
$ENV:RUBYLIB
function Invoke-Interpreter
{
[CmdletBinding()]
Param (
[Parameter()]
[String]
$Path,
[Parameter()]
[String]
$Arguments,
[Parameter()]
[Int32]
$Timeout,
[Parameter()]
[String]
$StdinInput = $Null
)
try
{
if (-not (Get-Command $Path -ErrorAction SilentlyContinue))
{
throw "Could not find executable '$Path' in ${ENV:PATH} on target node"
}
$startInfo = New-Object System.Diagnostics.ProcessStartInfo($Path, $Arguments)
$startInfo.UseShellExecute = $false
$startInfo.WorkingDirectory = Split-Path -Parent (Get-Command $Path).Path
$startInfo.CreateNoWindow = $true
if ($StdinInput) { $startInfo.RedirectStandardInput = $true }
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$stdoutHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteLine($EventArgs.Data) } }
$stderrHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteErrorLine($EventArgs.Data) } }
$invocationId = [Guid]::NewGuid().ToString()
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.EnableRaisingEvents = $true
# https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standarderror(v=vs.110).aspx#Anchor_2
$stdoutEvent = Register-ObjectEvent -InputObject $process -EventName 'OutputDataReceived' -Action $stdoutHandler
$stderrEvent = Register-ObjectEvent -InputObject $process -EventName 'ErrorDataReceived' -Action $stderrHandler
$exitedEvent = Register-ObjectEvent -InputObject $process -EventName 'Exited' -SourceIdentifier $invocationId
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
if ($StdinInput)
{
$process.StandardInput.WriteLine($StdinInput)
$process.StandardInput.Close()
}
# park current thread until the PS event is signaled upon process exit
# OR the timeout has elapsed
$waitResult = Wait-Event -SourceIdentifier $invocationId -Timeout $Timeout
if (! $process.HasExited)
{
$Host.UI.WriteErrorLine("Process $Path did not complete in $Timeout seconds")
return 1
}
return $process.ExitCode
}
catch
{
$Host.UI.WriteErrorLine($_)
return 1
}
finally
{
@($stdoutEvent, $stderrEvent, $exitedEvent) |
? { $_ -ne $Null } |
% { Unregister-Event -SourceIdentifier $_.Name }
if ($process -ne $Null)
{
if (($process.Handle -ne $Null) -and (! $process.HasExited))
{
try { $process.Kill() } catch { $Host.UI.WriteErrorLine("Failed To Kill Process $Path") }
}
$process.Dispose()
}
}
}
2017-11-07T14:04:29.722427 DEBUG localhost: Command returned successfully
2017-11-07T14:04:29.722670 DEBUG localhost: Executing command: $quoted_array = @(
'-NoProfile','-NonInteractive','-NoLogo','-ExecutionPolicy','Bypass','-File','"C:\Users\vagrant\AppData\Local\Temp\zwrsvvme.mui\bleed.ps1"'
)
$invokeArgs = @{
Path = "powershell.exe"
Arguments = $quoted_array -Join ' '
Timeout = 600
}
# winrm gem checks $? prior to using $LASTEXITCODE
# making it necessary to exit with the desired code to propagate status properly
exit $(Invoke-Interpreter @invokeArgs)
2017-11-07T14:04:33.052682 DEBUG localhost: stdout: InstanceId is 8affd834-09ca-4062-b749-bcc9b77d92c8
2017-11-07T14:04:33.052745 DEBUG localhost: stderr:
2017-11-07T14:04:33.055606 DEBUG localhost: stdout: RunspaceId is 02bb07a2-5685-4e59-a2b0-170e041702ac
2017-11-07T14:04:33.055654 DEBUG localhost: stderr:
2017-11-07T14:04:33.058464 DEBUG localhost: stdout: Foo: / MyValue:
2017-11-07T14:04:33.058512 DEBUG localhost: stderr:
2017-11-07T14:04:33.061493 DEBUG localhost: stdout: Foo: bar / MyValue: baz
2017-11-07T14:04:33.061538 DEBUG localhost: stderr:
2017-11-07T14:04:33.081333 DEBUG localhost: Command returned successfully
2017-11-07T14:04:33.081383 DEBUG localhost: Executing command: Remove-Item -Force "C:\Users\vagrant\AppData\Local\Temp\zwrsvvme.mui\bleed.ps1"
Remove-Item -Force "C:\Users\vagrant\AppData\Local\Temp\zwrsvvme.mui"
2017-11-07T14:04:33.128322 DEBUG localhost: Command returned successfully
2017-11-07T14:04:33.136884 DEBUG localhost: Closed session
2017-11-07T14:04:33.137897 DEBUG executor: Started with 1 thread(s)
2017-11-07T14:04:34.999464 DEBUG localhost: Opened session
2017-11-07T14:04:34.999522 INFO localhost: Running script '/Users/Iristyle/source/bolt/spec/fixtures/modules/sample/files/uploads/bleed.ps1'
2017-11-07T14:04:34.999543 DEBUG localhost: Executing command: $parent = [System.IO.Path]::GetTempPath()
$name = [System.IO.Path]::GetRandomFileName()
$path = Join-Path $parent $name
New-Item -ItemType Directory -Path $path | Out-Null
$path
2017-11-07T14:04:35.078009 DEBUG localhost: stdout: C:\Users\vagrant\AppData\Local\Temp\olrvq42v.te2
2017-11-07T14:04:35.078073 DEBUG localhost: stderr:
2017-11-07T14:04:35.088035 DEBUG localhost: Command returned successfully
2017-11-07T14:04:35.088110 DEBUG localhost: Uploading /Users/Iristyle/source/bolt/spec/fixtures/modules/sample/files/uploads/bleed.ps1 to C:\Users\vagrant\AppData\Local\Temp\olrvq42v.te2\bleed.ps1
2017-11-07T14:04:37.119690 DEBUG localhost: Executing command:
$ENV:PATH += ";${ENV:ProgramFiles}\Puppet Labs\Puppet\bin\;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\sys\ruby\bin\"
$ENV:RUBYLIB = "${ENV:ProgramFiles}\Puppet Labs\Puppet\puppet\lib;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\facter\lib;" +
"${ENV:ProgramFiles}\Puppet Labs\Puppet\hiera\lib;" +
$ENV:RUBYLIB
function Invoke-Interpreter
{
[CmdletBinding()]
Param (
[Parameter()]
[String]
$Path,
[Parameter()]
[String]
$Arguments,
[Parameter()]
[Int32]
$Timeout,
[Parameter()]
[String]
$StdinInput = $Null
)
try
{
if (-not (Get-Command $Path -ErrorAction SilentlyContinue))
{
throw "Could not find executable '$Path' in ${ENV:PATH} on target node"
}
$startInfo = New-Object System.Diagnostics.ProcessStartInfo($Path, $Arguments)
$startInfo.UseShellExecute = $false
$startInfo.WorkingDirectory = Split-Path -Parent (Get-Command $Path).Path
$startInfo.CreateNoWindow = $true
if ($StdinInput) { $startInfo.RedirectStandardInput = $true }
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$stdoutHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteLine($EventArgs.Data) } }
$stderrHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteErrorLine($EventArgs.Data) } }
$invocationId = [Guid]::NewGuid().ToString()
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.EnableRaisingEvents = $true
# https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standarderror(v=vs.110).aspx#Anchor_2
$stdoutEvent = Register-ObjectEvent -InputObject $process -EventName 'OutputDataReceived' -Action $stdoutHandler
$stderrEvent = Register-ObjectEvent -InputObject $process -EventName 'ErrorDataReceived' -Action $stderrHandler
$exitedEvent = Register-ObjectEvent -InputObject $process -EventName 'Exited' -SourceIdentifier $invocationId
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
if ($StdinInput)
{
$process.StandardInput.WriteLine($StdinInput)
$process.StandardInput.Close()
}
# park current thread until the PS event is signaled upon process exit
# OR the timeout has elapsed
$waitResult = Wait-Event -SourceIdentifier $invocationId -Timeout $Timeout
if (! $process.HasExited)
{
$Host.UI.WriteErrorLine("Process $Path did not complete in $Timeout seconds")
return 1
}
return $process.ExitCode
}
catch
{
$Host.UI.WriteErrorLine($_)
return 1
}
finally
{
@($stdoutEvent, $stderrEvent, $exitedEvent) |
? { $_ -ne $Null } |
% { Unregister-Event -SourceIdentifier $_.Name }
if ($process -ne $Null)
{
if (($process.Handle -ne $Null) -and (! $process.HasExited))
{
try { $process.Kill() } catch { $Host.UI.WriteErrorLine("Failed To Kill Process $Path") }
}
$process.Dispose()
}
}
}
2017-11-07T14:04:37.175677 DEBUG localhost: Command returned successfully
2017-11-07T14:04:37.175772 DEBUG localhost: Executing command: $quoted_array = @(
'-NoProfile','-NonInteractive','-NoLogo','-ExecutionPolicy','Bypass','-File','"C:\Users\vagrant\AppData\Local\Temp\olrvq42v.te2\bleed.ps1"'
)
$invokeArgs = @{
Path = "powershell.exe"
Arguments = $quoted_array -Join ' '
Timeout = 600
}
# winrm gem checks $? prior to using $LASTEXITCODE
# making it necessary to exit with the desired code to propagate status properly
exit $(Invoke-Interpreter @invokeArgs)
2017-11-07T14:04:40.402221 DEBUG localhost: stdout: InstanceId is 4de92bb2-1bc2-47a9-92be-01cc100b8876
2017-11-07T14:04:40.402264 DEBUG localhost: stderr:
2017-11-07T14:04:40.404069 DEBUG localhost: stdout: RunspaceId is 66d40958-8e60-4ad8-aaea-64b59b0a85be
2017-11-07T14:04:40.404099 DEBUG localhost: stderr:
2017-11-07T14:04:40.405711 DEBUG localhost: stdout: Foo: / MyValue:
2017-11-07T14:04:40.405739 DEBUG localhost: stderr:
2017-11-07T14:04:40.407291 DEBUG localhost: stdout: Foo: bar / MyValue: baz
2017-11-07T14:04:40.407315 DEBUG localhost: stderr:
2017-11-07T14:04:40.421133 DEBUG localhost: Command returned successfully
2017-11-07T14:04:40.421202 DEBUG localhost: Executing command: Remove-Item -Force "C:\Users\vagrant\AppData\Local\Temp\olrvq42v.te2\bleed.ps1"
Remove-Item -Force "C:\Users\vagrant\AppData\Local\Temp\olrvq42v.te2"
2017-11-07T14:04:40.469602 DEBUG localhost: Command returned successfully
2017-11-07T14:04:40.476808 DEBUG localhost: Closed session
ExecutionResult({'winrm://vagrant:vagrant@localhost:55985' => {'stdout' => "InstanceId is 4de92bb2-1bc2-47a9-92be-01cc100b8876\r\nRunspaceId is 66d40958-8e60-4ad8-aaea-64b59b0a85be\r\nFoo: / MyValue: \r\nFoo: bar / MyValue: baz\r\n", 'stderr' => '', 'exit_code' => 0}})
Summarized, we get the following:
1st invocation - InstanceId
is 8affd834-09ca-4062-b749-bcc9b77d92c8
and Runspace InstanceId
is 02bb07a2-5685-4e59-a2b0-170e041702ac82e273f4f
2nd invocation - InstanceId
is 4de92bb2-1bc2-47a9-92be-01cc100b8876
and Runspace InstanceId
is 66d40958-8e60-4ad8-aaea-64b59b0a85be
As such, the env var / variable set in the first invocation does not bleed to the next: Foo: / MyValue:
Note to self:
Invoke-Interpreter
could should not be sent across the wire multiple times... hmm...