[BOLT-830] Running a script via PCP fails on Windows Created: 2018/09/06  Updated: 2019/02/08  Resolved: 2019/02/08

Status: Resolved
Project: Puppet Task Runner
Component/s: None
Affects Version/s: BOLT 0.22.0
Fix Version/s: BOLT 1.11.0

Type: Bug Priority: Normal
Reporter: Michael Smith Assignee: Cas Donoghue
Resolution: Fixed Votes: 1
Labels: docs_reviewed
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Template:
Epic Link: Adoption Blockers
Sprint: Bolt Kanban
Method Found: Needs Assessment
Release Notes: Enhancement
Release Notes Summary: Powershell scripts can now be run on windows targets over the {{pcp}} transport.
QA Risk Assessment: Needs Assessment

 Description   

Trying to run a powershell script via PCP with Bolt fails. The file extension is not sent along, so the script task doesn't know how to run it. Additionally Bolt hard-codes how to run several types of scripts in the WinRM transport; we should send this mappings to the script task so it can implement similar support for ruby, powershell, puppet, and batch scripts.



 Comments   
Comment by Andy Fry [ 2018/09/06 ]

Debug information.

[root@cme-custa-pup01 ~]# bolt --version 
0.22.0

[root@cme-custa-pup01 ~]# bolt command run date --no-ssl --nodes winrm://cme-win01.internal.com --user ### --password "###" 
Started on cme-win01.internal.com... 
Finished on cme-win01.internal.com: 
STDOUT:

Friday, September 7, 2018 4:31:28 AM

Successful on 1 node: winrm://cme-win01.internal.com 
Ran on 1 node in 3.59 seconds

[root@cme-custa-pup01 ~]# bolt command run date --no-ssl --nodes $WINNODES 
Started on cme-win01.internal.com... 
Failed on cme-win01.internal.com: 
The command failed with exit code 1 
STDOUT: 
The current date is: Fri 09/07/2018 
Enter the new date: (mm-dd-yy) 
Failed on 1 node: pcp://cme-win01.internal.com 
Ran on 1 node in 2.76 seconds

[root@cme-custa-pup01 ~]# echo $WINNODES 
pcp://cme-win01.internal.com

[root@cme-custa-pup01 ~]# bolt script run TestConnection.ps1 -n $WINNODES --debug > /tmp/bolt_debug_pcp 2>&1 
[root@cme-custa-pup01 ~]# cat /tmp/bolt_debug_pcp 
Loaded inventory from /root/.puppetlabs/bolt/inventory.yaml 
Did not find config for pcp://cme-win01.internal.com in inventory 
Started with 100 max thread(s) 
Starting: script TestConnection.ps1 on pcp://cme-win01.internal.com 
Running script TestConnection.ps1 with '[]' on ["pcp://cme-win01.internal.com"] 
Submitting analytics: { 
"v": 1, 
"cid": "820a8fab-c9f8-4a27-8d98-ef29ff02e2f4", 
"tid": "UA-120367942-1", 
"an": "bolt", 
"av": "0.22.0", 
"aip": true, 
"ul": "en-US", 
"cd1": "CentOS 7", 
"t": "event", 
"ec": "Transport", 
"ea": "initialize", 
"el": "orch", 
"ev": 1 
} 
Submitting analytics: { 
"v": 1, 
"cid": "820a8fab-c9f8-4a27-8d98-ef29ff02e2f4", 
"tid": "UA-120367942-1", 
"an": "bolt", 
"av": "0.22.0", 
"aip": true, 
"ul": "en-US", 
"cd1": "CentOS 7", 
"t": "screenview", 
"cd": "script_run", 
"cd5": "human", 
"cd4": 1, 
"cd2": 7, 
"cd3": 3 
} 
Creating orchestrator client for {"User-Agent"=>"Bolt/0.22.0"} 
Started plan 
Completed analytics submission 
Completed analytics submission 
[\{"node":"pcp://cme-win01.internal.com","status":"failure","result":{"_error":{"msg":"Task exited 1:\nC:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:199:in `spawn': Exec format error - C:/Windows/Temp/bolt_script20180907-2508-1dfqn41 (Errno::ENOEXEC)\n\tfrom C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:199:in `popen_run'\n\tfrom C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:95:in `popen3'\n\tfrom C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:258:in `capture3'\n\tfrom C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:10:in `command'\n\tfrom C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:22:in `script'\n\tfrom C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:27:in `<main>'\n","kind":"puppetlabs.tasks/task-error","details":{"exit_code":1}},"_output":""}}] 
Finished: script TestConnection.ps1 with 1 failure in 1.79 sec 
Started on cme-win01.internal.com... 
Failed on cme-win01.internal.com: 
Task exited 1: 
C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:199:in `spawn': Exec format error - C:/Windows/Temp/bolt_script20180907-2508-1dfqn41 (Errno::ENOEXEC) 
from C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:199:in `popen_run' 
from C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:95:in `popen3' 
from C:/Program Files/Puppet Labs/Puppet/sys/ruby/lib/ruby/2.4.0/open3.rb:258:in `capture3' 
from C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:10:in `command' 
from C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:22:in `script' 
from C:/ProgramData/PuppetLabs/pxp-agent/tasks-cache/6f7fc86c2712323a0c0322f215b2cb2409f924dd153f411b3a8a84cdc682b568/script.rb:27:in `<main>'

{ 
} 
Failed on 1 node: pcp://cme-win01.internal.com 
Ran on 1 node in 1.79 seconds

[root@cme-custa-pup01 ~]# bolt script run TestConnection.ps1 -n winrm://cme-win01.internal.com --user ### --password "###" --no-ssl --debug > /tmp/bolt_debug_winrm 2>&1 
[root@cme-custa-pup01 ~]# cat /tmp/bolt_debug_winrm 
Loaded inventory from /root/.puppetlabs/bolt/inventory.yaml 
Did not find config for winrm://cme-win01.internal.com in inventory 
Started with 100 max thread(s) 
Starting: script TestConnection.ps1 on winrm://cme-win01.internal.com 
Submitting analytics: { 
"v": 1, 
"cid": "820a8fab-c9f8-4a27-8d98-ef29ff02e2f4", 
"tid": "UA-120367942-1", 
"an": "bolt", 
"av": "0.22.0", 
"aip": true, 
"ul": "en-US", 
"cd1": "CentOS 7", 
"t": "screenview", 
"cd": "script_run", 
"cd5": "human", 
"cd4": 1, 
"cd2": 7, 
"cd3": 3 
} 
Submitting analytics: { 
"v": 1, 
"cid": "820a8fab-c9f8-4a27-8d98-ef29ff02e2f4", 
"tid": "UA-120367942-1", 
"an": "bolt", 
"av": "0.22.0", 
"aip": true, 
"ul": "en-US", 
"cd1": "CentOS 7", 
"t": "event", 
"ec": "Transport", 
"ea": "initialize", 
"el": "winrm", 
"ev": 1 
} 
Running script TestConnection.ps1 with '[]' on ["winrm://cme-win01.internal.com"] 
Running script 'TestConnection.ps1' on winrm://cme-win01.internal.com 
Completed analytics submission 
Completed analytics submission 
Opened session 
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

stdout: C:\Users\andy\AppData\Local\Temp\v0keocuf.1ou

stderr: 
Command returned successfully 
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

Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization 
$utf8 = [System.Text.Encoding]::UTF8

function Write-Stream { 
PARAM( 
[Parameter(Position=0)] $stream, 
[Parameter(ValueFromPipeline=$true)] $string 
) 
PROCESS { 
$bytes = $utf8.GetBytes($string) 
$stream.Write( $bytes, 0, $bytes.Length ) 
} 
}

function Convert-JsonToXml { 
PARAM([Parameter(ValueFromPipeline=$true)] [string[]] $json) 
BEGIN { 
$mStream = New-Object System.IO.MemoryStream 
} 
PROCESS { 
$json | Write-Stream -Stream $mStream 
} 
END { 
$mStream.Position = 0 
try { 
$jsonReader = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonReader($mStream,[System.Xml.XmlDictionaryReaderQuotas]::Max) 
$xml = New-Object Xml.XmlDocument 
$xml.Load($jsonReader) 
$xml 
} finally { 
$jsonReader.Close() 
$mStream.Dispose() 
} 
} 
}

Function ConvertFrom-Xml { 
[CmdletBinding(DefaultParameterSetName="AutoType")] 
PARAM( 
[Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [Xml.XmlNode] $xml, 
[Parameter(Mandatory=$true,ParameterSetName="ManualType")] [Type] $Type, 
[Switch] $ForceType 
) 
PROCESS{ 
if (Get-Member -InputObject $xml -Name root) { 
return $xml.root.Objects | ConvertFrom-Xml 
} elseif (Get-Member -InputObject $xml -Name Objects) { 
return $xml.Objects | ConvertFrom-Xml 
} 
$propbag = @{} 
foreach ($name in Get-Member -InputObject $xml -MemberType Properties | Where-Object{$.Name -notmatch "^_|type"} | Select-Object -ExpandProperty name) { 
Write-Debug "$Name Type: $($xml.$Name.type)" -Debug:$false 
$propbag."$Name" = Convert-Properties $xml."$name" 
} 
if (!$Type -and $xml.HasAttribute("type")) { $Type = $xml.Type } 
if ($ForceType -and $Type) { 
try { 
$output = New-Object $Type -Property $propbag 
} catch { 
$output = New-Object PSObject -Property $propbag 
$output.PsTypeNames.Insert(0, $xml.type) 
} 
} elseif ($propbag.Count -ne 0) { 
$output = New-Object PSObject -Property $propbag 
if ($Type) { 
$output.PsTypeNames.Insert(0, $Type) 
} 
} 
return $output 
} 
}

Function Convert-Properties { 
PARAM($InputObject) 
switch ($InputObject.type) { 
"object" { 
return (ConvertFrom-Xml -Xml $InputObject) 
} 
"string" { 
$MightBeADate = $InputObject.get_InnerText() -as [DateTime] 
## Strings that are actually dates (grumble JSON is crap) 
if ($MightBeADate -and $propbag."$Name" -eq $MightBeADate.ToString("G")) { 
return $MightBeADate 
} else { 
return $InputObject.get_InnerText() 
} 
} 
"number" { 
$number = $InputObject.get_InnerText() 
if ($number -eq ($number -as [int])) { 
return $number -as [int] 
} elseif ($number -eq ($number -as [double])) { 
return $number -as [double] 
} else { 
return $number -as [decimal] 
} 
} 
"boolean" { 
return [bool]::parse($InputObject.get_InnerText()) 
} 
"null" { 
return $null 
} 
"array" { 
[object[]]$Items = $(foreach( $item in $InputObject.GetEnumerator() ) { 
Convert-Properties $item 
}) 
return $Items 
} 
default { 
return $InputObject 
} 
} 
}

Function ConvertFrom-Json2 { 
[CmdletBinding()] 
PARAM( 
[Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=1)] [string] $InputObject, 
[Parameter(Mandatory=$true)] [Type] $Type, 
[Switch] $ForceType 
) 
PROCESS { 
$null = $PSBoundParameters.Remove("InputObject") 
[Xml.XmlElement]$xml = (Convert-JsonToXml $InputObject).Root 
if ($xml) { 
if ($xml.Objects) { 
$xml.Objects.Item.GetEnumerator() | ConvertFrom-Xml @PSBoundParameters 
} elseif ($xml.Item -and $xml.Item -isnot [System.Management.Automation.PSParameterizedProperty]) { 
$xml.Item | ConvertFrom-Xml @PSBoundParameters 
} else { 
$xml | ConvertFrom-Xml @PSBoundParameters 
} 
} else { 
Write-Error "Failed to parse JSON with JsonReader" -Debug:$false 
} 
} 
}

function ConvertFrom-PSCustomObject 
{ 
PARAM([Parameter(ValueFromPipeline = $true)] $InputObject) 
PROCESS { 
if ($null -eq $InputObject) { return $null }

if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { 
$collection = @( 
foreach ($object in $InputObject) { ConvertFrom-PSCustomObject $object } 
)

$collection 
} elseif ($InputObject -is [System.Management.Automation.PSCustomObject]) { 
$hash = @{} 
foreach ($property in $InputObject.PSObject.Properties) { 
$hash[$property.Name] = ConvertFrom-PSCustomObject $property.Value 
}

$hash 
} else { 
$InputObject 
} 
} 
}

function Get-ContentAsJson 
{ 
[CmdletBinding()] 
PARAM( 
[Parameter(Mandatory = $true)] $Text, 
[Parameter(Mandatory = $false)] [Text.Encoding] $Encoding = [Text.Encoding]::UTF8 
)

# using polyfill cmdlet on PS2, so pass type info 
if ($PSVersionTable.PSVersion -lt [Version]'3.0') { 
$Text | ConvertFrom-Json2 -Type PSObject | ConvertFrom-PSCustomObject 
} else { 
$Text | ConvertFrom-Json | ConvertFrom-PSCustomObject 
} 
}

Command returned successfully 
Executing command: $invokeArgs = @{ 
ScriptBlock = (Get-Command "C:\Users\andy\AppData\Local\Temp\v0keocuf.1ou\TestConnection.ps1").ScriptBlock 
ArgumentList = @() 
}

try 
{ 
Invoke-Command @invokeArgs 
} 
catch 
{ 
Write-Error $.Exception_ 
exit 1 
}

stdout:

stderr: 
stdout: Source Destination IPV4Address IPV6Address Bytes Time(ms)

stderr: 
stdout: ------ ----------- ----------- ----------- ----- --------

stderr: 
stdout: cme-win01 example.com 93.184.216.34 256 181

stderr: 
stdout: cme-win01 example.com 93.184.216.34 256 181

stderr: 
stdout: cme-win01 example.com 93.184.216.34 256 181

stderr: 
stdout:

stderr: 
stdout:

stderr: 
Command returned successfully 
Executing command: Remove-Item -Force "C:\Users\andy\AppData\Local\Temp\v0keocuf.1ou\TestConnection.ps1" 
Remove-Item -Force "C:\Users\andy\AppData\Local\Temp\v0keocuf.1ou"

Command returned successfully 
Closed session 
{"node":"winrm://cme-win01.internal.com","status":"success","result":{"stdout":"\r\nSource Destination IPV4Address IPV6Address Bytes Time(ms) \r\n------ ----------- ----------- ----------- ----- -------- \r\ncme-win01 example.com 93.184.216.34 256 181 \r\ncme-win01 example.com 93.184.216.34 256 181 \r\ncme-win01 example.com 93.184.216.34 256 181 \r\n\r\n\r\n","stderr":"","exit_code":0}} 
Started on cme-win01.internal.com... 
Finished on cme-win01.internal.com: 
STDOUT:

Source Destination IPV4Address IPV6Address Bytes Time(ms) 
------ ----------- ----------- ----------- ----- -------- 
cme-win01 example.com 93.184.216.34 256 181 
cme-win01 example.com 93.184.216.34 256 181 
cme-win01 example.com 93.184.216.34 256 181

Finished: script TestConnection.ps1 with 0 failures in 5.9 sec 
Successful on 1 node: winrm://cme-win01.internal.com 
Ran on 1 node in 5.90 seconds

--------------------------------------------------------------------------------------------------------------------------------------------------------------

TestConnection.ps1:

Test-Connection -ComputerName "example.com" -Count 3 -Delay 2 -TTL 255 -BufferSize 256 -ThrottleLimit 32

Comment by Melissa Amos [ 2019/02/07 ]

Docs for review:

http://docs-internal.puppet.com/docs/bolt/dev/bolt_new_features.html#powershell-scripts-over-the-pcp-transport-1-11-0

Generated at Wed Aug 21 00:25:00 PDT 2019 using JIRA 7.7.1#77002-sha1:e75ca93d5574d9409c0630b81c894d9065296414.