Skip to content

Commit

Permalink
Fail on standard error option for azure powershell task (#6324)
Browse files Browse the repository at this point in the history
* adding failonstderror option to azure ps task

* updating the patch version to the correct number

* moving failonstandarderror option to the advanced section

* fix tests for redirecting errors

* adding test to check that the task succeeds if failonstandarderror is set to false and non-terminating errors are written to the error stream

* adding changes for erroractionpreference and failonstandarderror

* minor changes

* fixing tests

* adding L0 test to check that failonstandarderror is honored

* adding L0 test to check that failonstandarderror is honored

* fix common tests

* fix common tests

* mitigate alias not found bug in latest azure powershell releases

* use connect-azurermaccount cmdlet for usernamepassword authscheme as well

* changing temp directory path from system temp directory to agent temp directory for generating inline script

* update releasenotes and update comments
  • Loading branch information
arjgupta authored Mar 14, 2018
1 parent fc17d6e commit a5ac2c0
Show file tree
Hide file tree
Showing 21 changed files with 287 additions and 68 deletions.
38 changes: 27 additions & 11 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
$__vsts_input_failOnStandardError = Get-VstsInput -Name FailOnStandardError
$targetAzurePs = Get-VstsInput -Name TargetAzurePs
$customTargetAzurePs = Get-VstsInput -Name CustomTargetAzurePs

Expand Down Expand Up @@ -75,11 +77,12 @@ 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") {
$__vstsAzPSInlineScriptPath = [System.IO.Path]::Combine(([System.IO.Path]::GetTempPath()), ([guid]::NewGuid().ToString() + ".ps1"));
$scriptArguments = $null
$__vstsAzPSInlineScriptPath = [System.IO.Path]::Combine($env:Agent_TempDirectory, ([guid]::NewGuid().ToString() + ".ps1"));
($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") {
,$_
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,8 @@
"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.releaseNotes": "This is an early preview. Added support for Fail on standard error and ErrorActionPreference",
"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 +18,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
5 changes: 5 additions & 0 deletions Tasks/AzurePowerShell/Tests/ValidateInlineScriptFlow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ param()
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
Unregister-Mock Get-VstsInput
$targetAzurePs = "4.1.0"
if([string]::IsNullOrEmpty($env:Agent_TempDirectory)) {
$env:Agent_TempDirectory = $env:TEMP
}
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
45 changes: 39 additions & 6 deletions Tasks/AzurePowerShell/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@
],
"author": "Microsoft Corporation",
"version": {
"Major": 2,
"Major": 3,
"Minor": 0,
"Patch": 7
"Patch": 0
},
"releaseNotes": "This is an early preview. Added support for Fail on standard error and ErrorActionPreference",
"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 +68,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 +106,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 +156,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

0 comments on commit a5ac2c0

Please sign in to comment.