Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail on standard error option for azure powershell task #6324

Merged
merged 17 commits into from
Mar 14, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions Tasks/AzurePowerShell/AzurePowerShell.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ $scriptType = Get-VstsInput -Name ScriptType -Require
$scriptPath = Get-VstsInput -Name ScriptPath
$scriptInline = Get-VstsInput -Name Inline
$scriptArguments = Get-VstsInput -Name ScriptArguments
$__vsts_input_errorActionPreference = Get-VstsInput -Name errorActionPreference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why have a different naming convention just for these inputs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

erroractionpreference is a well known name. the other reason is that we remove the variables and functions from the current scope before executing the script, but we cannot remove these variables since we need them as the script is executing. And so, to avoid a conflict between these names and similar names defined in the user script, we use this scheme.

$__vsts_input_failOnStandardError = Get-VstsInput -Name FailOnStandardError
$targetAzurePs = Get-VstsInput -Name TargetAzurePs
$customTargetAzurePs = Get-VstsInput -Name CustomTargetAzurePs

Expand Down Expand Up @@ -75,10 +77,11 @@ Update-PSModulePathForHostedAgent -targetAzurePs $targetAzurePs -authScheme $aut
try {
# Initialize Azure.
Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_
Initialize-Azure -azurePsVersion $targetAzurePs
Initialize-Azure -azurePsVersion $targetAzurePs -strict
# Trace the expression as it will be invoked.
$__vstsAzPSInlineScriptPath = $null
If ($scriptType -eq "InlineScript") {
$scriptArguments = $null
$__vstsAzPSInlineScriptPath = [System.IO.Path]::Combine(([System.IO.Path]::GetTempPath()), ([guid]::NewGuid().ToString() + ".ps1"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be using Agent.TempDirectory instead of users temp path?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

($scriptInline | Out-File $__vstsAzPSInlineScriptPath)
$scriptPath = $__vstsAzPSInlineScriptPath
Expand Down Expand Up @@ -118,20 +121,33 @@ try {
# 2) The task result needs to be set to failed if an error record is encountered.
# As mentioned above, the requirement to handle this is an implication of changing
# the error action preference.
([scriptblock]::Create($scriptCommand)) |
([scriptblock]::Create($scriptCommand)) |
ForEach-Object {
Remove-Variable -Name scriptCommand
Write-Host "##[command]$_"
. $_ 2>&1
} |
} |
ForEach-Object {
# Put the object back into the pipeline. When doing this, the object needs
# to be wrapped in an array to prevent unraveling.
,$_

# Set the task result to failed if the object is an error record.
if ($_ -is [System.Management.Automation.ErrorRecord]) {
"##vso[task.complete result=Failed]"
if($_ -is [System.Management.Automation.ErrorRecord]) {
if($_.FullyQualifiedErrorId -eq "NativeCommandError" -or $_.FullyQualifiedErrorId -eq "NativeCommandErrorMessage") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this errorId localizable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

,$_
if($__vsts_input_failOnStandardError -eq $true) {
"##vso[task.complete result=Failed]"
}
}
else {
if($__vsts_input_errorActionPreference -eq "continue") {
,$_
if($__vsts_input_failOnStandardError -eq $true) {
"##vso[task.complete result=Failed]"
}
}
elseif($__vsts_input_errorActionPreference -eq "stop") {
throw $_
}
}
} else {
,$_
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"loc.helpMarkDown": "[More Information](https://go.microsoft.com/fwlink/?LinkID=613749)",
"loc.description": "Run a PowerShell script within an Azure environment",
"loc.instanceNameFormat": "Azure PowerShell script: $(ScriptType)",
"loc.group.displayName.AzurePowerShellVersionOptions": "Azure PowerShell version options",
"loc.input.label.ConnectedServiceNameSelector": "Azure Connection Type",
"loc.input.label.ConnectedServiceName": "Azure Classic Subscription",
"loc.input.help.ConnectedServiceName": "Azure Classic subscription to configure before running PowerShell",
Expand All @@ -16,6 +17,10 @@
"loc.input.help.Inline": "Enter the script to execute.",
"loc.input.label.ScriptArguments": "Script Arguments",
"loc.input.help.ScriptArguments": "Additional parameters to pass to PowerShell. Can be either ordinal or named parameters.",
"loc.input.label.errorActionPreference": "ErrorActionPreference",
"loc.input.help.errorActionPreference": "Select the value of the ErrorActionPreference variable for executing the 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.",
"loc.input.label.TargetAzurePs": "Azure PowerShell Version",
"loc.input.help.TargetAzurePs": "In case of hosted agents, the supported Azure PowerShell Versions are: 2.1.0, 3.8.0, 4.2.1 and 5.1.1(Hosted VS2017 Queue), 3.6.0(Hosted Queue).\nTo pick the latest version available on the agent, select \"Latest installed version\".\n\nFor private agents you can specify preferred version of Azure PowerShell using \"Specify version\"",
"loc.input.label.CustomTargetAzurePs": "Preferred Azure PowerShell Version",
Expand Down
24 changes: 24 additions & 0 deletions Tasks/AzurePowerShell/Tests/DoesNotFailOnStandardError.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
$targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/RedirectsErrors_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $false } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

# Act.
$actual = @( & $PSScriptRoot\..\AzurePowerShell.ps1 )
$global:ErrorActionPreference = 'Stop' # Reset to stop.

# Assert.
Assert-AreEqual 4 $actual.Length
Assert-AreEqual 'Some output 1' $actual[0]
Assert-AreEqual 'Some error 1' $actual[1].Exception.Message
Assert-AreEqual 'Some output 2' $actual[2]
Assert-AreEqual 'Some error 2' $actual[3].Exception.Message
24 changes: 24 additions & 0 deletions Tasks/AzurePowerShell/Tests/DoesNotThrowForNativeCommandError.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
$targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/NativeCommandError_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "stop" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $false } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

# Act.
$actual = @( & $PSScriptRoot\..\AzurePowerShell.ps1 )
$global:ErrorActionPreference = 'Stop' # Reset to stop.

# Assert.
Assert-AreEqual 4 $actual.Length
Assert-AreEqual 'output 1' $actual[0]
Assert-AreEqual 'NativeCommandError' $actual[1].FullyQualifiedErrorId
Assert-AreEqual 'NativeCommandErrorMessage' $actual[2].FullyQualifiedErrorId
Assert-AreEqual 'output 2' $actual[3]
2 changes: 2 additions & 0 deletions Tasks/AzurePowerShell/Tests/DoesNotUnravelOutput.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ $targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/DoesNotUnravelOutput_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

Expand Down
26 changes: 26 additions & 0 deletions Tasks/AzurePowerShell/Tests/FailsForNativeCommandError.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
$targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/NativeCommandError_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "silentlyContinue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

# Act.
$actual = @( & $PSScriptRoot\..\AzurePowerShell.ps1 )
$global:ErrorActionPreference = 'Stop' # Reset to stop.

# Assert.
Assert-AreEqual 6 $actual.Length
Assert-AreEqual 'output 1' $actual[0]
Assert-AreEqual 'NativeCommandError' $actual[1].FullyQualifiedErrorId
Assert-AreEqual '##vso[task.complete result=Failed]' $actual[2]
Assert-AreEqual 'NativeCommandErrorMessage' $actual[3].FullyQualifiedErrorId
Assert-AreEqual '##vso[task.complete result=Failed]' $actual[4]
Assert-AreEqual 'output 2' $actual[5]
9 changes: 9 additions & 0 deletions Tasks/AzurePowerShell/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ describe('AzurePowerShell Suite', function () {
it('redirects errors', (done) => {
psr.run(path.join(__dirname, 'RedirectsErrors.ps1'), done);
})
it('does not fail if failonstandarderror is set to false', (done) => {
psr.run(path.join(__dirname, 'DoesNotFailOnStandardError.ps1'), done);
})
it('removes functions and variables', (done) => {
psr.run(path.join(__dirname, 'RemovesFunctionsAndVariables.ps1'), done);
})
Expand All @@ -51,6 +54,12 @@ describe('AzurePowerShell Suite', function () {
it('throws when invalid script path', (done) => {
psr.run(path.join(__dirname, 'ThrowsWhenInvalidScriptPath.ps1'), done);
})
it('does not fail if native command writes to stderr and failonstderr is false', (done) => {
psr.run(path.join(__dirname, 'DoesNotThrowForNativeCommandError.ps1'), done);
})
it('fails for native command error if fail on standard error is true', (done) => {
psr.run(path.join(__dirname, 'FailsForNativeCommandError.ps1'), done);
})
it('Get-LatestModule returns the latest available module', (done) => {
psr.run(path.join(__dirname, 'Utility.Get-LatestModule.ps1'), done);
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Write-Output "output 1"
net user foobar
Write-Output "output 2"
2 changes: 2 additions & 0 deletions Tasks/AzurePowerShell/Tests/PerformsBasicFlow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/PerformsBasicFlow_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { 'arg1 arg2' } -- -Name ScriptArguments
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure
Register-Mock Get-VstsEndpoint { @{auth = @{ scheme = "ServicePrincipal" }} }
Expand Down
2 changes: 2 additions & 0 deletions Tasks/AzurePowerShell/Tests/RedirectsErrors.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ $targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/RedirectsErrors_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

Expand Down
2 changes: 2 additions & 0 deletions Tasks/AzurePowerShell/Tests/RemovesFunctionsAndVariables.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ $targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/RemovesFunctionsAndVariables_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent

# Arrange the mock task SDK module.
Expand Down
2 changes: 2 additions & 0 deletions Tasks/AzurePowerShell/Tests/ValidateInlineScriptFlow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ $targetAzurePs = "4.1.0"
Register-Mock Get-VstsInput { "InlineScript" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { ",@( 'item 1', 'item 2')" } -- -Name Inline
Register-Mock Get-VstsInput { $targetAzurePs } -- -Name TargetAzurePs
Register-Mock Get-VstsInput { "continue" } -- -Name errorActionPreference
Register-Mock Get-VstsInput { $true } -- -Name FailOnStandardError
Register-Mock Update-PSModulePathForHostedAgent
Register-Mock Initialize-Azure

Expand Down
44 changes: 38 additions & 6 deletions Tasks/AzurePowerShell/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@
],
"author": "Microsoft Corporation",
"version": {
"Major": 2,
"Major": 3,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "releaseNotes" attribute to call out what is changed in this major version.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"Minor": 0,
"Patch": 7
"Patch": 2
},
"demands": [
"azureps"
],
"minimumAgentVersion": "1.95.0",
"preview": true,
"groups": [
{
"name": "AzurePowerShellVersionOptions",
"displayName": "Azure PowerShell version options",
"isExpanded": true
}
],
"minimumAgentVersion": "2.0.0",
"inputs": [
{
"name": "ConnectedServiceNameSelector",
Expand Down Expand Up @@ -59,9 +67,9 @@
},
{
"name": "ScriptType",
"type": "pickList",
"type": "radio",
"label": "Script Type",
"required": true,
"required": false,
"helpMarkDown": "Type of the script: File Path or Inline Script",
"defaultValue": "FilePath",
"options": {
Expand Down Expand Up @@ -97,23 +105,46 @@
"type": "string",
"label": "Script Arguments",
"defaultValue": "",
"visibleRule": "ScriptType = FilePath",
"required": false,
"properties": {
"editorExtension": "ms.vss-services-azure.parameters-grid"
},
"helpMarkDown": "Additional parameters to pass to PowerShell. Can be either ordinal or named parameters."
},
{
"name": "errorActionPreference",
"type": "pickList",
"label": "ErrorActionPreference",
"required": false,
"defaultValue": "stop",
"options": {
"stop": "Stop",
"continue": "Continue",
"silentlyContinue": "SilentlyContinue"
},
"helpMarkDown": "Select the value of the ErrorActionPreference variable for executing the script."
},
{
"name": "FailOnStandardError",
"type": "boolean",
"label": "Fail on Standard Error",
"required": false,
"defaultValue": "false",
"helpMarkDown": "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."
},
{
"name": "TargetAzurePs",
"aliases": ["azurePowerShellVersion"],
"type": "pickList",
"type": "radio",
"label": "Azure PowerShell Version",
"defaultValue": "OtherVersion",
"required": false,
"options": {
"LatestVersion": "Latest installed version",
"OtherVersion": "Specify other version"
},
"groupName": "AzurePowerShellVersionOptions",
"helpMarkDown": "In case of hosted agents, the supported Azure PowerShell Versions are: 2.1.0, 3.8.0, 4.2.1 and 5.1.1(Hosted VS2017 Queue), 3.6.0(Hosted Queue).\nTo pick the latest version available on the agent, select \"Latest installed version\".\n\nFor private agents you can specify preferred version of Azure PowerShell using \"Specify version\""
},
{
Expand All @@ -124,6 +155,7 @@
"defaultValue": "",
"required": true,
"visibleRule": "TargetAzurePs = OtherVersion",
"groupName": "AzurePowerShellVersionOptions",
"helpMarkDown": "Preferred Azure PowerShell Version needs to be a proper semantic version eg. 1.2.3. Regex like 2.\\*,2.3.\\* is not supported. The Hosted VS2017 Pool currently supports versions: 2.1.0, 3.8.0, 4.2.1, 5.1.1"
}
],
Expand Down
Loading