How to pass a script block as parameter and execute multiple times? - powershell

I wish to pass "Write-Host mm" as script block to function "f", and I hope "f" will execute it 10 times, so I tried:
function f([ScriptBlock]$s)
{
1..10|$s
}
f(Write-Host mm)
Unfortunately, powershell gives error:
At C:\Users\engineer\Documents\Untitled1.ps1:3 char:11
+ 1..10|$s
+ ~~
Expressions are only allowed as the first element of a pipeline.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline
How to correct my script?
Thanks for Jason's first answer, but seems doesn't work:
Thanks, but seems this doesn't work, I've got PS 4.0 and
function f([ScriptBlock]$s)
{
1..10 | & $s
}
f { Write-Host mm }
Execute this script and it prints out:
Thanks, but seems this doesn't work, I've got PS 4.0 and
d:\ > function f([ScriptBlock]$s)
{
1..10 | & $s
}
f { Write-Host mm }
mm
This is strange! A script prints out itself! I've got PS4.0 and running ISE.
Why is that?

You are trying to execute your scriptblock 10 times, but instead you try to pipe in an array from 1 to 10. You should pipe that array to foreach-object instead.
function f([ScriptBlock]$s)
{
1..10 | % {& $s}
# % is an alias for Foreach-Object
}
f { Write-Host mm }

You need to use the invocation operator &. Also, script blocks are enclosed in curly braces, so your example would be:
function f([ScriptBlock]$s)
{
1..10 | & $s
}
f { Write-Host mm }
Note that you don't call PowerShell functions like you would a C# method - you don't use parentheses around the arguments, you call PowerShell functions like commands, with spaces between arguments.

Related

Powershell - An empty pipe element is not allowed problem

I Tried running this in powershell
cd "c:thetesttun2" ; if ($?) { nvcc thetestrun2.cu -o thetestrun2 } ; if ($?) { .\thetestrun2 } | Out-File "C:\thetesttun2\hellow.txt"
and this error keeps coming up
An empty pipe element is not allowed. + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : EmptyPipeElement
Please help I am new to powershell
Please give me some code to run on my machine
As hinted in comments, a pipeline statement cannot begin with a control flow structure like if(...){...} - the first element has to start with either a value expression or a command element.
There's a couple of options for solving this. If you simply want the output from the eventual invocation of the program, move | Out-File into the if body:
cd "c:thetesttun2"
if ($?) { nvcc thetestrun2.cu -o thetestrun2 }
if ($?) { .\thetestrun2 | Out-File "C:\thetesttun2\hellow.txt" }
If you also want to stream the output from the compilation to the file, wrap the whole thing in a scriptblock {...} (thus turning the whole thing into a command element) and invoke with the & call operator:
cd "c:thetesttun2"
& {
if ($?) { nvcc thetestrun2.cu -o thetestrun2 }
if ($?) { .\thetestrun2 }
} | Out-File "C:\thetesttun2\hellow.txt"
Finally, you can also wrap the if statements in the $(...) subexpression operator or the #(...) array subexpression operator, thus turning the whole thing into a value expression:
cd "c:thetesttun2"
#(
if ($?) { nvcc thetestrun2.cu -o thetestrun2 }
if ($?) { .\thetestrun2 }
) | Out-File "C:\thetesttun2\hellow.txt"
... but beware that this will block while executing the statements, and output won't start streaming until the subexpression has been fully evaluated, thus incurring higher memory allocations for the process - which is why I suggest the scriptblock approach detailed above instead :)

using powershell invoke-expression to run code output

I have been doing a lot of reading on invoke-expression (also known as iex) and I'm having trouble getting it to work for me.
My understanding is, it will run any powershell code you give to it. However, when I run my tests on it, it does not run the code.
Example:
## testcode.ps1
$myvar = "i am here"
if ($myvar -ne $null) {
"($myvar) variable is Full"
} else {
"($myvar) variable is Empty"
}
Now, if I cat(gc) this file and I pass it to iex, it outputs a bunch of errors. Same thing happens when I save the code into a variable and then feed the variable to iex. Neither works.
Despite the fact that I've tried numerous examples, I feel there's something minor I'm doing wrong that I'm hoping someone can point out for me.
I'm new to Windows scripting, so please bear with me. These are the results of the tests I performed:
First Test:
PS C:\Users\J> gc C:\Users\J\testcode.ps1 | iex
Invoke-Expression : Cannot bind argument to parameter 'Command' because it is an empty string.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : InvalidData: (:PSObject) [Invoke-Expression], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand
iex : At line:1 char:23
+ if ($myvar -ne $null) {
+ ~
Missing closing '}' in statement block or type definition.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : MissingEndCurlyBrace,Microsoft.PowerShell.Commands.InvokeExpressionCommand
Second Test:
PS C:\Users\J> $scriptBlock = gc C:\Users\J\testcode.ps1
PS C:\Users\J>
PS C:\Users\J> iex -Command "$scriptBlock"
iex : At line:1 char:23
+ $myvar = "i am here" if ($myvar -ne $null) { "($myvar) variable ...
+ ~~
Unexpected token 'if' in expression or statement.
At line:1 char:1
+ iex -Command "$scriptBlock"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : UnexpectedToken,Microsoft.PowerShell.Commands.InvokeExpressionCommand
PS C:\Users\J>
I'm aware that I can just run the file containing the code. However, I need help figuring out how iex works and what it is I'm doing wrong.
Please kindly advise.
First things first:
Invoke-Expression should generally be avoided and used only as a last resort, due to its security risks. In short: avoid it, if possible, given that superior alternatives are usually available. If there truly is no alternative, only ever use it on input you either provided yourself or fully trust - see this answer.
For the record: in the case at hand, the superior alternative is to directly invoke the script file:
# Prepend `& `, if the script file path is quoted or references a variable.
C:\Users\J\testcode.ps1
Invoke-Expression (iex) accepts multiple strings via the pipeline, and evaluates each individually, as a self-contained script.
Therefore, you must provide the contents of your script as a whole, as a single string, which is what Get-Content's (gc's) -Raw switch does[1]:
Get-Content -Raw C:\Users\J\testcode.ps1 | Invoke-Expression
Alternatively, pass the script-file contents as an argument:
Invoke-Expression (Get-Content -Raw C:\Users\J\testcode.ps1)
Note that passing the string to evaluate as an argument truly only accepts a single string, so the command would fail without -Raw.
[1] By default, the Get-Content cmdlet reads a file line by line, passing each line through the pipeline as it is being read.
$myvar = "I'm Here"
#Using Invoke-Expression - Accepts a STRING as Input
$SBCode = 'if ($Null -ne $myvar) {"($myvar) variable is Full"}' +
'else {"`$myvar variable is Empty"}'
Clear-Host
"Before Invoke-Expression `$myvar = $myvar"
$Result = Invoke-Expression $SBCode
"Invoke-Expression Returns: $Result"
#Using Invoke-Command - Accepts Script Block as Input
$SBCode = {
if ($myvar -ne $null) {
"($myvar) variable is Full"
}
else {
"`$myvar variable is Empty"
}
} #End $SBCode Script Block
"Before Invoke-Command `$myvar = $myvar"
$Result = Invoke-Command -ScriptBlock $SBCode
"Invoke-Command Returns: $Result"
Results:
Before Invoke-Expression $myvar = I'm Here
Invoke-Expression Returns: (I'm Here) variable is Full
Before Invoke-Command $myvar = I'm Here
Invoke-Command Returns: (I'm Here) variable is Full
# After changing $MyVar = $Null
Before Invoke-Expression $myvar =
Invoke-Expression Returns: $myvar variable is Empty
Before Invoke-Command $myvar =
Invoke-Command Returns: $myvar variable is Empty
HTH
You can use out-string to convert output into string.
cat C:\Users\J\testcode.ps1 | out-string | Invoke-Expression

Invoke-Expression cannot execute multi-line functions

I was trying to run a PowerShell script saved in a text document by using the following:
cat myScript.txt | Invoke-Expression
It works when each command is on its own line but throws an error saying I am missing syntax:
Invoke-Expression: At line:1 char:14
+ function foo {
+ ~
Missing closing '}' in statement block or type definition.
The code in myScript.txt is as follows:
function foo {
param([int]$a, [int]$b)
echo "$a + $b"
}
foo 1 2
Is there a way to run this script without changing the text file to merge all functions to one line?
You can also use out-string to convert output into string.
cat myScript.txt | out-string | Invoke-Expression

Why passing args from powershell gives error while working from inside script itself

Why calling .\MyScript.ps1 -Uninstall from Powershell gives an error
+ Super-Function $Args
+ ~~~~~
+ CategoryInfo : InvalidData : (:) [Super-Function], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Super-Function
While calling "Super-Function" from the script itself with Super-Function -Uninstall , replacing $Args with the switch works ?
Why copy pasting the function on Powershell and then going for Super-Function -Uninstall works too ?
Here's the content of MyScript.ps1
function Super-Function([parameter(Mandatory=$False)][ValidateScript({Test-Path _$})][String]$var1 = ".\MyFile.ext",
[parameter(Mandatory=$False)][ValidateScript({Test-Path _$})][String]$var2 = "HKLM:\SOFTWARE\Wow6432Node\Publisher\SoftwareName",
[parameter(Mandatory=$False)][ValidateScript({([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")})][Switch]$Uninstall,
[parameter(Mandatory=$False)][ValidateScript({([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")})][Switch]$Install
)
{
}
Super-Function $Args
You have a few issues there that I see. Your ValidateScript for each argument have an issue. First, might just be a typo, you have the characters backwards for current pipe item. Should be $_ instead of _$. Next I find it befuddling that you test the presence of the admin role against a couple of boolean switches. Lets just move that inside the function (If what you had works that is fine. Just does make much sense)
Lastly, and most importantly, what you are trying to do with $args is called splatting. Use #args which will splat the hashtable of arguments, passed in from the script, against the function.
function Super-Function{
param(
[parameter(Mandatory=$False)][ValidateScript({Test-Path $_})][String]$var1 = ".\MyFile.ext",
[parameter(Mandatory=$False)][ValidateScript({Test-Path $_})][String]$var2 = "HKLM:\SOFTWARE\Wow6432Node\Publisher\SoftwareName",
[parameter(Mandatory=$False)][Switch]$Uninstall,
[parameter(Mandatory=$False)][Switch]$Install
)
# Use this to verify what has been assinged to your parameters. Will not show default values.
#$PSBoundParameters
If(([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
"Sure"
} Else {
Throw "Nope"
}
}
Super-Function #args

powershell: how to print the total call stacks when error happen?

suppose I have the following code, when the error happens, I'd like to see the error that the error first happened at function b, and then happened at function a. But in fact it only tells me the error happen at function a, since function a could be called many times, I don't know which outer function calling function a caused the problem
cls
function a{
Remove-Item "not-exist-item"
}
function b{
a
}
b
Remove-Item : Cannot find path 'C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\not-exis
t-item' because it does not exist.
At C:\Users\Daniel.Wu\AppData\Local\Temp\2\a.ps1:***3 char:14***
+ Remove-Item <<<< "not-exist-item"
+ CategoryInfo : ObjectNotFound: (C:\Program File...\not-exist-item:String) [Remove-Item], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand
If you are on PowerShell v2.0, use Get-PSCallStack. If you're still on v1, use a function like this:
function Get-CallStack {
trap { continue }
1..100 | foreach {
$var = Get-Variable -scope $_ MyInvocation
$var.Value.PositionMessage -replace "`n"
}
}
One option is to set
$ErrorActionPreference = 'inquire'
and then invoke the problematic script. On error you are prompted for choices, choose Suspend to enter into the nested prompt mode. Then type
Get-PSCallStack
or even
Get-PSCallStack | fl
to get the current call stack information.
Does get-help about_debuggers provide any illumination?