How to pass a named function as a parameter (scriptblock) - powershell

Let's take the classic first-order functions example:
function Get-MyName { "George" }
function Say-Hi([scriptblock]$to) {
Write-Host ("Hi "+(& $to))
}
This works just fine:
Say-Hi { "Fred Flintstone" }
this does not:
Say-Hi Get-MyName
because Get-MyName is evaluated, not passed as a value itself. How do I pass Get-MyName as a value?

You have to pass Get-Myname as a scriptblock, because that's how you've defined the variable type.
Say-Hi ${function:Get-MyName}

If you are ready to sacrifice the [scriptblock] parameter type declaration
then there is one more way, arguably the simplest to use and effective. Just
remove [scriptblock] from the parameter (or replace it with [object]):
function Get-MyName { "George" }
function Say-Hi($to) {
Write-Host ("Hi "+(& $to))
}
Say-Hi Get-MyName
Say-Hi { "George" }
So now $to can be a script block or a command name (not just a function but also alias, cmdlet, and script).
The only disadvantage is that the declaration of Say-Hi is not so self describing.
And, of course, if you do not own the code and cannot change it then this is
not applicable at all.
I wish PowerShell has a special type for this, see this suggestion.
In that case function Say-Hi([command]$to) would be ideal.

This might be a better example to illustrate the question, and details of execution scope. #mjolinor's answer appears to work nicely for this use case:
function Get-MyName($name) { $name; throw "Meh" }
function Say-Hi([scriptblock]$to) {
try {
Write-Host ("Hi "+(& $to $args)) # pass all other args to scriptblock
} catch {
Write-Host "Well hello, $_ exception!"
}
}
The command and its output:
PS C:\> Say-Hi ${function:Get-MyName} 'George'
Well hello, Meh exception
In particular, I'm using this pattern for wrapping functions that work with a flaky remote SQL Server database connection, which sleep, then retry several times before finally succeeding or throwing a higher exception.

Strictly based on your code, the correct answer is this:
Say-Hi {(Get-MyName)}
This will produce "Hi George"

Related

Two PowerShell methods: SetString() accept a string from the user and PrintString() print the string in upper case

What can I do so that I write the desired phrase in brackets and it will be accepted?
Where exactly to get the "basis" in order to make a return using the "PrintString" method?
# Example 1
$Object.SetString("gamarjoba")
# Example 2
$Object.PrintString()
# Returns
GAMARJOBA
Here is one of my attempts:
class Object {
[string]$gamarjoba
[string]SetString() {
return Write-Host "gamarjoba"
}
[string]PrintString() {
return Write-Host "gamarjoba".ToUpper()
}
}
I understand that this is a very basic question, but I have already spent too much time on it.
You're probably looking for this:
class MyObject {
[string] $gamarjoba = '' # instance variable
# Method has no return type; accepts a string parameter
# and assigns it to instance variable $this.gamarjoba
[void] SetString([string] $gamarjoba) {
$this.gamarjoba = $gamarjoba
}
# Returns the uppercased form of the $this.gamarjoba instance variable.
# Do NOT use Write-Host.
[string] PrintString() {
return $this.gamarjoba.ToUpper()
}
}
$obj = [MyObject]::new() # create an instance
$obj.SetString('aha') # set the string
$obj.PrintString() # print the uppercased string
Note that I've named the class MyObject, because Object would clash with the root class of the .NET type hierarchy.
As for what you tried:
return Write-Host "gamarjoba".ToUpper()
Fundamentally, do NOT use Write-Host to return or output data - it is meant for printing information to the display (console), and bypasses the success output stream and thereby the ability to send output to other commands, capture it in a variable, or redirect it to a file - see this answer for more information.
In the context of class definitions, use only return to return data from a method, passing it an expression or command as-is (e.g, return 'foo'.ToUpper() or return Get-Date -Format yyy)
PowerShell classes participate in the system of output streams only in a very limited way:
returning a value from a method writes it to the success output stream.
Notably, implicit output and use of Write-Output (i.e. what you would use in a script or function) are not supported and quietly ignored. (While you can use return Write-Output ..., there's no good reason to do so.)
throwing an error writes it to the error stream - but you'll only see that if you catch or silence such a fatal-by-default error.
Notably, using Write-Error to write non-terminating errors does not work and is quietly ignored.
However, you can write to all other output streams using their respective Write-* cmdlets, such as Write-Warning.

How to define the return type / OutputType of a function

Why is the following changing type?
function SomeFunction($SomeParameter){
return $SomeParameter
}
I guess I need to set a return type, but how?
An example is using:
$NewFolder=Join-Path $CurrentFolder -ChildPath $FolderName
$Tmp=SomeFunction($NewFolder)
Now $Tmp is an array and not just a path
While this answer explains the behavior you're seeing, here I will attempt to answer the actual question: how to declare the expected output type of a function!
You do so by adding an [OutputType] attribute to the param() block of your function - so the first thing you'll want to do is to skip the C#-style param list and declare a proper param block instead:
function SomeFunction
{
param($SomeParameter)
return $SomeParameter
}
Now we just need to add the [OutputType] attribute decorator:
function SomeFunction
{
[OutputType([string])]
param($SomeParameter)
return $SomeParameter
}
since we're just returning the parameter argument value as-is in this example, we should play nice and make sure it's actually also a string:
function SomeFunction
{
[OutputType([string])]
param(
[string]$SomeParameter
)
return $SomeParameter
}
Worth noting that [OutputType()] makes no guarantees as to the type of objects emitted during execution, it's simply a way for the author of a function to indicate the intended output type.
Read more about [OutputType] in the about_Functions_OutputTypeAttribute help file
Your issue is per "design". PowerShell will return an array in chunks so that it can be forwarded the PowerShell pipeline.
Example:
SomeFunction -SomeParameter #(1,2,3,4) | Where-Object { $_ -gt 2 }
Without this behavior pipelining the output of the function to another function/cmdlet won't be possible.
If you want to return an array you can change to code to:
function SomeFunction($SomeParameter){
<#
# Through the unary operator we can return an array with one entry.
# This entry contains the original array.
#>
,$SomeParameter
}
Another option would be the use of #() when at the calling side:
function SomeFunction($SomeParameter){
# return to pipelin
$SomeParameter
}
$array = #(SomeFunction -SomeParameter 1,2,3,4)
There is also this reddit answer explaining the behavior in more detail.
Hope that helps.

Referring to current object from different scope

I want to be able to refer to $_ object in catch block from the function I call in catch block like this:
function foo
{
$something = ((Get-Variable -Name "_" -Scope 1).Value).Exception.Message
Write-Host $something
}
I want to use this in situations like these:
foo #write-host should print empty line
try {
throw
} catch {
foo #write-host should print $_.Exception.Message from this catch block
}
How to do that properly?
The goal is to avoid passing $_ as parameter to foo every time I use it in a catch block, and not to print anything when I call foo not in a catch block.
I have also tried this:
function foo
{
$something = (($ExecutionContext.SessionState.PSVariable.Get("_")).Value).Exception.Message
Write-Host $something
}
This seems to produce the result I want when working interactively, but not when launching script.
The clean way of doing this is, of course, an (optional) parameter on foo, called with foo $_ from catch blocks. This makes it perfectly clear what we're calling foo with, it has no issues with scoping, it makes foo testable, all that niceness. You will see this approach in most anything that prints errors (as in the answers to this question, for example).
Even so, if you insist on plucking the error variable from the parent frame, it can be done:
function foo {
$lastError = (Get-PSCallStack)[1].GetFrameVariables()["_"].Value
if ([Object]::ReferenceEquals($lastError.Exception, $error[0].Exception)) {
$lastError.Exception.Message
}
}
Note the extra trickery with Object.ReferenceEquals to be absolutely sure that $_ is referring to the last error record and not some arbitrary pipeline item (which also use $_). This will still fail if the error record itself is in the pipeline (e.g. $error |% { foo }), but at that point you might as well call it a feature.

Default value of mandatory parameter in PowerShell

Is it possible to set a default value on a mandatory parameter in a function?
It works without having it set as an mandatory ...
Ie.
Function Get-Hello {
[CmdletBinding()]
Param([Parameter(Mandatory=$true)]
[String]$Text = $Script:Text
)
BEGIN {
}
PROCESS {
Write-Host "$Script:Text"
Write-Host "$Text"
}
END {
}
}
$Text = "hello!"
Get-Hello
Reason for asking this is because i have a function that has some required parameters and the function works perfect with calling it with the required parameters but i also want to make it possible for these variables to be defined in the scripts that use this function in a "better presentable & editable" way along with the function to be able to be run with defining the required parameters.
Hence if defined in the script scope it should take that as default else it should prompty for the value.
Thanks in Advance,
If you targeting to PowerShell V3+, then you can use $PSDefaultParameterValues preferences variable:
$PSDefaultParameterValues['Get-Hello:Text']={
if(Test-Path Variable::Script:Text){
# Checking that variable exists, so we does not return $null, or produce error in strict mode.
$Script:Text
}
}

How to define named parameter as [ref] in PowerShell

I'm trying to use [ref] named parameters. However, I am getting an error:
workflow Test
{
Param([Parameter(Mandatory=$true)][String][ref]$someString)
write-verbose $someString -Verbose
$someString = "this is the new string"
}
cls
$someString = "hi"
Test -someString [ref]$someString
write-host $someString
#Error: Cannot process argument transformation on parameter 'someString'. Reference type is expected in argument.
How can I fix this problem?
I noticed that you are using a "workflow" in your example of a [ref] parameter.
For simplicity, let's call it a "function" and get back to "workflow" later.
There are three things you need to change in your code:
When passing a [ref] parameter to function, you need to enclose the parameter in parenthesis ().
When using a [ref] parameter within a function refer to $variable.value
Remove [string] type from your parameter definition. It can be a [string], or [ref], but not both.
Here is code that works:
function Test
{
Param([Parameter(Mandatory=$true)][ref]$someString)
write-verbose $someString.value -Verbose
$someString.value = "this is the new string"
}
cls
$someString = "hi"
Test -someString ([ref]$someString)
write-host $someString
As for "workflows". They are very restricted, read PowerShell Workflows: Restrictions. In particular you can't invoke a method on an object within workflow. This will break the line:
$someString.value = "this is the new string"
I don't think that using [ref] parameters in a workflow is practical, because of workflow restrictions.
I felt I needed to write this complementary very simplistic answer since this was the first google hit when searching for info on using reference parameters in Powershell functions. Although your question was not about functions but workflows:
Example using reference parameters in functions (doesn't work with workflow):
Function myFunction ([ref]$aString) {
$aString.Value = "newValue";
}
$localVariable = "oldValue"
Write-Host $localVariable # Outputs: oldValue
myFunction ([ref]$localVariable);
Write-Host $localVariable # Outputs: newValue
With functions you can define parameter to be both a reference and another type, like this (but not with workflows):
Function myFunction ([ref][string]$aString) {
$aString.Value = "newValue";
}
$localVariable = "oldValue"
Write-Host $localVariable # Outputs: oldValue
myFunction ([ref]$localVariable);
Write-Host $localVariable # Outputs: newValue
I agree with Jan, you should not be trying to use reference parameters in workflows because of workflow restrictions (Method invocation on objects): https://blogs.technet.microsoft.com/heyscriptingguy/2013/01/02/powershell-workflows-restrictions/