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:
There are some additional tests demonstrating state behavior in puppetlabs/bolt#153
The
shell_init
is occurring multiple times against the same endpoint because when task plans are executed, newWinRM
instances (nodes) are created each time. Therefore they will always be new connections, rather than existing connections. This behavior will likely change in the future