diff --git a/Tasks/PowerShellV2/Tests/L0.ts b/Tasks/PowerShellV2/Tests/L0.ts index 585c7e83b3ba..339d7edb3f05 100644 --- a/Tasks/PowerShellV2/Tests/L0.ts +++ b/Tasks/PowerShellV2/Tests/L0.ts @@ -98,6 +98,36 @@ describe('PowerShell Suite', function () { }, tr); }); + it('Respects WarningAction when showWarnings is enabled', async () => { + this.timeout(5000); + + let tp: string = path.join(__dirname, 'L0ShowWarnings.js'); + let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); + + await tr.runAsync(); + + runValidations(() => { + assert(tr.succeeded, 'PowerShell should have succeeded.'); + assert(tr.stderr.length === 0, 'PowerShell should not have written to stderr'); + + // Should only show one Azure DevOps warning (the first one without WarningAction) + const azureWarnings = tr.stdout.match(/##vso\[task\.logissue type=warning;\]/g); + assert(azureWarnings && azureWarnings.length === 1, 'Should only show one Azure DevOps warning for the default warning'); + + // Should show the default warning as Azure DevOps warning + assert(tr.stdout.indexOf('##vso[task.logissue type=warning;]a) Default: Shows = true & Expected = true') > 0, + 'Should show the default warning as Azure DevOps warning'); + + // Should NOT show suppressed warnings as Azure DevOps warnings + assert(tr.stdout.indexOf('##vso[task.logissue type=warning;]b) SilentlyContinue') === -1, + 'Should not show SilentlyContinue warning as Azure DevOps warning'); + assert(tr.stdout.indexOf('##vso[task.logissue type=warning;]c) Ignore') === -1, + 'Should not show Ignore warning as Azure DevOps warning'); + assert(tr.stdout.indexOf('##vso[task.logissue type=warning;]d) Redirect') === -1, + 'Should not show redirected warning as Azure DevOps warning'); + }, tr); + }); + describe('Environment variable expansion', testEnvExpansion); describe('Validate file arguments', runValidateFileArgsTests) }); diff --git a/Tasks/PowerShellV2/Tests/L0ShowWarnings.ts b/Tasks/PowerShellV2/Tests/L0ShowWarnings.ts new file mode 100644 index 000000000000..182876152f0e --- /dev/null +++ b/Tasks/PowerShellV2/Tests/L0ShowWarnings.ts @@ -0,0 +1,85 @@ +import ma = require('azure-pipelines-task-lib/mock-answer'); +import tmrm = require('azure-pipelines-task-lib/mock-run'); +import path = require('path'); + +let taskPath = path.join(__dirname, '..', 'powershell.js'); +let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath); + +tmr.setInput('targetType', 'inline'); +tmr.setInput('script', ` +Write-Warning "a) Default: Shows = true & Expected = true" +Write-Warning "b) SilentlyContinue: Shows = false & Expected = false" -WarningAction SilentlyContinue +Write-Warning "c) Ignore: Shows = false & Expected = false" -WarningAction Ignore +Write-Warning "d) Redirect null: Shows = false & Expected = false" 3> $null +Write-Warning "e) Redirect file: Shows = false & Expected = false" 3> "warnings.txt" +`); +tmr.setInput('workingDirectory', '/fakecwd'); +tmr.setInput('ignoreLASTEXITCODE', 'true'); +tmr.setInput('showWarnings', 'true'); + +//Create assertAgent and getVariable mocks, support not added in this version of task-lib +const tl = require('azure-pipelines-task-lib/mock-task'); +const tlClone = Object.assign({}, tl); +tlClone.getVariable = function(variable: string) { + if (variable.toLowerCase() == 'agent.tempdirectory') { + return 'temp/path'; + } + return null; +}; +tlClone.assertAgent = function(variable: string) { + return; +}; +tmr.registerMock('azure-pipelines-task-lib/mock-task', tlClone); + +// Mock task-lib +let a: ma.TaskLibAnswers = { + 'checkPath' : { + '/fakecwd' : true, + 'path/to/powershell': true, + 'temp/path': true + }, + 'which': { + 'powershell': 'path/to/powershell' + }, + 'assertAgent': { + '2.115.0': true + }, + 'exec': { + 'path/to/powershell --noprofile --norc -c pwd': { + "code": 0, + "stdout": "temp/path" + }, + "path/to/powershell -NoLogo -NoProfile -NonInteractive -Command . 'temp\\path\\fileName.ps1'": { + "code": 0, + "stdout": "##vso[task.logissue type=warning;]a) Default: Shows = true & Expected = true\nWARNING: a) Default: Shows = true & Expected = true" + }, + "path/to/powershell -NoLogo -NoProfile -NonInteractive -Command . 'temp/path/fileName.ps1'": { + "code": 0, + "stdout": "##vso[task.logissue type=warning;]a) Default: Shows = true & Expected = true\nWARNING: a) Default: Shows = true & Expected = true" + } + }, + 'stats': { + 'path/to/script.ps1': { + isFile() { + return true; + } + } + } +}; +tmr.setAnswers(a); + +// Mock fs +const fs = require('fs'); +const fsClone = Object.assign({}, fs); +fsClone.writeFileSync = function(filePath, contents, options) { + // Normalize to linux paths for logs we check + console.log(`Writing ${contents} to ${filePath.replace(/\\/g, '/')}`); +} +tmr.registerMock('fs', fsClone); + +// Mock uuidv4 +tmr.registerMock('uuid/v4', function () { + return 'fileName'; +}); + +tmr.run(); \ No newline at end of file diff --git a/Tasks/PowerShellV2/powershell.ps1 b/Tasks/PowerShellV2/powershell.ps1 index 93ecb6176f31..717fb69d5837 100644 --- a/Tasks/PowerShellV2/powershell.ps1 +++ b/Tasks/PowerShellV2/powershell.ps1 @@ -135,15 +135,21 @@ try { $contents); if ($input_showWarnings) { $joinedContents = ' - $warnings = New-Object System.Collections.ObjectModel.ObservableCollection[System.Management.Automation.WarningRecord]; - Register-ObjectEvent -InputObject $warnings -EventName CollectionChanged -Action { - if($Event.SourceEventArgs.Action -like "Add"){ - $Event.SourceEventArgs.NewItems | ForEach-Object { - Write-Host "##vso[task.logissue type=warning;]$_"; + # Capture warnings that PowerShell actually processes (respects WarningAction) + $originalContents = {' + $joinedContents + '} + $warnings = @() + $null = & { + # Execute the script and capture warning stream (stream 3) + & $originalContents 3>&1 | ForEach-Object { + if ($_ -is [System.Management.Automation.WarningRecord]) { + $warnings += $_ + Write-Host "##vso[task.logissue type=warning;]$_" + } else { + # Pass through other streams normally + $_ } } - }; - Invoke-Command {' + $joinedContents + '} -WarningVariable +warnings'; + }'; } # Write the script to disk. diff --git a/Tasks/PowerShellV2/powershell.ts b/Tasks/PowerShellV2/powershell.ts index 2dd7239acf38..ee5cc0dc1024 100644 --- a/Tasks/PowerShellV2/powershell.ts +++ b/Tasks/PowerShellV2/powershell.ts @@ -101,15 +101,21 @@ async function run() { } if (input_showWarnings) { script = ` - $warnings = New-Object System.Collections.ObjectModel.ObservableCollection[System.Management.Automation.WarningRecord]; - Register-ObjectEvent -InputObject $warnings -EventName CollectionChanged -Action { - if($Event.SourceEventArgs.Action -like "Add"){ - $Event.SourceEventArgs.NewItems | ForEach-Object { - Write-Host "##vso[task.logissue type=warning;]$_"; + # Capture warnings that PowerShell actually processes (respects WarningAction) + $originalContents = {${script}} + $warnings = @() + $null = & { + # Execute the script and capture warning stream (stream 3) + & $originalContents 3>&1 | ForEach-Object { + if ($_ -is [System.Management.Automation.WarningRecord]) { + $warnings += $_ + Write-Host "##vso[task.logissue type=warning;]$_" + } else { + # Pass through other streams normally + $_ } } - }; - Invoke-Command {${script}} -WarningVariable +warnings; + } `; } contents.push(script);