I know, there are hundreds of pages that address the -in my opinion- strange way that Powershell handles return values from functions, and I must have visited about half of them ;=)
This particular one drives me nuts.
Consider the following:
I'm doing a call to a function with one parameter (call is not done from within another function):
$result = getVMinfo($vm)
The function getVMinfo looks like this :
function getVMinfo {
param (
[string]$vm
)
try {
Get-WmiObject -Class Win32_DiskDrive -Computername $vm -ErrorAction Stop
}
catch {
$ErrorReturn = $_.Exception.Message
}
if ($ErrorReturn) {
Write-Host "Error =" $ErrorReturn
return $ErrorReturn
}
}
Looks simple enough, and other functions do work when returning a value in this way.
Now, if I run the script, The write-Host bit in the catch does show me that $ErrorReturn is filled with a string (tested that with $ErrorReturn.GetType() ).
However, $result in the calling statement is always empty.
I have tried many suggestions, like creating an array, and use the .Add() to add the errorstring to the array, and then return the array. Nothing seems to work.
I am really at a loss here. Don't understand what I am doing wrong.
Please, help me !
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.
What options are available for handling exceptions from class methods?
i.e. with advanced functions we can do this:
function Test-Error {
[CmdletBinding()]
Param()
throw "Error!"
}
"1. Error's about to happen"
Test-Error -ErrorAction SilentlyContinue
"1. Error just happenned (you didn't see it)"
"2. Error's about to happen"
Test-Error -ErrorAction Stop
"2. Error just happened (you saw it; but you won't see this)"
To get the same behaviour when calling a class method, I could do this:
class ErrorTest
{
ErrorTest(){}
TestError(){ throw "Error!" }
}
[ErrorTest]$e = [ErrorTest]::new()
"1. Error's about to happen"
$ErrorActionPreference = 'SilentlyContinue'
$e.TestError()
"1. Error just happened (you didn't see it)"
"2. Error's about to happen"
$ErrorActionPreference = 'Stop'
$e.TestError()
"2. Error just happened (you saw it; but you won't see this)"
However I don't like changing ErrorActionPreference. Is there a better solution / a standard way of dealing with such scenarios?
Update
Per TheIncorrigible1's comment, also tried using this method of throwing exceptions:
function Test-Error {
[CmdletBinding()]
Param()
$badObject = 'whatever object caused the issue'
$actualException = [System.NotSupportedException]::new('I take exception to your use of this function')
$errorRecord = [System.Management.Automation.ErrorRecord]::new($actualException, 'ExampleException1', [System.Management.Automation.ErrorCategory]::InvalidData, $badObject)
$PSCmdlet.ThrowTerminatingError($errorRecord)
}
I need an example that use try..catch..finally clause where the finally is NECESSARY vs try..catch clause where finally is optional. The example below only demonstrated that finally is optional because with or without it, it won't make any different to my output.
My Example (note: $ErrorActionPreference is Continue):
try {
$value = 5 / 0
} catch {
Write-Output "illegal operation"
}
$t = Get-Date
Write-Output ("operation is done at " + "$t")
The reason is I need to know where finally clause become necessary vs just put finally clause no matter what.
A finally clause is just a logical construct saying "this statement or group of statements should always be run at the end of the try block, regardless of whether there was an error or not". It tells people reading the code that there is a logical connection between the code in the try and finally blocks (e.g. opening and closing a database connection). However, beyond that there is no essential difference between
try {
5 / 0
} catch {
'illegal operation'
}
'continued'
and
try {
5 / 0
} catch {
'illegal operation'
} finally {
'continued'
}
You can find some discussion of the subject here.
I think the only way it would make a difference is if you return or exit in the try block:
try {
'foo' # <-- displayed
exit
} finally {
'bar' # <-- displayed
}
'baz' # <-- not displayed
but maybe something like that is just bad design.
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
}