diff --git a/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson b/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson index 9ad4c73ae6eb..117a4a17913f 100644 --- a/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/CmdLine/Strings/resources.resjson/en-US/resources.resjson @@ -1,16 +1,16 @@ { "loc.friendlyName": "Command Line", "loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613735)", - "loc.description": "Run a command line with arguments", - "loc.instanceNameFormat": "Run $(filename)", + "loc.description": "Run a command line script using cmd.exe on Windows and bash on Mac and Linux.", + "loc.instanceNameFormat": "Command Line Script", + "loc.releaseNotes": "Script task consistency. Added support for multiple lines.", "loc.group.displayName.advanced": "Advanced", - "loc.input.label.filename": "Tool", - "loc.input.help.filename": "Tool name to run. Tool should be found in your path. Optionally, a fully qualified path can be supplied but that relies on that being present on the agent.
Note: You can use **$(Build.SourcesDirectory)**\\\\ if you want the path relative to repo.", - "loc.input.label.arguments": "Arguments", - "loc.input.help.arguments": "Arguments passed to the tool. Use double quotes to escape spaces.", - "loc.input.label.workingFolder": "Working folder", - "loc.input.label.failOnStandardError": "Fail on Standard Error", - "loc.input.help.failOnStandardError": "If this is true, this task will fail if any errors are written to the StandardError stream.", + "loc.input.label.script": "Script", + "loc.input.label.workingDirectory": "Working Directory", + "loc.input.label.failOnStderr": "Fail on Standard Error", + "loc.input.help.failOnStderr": "If this is true, this task will fail if any errors are written to the StandardError stream.", "loc.messages.CmdLineReturnCode": "%s exited with return code: %d", - "loc.messages.CmdLineFailed": "%s failed with error: %s" + "loc.messages.CmdLineFailed": "%s failed with error: %s", + "loc.messages.PS_ExitCode": "powershell exited with code '{0}'.", + "loc.messages.PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from powershell." } \ No newline at end of file diff --git a/Tasks/CmdLine/cmdline.ps1 b/Tasks/CmdLine/cmdline.ps1 new file mode 100644 index 000000000000..87b15dddb03d --- /dev/null +++ b/Tasks/CmdLine/cmdline.ps1 @@ -0,0 +1,130 @@ +[CmdletBinding()] +param() + +Trace-VstsEnteringInvocation $MyInvocation +try { + Import-VstsLocStrings "$PSScriptRoot\task.json" + + # TODO MOVE ASSERT-VSTSAGENT TO THE TASK LIB AND LOC + function Assert-VstsAgent { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [version]$Minimum) + + if ($Minimum -lt ([version]'2.104.1')) { + Write-Error "Assert-Agent requires the parameter to be 2.104.1 or higher" + return + } + + $agent = Get-VstsTaskVariable -Name 'agent.version' + if (!$agent -or (([version]$agent) -lt $Minimum)) { + Write-Error "Agent version $Minimum or higher is required." + } + } + + # Get inputs. + $input_failOnStderr = Get-VstsInput -Name 'failOnStderr' -AsBool + $input_script = Get-VstsInput -Name 'script' + $input_workingDirectory = Get-VstsInput -Name 'workingDirectory' -Require + Assert-VstsPath -LiteralPath $input_workingDirectory -PathType 'Container' + + # Generate the script contents. + #TODO: CONVERT NEWLINES? + $contents = "$input_script" + + # Write the script to disk. + Assert-VstsAgent -Minimum '2.115.0' + $tempDirectory = Get-VstsTaskVariable -Name 'agent.tempDirectory' -Require + Assert-VstsPath -LiteralPath $tempDirectory -PathType 'Container' + $filePath = [System.IO.Path]::Combine($tempDirectory, "$([System.Guid]::NewGuid()).cmd") + $null = [System.IO.File]::WriteAllText( + $filePath, + $contents.ToString(), + ([System.Console]::OutputEncoding)) + + # Prepare the external command values. + $cmdPath = $env:ComSpec + Assert-VstsPath -LiteralPath $cmdPath -PathType Leaf + # Command line switches: + # /Q Turns echo off. + # /D Disable execution of AutoRun commands from registry. + # /E:ON Enable command extensions. Note, command extensions are enabled + # by default, unless disabled via registry. + # /V:OFF Disable delayed environment expansion. Note, delayed environment + # expansion is disabled by default, unless enabled via registry. + # /S Will cause first and last quote after /C to be stripped. + # + # TODO: ADD EXPLANATION WHY "CALL" IS REQUIRED + $arguments = "/Q /D /E:ON /V:OFF /S /C `"CALL `"$filePath`"`"" + $splat = @{ + 'FileName' = $cmdPath + 'Arguments' = $arguments + 'WorkingDirectory' = $input_workingDirectory + } + + # Switch to "Continue". + $global:ErrorActionPreference = 'Continue' + $failed = $false + + # Run the script. + if (!$input_failOnStderr) { + Invoke-VstsTool @splat + } else { + $inError = $false + $errorLines = New-Object System.Text.StringBuilder + Invoke-VstsTool @splat 2>&1 | + ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { + # Buffer the error lines. + $failed = $true + $inError = $true + $null = $errorLines.AppendLine("$($_.Exception.Message)") + + # Write to verbose to mitigate if the process hangs. + Write-Verbose "STDERR: $($_.Exception.Message)" + } else { + # Flush the error buffer. + if ($inError) { + $inError = $false + $message = $errorLines.ToString().Trim() + $null = $errorLines.Clear() + if ($message) { + Write-VstsTaskError -Message $message + } + } + + Write-Host "$_" + } + } + + # Flush the error buffer one last time. + if ($inError) { + $inError = $false + $message = $errorLines.ToString().Trim() + $null = $errorLines.Clear() + if ($message) { + Write-VstsTaskError -Message $message + } + } + } + + # Fail on $LASTEXITCODE + if (!(Test-Path -LiteralPath 'variable:\LASTEXITCODE')) { + $failed = $true + Write-Verbose "Unable to determine exit code" + Write-VstsTaskError -Message (Get-VstsLocString -Key 'PS_UnableToDetermineExitCode') + } else { + if ($LASTEXITCODE -ne 0) { + $failed = $true + Write-VstsTaskError -Message (Get-VstsLocString -Key 'PS_ExitCode' -ArgumentList $LASTEXITCODE) + } + } + + # Fail if any errors. + if ($failed) { + Write-VstsSetResult -Result 'Failed' -Message "Error detected" -DoNotThrow + } +} finally { + Trace-VstsLeavingInvocation $MyInvocation +} \ No newline at end of file diff --git a/Tasks/CmdLine/make.json b/Tasks/CmdLine/make.json new file mode 100644 index 000000000000..8ba5fd228ba1 --- /dev/null +++ b/Tasks/CmdLine/make.json @@ -0,0 +1,24 @@ +{ + "externals": { + "nugetv2": [ + { + "name": "VstsTaskSdk", + "version": "0.8.1", + "repository": "https://www.powershellgallery.com/api/v2/", + "cp": [ + { + "source": [ + "*.ps1", + "*.psd1", + "*.psm1", + "lib.json", + "Strings" + ], + "dest": "ps_modules/VstsTaskSdk/", + "options": "-R" + } + ] + } + ] + } +} diff --git a/Tasks/CmdLine/task.json b/Tasks/CmdLine/task.json index df667d9ce077..6df50ca1e12e 100644 --- a/Tasks/CmdLine/task.json +++ b/Tasks/CmdLine/task.json @@ -2,7 +2,7 @@ "id": "D9BAFED4-0B18-4F58-968D-86655B4D2CE9", "name": "CmdLine", "friendlyName": "Command Line", - "description": "Run a command line with arguments", + "description": "Run a command line script using cmd.exe on Windows and bash on Mac and Linux.", "helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613735)", "category": "Utility", "visibility": [ @@ -15,10 +15,12 @@ ], "author": "Microsoft Corporation", "version": { - "Major": 1, - "Minor": 1, - "Patch": 3 + "Major": 2, + "Minor": 120, + "Patch": 0 }, + "releaseNotes": "Script task consistency. Added support for multiple lines.", + "preview": true, "groups": [ { "name": "advanced", @@ -28,31 +30,28 @@ ], "inputs": [ { - "name": "filename", - "type": "string", - "label": "Tool", - "defaultValue": "", + "name": "script", + "type": "multiLine", + "label": "Script", "required": true, - "helpMarkDown": "Tool name to run. Tool should be found in your path. Optionally, a fully qualified path can be supplied but that relies on that being present on the agent.
Note: You can use **$(Build.SourcesDirectory)**\\\\ if you want the path relative to repo." - }, - { - "name": "arguments", - "type": "string", - "label": "Arguments", - "defaultValue": "", - "helpMarkDown": "Arguments passed to the tool. Use double quotes to escape spaces.", - "required": false + "defaultValue": "echo Write your commands here\necho Use the Environment input below to map secret variables into environment variables", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + }, + "helpMarkDown": "" }, { - "name": "workingFolder", + "name": "workingDirectory", "type": "filePath", - "label": "Working folder", + "label": "Working Directory", "defaultValue": "", "required": false, "groupName": "advanced" }, { - "name": "failOnStandardError", + "name": "failOnStderr", "type": "boolean", "label": "Fail on Standard Error", "defaultValue": "false", @@ -61,12 +60,10 @@ "groupName": "advanced" } ], - "instanceNameFormat": "Run $(filename)", + "instanceNameFormat": "Command Line Script", "execution": { - "Process": { - "target": "$(filename)", - "argumentFormat": "$(arguments)", - "workingDirectory": "$(workingFolder)", + "PowerShell3": { + "target": "cmdline.ps1", "platforms": [ "windows" ] @@ -78,6 +75,8 @@ }, "messages": { "CmdLineReturnCode": "%s exited with return code: %d", - "CmdLineFailed": "%s failed with error: %s" + "CmdLineFailed": "%s failed with error: %s", + "PS_ExitCode": "powershell exited with code '{0}'.", + "PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from powershell." } } \ No newline at end of file diff --git a/Tasks/CmdLine/task.loc.json b/Tasks/CmdLine/task.loc.json index ef83eeb7572c..ba661dc49517 100644 --- a/Tasks/CmdLine/task.loc.json +++ b/Tasks/CmdLine/task.loc.json @@ -15,10 +15,12 @@ ], "author": "Microsoft Corporation", "version": { - "Major": 1, - "Minor": 1, - "Patch": 3 + "Major": 2, + "Minor": 120, + "Patch": 0 }, + "releaseNotes": "ms-resource:loc.releaseNotes", + "preview": true, "groups": [ { "name": "advanced", @@ -28,45 +30,40 @@ ], "inputs": [ { - "name": "filename", - "type": "string", - "label": "ms-resource:loc.input.label.filename", - "defaultValue": "", + "name": "script", + "type": "multiLine", + "label": "ms-resource:loc.input.label.script", "required": true, - "helpMarkDown": "ms-resource:loc.input.help.filename" - }, - { - "name": "arguments", - "type": "string", - "label": "ms-resource:loc.input.label.arguments", - "defaultValue": "", - "helpMarkDown": "ms-resource:loc.input.help.arguments", - "required": false + "defaultValue": "echo Write your commands here\necho Use the Environment input below to map secret variables into environment variables", + "properties": { + "resizable": "true", + "rows": "10", + "maxLength": "5000" + }, + "helpMarkDown": "" }, { - "name": "workingFolder", + "name": "workingDirectory", "type": "filePath", - "label": "ms-resource:loc.input.label.workingFolder", + "label": "ms-resource:loc.input.label.workingDirectory", "defaultValue": "", "required": false, "groupName": "advanced" }, { - "name": "failOnStandardError", + "name": "failOnStderr", "type": "boolean", - "label": "ms-resource:loc.input.label.failOnStandardError", + "label": "ms-resource:loc.input.label.failOnStderr", "defaultValue": "false", "required": false, - "helpMarkDown": "ms-resource:loc.input.help.failOnStandardError", + "helpMarkDown": "ms-resource:loc.input.help.failOnStderr", "groupName": "advanced" } ], "instanceNameFormat": "ms-resource:loc.instanceNameFormat", "execution": { - "Process": { - "target": "$(filename)", - "argumentFormat": "$(arguments)", - "workingDirectory": "$(workingFolder)", + "PowerShell3": { + "target": "cmdline.ps1", "platforms": [ "windows" ] @@ -78,6 +75,8 @@ }, "messages": { "CmdLineReturnCode": "ms-resource:loc.messages.CmdLineReturnCode", - "CmdLineFailed": "ms-resource:loc.messages.CmdLineFailed" + "CmdLineFailed": "ms-resource:loc.messages.CmdLineFailed", + "PS_ExitCode": "ms-resource:loc.messages.PS_ExitCode", + "PS_UnableToDetermineExitCode": "ms-resource:loc.messages.PS_UnableToDetermineExitCode" } } \ No newline at end of file diff --git a/make-options.json b/make-options.json index 2a757638e83e..20dab2cf8bda 100644 --- a/make-options.json +++ b/make-options.json @@ -16,7 +16,6 @@ "Chef", "ChefKnife", "CMake", - "CmdLine", "CocoaPods", "CopyFiles", "CopyFilesOverSSH",