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

Users/pragupta/azurepwrshelltask #3175

Merged
merged 3 commits into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
106 changes: 62 additions & 44 deletions Tasks/AzurePowerShell/AzurePowerShell.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,82 @@ Trace-VstsEnteringInvocation $MyInvocation
Import-VstsLocStrings "$PSScriptRoot\Task.json"

# Get inputs.
$scriptPath = Get-VstsInput -Name ScriptPath -Require
$scriptType = Get-VstsInput -Name ScriptType -Require
Copy link
Contributor

Choose a reason for hiding this comment

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

when script type is filepath, then shouldn't ScriptPath be required? i.e. Get-VstsInput -Name ScriptPath -Require

similar thing for inline scenario

$scriptPath = Get-VstsInput -Name ScriptPath
$scriptInline = Get-VstsInput -Name Inline
$scriptArguments = Get-VstsInput -Name ScriptArguments

# Validate the script path and args do not contains new-lines. Otherwise, it will
# break invoking the script via Invoke-Expression.
if ($scriptPath -match '[\r\n]') {
throw (Get-VstsLocString -Key InvalidScriptPath0 -ArgumentList $scriptPath)
if ($scriptType -eq "FilePath") {
if ($scriptPath -match '[\r\n]' -or [string]::IsNullOrWhitespace($scriptPath)) {
throw (Get-VstsLocString -Key InvalidScriptPath0 -ArgumentList $scriptPath)
}
}

if ($scriptArguments -match '[\r\n]') {
throw (Get-VstsLocString -Key InvalidScriptArguments0 -ArgumentList $scriptArguments)
}

# Initialize Azure.
Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_
Initialize-Azure
try {
# Initialize Azure.
Import-Module $PSScriptRoot\ps_modules\VstsAzureHelpers_
Initialize-Azure

# Trace the expression as it will be invoked.
If ($scriptType -eq "InlineScript") {
$tempFileName = [guid]::NewGuid().ToString() + ".ps1";
$scriptPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), $tempFileName);
($scriptInline | Out-File $scriptPath)
}

# Trace the expression as it will be invoked.
$scriptCommand = "& '$($scriptPath.Replace("'", "''"))' $scriptArguments"
Remove-Variable -Name scriptPath
Remove-Variable -Name scriptArguments
$scriptCommand = "& '$($scriptPath.Replace("'", "''"))' $scriptArguments"
Remove-Variable -Name scriptArguments

# Remove all commands imported from VstsTaskSdk, other than Out-Default.
# Remove all commands imported from VstsAzureHelpers_.
Get-ChildItem -LiteralPath function: |
Where-Object {
($_.ModuleName -eq 'VstsTaskSdk' -and $_.Name -ne 'Out-Default') -or
($_.Name -eq 'Invoke-VstsTaskScript') -or
($_.ModuleName -eq 'VstsAzureHelpers_' )
} |
Remove-Item
# Remove all commands imported from VstsTaskSdk, other than Out-Default.
# Remove all commands imported from VstsAzureHelpers_.
Get-ChildItem -LiteralPath function: |
Where-Object {
($_.ModuleName -eq 'VstsTaskSdk' -and $_.Name -ne 'Out-Default') -or
($_.Name -eq 'Invoke-VstsTaskScript') -or
($_.ModuleName -eq 'VstsAzureHelpers_' )
} |
Remove-Item

# For compatibility with the legacy handler implementation, set the error action
# preference to continue. An implication of changing the preference to Continue,
# is that Invoke-VstsTaskScript will no longer handle setting the result to failed.
$global:ErrorActionPreference = 'Continue'
# For compatibility with the legacy handler implementation, set the error action
# preference to continue. An implication of changing the preference to Continue,
# is that Invoke-VstsTaskScript will no longer handle setting the result to failed.
$global:ErrorActionPreference = 'Continue'

# Run the user's script. Redirect the error pipeline to the output pipeline to enable
# a couple goals due to compatibility with the legacy handler implementation:
# 1) STDERR from external commands needs to be converted into error records. Piping
# the redirected error output to an intermediate command before it is piped to
# Out-Default will implicitly perform the conversion.
# 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)) |
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.
,$_
# Run the user's script. Redirect the error pipeline to the output pipeline to enable
# a couple goals due to compatibility with the legacy handler implementation:
# 1) STDERR from external commands needs to be converted into error records. Piping
# the redirected error output to an intermediate command before it is piped to
# Out-Default will implicitly perform the conversion.
# 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)) |
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]"
# Set the task result to failed if the object is an error record.
if ($_ -is [System.Management.Automation.ErrorRecord]) {
"##vso[task.complete result=Failed]"
}
}
}
Finally {
If ($scriptType -eq "InlineScript" -and (Test-Path $scriptPath) -eq $true ) {
Remove-Item $scriptPath -ErrorAction 'SilentlyContinue'
}

Remove-Variable -Name scriptPath
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
"loc.friendlyName": "Azure PowerShell",
"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: $(ScriptPath)",
"loc.instanceNameFormat": "Azure PowerShell script: $(ScriptType)",
"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",
"loc.input.label.ConnectedServiceNameARM": "Azure RM Subscription",
"loc.input.help.ConnectedServiceNameARM": "Azure Resource Manager subscription to configure before running PowerShell",
"loc.input.label.ScriptType": "Script Type",
"loc.input.help.ScriptType": "Type of the script: File Path or Inline Script",
"loc.input.label.ScriptPath": "Script Path",
"loc.input.help.ScriptPath": "Path of the script. Should be fully qualified path or relative to the default working directory.",
"loc.input.label.Inline": "Inline Script",
"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.messages.InvalidScriptArguments0": "Invalid script arguments '{0}'. Line breaks are not allowed.",
Expand Down
3 changes: 2 additions & 1 deletion Tasks/AzurePowerShell/Tests/DoesNotUnravelOutput.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
Register-Mock Get-VstsInput { "$PSScriptRoot/DoesNotUnravelOutput_TargetScript.ps1" } -- -Name ScriptPath -Require
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/DoesNotUnravelOutput_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Initialize-Azure

# Act.
Expand Down
3 changes: 3 additions & 0 deletions Tasks/AzurePowerShell/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ describe('AzurePowerShell Suite', function () {
it('performs basic flow', (done) => {
psr.run(path.join(__dirname, 'PerformsBasicFlow.ps1'), done);
})
it('validates inline script flow', (done) => {
psr.run(path.join(__dirname, 'ValidateInlineScriptFlow.ps1'), done);
})
it('redirects errors', (done) => {
psr.run(path.join(__dirname, 'RedirectsErrors.ps1'), done);
})
Expand Down
3 changes: 2 additions & 1 deletion Tasks/AzurePowerShell/Tests/PerformsBasicFlow.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
Register-Mock Get-VstsInput { "$PSScriptRoot/PerformsBasicFlow_TargetScript.ps1" } -- -Name ScriptPath -Require
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/PerformsBasicFlow_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Get-VstsInput { 'arg1 arg2' } -- -Name ScriptArguments
Register-Mock Initialize-Azure

Expand Down
3 changes: 2 additions & 1 deletion Tasks/AzurePowerShell/Tests/RedirectsErrors.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
Register-Mock Get-VstsInput { "$PSScriptRoot/RedirectsErrors_TargetScript.ps1" } -- -Name ScriptPath -Require
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/RedirectsErrors_TargetScript.ps1" } -- -Name ScriptPath
Register-Mock Initialize-Azure

# Act.
Expand Down
4 changes: 2 additions & 2 deletions Tasks/AzurePowerShell/Tests/RemovesFunctionsAndVariables.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ param()
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1

# Arrange the task inputs.
Register-Mock Get-VstsInput { "$PSScriptRoot/RemovesFunctionsAndVariables_TargetScript.ps1" } -- -Name ScriptPath -Require
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { "$PSScriptRoot/RemovesFunctionsAndVariables_TargetScript.ps1" } -- -Name ScriptPath

# Arrange the mock task SDK module.
New-Module -Name VstsTaskSdk -ScriptBlock {
Expand Down Expand Up @@ -39,6 +40,5 @@ Assert-AreEqual $false $actual.FunctionNames.ContainsKey('SomeAzureHelpersFuncti
Assert-AreEqual $false $actual.FunctionNames.ContainsKey('SomeAzureHelpersFunction2')

# Assert the local variables from the task script were removed.
Assert-AreEqual $false $actual.VariableNames.ContainsKey('scriptPath')
Assert-AreEqual $false $actual.VariableNames.ContainsKey('scriptArguments')
Assert-AreEqual $false $actual.VariableNames.ContainsKey('scriptCommand')
3 changes: 2 additions & 1 deletion Tasks/AzurePowerShell/Tests/ThrowsWhenInvalidScriptPath.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ param()
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
foreach ($path in @( "script`rpath", "script`npath" )) {
Unregister-Mock Get-VstsInput
Register-Mock Get-VstsInput { $path } -- -Name ScriptPath -Require
Register-Mock Get-VstsInput { "FilePath" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { $path } -- -Name ScriptPath

# Act/Assert.
Assert-Throws {
Expand Down
21 changes: 21 additions & 0 deletions Tasks/AzurePowerShell/Tests/ValidateInlineScriptFlow.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\..\..\Tests\lib\Initialize-Test.ps1
Unregister-Mock Get-VstsInput
Register-Mock Get-VstsInput { "InlineScript" } -- -Name ScriptType -Require
Register-Mock Get-VstsInput { ",@( 'item 1', 'item 2')" } -- -Name Inline
Register-Mock Initialize-Azure

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

# Assert the correct number of elements is returned.
Assert-AreEqual 1 $actual.Length

# Assert item 1 and 2 are in an array together.
Assert-AreEqual 2 @($actual[0]).Length
Assert-AreEqual 'item 1' $actual[0][0]
Assert-AreEqual 'item 2' $actual[0][1]
35 changes: 31 additions & 4 deletions Tasks/AzurePowerShell/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"version": {
"Major": 1,
"Minor": 1,
"Patch": 3
"Patch": 4
},
"demands": [
"azureps"
Expand Down Expand Up @@ -54,13 +54,40 @@
"helpMarkDown": "Azure Resource Manager subscription to configure before running PowerShell",
"visibleRule": "ConnectedServiceNameSelector = ConnectedServiceNameARM"
},
{
"name": "ScriptType",
"type": "pickList",
"label": "Script Type",
"required": true,
"helpMarkDown": "Type of the script: File Path or Inline Script",
"defaultValue": "FilePath",
"options": {
"FilePath": "Script File Path",
"InlineScript": "Inline Script"
}
},
{
"name": "ScriptPath",
"type": "filePath",
"label": "Script Path",
"defaultValue": "",
"required": true,
"helpMarkDown": "Path of the script. Should be fully qualified path or relative to the default working directory."
"required": false,
"helpMarkDown": "Path of the script. Should be fully qualified path or relative to the default working directory.",
"visibleRule": "ScriptType = FilePath"
},
{
"name": "Inline",
"type": "multiLine",
"label": "Inline Script",
"required": false,
"defaultValue": "# You can write your azure powershell scripts inline here. \n# You can also pass predefined and custom variables to this script using arguments",
"helpMarkDown": "Enter the script to execute.",
"visibleRule": "ScriptType = InlineScript",
"properties": {
"resizable": "true",
"rows": "10",
"maxLength": "500"
}
},
{
"name": "ScriptArguments",
Expand All @@ -74,7 +101,7 @@
"helpMarkDown": "Additional parameters to pass to PowerShell. Can be either ordinal or named parameters."
}
],
"instanceNameFormat": "Azure PowerShell script: $(ScriptPath)",
"instanceNameFormat": "Azure PowerShell script: $(ScriptType)",
"execution": {
"PowerShell3": {
"target": "AzurePowerShell.ps1"
Expand Down
33 changes: 30 additions & 3 deletions Tasks/AzurePowerShell/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"version": {
"Major": 1,
"Minor": 1,
"Patch": 3
"Patch": 4
},
"demands": [
"azureps"
Expand Down Expand Up @@ -54,13 +54,40 @@
"helpMarkDown": "ms-resource:loc.input.help.ConnectedServiceNameARM",
"visibleRule": "ConnectedServiceNameSelector = ConnectedServiceNameARM"
},
{
"name": "ScriptType",
"type": "pickList",
"label": "ms-resource:loc.input.label.ScriptType",
"required": true,
"helpMarkDown": "ms-resource:loc.input.help.ScriptType",
"defaultValue": "FilePath",
"options": {
"FilePath": "Script File Path",
"InlineScript": "Inline Script"
}
},
{
"name": "ScriptPath",
"type": "filePath",
"label": "ms-resource:loc.input.label.ScriptPath",
"defaultValue": "",
"required": true,
"helpMarkDown": "ms-resource:loc.input.help.ScriptPath"
"required": false,
"helpMarkDown": "ms-resource:loc.input.help.ScriptPath",
"visibleRule": "ScriptType = FilePath"
},
{
"name": "Inline",
"type": "multiLine",
"label": "ms-resource:loc.input.label.Inline",
"required": false,
"defaultValue": "# You can write your azure powershell scripts inline here. \n# You can also pass predefined and custom variables to this script using arguments",
"helpMarkDown": "ms-resource:loc.input.help.Inline",
"visibleRule": "ScriptType = InlineScript",
"properties": {
"resizable": "true",
"rows": "10",
"maxLength": "500"
}
},
{
"name": "ScriptArguments",
Expand Down