Skip to content

Fix PowerShell@2 showWarnings to respect WarningAction preferences #21106

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
30 changes: 30 additions & 0 deletions Tasks/PowerShellV2/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
});
85 changes: 85 additions & 0 deletions Tasks/PowerShellV2/Tests/L0ShowWarnings.ts
Original file line number Diff line number Diff line change
@@ -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 = <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();
20 changes: 13 additions & 7 deletions Tasks/PowerShellV2/powershell.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 13 additions & 7 deletions Tasks/PowerShellV2/powershell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down