Looking for a way to have all functions send output to another function - powershell

I realize the title is a little confusing but I couldn't figure out a better way to phrase it.
I have a Powershell script with a couple dozen functions. Currently, I have the exact same code in every function to format the output. Here's a snippet:
function function1 () {
do something...
output code here
}
function function2 () {
do something...
output code here
}
The output code is exactly the same. Being a fan of code deduplication, this is driving me crazy because every time I add a new function I have this template code that I have to apply. I've tried putting the entire script in a try/catch block and throwing the object that is output but I couldn't get it to work and this still requires coding in the same throw statement in every function.
Does anyone know of something I can do to have all of these functions in this script to automatically send their output to another function or am I just going to have to live with this?

If the functions have no parameters, you can use this simple solution:
function addOutputCode {
param($name)
$oldBody = (get-item function:$name).ScriptBlock
$newBody = {
param($computer)
$funcOutput = . $oldBody $computer
# some formatting
$funcOutput | % { 'FORMATTED: ' + $_ }
}.GetNewClosure()
Set-Item function:$name -value $newBody
}
As you can see the functions gets the body of the function and assignes new body with formatting code. You can try it, just copy & paste the code below.
# this is your file with defined functions
function f1 { param($c) 'this is test of ' + $c }
function f2 { $c.Length; 'this was length of $c' }
# now f1 and f2 would return unformatted data
# f1
# f2
# add formatting code
addOutputCode f1
addOutputCode f2
# now if you call f1 or f2, they return formatted data
# f1 comp1
# f2 comp2

Related

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.

PowerShell - execute script block in specific scope

I am trying to implement RSpec/Jasmine like BDD framework in Powershell (or at least research the potential problems with making one).
Currently I am having problems with implementing simple before/after functionality. Given
$ErrorActionPreference = "Stop"
function describe()
{
$aaaa = 0;
before { $aaaa = 2; };
after { $aaaa; }
}
function before( [scriptblock]$sb )
{
& $sb
}
function after( $sb )
{
& $sb
}
describe
the output is 0, but I would like it to be 2. Is there any way to achieve it in Powershell (short of making $aaaa global, traversing parent scopes in script blocks till $aaaa is found, making $aaaa an "object" and other dirty hacks:) )
What I would ideally like is a way to invoke a script block in some other scope but I don't have a clue whether it is possible at all. I found an interesting example at https://connect.microsoft.com/PowerShell/feedback/details/560504/scriptblock-gets-incorrect-parent-scope-in-module (see workaround), but am not sure how it works and if it helps me in any way.
TIA
The call operator (&) always uses a new scope. Instead, use the dot source (.) operator:
$ErrorActionPreference = "Stop"
function describe()
{
$aaaa = 0;
. before { $aaaa = 2; };
. after { $aaaa; }
}
function before( [scriptblock]$sb )
{
. $sb
}
function after( $sb )
{
. $sb
}
describe
Note the use of . function to invoke the function in same scope as where `$aaaa is defined.

How do I avoid getting data printed to stdout in my return value?

In doing some Powershell automation, I'm having trouble with the way that data written to stdout by a .cmd file is automatically captured. I have two functions that do something like the following:
function a {
& external.cmd # prints "foo"
return "bar"
}
function b {
$val = a
echo $val # prints "foobar", rather than just "bar"
}
Basically, data that external.cmd sends to stdout is added to the return value of a, even though all I really want to return from a is the string that I specified. How can I prevent this?
Here are a few different approaches for handling this:
capture the output of the .cmd script:
$output = & external.cmd # saves foo to $output so it isn't returned from the function
redirect the output to null (throw it away)
& external.cmd | Out-Null # throws stdout away
redirect it to a file
& external.cmd | Out-File filename.txt
ignore it in the caller by skipping it in the array of objects that's returned from the function
$val = a
echo $val[1] #prints second object returned from function a (foo is object 1... $val[0])
In PowerShell, any output value your code does not capture is returned the caller (including stdout, stderr, etc). So you have to capture or pipe it to something that doesn't return a value, or you'll end up with an object[] as your return value from the function.
The return keyword is really just for clarity and immediate exit of a script block in PowerShell. Something like this would even work (not verbatim but just to give you the idea):
function foo()
{
"a"
"b"
"c"
}
PS> $bar = foo
PS> $bar.gettype()
System.Object[]
PS> $bar
a
b
c
function foobar()
{
"a"
return "b"
"c"
}
PS> $myVar = foobar
PS> $myVar
a
b
I generally prefer to use one of the following two techniques which, in my opinion, make the code more readable:
Cast an expression to void in order to suppress the return value:
[void] (expression)
Assign the output value to the $null variable:
$null = expression
For example:
function foo
{
# do some work
return "success"
}
function bar
{
[void] (foo) # no output
$null = foo # no output
return "bar"
}
bar # outputs bar
If you want the output from the command to still print to the powershell command line, you can do a variant of the accepted answer:
& external.cmd | Out-Host

powershell function return

I try to start a function of an script out of another script.
I want to save the return into a variable but this doesn't work.
script1.ps1:
function test
{
return "hallo"
}
script2.ps1:
./script1.ps1; $p=test
or
$p = ./script1.ps1; test
It seems that $p is null, but I don't know what's wrong.
Can anybody please help me?
thx
Try this:
. ./script1.ps1; $p=test
Why: you have to load the function into current scope (that's the period at the beginning – the dot source operator).
If you use ';', then completely new statement begins. So from you example $p = ./script.ps1; test, you assign output from script.ps1 to $p and then run the function.