I have a script that I am running both remotely and locally in different situations. In the remote situations I would like to return some additional job information. However, the script also needs to complete with a Restart, LogOff, etc.
However, if I Return $Results, no code that follows is executed. Whereas if I Write-Host the return info, I see it as additional data for the job, and the script completion code that follows can still execute.
I can make this work, but it causes two issues.
I have two bits of data I need to return, which I had planned to do in an array. But with Write-Host I need to use the kludge of $value1|$value2 and then split the string on | to get at the two bits of data.
Write-Host somewhat pollutes my console so I need some extra code to manage that.
Is there any way to return intermediate info and continue execution? Or am I going to have no choice but to go the Write-Host route? Or is there perhaps another approach that is better than the Write-Host hack?
You seem to be under the impression that you have to use the return keyword to make a function(?) return something. PowerShell works a little differently, though. PowerShell functions return the entire non-captured output on the success output stream. The return keyword is just for making a function return at that particular point.
There is no difference at all between
function Foo {
return 'something' # return with value
}
and
function Foo {
'something'
return # echo first, then return
}
and
function Foo {
'something' # just echo, return implicitly
}
That way you can have a function generate the output, then do more stuff before actually returning:
function Foo {
'foo'
'bar'
Get-ChildItem . -Recurse | Out-Null # output not returned (suppressed)
'baz' > 'C:\some.txt' # output not returned (redirected to file)
}
The caller will receive the returned values (in this case 'foo', 'bar') only after the function call returns, though. If you need immediate feedback you'll need other means.
Don't use Return. Just output $Results, and let the rest of the script continue on.
Related
I want to include Whatif and Confirm to my functions but I encountered an issue with these parameters.
My functions are structured like this:
function Get-Stuff {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
param ( {...} )
process {
if ($PSCmdlet.ShouldProcess($Name, "Delete user")) {
$result = Invoke-RestMethod #restBody
}
}
end {
Write-Output -InputObject $result
Remove-Variable -Name result
}
}
I took on a habit to clean up my variables in the end-block with Remove-Variable. When I use now the -WhatIf or the -Confirm parameter (and denying it), I get an error that the $result variable is null.
ErrorRecord : Cannot find a variable with the name 'result'.
I understand that the RestMethod is skipped in this case but I would assume that the rest of the function would not be executed further.
My question is now, does one add an else-clause to end the continuing execution of the function or do I use these parameters incorrectly?
There's no good reason to remove your variables in the end block, since they go out of scope automatically anyway, given that they're local to your function.
(The only thing that makes sense is to .Dispose() of variables containing objects that implement the System.IDisposable interface; if releasing memory as quickly as possible is paramount - at the expense of blocking execution temporarily - you can additionally call [GC]::Collect(); [GC]::WaitForPendingFinalizers())
If you still want to call Remove-Variable, you have two options:
Simply ignore a non-existent variable by adding -ErrorAction Ignore to the Remove-Variable call.
Remove-Variable -Name result -ErrorAction Ignore
Alternatively, protect the call - and the Write-Output object - with an explicit test for the existence of the variable:
if (Get-Variable -Scope Local result -ErrorAction Ignore) {
$result # short for: Write-Output -InputObject
Remove-Variable -Name result
}
Also note that it's typical for output objects to be emitted directly from the process block - emitting from the end block is only a necessity for commands that for conceptual reasons must collect all input first, such as Sort-Object.
Emitting output objects from the process block - which is invoked for each input object - ensures the streaming output behavior - emitting objects one by one, as soon as they're available - that the pipeline was designed for.
i'm currently trying to do a small script to retrieve data from a server using REST endpoint. As it's the first time i work with powershell, i'm a bit disappointed by the 'lack' of structure and 'typing' in it.
Then i structured my code like this with comments :
# declare ------------------------------------------------------------------------------------------
// All the var i'll need in my process
# do -----------------------------------------------------------------------------------------------
// Ask for user to enter URL and credentials
// Check if credentials are correct
// Connect to the server
// Retrieves the data in a list (JSON formatted) --> List REST EndPoint
// Foreach document in my list
// retrieve the document's details --> single file REST EndPoint
// download the file into local directory
// End Foreach
# display results ----------------------------------------------------------------------------------
// display :
// Downloaded files
// Non-Downloaded files
During review, my colleague told me "Oh ! What you need is the 'begin-process-end' " and then just leave.
I then read somethings about this here but for what i see, the structure is the same as i did with my comments but i don't see the point where it's "What i need" as a functionalities.
Since i'm a real beginner, i maybe miss the point. Could you explain it to me ?
(btw, thx to the kind person who'll edit my ugly english mistakes)
Think of these blocks as a pre-processor (the begin block), actual processor (the process block), and a post-processor(the end block) to a function.
You don't need to define any of these blocks (although Begin and End block will always need a Process block), and can write code just fine without them, but the idea behind them is to divide the function into three separate areas of code. The following function should make it a little clearer:
function addition_by_one {
# Arguments to a powershell functions are defined inside the param block.
Param (
[int]$number
)
# In the below begin block, the variable $a was initialized to 1.
Begin {
$a = 1
}
# The Process block does the main work. In this case, $a is added to the argument supplied to the function.
Process {
$sum = $number + $a
}
# Finally, the End block will execute after the process block completes.
End {
"Sum is $sum"
}
}
# Call the function with any integer argument.
addition_by_one -number 3
Being/process/end is really for pipelines. You can't process from the pipe without a process block.
1..5 | & { process {$_ * 2} }
2
4
6
8
10
I'm using a function that I call from another script. It prompts a user for input until it gets back something that is not empty or null.
function GetUserInputValue($InputValue)
{
do{
$UserValue = Read-Host -Prompt $InputValue
if (!$UserValue) { $InputValue + ' cannot be empty' }
}while(!$UserValue)
$UserValue
return $UserValue
}
The issue is quite strange and likely a result of my lack of powershell experience. When I run the code and provide empty results, the messages from the if statement queue up and only display when I finally provide a valid input. See my console output below.
Console Results
test:
test:
test:
test:
test:
test:
test: 1
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
test cannot be empty
1
I can make this work however in the main file with hard coded values.
do{
$Server = Read-Host -Prompt 'Server'
if (!$Server) { 'Server cannot be empty' }
}while(!$Server)
I'm working Visual Studio Code. This is a function I have in another file I've named functions.ps1.
I call this from my main file like this,
$test = GetUserInputValue("test")
$test
When you put a naked value in a script like "here's a message" or 5 or even a variable by itself $PID what you're implicitly doing is calling Write-Output against that value.
That returns the object to the pipeline, and it gets added to the objects that that returns. So in a function, it's the return value of the function, in a ForEach-Object block it's the return value of the block, etc. This bubbles all the back up the stack / pipeline.
When it has nowhere higher to go, the host handles it.
The console host (powershell.exe) or ISE host (powershell_ise.exe) handle this by displaying the object on the console; this just happens to be the way they handle it. Another host (a custom C# application for example can host the powershell runtime) might handle it differently.
So what's happening here is that you are returning the message that you want to display, as part of the return value of your function, which is not what you want.
Instead, you should use Write-Host, as this writes directly to the host, skipping the pipeline. This is the correct command to use when you want to display a message to the user that must be shown (for other information you can use different commands like Write-Verbose, Write-Warning, Write-Error, etc.).
Doing this will give you the correct result, and prevent your informational message from being part of the return value of your function.
Speaking of which, you are returning the value twice. You don't need to do:
$UserValue
return $UserValue
The first one returns the value anyway (see the top of this answer); the second one does the same thing except that it returns immediately. Since it's at the end of the function anyway, you can use wither one, but only use one.
One more note: do not call PowerShell functions with parentheses:
$test = GetUserInputValue("test")
This works only because the function has a single parameter. If it had multiple params and you attempted to call it like a method (with parentheses and commas) it would not work correctly. You should separate arguments with spaces, and you should usually call parameters by name:
$test = GetUserInputValue "test"
# better:
$test = GetUserInputValue -InputValue "test"
I came across this weird issue in Powershell (not in other languages). Could anyone please explain to me why this happened?
I tried to return a specified number (number 8), but the function keeps throwing everything at me. Is that a bug or by design?
Function GetNum() {
Return 10
}
Function Main() {
$Number10 = GetNum
$number10 #1 WHY NO OUTPUT HERE ??????? I don't want to use write host
$result = 8 # I WANT THIS NUMBER ONLY
PAUSE
return $result
}
do {
$again = Main
Write-Host "RESULT IS "$again # Weird Result, I only want Number 8
} While ($again -eq 10) # As the result is wrong, it loops forever
Is that a bug or by design?
By design. In PowerShell, cmdlets can return a stream of objects, much like using yield return in C# to return an IEnumerable collection.
The return keyword is not required for output values to be returned, it simply exits (or returns from) the current scope.
From Get-Help about_Return (emphasis added):
The Return keyword exits a function, script, or script block. It can be
used to exit a scope at a specific point, to return a value, or to indicate
that the end of the scope has been reached.
Users who are familiar with languages like C or C# might want to use the
Return keyword to make the logic of leaving a scope explicit.
In Windows PowerShell, the results of each statement are returned as
output, even without a statement that contains the Return keyword.
Languages like C or C# return only the value or values that are specified
by the Return keyword.
Mathias is spot on as usual.
I want to address this comment in your code:
$number10 #1 WHY NO OUTPUT HERE ??????? I don't want to use write host
Why don't you want to use Write-Host? Is it because you may have come across this very popular post from PowerShell's creator with the provocative title Write-Host Considered Harmful?
If so, I encourage you to read what I think is a great follow-up/companion piece by tby, titled Is Write-Host Really Harmful?
With this information, it should be clear that as Mathias said, you are returning objects to the pipeline, but you should also be armed with the information needed to choose an alternative, whether it's Write-Verbose, Write-Debug, or even Write-Host.
If I were going to be opinionated about it, I would go with Write-Verbose, altering your function definition slightly in order to support it:
function Main {
[CmdletBinding()]
param()
$Number10 = GetNum
Write-Verbose -Message $number10
$result = 8 # I WANT THIS NUMBER ONLY
PAUSE
$result
}
When you invoke it by just calling $again = Main you'll see nothing on the screen, and $again will have a value of 8. However if you call it this way:
$again = Main -Verbose
then $again will still have the value of 8, but on the screen you'll see:
VERBOSE: 10
likely in differently colored text.
What that gives is not only a way to show the value, but a way for the caller to control whether they see the value or not, without changing the return value of the function.
To drive some of the points in the articles home further, consider that it's not necessarily necessary to invoke your function with -Verbose to get that.
For example, let's say you stored that whole script in a file called FeelingNum.ps1.
If, in addition to the changes I made above, you also add the following to the very top of your file:
[CmdletBinding()]
param()
Then, you still invoked your function "normally" as $again = Main, you could still get the verbose output by invoking your script with -Verbose:
powershell.exe -File FeelingNum.ps1 -Verbose
What happens there is that using the -Verbose parameter sets a variable called $VerbosePreference, and that gets inherited on each function called down the stack (unless it's overridden). You can also set $VerbosePreference manually.
So what you get by using these built-in features is a lot of flexibility, both for you as the author and for anyone who uses your code, which is a good thing even if the only person using it is you.
I am calling a function and passing 3 parameters to that. I am taking each parameter one be one and performing some operations on that. At the end i am traversing the loop so as to perform the same operations on the rest of the parameters.
Suppose the problem is like this:
print "Starting the operations";
# calling the function say NetworkMode
NetworkMode(SONET,SDH,SDH-J) #This will perform certain steps
print "Ending of the test case"
Output i want should come like this:
#Starting the operaions
#Whatever the output function will give using first parameter
#Ending the test case
#Starting the operaions
#Whatever the output function will give using second parameter
#Ending the test case
#Starting the operaions
#Whatever the output function will give using third parameter
#Ending the test case
Is there any way i can do that.
If you call one single function [once], then you get one chance to print out a result; if you want to print out once per parameter, then the function itself is going to have to do the printing out as it operates on each parameter. If, however, you're repeating the exact same steps on each parameter then perhaps your function should only take one parameter?
You mean that the NetworkMode function actually takes only one parameter? If so, then this code will probably do what you want:
foreach my $mode (SONET, SDH, SDH-J)
{
print "Starting the operations";
# calling the function say NetworkMode
NetworkMode($mode); #This will perform certain steps
print "Ending of the test case";
}