Skip to content

Commit

Permalink
[PowerShell SDK] Add Invoke-VstsProcess (#978)
Browse files Browse the repository at this point in the history
* Update package-lock

* Add Invoke-Process function

* Add Invoke-Process to members list

* Fix $LastExitCode

* Revert VstsTaskSdk crlf changes

* Revert "refactor: remove Q library from Powershell SDK in favor of native promises (#944)"

This reverts commit 44e727d.

* Fix proc exit code

* Update Invoke-Process

* Fix SupportsWorkingDirectory test

* Remove encoding param, update encoding test

* Fix path in test

* update loc resources

* Revert "Revert "refactor: remove Q library from Powershell SDK in favor of native promises (#944)""

This reverts commit a703a0a.

* Gen doc

* Update changelog
  • Loading branch information
KonstantinTyukalov authored Oct 23, 2023
1 parent 0b4a3c7 commit 5fb0f50
Show file tree
Hide file tree
Showing 9 changed files with 1,161 additions and 1,403 deletions.
1,648 changes: 842 additions & 806 deletions powershell/Docs/Commands.md

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions powershell/Docs/FullHelp/Invoke-VstsProcess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Invoke-VstsProcess
[table of contents](../Commands.md#toc) | [brief](../Commands.md#invoke-vstsprocess)
```
NAME
Invoke-VstsProcess
SYNOPSIS
Executes an external program as a child process.
SYNTAX
Invoke-VstsProcess [-FileName] <String> [[-Arguments] <String>] [[-WorkingDirectory] <String>]
[[-StdOutPath] <String>] [[-StdErrPath] <String>] [-RequireExitCodeZero] [<CommonParameters>]
DESCRIPTION
Executes an external program and waits for the process to exit.
After calling this command, the exit code of the process can be retrieved from the variable $LASTEXITCODE
or from the pipe.
PARAMETERS
-FileName <String>
File name (path) of the program to execute.
Required? true
Position? 1
Default value
Accept pipeline input? false
Accept wildcard characters? false
-Arguments <String>
Arguments to pass to the program.
Required? false
Position? 2
Default value
Accept pipeline input? false
Accept wildcard characters? false
-WorkingDirectory <String>
Required? false
Position? 3
Default value
Accept pipeline input? false
Accept wildcard characters? false
-StdOutPath <String>
Path to a file to write the stdout of the process to.
Required? false
Position? 4
Default value
Accept pipeline input? false
Accept wildcard characters? false
-StdErrPath <String>
Path to a file to write the stderr of the process to.
Required? false
Position? 5
Default value
Accept pipeline input? false
Accept wildcard characters? false
-RequireExitCodeZero [<SwitchParameter>]
Indicates whether to write an error to the error pipeline if the exit code is not zero.
Required? false
Position? named
Default value False
Accept pipeline input? false
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer, PipelineVariable, and OutVariable. For more information, see
about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216).
OUTPUTS
Exit code of the invoked process. Also available through the $LASTEXITCODE.
NOTES
To change output encoding, redirect stdout to file and then read the file with the desired encoding.
```
4 changes: 4 additions & 0 deletions powershell/Docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes

## 0.17.0

* Added `Invoke-VstsProcess` cmdlet (<https://github.com/microsoft/azure-pipelines-task-lib/pull/978>)

## 0.16.0
* Replaced deprecated "sync-request" libraryr and Added new async methods for DownloadArchive

Expand Down
16 changes: 16 additions & 0 deletions powershell/Tests/L0/Invoke-Process.ReturnsCorrectExitCode.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\lib\Initialize-Test.ps1
$Global:LASTEXITCODE = 1

Invoke-VstsTaskScript -ScriptBlock {

# Act.
$actualEC = Invoke-VstsProcess -FileName 'cmd.exe' -Arguments '/c echo test'

# Assert.
Assert-AreEqual -Expected 0 -Actual $actualEC
Assert-AreEqual 0 $LASTEXITCODE
}
56 changes: 56 additions & 0 deletions powershell/Tests/L0/Invoke-Process.SupportsOutputEncoding.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[CmdletBinding()]
param()

# Actually, Invoke-VstsProcess does not support encoding by itself, it just allows to get the output of the process in a file, and then you can use Get-Content to read the file with the encoding you want.

# Arrange.
. $PSScriptRoot\..\lib\Initialize-Test.ps1
Invoke-VstsTaskScript -ScriptBlock {
$tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName }
try {
set-Content -LiteralPath $tempDirectory\Program.cs -Value @"
namespace TestEncoding {
public static class Program {
public static void Main() {
System.Text.Encoding encoding = System.Text.Encoding.Unicode;
byte[] bytes = encoding.GetBytes("Hello world");
using (System.IO.Stream stdout = System.Console.OpenStandardOutput()) {
stdout.Write(bytes, 0, bytes.Length);
stdout.Flush();
}
}
}
}
"@
Add-Type -LiteralPath $tempDirectory\Program.cs -OutputType ConsoleApplication -OutputAssembly $tempDirectory\TestEncoding.exe
$originalEncoding = [System.Console]::OutputEncoding
$variableSets = @(
@{ Encoding = $null ; Expected = "H_e_l_l_o_ _w_o_r_l_d_" }
@{ Encoding = 'unicode' ; Expected = "Hello world" }
)
foreach ($variableSet in $variableSets) {
$stdOutPath = [System.IO.Path]::Combine($tempDirectory, [System.IO.Path]::GetRandomFileName())

# Act.
Invoke-VstsProcess `
-FileName $tempDirectory\TestEncoding.exe `
-StdOutPath $stdOutPath `

if ($variableSet.Encoding) {
$actual = Get-Content -LiteralPath $stdOutPath -Encoding $variableSet.Encoding
}
else {
$actual = Get-Content -LiteralPath $stdOutPath -Raw
}

# Assert.
$actual = $actual.Replace("`0", "_") # Replace null characters with spaces in order for the string comparison to be accurate.
Assert-AreEqual $variableSet.Expected $actual
Assert-AreEqual $originalEncoding ([System.Console]::OutputEncoding)
}
}
finally {
Remove-Item $tempDirectory -Recurse
}
}
39 changes: 39 additions & 0 deletions powershell/Tests/L0/Invoke-Process.SupportsWorkingDirectory.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[CmdletBinding()]
param()

# Arrange.
. $PSScriptRoot\..\lib\Initialize-Test.ps1
Invoke-VstsTaskScript -ScriptBlock {
$originalLocation = $PWD
$tempDirectory = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
New-Item -Path $tempDirectory -ItemType Directory | ForEach-Object { $_.FullName }
try {
Set-Location $env:TMP
$variableSets = @(
@{ Expected = [System.IO.Path]::GetTempPath().TrimEnd('\') ; Splat = @{ } }
@{ Expected = [System.IO.Path]::GetTempPath().TrimEnd('\') ; Splat = @{ WorkingDirectory = [System.IO.Path]::GetTempPath() } }
@{ Expected = $tempDirectory ; Splat = @{ WorkingDirectory = $tempDirectory } }
)
foreach ($variableSet in $variableSets) {
$splat = $variableSet.Splat

$stdOutPath = [System.IO.Path]::Combine($tempDirectory, [System.IO.Path]::GetRandomFileName())

# Act.
Invoke-VstsProcess `
-FileName 'cmd.exe' `
-Arguments '/c "CD"' `
-StdOutPath $stdOutPath `
@splat

$actual = Get-Content -LiteralPath $stdOutPath -Encoding UTF8

# Assert.
Assert-AreEqual $variableSet.Expected $actual
Assert-AreEqual ([System.IO.Path]::GetTempPath().TrimEnd('\')) (Get-Location).Path
}
} finally {
Set-Location $originalLocation
Remove-Item $tempDirectory -Recurse
}
}
103 changes: 99 additions & 4 deletions powershell/VstsTaskSdk/ToolFunctions.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ function Assert-Path {

if ($PathType -eq [Microsoft.PowerShell.Commands.TestPathType]::Any) {
Write-Verbose "Asserting path exists: '$LiteralPath'"
} else {
}
else {
Write-Verbose "Asserting $("$PathType".ToLowerInvariant()) path exists: '$LiteralPath'"
}

Expand Down Expand Up @@ -75,7 +76,7 @@ This parameter not required for most scenarios. Indicates how to interpret the e
.PARAMETER RequireExitCodeZero
Indicates whether to write an error to the error pipeline if the exit code is not zero.
#>
function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS?
function Invoke-Tool {
[CmdletBinding()]
param(
[ValidatePattern('^[^\r\n]*$')]
Expand Down Expand Up @@ -107,7 +108,8 @@ function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS?
Write-Host "##[command]""$FileName"" $Arguments"
try {
Invoke-Expression "& '$FileName' --% $Arguments"
} catch [System.Management.Automation.Host.HostException] {
}
catch [System.Management.Automation.Host.HostException] {
if ($IgnoreHostException -eq $False) {
throw
}
Expand All @@ -118,7 +120,8 @@ function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS?
if ($RequireExitCodeZero -and $LASTEXITCODE -ne 0) {
Write-Error (Get-LocString -Key PSLIB_Process0ExitedWithCode1 -ArgumentList ([System.IO.Path]::GetFileName($FileName)), $LASTEXITCODE)
}
} finally {
}
finally {
if ($originalEncoding) {
[System.Console]::OutputEncoding = $originalEncoding
}
Expand All @@ -130,3 +133,95 @@ function Invoke-Tool { # TODO: RENAME TO INVOKE-PROCESS?
Trace-LeavingInvocation $MyInvocation
}
}

<#
.SYNOPSIS
Executes an external program as a child process.
.DESCRIPTION
Executes an external program and waits for the process to exit.
After calling this command, the exit code of the process can be retrieved from the variable $LASTEXITCODE or from the pipe.
.PARAMETER FileName
File name (path) of the program to execute.
.PARAMETER Arguments
Arguments to pass to the program.
.PARAMETER StdOutPath
Path to a file to write the stdout of the process to.
.PARAMETER StdErrPath
Path to a file to write the stderr of the process to.
.PARAMETER RequireExitCodeZero
Indicates whether to write an error to the error pipeline if the exit code is not zero.
.OUTPUTS
Exit code of the invoked process. Also available through the $LASTEXITCODE.
.NOTES
To change output encoding, redirect stdout to file and then read the file with the desired encoding.
#>
function Invoke-Process {
[CmdletBinding()]
param(
[ValidatePattern('^[^\r\n]*$')]
[Parameter(Mandatory = $true)]
[string]$FileName,
[ValidatePattern('^[^\r\n]*$')]
[Parameter()]
[string]$Arguments,
[string]$WorkingDirectory,
[string]$StdOutPath,
[string]$StdErrPath,
[switch]$RequireExitCodeZero
)

Trace-EnteringInvocation $MyInvocation
try {
$FileName = $FileName.Replace('"', '').Replace("'", "''")
Write-Host "##[command]""$FileName"" $Arguments"

$processOptions = @{
FilePath = $FileName
NoNewWindow = $true
PassThru = $true
}
if ($Arguments) {
$processOptions.Add("ArgumentList", $Arguments)
}
if ($WorkingDirectory) {
$processOptions.Add("WorkingDirectory", $WorkingDirectory)
}
if ($StdOutPath) {
$processOptions.Add("RedirectStandardOutput", $StdOutPath)
}
if ($StdErrPath) {
$processOptions.Add("RedirectStandardError", $StdErrPath)
}

# TODO: For some reason, -Wait is not working on agent.
# Agent starts executing the System usage metrics and hangs the step forever.
$proc = Start-Process @processOptions

# https://stackoverflow.com/a/23797762
$null = $($proc.Handle)
$proc.WaitForExit()

$procExitCode = $proc.ExitCode
Write-Verbose "Exit code: $procExitCode"

if ($RequireExitCodeZero -and $procExitCode -ne 0) {
Write-Error (Get-LocString -Key PSLIB_Process0ExitedWithCode1 -ArgumentList ([System.IO.Path]::GetFileName($FileName)), $procExitCode)
}

$global:LASTEXITCODE = $procExitCode

return $procExitCode
}
finally {
Trace-LeavingInvocation $MyInvocation
}
}
1 change: 1 addition & 0 deletions powershell/VstsTaskSdk/VstsTaskSdk.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Export-ModuleMember -Function @(
'Assert-Agent'
'Assert-Path'
'Invoke-Tool'
'Invoke-Process'
# Trace functions.
'Trace-EnteringInvocation'
'Trace-LeavingInvocation'
Expand Down
Loading

0 comments on commit 5fb0f50

Please sign in to comment.