Run code on powershell script termination to close files? - powershell

I've got a script that's chewing through a lot of objects, and sometimes I want to kill it in the middle of the run because I see something going south. Unfortunately, I'm writing to a log file using System.IO.StreamWriter, and whenever I send a Ctrl-C, my log files are stuck open.
Is there any way I can define some kind of handler or exiting function that allows me to gracefully close filehandles and connections that I have open?

Might try using Try/Catch/Finally, putting your close() commands in the Finally block.

With PowerShell 2.0 and up, you can define a Trap which will fire when a terminating error occurs. You can define multiple traps to capture different exceptions. This could result in much cleaner code than try/catch littered everywhere, or wrapping the entire script in one big try/catch.

To terminate a script, use exit .If an exception is thrown, use try/catch/finally with close() commands in finally. If it's just an if-test, try something like this:
function Close-Script {
#If stream1 is created
if($stream1) {
$stream1.Close()
}
#Terminate script
exit
}
$stream1 = New-Object System.IO.StreamWriter filename.txt
If(a test that detects your error) {
Close-Script
}
If the amounts of streamwriters varies from time to time, you can collect them to an array and close them. Ex:
function Close-Script {
#Close streams
$writers | % { $_.Close() }
#Terminate script
exit
}
$writers = #()
$stream1 = New-Object System.IO.StreamWriter filename.txt
$writers += $stream1
$stream2 = New-Object System.IO.StreamWriter filename2.txt
$writers += $stream2
If(a test that detects your error) {
Close-Script
}

Related

PowerShell error variable loses the content

I have a quite big project with many functions in there.
Just 2 questions:
What would be here the "best practice" regarding Error-Handling? To use a local handling per Function, or use only one Error-Logging in the Main-Section?
The tricky part (!), let's have a look at the strange Error behaviour in the function F_XXX: only $_ delivers an error message, $Error[0] is here empty! Strange enough, when I start the function F_XXX separately (cut out from the module), it behaves as expected, it means: $Error[0] gives an error back. The code:
Blockquote
$ErrorActionPreference = "Stop"
Function F1
{
try
{
# do something
}
catch
{
# 1. cascade Error to Main?
# throw $Error[0].InnerException
# or
# local Error-Logging?
write-MyErrorLogging -message $Error[0].InnerException
}
}
Function F2
{
try
{
# do something
}
catch
{
# 1. cascade Error to Main?
# throw $Error[0].InnerException
# or
# local Error-Logging?
write-MyErrorLogging -message $Error[0].InnerException
}
}
Function F_XXXXXX
{
try
{
cls
write-host "The install data is copied.."
$share = "\\my_wrong_path\sql_sources\"
Copy-Item $share -Destination $installDrive -Force -Recurse
}
catch
{
$Error[0] #here is nothing!
$null -eq $Error[0] # here true
$_.Exception # only here the error-message: wrong path!
}
}
Blockquote
# here Main
try
{
F1
F2
F_XXXXXX
}
catch
{
write-MyErrorLogging -message $Error[0].InnerException
}
Blockquote
Inside a catch block, it's best to avoid $Error[0], given that the error at hand is reliably reflected in the automatic $_ variable.
If you do need access to previous errors via the automatic $Error variable, use $global:Error inside modules - see the bottom section for details.
Unless you need to perform additional actions when an error occurs, you can let a script-terminating (fatal) error (which your $ErrorActionPreference = "Stop" statement turns all errors in your code into) bubble up the call stack until it is either caught by a try / catch statement or, in the absence of one, terminates the entire call stack (i.e., the scripts and its callers).
If you do need to perform additional actions, use try / catch, and place the actions inside the catch block (as well as potential cleanup actions in a finally block), followed by re-throwing the error simply by calling throw without an argument.
Thus, you can make do with a single try / catch statement in the top-level scope of your script:
# Turn all errors in this and descendant scopes into
# script-terminating (fatal) ones.
$ErrorActionPreference = 'Stop'
# ... other function definitions, without try / catch
# Top-level code that calls the other functions and catches
# any errors.
try
{
F1
F2
F_XXXXXX
}
catch
{
write-MyErrorLogging -message $_.InnerException
}
The automatic $Error variable in modules:
Strangely, up to at least PowerShell 7.2.3 (current as of this writing):
Errors occurring in modules - just like ones occurring outside modules - are recorded in the $Error variable that exists in the global scope.
However, a seemingly unused, module-local copy of $Error exists, which shadows the global variable.
The workaround is to use use $global:Error from inside modules.
The behavior suggests a bug, given that the module-local copy is seemingly never touched and serves no apparent purpose.

PowerShell: How to clear cache of included files?

I include an external .ps1 into antother .ps1:
foo.ps1:
.("C:\test\bar.ps1");
$obj = [bar]::new();
$obj.out();
bar.ps1:
class bar{
$output;
bar(){
$this.output = 1;
}
[void] out(){
write-host $this.output;
}
}
The first time I execute foo.ps1 in the Windows PowerShell ISE the output is "1", as expected.
Then I go to bar.ps1 and change $this.output = 1; to $this.output = 2;. After executing foo.ps1 again the output is still "1". When I change something in foo.ps1, like simply appending a new line, and execute it once again, the output becomes "2". Changing back, like removing the new line, will make an output of "1" again.
For me it looks like an caching issue. Is it possible to clear or disable the caching?
Thanks in advance!

Explicit Return in Powershell

I can write the following code in javascript:
function sum(num1, num2) {
return num1 + num2;
}
and then get a value
var someNum = sum(2,5);
I would like to do the same thing in Powershell, but I read the following guide:
PowerShell also knows the return keyword; however, it follows a
different logic. In general, the purpose of return is to end the
execution of a code section and to give the control back to the parent
block.
If you add a parameter to the return statement, the value will indeed
be returned to the calling subroutine. However, this also applies for
all other statements with an output. This means that any output
produced in the function will be stored in the variable together with
the return parameter.
I want to do this for the sake of having pure functions. However, it seems doing
var someNum = sum(2,5);
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
A bit tangential, but here is my actual code:
function GetPreviousKeyMD5Hashes() {
$query = "SELECT Name, MD5, executed FROM [AMagicDb].[dbo].cr_Scripts";
$command = New-Object System.Data.SQLClient.SQLCommand;
$command.Connection = $connection;
$command.CommandText = $query;
try {
$reader = $command.ExecuteReader();
while ($reader.Read()) {
$key = $reader.GetString(1)
$previousScripts.Add($key) | Out-Null
}
$reader.Close();
Write-Output "$(Get-Date) Finished querying previous scripts"
}
catch {
$exceptionMessage = $_.Exception.Message;
Write-Output "$(Get-Date) Error running SQL at with exception $exceptionMessage"
}
}
and then:
$previousScripts = New-Object Collections.Generic.HashSet[string];
GetPreviousKeyMD5Hashes;
This code isn't clear to me at all - running GetPreviousKeyMD5Hashes does set $previousScripts, but this is entirely unclear to whoever modifies this after me. My only other alternative (afaik) is to have all this in line, which also isn't readable.
is entirely redundant, when I can just call the function above, define someNum inside of it, and it will be available in the global scope.
No: functions execute in a child scope (unless you dot-source them with .), so variables created or assigned to inside a function are local to it.
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
Yes: The implicit output behavior only applies to statements whose output is neither captured - $var = ... - nor redirected - ... > foo.txt
If there are statements that happen to produce output that you'd like to discard, use $null = ... or ... > $null
Note: ... | Out-Null works in principle too, but will generally perform worse, especially in earlier PowerShell versions - thanks, TheIncorrigible1.
If there are status messages that you'd like to write without their becoming part of the output, use Write-Host or, preferably Write-Verbose or, in PSv5+, Write-Information, though note that the latter two require opt-in for their output to be visible in the console.
Do NOT use Write-Output to write status messages, as it writes to the success output stream, whose purpose is to output data ("return values").
See this answer of mine for more information about PowerShell's output streams.
The equivalent of your JavaScript code is therefore:
function sum($num1, $num2) {
Write-Host "Adding $num1 and $num2..." # print status message to host (console)
$num1 + $num2 # perform the addition and implicitly output result
}
PS> $someNum = sum 1 2 # NOTE: arguments are whitespace-separated, without (...)
Adding 1 and 2... # Write-Host output was passed through to console
PS> $someNum # $someNum captured the success output stream of sum()
3
Am I missing something or is it possible to write pure functions in Powershell that don't return everything inside the function?
You can't have your cake and eat it too...
If you have no out put in your function, then it is "pure" like you desire. If you have output, that also becomes part of the return.
You can use [ref] params. See below for example.
function DoStuff([ref]$refObj)
{
Write-Output "DoStuff: Enter"
$refObj.Value += $(1 + 2)
$refObj.Value += "more strings"
Write-Output "DoStuff: Exit"
}
$refRet = #()
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
"`n`nagain"
$allRet = DoStuff([ref]$refRet)
"allRet"
$allRet
"refRet"
$refRet
Note: Powershell doesn't need semicolons at the end of each statement; only for separating multiple statements on the same line.
Whenever possible, it's a good idea to avoid changing global state within a function. Pass input as parameters, and return the output, so you aren't tied to using the function in only one way. Your sample could look like this:
function sum
{
param($num1,$num2)
return $num1+$num2
}
$somenum=sum 2 5
Now, with Powershell, the return statement isn't needed. The result of every statement that isn't otherwise assigned, captured, redirected, or otherwise used, is just thrown in with the return value. So we could replace the return statement above with simply
$num1+$num2
You're already making use of this in your code with:
$previousScripts.Add($key) | Out-Null
where you are discarding the result of .Add(). Otherwise it would be included in the return value.
Personally, I find using return to explicitly mark the return value makes it easier to read. Powershell's way of putting all if the output in the return caused a lot of trouble for me as I was learning.
So, the only fixes to your code I would make are:
Move $previousScripts = New-Object Collections.Generic.HashSet[string] to inside the function, making it local.
Add return $previousScripts to the end of the function.

Exiting a secondary Perl script and returning to the main program

I am running a Perl script to control two other scripts. The user of the main program can call the two secondary scripts with the following line.
do 'Task_A.pl';
The script then runs Task_A.pl but I would like to find a way of then exiting Task_A.pl and return to the main program. If possible I would like to return to the function where I last called the secondary script. I am not sure as to what this is called but I appreciate any input for a possible solution.
This is the whole main program, not much to it at the moment.
my $selecion;
#Looping variables.
my $program_loop = 1;
while ($program_loop == 1)
{
print "Please choose one of the programs listed in the menu.\n";
#Program menu where the user chooses from the presented options.
print "[1] - Script A.\n";
print "[2] - Script B.\n";
print "[3] - Exit program.\n";
my $user_input = <>;
if ($user_input == 1) # <-- Scrip_A.pl
{
do 'Task_A.pl';
}
elsif ($user_input == 2) # <-- Scrip_B.pl
{
do 'Task_B.pl';
}
elsif ($user_input == 3) # <-- Exit Program
{
#The user can choose to exit from the menu.
print "The program will now exit.\n";
exit;
}
}
do does not start another process. If that's what you want, use "system()".
If you want do, then it's probably best to just put everything into a function in the file to be evaluated and call the function at the end of the file.
Use "return" to leave the function and return to next instruction after do.

simple parallel processing in perl

I have a few blocks of code, inside a function of some object, that can run in parallel and speed things up for me.
I tried using subs::parallel in the following way (all of this is in a body of a function):
my $is_a_done = parallelize {
# block a, do some work
return 1;
};
my $is_b_done = parallelize {
# block b, do some work
return 1;
};
my $is_c_done = parallelize {
# block c depends on a so let's wait (block)
if ($is_a_done) {
# do some work
};
return 1;
};
my $is_d_done = parallelize {
# block d, do some work
return 1;
};
if ($is_a_done && $is_b_done && $is_c_done && $is_d_done) {
# just wait for all to finish before the function returns
}
First, notice I use if to wait for threads to block and wait for previous thread to finish when it's needed (a better idea? the if is quite ugly...).
Second, I get an error:
Thread already joined at /usr/local/share/perl/5.10.1/subs/parallel.pm line 259.
Perl exited with active threads:
1 running and unjoined
-1 finished and unjoined
3 running and detached
I haven't seen subs::parallel before, but given that it's doing all of the thread handling for you, and it seems to be doing it wrong, based on the error message, I think it's a bit suspect.
Normally I wouldn't just suggest throwing it out like that, but what you're doing really isn't any harder with the plain threads interface, so why not give that a shot, and simplify the problem a bit? At the same time, I'll give you an answer to the other part of your question.
use threads;
my #jobs;
push #jobs, threads->create(sub {
# do some work
});
push #jobs, threads->create(sub {
# do some other work
});
# Repeat as necessary :)
$_->join for #jobs; # Wait for everything to finish.
You need something a little bit more intricate if you're using the return values from those subs (simply switching to a hash would help a good deal) but in the code sample you provided, you're ignoring them, which makes things easy.