How do you get powershell script output in the deployment log for a vNext Release Template? - powershell

This blog post is the only thing I have found that comes close to the problem but it doesn't explain how to configure the Deploy Using PS/DSC to run with the verbose option:
http://nakedalm.com/create-log-entries-release-management/
I can get this Agent-based Release Template to run the script:
Write-Debug "debug"
Write-Output "output"
Write-Verbose "verbose"
Write-Warning "warning"
The drilling down into deployment log for this release provides a log with the lines:
output
WARNING: warning
If I add -verbose to the Arguments field I also get a "VERBOSE: verbose" line in the log.
This is great, but I need the access to the System Variables ($Stage, $BuildNumber, etc). When I create a vNext template to run the same script (instructions are here: http://www.visualstudio.com/en-us/get-started/deploy-no-agents-vs.aspx), the log reports:
Copying recursively from \\vsalm\Drops2\TestBuild\TestBuild_20130710.3 to c:\Windows\DtlDownloads\my vnext component succeeded.
It is nice that this copying operation succeeded and all, but I'd like my script's output to be in this log as well. Does anyone have any idea about configuring a "Deploy Using PS/DSC" action so that the powershell script output is captured by Release Management?

For a vNext Release Template, try Write-Verbose with a -verbose switch if you want to see the powershell script output in the logs.
Eg. Write-Verbose "Some text" -verbose

Allow me to shamelessly plug my own blog article about this subject, because I found that it's not easy to get a script that does everything right.
The following script skeleton ensures that stdout output is logged without empty lines, and that processing is halted on the first error, in which case both the error details and the stdout output upto that point are visible in MSRM:
function Deploy()
{
$ErrorActionPreference = "Stop"
try
{
#
# Deployment actions go here.
#
}
catch
{
# Powershell tracks all Exceptions that occured so far in $Error
Write-Output "$Error"
# Signal failure to MSRM:
$ErrorActionPreference = "Continue"
Write-Error "Error: $Error"
}
}
pushd $Global:ApplicationPath
Deploy | Out-String | Write-Verbose -Verbose
popd
This is just the final result, the explanation behind it can be found here.

Related

Is there a generic way to capture all verbose output to a file but only show stdout on console?

We have a bunch of different powershell tools that we use for build/deploy and other development and admin activity in my team. Mostly these are calling other Powershell scripts and Cmdlets but there are some x86 command line apps also (e.g. msbuild)
They largely all are setup to output verbose output. I do that so that I can troubleshoot retrospectively when something goes wrong in the team
However the team have asked to have less noisy output to the console. I still want the verbose output to be available retrospectively
So it feels like I need something like a continuous loop of the last 100k rows of verbose activity. Including any console input and output written to a file - regardless of the -verbose setting that the developer applied
Is there anything like this available in Powershell?
I know about redirection but I'm not sure how it can solve this problem as it seems that you have to match the stdout with the redirect even if you use Tee-Object - also it wouldn't capture inputs.
Eager to learn some hidden secrets or elegant creative solutions! :)
UPDATE: as mentioned redirect is not a practical solution. I've created a request https://github.com/PowerShell/PowerShell/issues/17482
An article that addresses this topic is about_Redirection. Each output stream has a numeric identifier - the range is 1 to 6. The referenced article has a table showing the mapping between the ID's and streams.
Write-Host's ID is 6, so if you wanted everything to go to a log file except that, you can try redirecting all streams except 6:
PS:> Start-VeryNoiseyOperation -Debug 1>C:\mylogs\noisey.log 2>&1 3>&1 4>&1 5>&1
This example is redirecting all streams generated by the Write-____ cmdlets, except Write-Host and Write-Information. To send all streams to the log file, you can use *>C:\mylogs\noisey.log.
If you want to experiment, you can run this function with different combos of redirection to see the effect.
PS:> function Foo {
[CmdletBinding()]param()
Write-Output #{message = "my printed object"} # 1
Write-Error "This is an error message." # 2
Write-Warning "This is a warning message." # 3
Write-Verbose "This is a verbose message." # 4
Write-Debug "This is a debug message." # 5
Write-Host "This is a host message." # 6
}
PS:> $log = New-TemporaryFile
PS:> $logPath = $log.FullName
PS:>
PS:> Foo -Debug -Verbose # Print everything.
:
PS:> Foo -Debug -Verbose 1>$logPath # Send object stream to file.
:
PS:> Get-Content $logPath # Object should print to console.
:
PS:> Foo -Debug -Verbose 1>$logPath 2>&1 # Try different combos.
: # see effect on console output #
PS:> Get-Content $logPath
: # see log content #

azure cli not stopping on error in PS script

Boiled down to the minimum I have a Powershell script that looks like this:
$ErrorActionPreference='Stop'
az group deployment create -g ....
# Error in az group
# More az cli commands
Even though there is an error in the az group deployment create, it continues to execute beyond the error. How do I stop the script from executing on error?
Normally, the first thing to try is to wrap everything in a try...catch block.
try {
$ErrorActionPreference='Stop'
az group deployment create -g ....
# Error in az group
# More az cli commands
}
catch {
Write-Host "ERROR: $Error"
}
Aaaaand it doesn't work.
This is when you scratch your head and realize that we are dealing with Azure CLI commands and not Azure PowerShell. They are not native PowerShell commands which would honor $ErrorActionPreference, instead, (as bad as it sounds), we have to treat each Azure CLI command independently as if we were running individual programs (in the back end, the Azure CLI is basically aliases which run python commands. Ironically most of Azure PowerShell commands are just PowerShell wrappers around Azure CLI commands ;-)).
Knowing that the Azure CLI will not throw a terminating error, instead, we have to treat it like a program, and look at the return code (stored in the variable $LASTEXITCODE) to see if it was successful or not. Once we evaluate that, we can then throw an error:
az group deployment create -g ....
if($LASTEXITCODE){
Write-Host "ERROR: in Az Group"
Throw "ERROR: in Az Group"
}
This then can be implemented into a try...catch block to stop the subsequent commands from running:
try {
az group deployment create -g ....
if($LASTEXITCODE){
Write-Host "ERROR: in Az Group"
Throw "ERROR: in Az Group"
}
# Error in az group
# More az cli commands
}
catch {
Write-Host "ERROR: $Error"
}
Unfortunately this means you have to evaluate $LASTEXITCODE every single time you execute an Azure CLI command.
You may use the automatic variable $?. This contains the result of the last execution, i.e. True if succeded or False if it failed: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-5.1#section-1
Your code would look something like this:
az group deployment create -g ....
if(!$?){
Write-Error "Your error message"
# Handle your error
}
Unfortunately, and same to #HAL9256 answer, you will need to add this code every single time you execute azure-cli
Update - PS Version 7 error trapping for Az CLI (or kubectl)
This behavior changed significantly in PowerShell v7:
https://github.com/PowerShell/PowerShell/issues/4002
https://github.com/PowerShell/PowerShell/pull/13361
I ended up using the following solution instead which is consistent with PowerShell v5/7. It has the advantage of accepting pipeline input for commands like kubectl where stdio can be used for applying configuration etc.
I used scriptblocks since this integrates nicely into existing scripts without breaking syntax:
# no error
# Invoke-Cli {cmd /c echo hi}
# throws terminating error
# (with psv7 you can now call Get-Error to see details)
# Invoke-Cli {az bad}
# outputs a json object
# Invoke-Cli {az account show} -AsJson
# applies input from pipeline to kubernetes cluster
# Read-Host "Enter some json" | Invoke-Cli {kubectl apply -f -}
function Invoke-Cli([scriptblock]$script, [switch]$AsJson)
{
$ErrorActionPreference = "Continue"
$jsonOutputArg = if ($AsJson)
{
"--output json"
}
$scriptBlock = [scriptblock]::Create("$script $jsonOutputArg 2>&1")
if ($MyInvocation.ExpectingInput)
{
Write-Verbose "Invoking with input: $script"
$output = $input | Invoke-Command $scriptBlock 2>&1
}
else
{
Write-Verbose "Invoking: $script"
$output = Invoke-Command $scriptBlock
}
if ($LASTEXITCODE)
{
Write-Error "$Output" -ErrorAction Stop
}
else
{
if ($AsJson)
{
return $output | ConvertFrom-Json
}
else
{
return $output
}
}
}
Handling command shell errors in PowerShell <= v5
Use $ErrorActionPreference = 'Stop' and append 2>&1 to the end of the statement.
# this displays regular output:
az account show
# this also works as normal:
az account show 2>&1
# this demonstrates that regular output is unaffected / still works:
az account show -o json 2>&1 | ConvertFrom-Json
# this displays an error as normal console output (but unfortunately ignores $ErrorActionPreference):
az gibberish
# this throws a terminating error like the OP is asking:
$ErrorActionPreference = 'Stop'
az gibberish 2>&1
Background
PowerShell native and non-native streams, while similar, do not function identically. PowerShell offers extended functionality with streams and concepts that are not present in the Windows command shell (such as Write-Warning or Write-Progress).
Due to the way PowerShell handles Windows command shell output, the error stream from a non-native PowerShell process is unable (by itself) to throw a terminating error in PowerShell. It will appear in the PowerShell runspace as regular output, even though in the context of Windows command shell, it is indeed writing to the error stream.
This can be demonstrated:
# error is displayed but appears as normal text
cmd /c "asdf"
# nothing is displayed since the stdio error stream is redirected to nul
cmd /c "asdf 2>nul"
# error is displayed on the PowerShell error stream as red text
(cmd /c asdf) 2>&1
# error is displayed as red text, and the script will terminate at this line
$ErrorActionPreference = 'Stop'
(cmd /c asdf) 2>&1
Explanation of 2>&1 workaround
Unless otherwise specified, PowerShell will redirect Windows command shell stdio errors to the console output stream by default. This happens outside the scope of PowerShell. The redirection applies before any errors reach the PowerShell error stream, making $ErrorActionPreference irrelevant.
The behavior changes when explicitly specified to redirect the Windows command shell error stream to any other location in PowerShell context. As a result, PowerShell is forced to remove the stdio error redirection, and the output becomes visible to the PowerShell error stream.
Once the output is on the PowerShell error stream, the $ErrorActionPreference setting will determine the outcome of how error messages are handled.
Further info on redirection and PowerShell streams
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection?view=powershell-7.2
The sample from HAL9256 didn't work for me either, but I did find a workaround:
az group deployment create -g ....
if($error){
Write-Host "ERROR: in Az Group"
#throw error or take corrective action
$error.clear() # optional - clear the error
}
# More az cli commands
I used this to try to deploy a keyvault, but in my environment, soft delete is enabled, so the old key vault is kept for a number of days. If the deployment failed, I'd run a key vault purge and then try the deployment again.
Building from HAL9256's and sam's answers, add this one-liner after each az cli or Powershell az command in a Powershell script to ensure that the catch block is hit when an error occurs:
if($error){ throw $error }
Then for the catch block, clear the error, e.g.
catch {
$ErrorMessage = $_.Exception.Message
$error.Clear()
Write-Warning "-- Operation Failed: $ErrorMessage"
}

How to determine if Write-Host will work for the current host

Is there any sane, reliable contract that dictates whether Write-Host is supported in a given PowerShell host implementation, in a script that could be run against any reasonable host implementation?
(Assume that I understand the difference between Write-Host and Write-Output/Write-Verbose and that I definitely do want Write-Host semantics, if supported, for this specific human-readable text.)
I thought about trying to interrogate the $Host variable, or $Host.UI/$Host.UI.RawUI but the only pertinent differences I am spotting are:
in $Host.Name:
The Windows powershell.exe commandline has $Host.Name = 'ConsoleHost'
ISE has $Host.Name = 'Windows PowerShell ISE Host'
SQL Server Agent job steps have $Host.Name = 'Default Host'
I have none of the non-Windows versions installed, but I expect they are different
in $Host.UI.RawUI:
The Windows powershell.exe commandline returns values for all properties of $Host.UI.RawUI
ISE returns no value (or $null) for some properties of $Host.UI.RawUI, e.g. $Host.UI.RawUI.CursorSize
SQL Server Agent job steps return no values for all of $Host.UI.RawUI
Again, I can't check in any of the other platforms
Maintaining a list of $Host.Name values that support Write-Host seems like it would be bit of a burden, especially with PowerShell being cross-platform now. I would reasonably want the script to be able to be called from any host and just do the right thing.
Background
I have written a script that can be reasonably run from within the PowerShell command prompt, from within the ISE or from within a SQL Server Agent job. The output of this script is entirely textual, for human reading. When run from the command prompt or ISE, the output is colorized using Write-Host.
SQL Server jobs can be set up in two different ways, and both support capturing the output into the SQL Server Agent log viewer:
via a CmdExec step, which is simple command-line execution, where the Job Step command text is an executable and its arguments, so you invoke the powershell.exe executable. Captured output is the stdout/sterr of the process:
powershell.exe -Command x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
via a PowerShell step, where the Job Step command text is raw PS script interpreted by its own embedded PowerShell host implementation. Captured output is whatever is written via Write-Output or Write-Error:
#whatever
Do-WhateverPowershellCommandYouWant
x:\pathto\script.ps1 -Arg1 -Arg2 -Etc
Due to some other foibles of the SQL Server host implementation, I find that you can emit output using either Write-Output or Write-Error, but not both. If the job step fails (i.e. if you throw or Write-Error 'foo' -EA 'Stop'), you only get the error stream in the log and, if it succeeds, you only get the output stream in the log.
Additionally, the embedded PS implementation does not support Write-Host. Up to at least SQL Server 2016, Write-Host throws a System.Management.Automation.Host.HostException with the message A command that prompts the user failed because the host program or the command type does not support user interaction.
To support all of my use-cases, so far, I took to using a custom function Write-Message which was essentially set up like (simplified):
$script:can_write_host = $true
$script:has_errors = $false
$script:message_stream = New-Object Text.StringBuilder
function Write-Message {
Param($message, [Switch]$iserror)
if ($script:can_write_host) {
$private:color = if ($iserror) { 'Red' } else { 'White' }
try { Write-Host $message -ForegroundColor $private:color }
catch [Management.Automation.Host.HostException] { $script:can_write_host = $false }
}
if (-not $script:can_write_host) {
$script:message_stream.AppendLine($message) | Out-Null
}
if ($iserror) { $script:has_errors = $true }
}
try {
<# MAIN SCRIPT BODY RUNS HERE #>
}
catch {
Write-Message -Message ("Unhandled error: " + ($_ | Format-List | Out-String)) -IsError
}
finally {
if (-not $script:can_write_host) {
if ($script:has_errors) { Write-Error ($script:message_stream.ToString()) -EA 'Stop' }
else { Write-Output ($script:message_stream.ToString()) }
}
}
As of SQL Server 2019 (perhaps earlier), it appears Write-Host no longer throws an exception in the embedded SQL Server Agent PS host, but is instead a no-op that emits nothing to either output or error streams. Since there is no exception, my script's Write-Message function can no longer reliably detect whether it should use Write-Host or StringBuilder.AppendLine.
The basic workaround for SQL Server Agent jobs is to use the more-mature CmdExec step type (where Write-Output and Write-Host both get captured as stdout), but I do prefer the PowerShell step type for (among other reasons) its ability to split the command reliably across multiple lines, so I am keen to see if there is a more-holistic, PowerShell-based approach to solve the problem of whether Write-Host does anything useful for the host I am in.
Just check if your host is UserInteractive or an service type environment.
$script:can_write_host = [Environment]::UserInteractive
Another way to track the output of a script in real time is to push that output to a log file and then monitor it in real time using trace32. This is just a workaround, but it might work out for you.
Add-Content -Path "C:\Users\username\Documents\PS_log.log" -Value $variablewithvalue

Powrshell Template to expedite script creation

Been writing more and more scripts, specifically to make API calls, get information, and alert of certain conditions are met. The alerts are sent to microsoft teams channel. These scripts are run untended as scheduled tasks. I only use powershell v5+
I've attempted to make a simple template to start from, with what I've found online and made some modifications. I need error checking and reporting, which I feel can be done through Try/Catch/Finally. I also want some basic logging output to go in and check for errors.
Will the logging in the final block be sufficient to know the date and time the script completed? Will using the $error.Does the exception catch the error message? I seem to have luck using it and some times I don't. What am I leaving out that is critical in ensuring a script runs unattended and that errors get reported when there's a problem. I will also be including a block of code in the Catch section to send the error to the MS teams channel.
<#
.SYNOPSIS
<Overview of script>
.SYNTAX
<Cmdlet-Name> -Parameter <value>
.DESCRIPTION
<Brief description of script>
.PARAMETER <Parameter_Name>
<Brief description of parameter input required. Repeat this attribute if required>
.INPUTS
<Inputs if any, otherwise state None>
.OUTPUTS
<Outputs if any, otherwise state None - example: Log file stored in C:\Windows\Temp\<name>.log>
.EXAMPLE
<Example goes here. Repeat this attribute for more than one example>
.REMARKS
Version: 1.0
Author: <Name>
Creation Date: <Date>
#>
#---------------------------------------------------------[Variables]------------------------------------------------------------
$var1 = <stuff>
$var2 = <stuff>
#---------------------------------------------------------[Import Modules]--------------------------------------------------------
Import-Module <name>
#-----------------------------------------------------------[Functions]------------------------------------------------------------
Function <FunctionName> {
[CmdletBindinging()]
param(
[Parameter()]
[string]$MyOptionalParameter,
[Parameter(Mandatory)]
[int]$MyMandatoryParameter
)
Try{
<code goes here>
}
Catch {
Write-Host $Error.Exception
Out-File $Error.Exception
Break
}
Finally {
$time = Get-Date
"Script completed at $time" | Out-File $env:TEMP\MyScriptName.log
}
}
#-----------------------------------------------------------[Execution]------------------------------------------------------------
<FunctionName>

How to confirm completion of previous command in powershell

I have a simple powershell script that gets ran daily to compress and move some log files. How can i test that the command completes successfully before deleting the original log file.
set-location $logpath1
& $arcprg $pram $dest_file $source_file
Move-Item $dest_file $arcdir
If the Move-Item completes ok i want to remove-item $source_file
The completion status of the previous command can be accessed via the special variable $?.
Note that this works best with non-terminating errors (like you would get from Move-Item). Terminating errors are the result of a direct throw or an exception getting thrown in .NET and they alter the flow of your code. Best to use a trap or try/catch statement to observe those type of errors.
One other thing to watch out for WRT $? and console exes is that PowerShell assumes an exit code of 0 means success (i.e. $? is set to $true) and anything else means failure ($? set to $false). Unfortunately not all console exe's observe that exit code convention e.g. there may be multiple success codes and a single failure code (0). For those exes that don't follow the exit code rules, use $LastExitCode as pointed out in the comments to determine success or failure.
Depending on how parnoid you are and what component you are using for archiving, you can check the archive to confirm the file exixts. We use DotNetZip component to zip our archive log files (http://dotnetzip.codeplex.com/).
$zipFileObj = new-object Ionic.Zip.ZipFile($zipName);
[void] $zipFileObj.UpdateFile( "$fileName", "" ) # adds file if doesn't already exist
trap #catch an zip errors and Stop processing
{
write-error "Caught a system exception. Execution stopped"
write-error $("TRAPPED: " + $_.Exception.Message);
exit
}
if ( $zipFileObj.ContainsEntry( $fileName) )
{
remove-item $pathFile # delete file from file-system
}
else
{
# throw error
}