Skip to content

Commit

Permalink
Script tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsciple committed Jun 20, 2017
1 parent bf85e8f commit 10703df
Show file tree
Hide file tree
Showing 17 changed files with 4,338 additions and 132 deletions.
27 changes: 15 additions & 12 deletions Tasks/powerShell/Strings/resources.resjson/en-US/resources.resjson
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
{
"loc.friendlyName": "PowerShell",
"loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613736)",
"loc.description": "Run a PowerShell script",
"loc.description": "Run a PowerShell script on Windows, Mac, or Linux.",
"loc.instanceNameFormat": "PowerShell Script",
"loc.releaseNotes": "Script task consistency.",
"loc.group.displayName.advanced": "Advanced",
"loc.input.label.scriptType": "Type",
"loc.input.help.scriptType": "Type of the script: File Path or Inline Script",
"loc.input.label.scriptName": "Script Path",
"loc.input.help.scriptName": "Path of the script to execute. Should be fully qualified path or relative to the default working directory.",
"loc.input.label.arguments": "Arguments",
"loc.input.help.arguments": "Arguments passed to the PowerShell script. Either ordinal parameters or named parameters",
"loc.input.label.workingFolder": "Working folder",
"loc.input.help.workingFolder": "Current working directory when script is run. Defaults to the folder where the script is located.",
"loc.input.label.inlineScript": "Inline Script",
"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 error pipeline, or if any data is written to the Standard Error stream. Otherwise the task will rely solely on $LASTEXITCODE and the exit code to determine failure."
"loc.input.label.script": "Script",
"loc.input.label.errorActionPreference": "ErrorActionPreference",
"loc.input.help.errorActionPreference": "Prepends the line `$ErrorActionPreference = 'VALUE'` at the top of your script.",
"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 error pipeline, or if any data is written to the Standard Error stream. Otherwise the task will rely on the exit code to determine failure.",
"loc.input.label.ignoreLASTEXITCODE": "Ignore $LASTEXITCODE",
"loc.input.help.ignoreLASTEXITCODE": "If this is false, the line `if ((Test-Path -LiteralPath variable:\\LASTEXITCODE)) { exit $LASTEXITCODE }` is appended to the end of your script. This will cause the last exit code from an external command to be propagated as the exit code of powershell. Otherwise the line is not appended to the end of your script.",
"loc.input.label.workingDirectory": "Working Directory",
"loc.input.help.workingDirectory": "Working directory where the script is run.",
"loc.messages.JS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '%s'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'",
"loc.messages.PS_ExitCode": "powershell exited with code '{0}'.",
"loc.messages.PS_InvalidErrorActionPreference": "Invalid ErrorActionPreference '{0}'. The value must be one of: 'Stop', 'Continue', or 'SilentlyContinue'",
"loc.messages.PS_UnableToDetermineExitCode": "Unexpected exception. Unable to determine the exit code from powershell."
}
24 changes: 24 additions & 0 deletions Tasks/powerShell/make.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
}
16 changes: 16 additions & 0 deletions Tasks/powerShell/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "vsts-powershell-task",
"version": "1.0.0",
"description": "VSTS PowerShell Task",
"main": "powershell.js",
"repository": {
"type": "git",
"url": "git+https://github.com/Microsoft/vsts-tasks.git"
},
"author": "Microsoft Corporation",
"license": "MIT",
"dependencies": {
"uuid": "^3.0.1",
"vsts-task-lib": "2.0.5"
}
}
142 changes: 142 additions & 0 deletions Tasks/powerShell/powershell.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
[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_errorActionPreference = Get-VstsInput -Name 'errorActionPreference' -Default 'Stop'
switch ($input_errorActionPreference.ToUpperInvariant()) {
'STOP' { }
'CONTINUE' { }
'SILENTLYCONTINUE' { }
default {
Write-Error (Get-VstsLocString -Key 'PS_InvalidErrorActionPreference' -ArgumentList $input_errorActionPreference)
}
}
$input_failOnStderr = Get-VstsInput -Name 'failOnStderr' -AsBool
$input_ignoreLASTEXITCODE = Get-VstsInput -Name 'ignoreLASTEXITCODE' -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.
$contents = @()
$contents += "`$ErrorActionPreference = '$input_errorActionPreference'"
$contents += $input_script
if (!$ignoreLASTEXITCODE) {
$contents += 'if (!(Test-Path -LiteralPath variable:\LASTEXITCODE)) {'
$contents += ' Write-Verbose ''Last exit code is not set.'''
$contents += '} else {'
$contents += ' Write-Verbose (''$LASTEXITCODE: {0}'' -f $LASTEXITCODE)'
$contents += ' exit $LASTEXITCODE'
$contents += '}'
}

# 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()).ps1")
$joinedContents = [System.String]::Join(
([System.Environment]::NewLine),
$contents)
$null = [System.IO.File]::WriteAllText(
$filePath,
$joinedContents,
([System.Text.Encoding]::UTF8))

# Prepare the external command values.
$powershellPath = (Get-Command -Name powershell.exe -CommandType Application).Path
Assert-VstsPath -LiteralPath $powershellPath -PathType 'Leaf'
$arguments = "-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File `"$filePath`""
$splat = @{
'FileName' = $powershellPath
'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
}
75 changes: 75 additions & 0 deletions Tasks/powerShell/powershell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import fs = require('fs');
import path = require('path');
import os = require('os');
import tl = require('vsts-task-lib/task');
import tr = require('vsts-task-lib/toolrunner');
var uuidV4 = require('uuid/v4');

async function run() {
try {
tl.setResourcePath(path.join(__dirname, 'task.json'));

// Get inputs.
let errorActionPreference: string = tl.getInput('errorActionPreference', false) || 'Stop';
switch (errorActionPreference.toUpperCase()) {
case 'STOP':
case 'CONTINUE':
case 'SILENTLYCONTINUE':
break;
default:
throw new Error(tl.loc('JS_InvalidErrorActionPreference', errorActionPreference));
}
let executionPolicy: string = tl.getInput('executionPolicy', false) || 'Unrestricted';
let failOnStderr = tl.getBoolInput('failOnStderr', false);
let ignoreExitCode = tl.getBoolInput('ignoreExitCode', false);
let ignoreLASTEXITCODE = tl.getBoolInput('ignoreLASTEXITCODE', false);
let script: string = tl.getInput('script', false) || '';
let workingDirectory = tl.getPathInput('workingDirectory', /*required*/ true, /*check*/ true);

// Generate the script contents.
let contents: string[] = [];
contents.push(`$ErrorActionPreference = '${errorActionPreference}'`);
contents.push(script);
if (!ignoreLASTEXITCODE) {
contents.push(`if (!(Test-Path -LiteralPath variable:\LASTEXITCODE)) {`);
contents.push(` Write-Verbose 'Last exit code is not set.'`);
contents.push(`} else {`);
contents.push(` Write-Verbose ('$LASTEXITCODE: {0}' -f $LASTEXITCODE)`);
contents.push(` exit $LASTEXITCODE`);
contents.push(`}`);
}

// Write the script to disk.
tl.assertAgent('2.115.0');
let tempDirectory = tl.getVariable('agent.tempDirectory');
tl.checkPath(tempDirectory, `${tempDirectory} (agent.tempDirectory)`);
let filePath = path.join(tempDirectory, uuidV4() + '.ps1');
await fs.writeFile(
filePath,
'\ufeff' + contents.join(os.EOL), // Prepend the Unicode BOM character.
{ encoding: 'utf8' }); // Since UTF8 encoding is specified, node will
// // encode the BOM into its UTF8 binary sequence.

// Run the script.
let powershell = tl.tool(tl.which('powershell', true))
.arg('-NoLogo')
.arg(`-Sta`)
.arg('-NoProfile')
.arg('-NonInteractive')
.arg('-ExecutionPolicy')
.arg(executionPolicy)
.arg('-File')
.arg(filePath);
let options = <tr.IExecOptions>{
cwd: workingDirectory,
failOnStdErr: failOnStderr,
ignoreReturnCode: ignoreExitCode
};
await powershell.exec(options);
}
catch (err) {
tl.setResult(tl.TaskResult.Failed, err.message || 'run() failed');
}
}

run();
Loading

0 comments on commit 10703df

Please sign in to comment.