posh-vsts-cli.psm1
<# .SYNOPSIS Installs the VSTS CLI. Requires PowerShell to be launched as Administrator. .EXAMPLE Install-VstsCli #> Function Install-VstsCli { [CmdletBinding()] param() Import-Module BitsTransfer # in case the machine is locked down, one can use Invoke-WebRequest instead as well but BitsTransfer is much faster Write-Verbose "Downloading installer" -Verbose Start-BitsTransfer -Source https://aka.ms/vsts-cli-windows-installer -Destination vsts-cli_installer.msi Write-Verbose "Installing VSTS-CLI" -Verbose $result = Start-Process msiexec.exe -Wait -ArgumentList "/I $((Get-ChildItem .\vsts-cli_installer.msi).FullName) /quiet" -PassThru -Verb runas Write-Verbose "Installer Exit Code: $($result.ExitCode)" # refresh path in powershell https://gist.github.com/bill-long/230830312b70742321e0 foreach($level in "Machine","User") { [Environment]::GetEnvironmentVariables($level).GetEnumerator() | ForEach-Object { # For Path variables, append the new values, if they're not already in there if($_.Name -match 'Path$') { $_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select-Object -unique) -join ';' } $_ } | Set-Content -Path { "Env:$($_.Name)" } } Remove-Item .\vsts-cli_installer.msi vsts --version } <# .SYNOPSIS Invokes the VSTS CLI and converts the output to a PowerShell object. .EXAMPLE ivc build list .EXAMPLE ivc build list --output table #> Function Invoke-VstsCli { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "")] param() $arguments = $args -join " " & Invoke-Expression "vsts $($arguments)" | ConvertFrom-VstsCli } <# .SYNOPSIS Converts the JSON or table output of the vsts-cli output to PowerShell objects .EXAMPLE vsts build list | ConvertFrom-VstsCli .EXAMPLE vsts build list --output table | ConvertFrom-VstsCli #> function ConvertFrom-VstsCli { [CmdletBinding()] param ( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [string]$InputObject ) begin { $positions = $null; $scippedSecondLine = $false; } process { if ($null -eq $isTableFormat) { $isTableFormat = $InputObject.startswith("[") } if (-not $isTableFormat) { foreach ($oneInputObject in $InputObject) { if ($null -eq $positions) { $positions = GetColumnInfo -HeaderRow $oneInputObject } else { if ($scippedSecondLine) { ParseRow -row $oneInputObject -ColumnInfo $positions } else { $scippedSecondLine = $true # the second line consists only of dashes } } } } else { # the JSON string comes in one character at a time in the pipeline if ($null -eq $stringBuilder) { $stringBuilder = [System.Text.StringBuilder]::new() } foreach($oneInputObject in $InputObject) { [void]$stringBuilder.AppendLine($oneInputObject) } } } end { if ($null -ne $stringBuilder) { $stringBuilder.ToString() | ConvertFrom-Json } $isTableFormat = $null } } #region Table output parsing function PascalName($name) { $name = $name.Trim(' ') # get rid of leading whitespace $parts = $name.Split(" ") for ($i = 0 ; $i -lt $parts.Length ; $i++) { $parts[$i] = [char]::ToUpper($parts[$i][0]) + $parts[$i].SubString(1).ToLower(); } $parts -join "" } function GetHeaderBreak($headerRow, $startPoint = 0) { $i = $startPoint while ( $i + 1 -lt $headerRow.Length) { if ($headerRow[$i] -eq ' ' -and $headerRow[$i + 1] -eq ' ') { return $i break } $i += 1 } return -1 } function GetHeaderNonBreak($headerRow, $startPoint = 0) { $i = $startPoint while ( $i + 1 -lt $headerRow.Length) { if ($headerRow[$i] -ne ' ') { return $i break } $i += 1 } return -1 } function GetColumnInfo($headerRow) { $lastIndex = 2 # the first 2 characters are just whitespace $i = 4 # id starts at 2 while ($i -lt $headerRow.Length) { $i = GetHeaderBreak $headerRow $lastIndex if ($i -lt 0) { $name = $headerRow.Substring($lastIndex) New-Object PSObject -Property @{ HeaderName = $name; Name = PascalName $name; Start = $lastIndex; End = -1} break } else { $name = $headerRow.Substring($lastIndex, $i - $lastIndex) $temp = $lastIndex $lastIndex = GetHeaderNonBreak $headerRow $i if ($temp -eq 2) # the first ID columns is right aligned -> move position to the very left { $temp = 0 } if ($lastIndex -lt 0) { $lastIndex = $headerRow.Length - 1 # last columns sometimes 'overflows' } New-Object PSObject -Property @{ HeaderName = $name; Name = PascalName $name; Start = $temp; End = $lastIndex} } } } function ParseRow($row, $columnInfo) { $values = @{} $columnInfo | ForEach-Object { if ($_.End -lt 0) { $len = $row.Length - $_.Start } else { $len = $_.End - $_.Start } $values[$_.Name] = $row.SubString($_.Start, $len).Trim() } New-Object PSObject -Property $values } #endregion |