How to confirm completion of previous command in powershell - 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
}

Related

Execute the other powershell script if the previous one is successful only

I know this sounds very common but still even searching not able to find soltion for my use case.
What i a trying to do is i am calling one powershell after another from one file say "Test"
and scripts Script A and Script B
......
ScriptA.ps1
ScriptB.ps1
.....other stuff
Now if my scriptA gets executed successfully then ScriptB should execute but if ScriptA throws any exception for which i have already put try/catch block inside ScriptA, ScriptB should not execute.
But i am not able to enforce any check from my "Test" to stop execution of ScriptB.ps1.
Looked for the exit codes but not sure how to collect back in the "Test" something like
......
ScriptA.ps1----returns exit code 1
if(exit code!=1)
{
ScriptB.ps1
}
else
{
"Cant execute due to error"
}
.....other stuff
If your .ps1 scripts communicate failure by nonzero exit code - via an exit statement - you must use the automatic $LASTEXITCODE variable to check for such an exit code:
.\ScriptA.ps1
if ($LASTEXITCODE -eq 0) {
.\ScriptB.ps1
}
# ...
In PowerShell (Core) 7+, you can more simply use &&, the pipeline-chain AND operator:
# PowerShell 7+ only.
.\ScriptA.ps1 && .\ScriptB.ps1 # Execute ScriptB only if ScriptA succeeded.
Use a loop statement (like foreach($thing in $collection){...}) to invoke each script in succession, then break out of the loop on failure - which you can assess by inspecting the $? automatic variable:
$failed = $false
foreach($scriptFile in Get-ChildItem -Filter Script*.ps1){
& $scriptFile.FullName
if(-not $?){
$failed = $true
break
}
}
if($failed){
# handle failure (exit/return or whatever is appropriate)
} else {
# success, continue here
}

How to save powershell hyper-v errors to file

I am making a script to turn virtual machines on and off in hyper-v.
Sometimes the Stop-VM command fails and I need to save the bug or reflect it in some way in a log file
I tried putting the command in a trycath but it didn't work.
Command:
Stop-VM $VMapagar
Sometimes the command gives me this error and does not turn off the machine
Stop-VM: Could not stop.
I would like to be able to reflect the failure in some way in a log.txt
Thanks!
Use Try..Catch to trap the error by telling PS to treat it as a terminating error, then process it as you require:
# Rest of your script
Try {
# Run your command, but tell PS to stop if it find an error
# You can explore the effects of the other possible values for -ErrorAction in PS documentation.
Stop-VM $VMpagar -ErrorAction Stop
# If it's got this far, then there can't have been an error so write a success message to console
Write-Host "OK"
}
Catch {
# This code will process if there was an error in the "Try" block
# By default, within the "Catch" block, the "$_" variable contains the error message
Write-Host "Error: $_"
# Write the error to a log file - "`n" tells PS to write a newline before the subsequent text
Add-Content -Path 'c:\temp\log.txt' -Value "`n$_"
# You could stop the script here using "Throw" or "Exit" commands if you want the whole script to stop on ANY error
}
# Your script will continue from this point if you haven't stopped it
Scepticalist's helpful answer shows how to capture a terminating error, by using the common -ErrorAction (-ea) parameter with value 'Stop' in order to promote non-terminating errors (the most common kind) to terminating ones, which allows them to be trapped with a try/ catch / finally statement.
Note that this approach limits you to capturing the first non-terminating error (whereas a single cmdlet call may emit multiple ones), because it - thanks to -ErrorAction Stop - then instantly terminates the statement and transfers control the catch block (where the automatic $_ variable reflects the triggering error in the form of an [ErrorRecord] instance).
Also note that execution continues after a catch block by default - unless you explicitly use throw to re-throw the terminating error (or use a statement such as exit to exit the script).
To capture - potentially multiple - non-terminating errors you have two options:
Redirect them directly to a file, using the redirection operator > with the number of the error stream, 2:
Stop-Vm $vms 2>errs.txt
This sends any errors quietly to file errs.txt; that is, you won't see them in the console. If no errors occur, an empty file is created.
Note: This technique is the only option for directly redirecting an external program's errors (stderr output); however, using redirection 2>&1 you can capture success output (stdout) and errors (stderr) combined, and split them by their source stream later - see the bottom section of this answer.
Use the common -ErrorVariable (-ev) parameter to collect any non-terminating errors in a variable - note that the target variable must be specified without the $:
Stop-Vm $vms -ErrorVariable errs
By default, the errors are still output as well and therefore print to the console (host) by default, but you can add -ErrorAction SilentlyContinue to prevent that. Caveat: Do not use -ErrorAction Ignore, as that will categorically suppress errors and prevent their collection.
You can then inspect the $errs array (list), which is empty if no errors occurred and otherwise contains one or more [ErrorRecord] instances, and send the collected errors to a file on demand; e.g.:
if ($errs) { $errs > errs.txt }
See also:
This answer for information about PowerShell's two fundamental error types.
GitHub docs issue #1583 for a comprehensive overview of PowerShell's surprisingly complex error handling.

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

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.

Only allow 1 invocation of powershell script to run

I have a powershell script which starts 2 different Access database applications running. This in a volunteer setting and when the computer is first turned on, it can take a minute or two for the startup to complete. Sometimes the user gets impatient and clicks on the shortcut to the Powershell script more than once, causing the Access databases to start multiple times.
To solve this I thought that the first thing that the script would do would be to create a file. If the create failed due to the file already existing, it would ask the user if they wanted to continue. If yes, run the rest of the script otherwise exit. The problem is that the "catch" after the "try" isn't catching anything. How do I fix this and/or what other solutions do people have?
try {New-Item ($dbDir + "lock_file") -type file | Out-Null} # create lock_file. If it exists, another copy of this is probably running and the catch will run
catch
{
$answer = [System.Windows.Forms.MessageBox]::Show("It appears that the database is already starting. Start again?" , "Start Again" , 4)
if ($answer -eq "NO")
{Exit}
}
Catch only works for terminating errors. Cmdlets that throw errors but continue processing will not be caught by catch (Also called non-terminating errors). One way to change this behavior is to set the -erroraction of a cmdlet which should be common to most of them. In your case I would do this:
try {New-Item ($dbDir + "lock_file") -type file -ErrorAction Stop | Out-Null}
The catch block should trigger now.

How to get the error code when there is error in powershell?

My snippet is something like this:
$msg=Remove-Item -Recurse -Force C:\users\bkp 2>&1
if ($LASTEXITCODE -eq 1)
{
"Encountered error during Deleting the Folder. Error Message is $msg. Please check." >> $LogFile
exit
}
The folder C:\users\bkp does not exist. Even though $msg gives me the error message $LASTEXITCODE is still 0. How do I capture as a flag?
You can use the $? automatic variable to determine the result of the last command. If you need access to the actual error, you can use the $Error automatic variable. The first item in the array is the last error thrown:
Remove-Item -Recurse -Force C:\users\bkp 2>&1
if( -not $? )
{
$msg = $Error[0].Exception.Message
"Encountered error during Deleting the Folder. Error Message is $msg. Please check." >> $LogFile
exit
}
$LASTEXITCODE is strictly for command line programs to return their status. Cmdlets that are built into PS, such as Remove-item return their errors in up to 3 ways. For warnings, they write messages (or other .NET objects) to the "warning stream". In PSv3 there is a straightforward way to redirect that stream to a file: cmdlet blah blah blah 3>warning.out. The second is via the error stream. That stream can be redirected as well ... 2>error.out, or more typically errors are caught with try/catch or trap, or written to a variable with the -ErrorVariable parameter (see help about_commonparameters). The third way is for errors to be "thrown". Unless caught (try/catch or trap), a thrown error will cause the script to terminate. Thrown errors generally are subclasses of the .NET class system.Management.Automation.ErrorRecord. An ErrorRecord provides a lot more information about an error than a return code.
If remove-item fails due to a file not found error, it writes a System.Management.Automation.ItemNotFoundException to the error stream. Using a try/catch you can filter for that specific error or other specific errors from remove-item. If you are just typing in PS commands from the command line you can enter $error[0]|select-object * to get a lot of info on the last error.
You could do this:
try {
Remove-Item -Recurse -Force C:\users\bkp 2>&1
} catch {
# oops remove-item failed. Write warning then quit
# replace the following with what you want to do
write-warning "Remove-item encounter error: $_"
return # script failed
}