Why can't I see the output of another executable in an invoked ScriptBlock? - powershell

I am creating and invoking PowerShell ScriptBlock objects. I noticed that when the ScriptBlock includes another executable, I am not able to see the output from that process. Keyboard input is still accepted though.
Here is a simplified version of the problem. In this case, I do not see any output from cmd.exe but I can type 'exit{ENTER}' and return to PowerShell
function Test-CMD {cmd.exe}
$sb = [System.Management.Automation.ScriptBlock]::Create('Test-CMD')
$sb.Invoke()
Is there a way to get the executable output to the console? Other ScriptBlock output works as expected.

PowerShell is blocked until the method ($sb.Invoke()) returns - to avoid this, use the & call operator instead:
function Test-CMD {cmd.exe}
$sb = [System.Management.Automation.ScriptBlock]::Create('Test-CMD')
& $sb

Related

Powershell Start-Job with command [duplicate]

I'd like to setup a cmdlet to start and stop mysql, and I'm trying to do so with Start-Job. the I've got the following in my Powershell profile:
$mysqlpath = "C:\Program Files\MySQL\MySQL Server 5.5\bin"
Function Start-Mysql
{
Start-Job -ScriptBlock { & "$mysqlpath\mysqld.exe" }
}
The variable doesn't seem to be expanding in the job command however? I must be missing some sort of scoping rule. Could someone please advise? Thanks!
you have to use the -argumentlist parameter see get-help start-job :
start-job -ScriptBlock { & $args[0] } -ArgumentList #($mysqlpath )
note that in V3 you just have to use the prefix using: before your varname ex:
Start-Job -ScriptBlock { & "$using:mysqlpath\mysqld.exe" }
Loïc MICHEL's answer is correct, but if you find it becomes difficult to deal with remembering which positional argument is which within the ScriptBlock, I'd like to offer a trick using the param keyword. Within the ScriptBlock, begin the body with param like you would for an advanced function, and put your code after also as if it were a function:
Note: The ScriptBlock param name does not need to be the same in the ScriptBlock and current session, it can be the same or something totally different. The important thing is you match the correct argument positionally in the -ArgumentList.
Start-Job { param( $mysqlpath ) & "$mysqlpath\mysqld.exe" } -ArgumentList $mysqlpath
This works because a ScriptBlock is just an unnamed function, so you can define parameters in mostly the same way you can when defining a proper function. The arguments in -ArgumentList are passed to the ScriptBlock as positional arguments in the order provided, so by default the order the arguments are passed is the same order they will be bound to named parameters in.
While the $using: scope is syntactically easier to work with, this method gets you the best of all worlds here, as the $using: scope cannot be used within the current session. This is incredibly useful you have a ScriptBlock that needs to be able to execute in any context and it's complicated enough that referencing the arguments by index becomes difficult to manage. This approach allows you to name your parameters and works with any ScriptBlock in all execution contexts, whether it's Start-Job, Invoke-Command, powershell.exe, or executing a ScriptBlock with the call operator &.
This is one of (if not the) most portable solution if you want to use named variables instead of referencing $args[i] for every variable.

start powershell wont accept variable as parameter

issue
the called powershell script will accept parameters but not all of them:
Current Set-Up and code:
I have a common folder where two .ps1 scripts are located:
DoWork.ps1
Workmanager.ps1
Workmanager.ps1 calls the Dowork.ps1:
$targetPath="M:\target"
echo "target path: $targetPath"
start powershell {.\DoWork.ps1 -target $targetPath -tempdrive D:\}
output (as expected):
target path: M:\target
DoWork.ps1 contains some start code:
param
(
[string]$tempdrive,
[string]$target,
[int] $threads = 8,
[int] $queuelength = -1
)
echo "variables:"
echo "temp drive: $tempdrive"
echo "target path: $target"
Unexpectedly, the $target is not beeing assigned. Previously I had the variable named $targetpath, which did not work either.
variables:
temp drive: D:\
target path:
Findings
It appears that the issue relies in Workmanager.ps1. Spcifying the parameter as fixed string rather than as variable will load the parameter. Any solution for this?
start powershell {.\DoWork.ps1 -target "foo" -tempdrive D:\}
When you use a ScriptBlock as an argument to powershell.exe, variables aren't going to be evaluated until after the new session starts. $targetPath has not been set in the child PowerShell process called by Workmanager.ps1 and so it has no value. This is actually an expected behavior of a ScriptBlock in general and behaves this way in other contexts too.
The solution is mentioned in the help text for powershell -?:
[-Command { - | <script-block> [-args <arg-array>] <========== THIS GUY
| <string> [<CommandParameters>] } ]
You must provide the -args parameter which will be passed to the ScriptBlock on execution (separate multiple arguments with a ,). Passed arguments are passed positionally, and must be referenced as though you were processing the arguments to a function manually using the $args array. For example:
$name = 'Bender'
& powershell { Write-Output "Hello, $($args[0])" } -args $name
However, especially with more complicated ScriptBlock bodies, having to remember which index of $args[i] contains the value you want at a given time is a pain in the butt. Luckily, we can use a little trick with defining parameters within the ScriptBlock to help:
$name = 'Bender'
& powershell { param($name) Write-Output "Hello, $name" } -args $name
This will print Hello, Bender as expected.
Some additional pointers:
The ScriptBlock can be multiline as though you were defining a function. way. The examples above are single line due to their simplicity.
A ScriptBlock is just an unnamed function, which is why defining parameters and referencing arguments within one works the same way.
To exemplify this behavior outside of powershell.exe -Command, Invoke-Command requires you to pass variables to its ScriptBlock in a similar fashion. Note however that answer uses an already-defined function body as the ScriptBlock (which is totally valid to do)
You don't need to use Start-Process here (start is its alias), at least as demonstrated in your example. You can simply use the call operator & unless you need to do something more complex than "run the program and wait for it to finish". See this answer of mine for more information.
If you opt to pass a string to powershell.exe instead, you don't need to provide arguments and your variables will get rendered in the current PowerShell process. However, so will any other unescaped variables that might be intended to set within the child process, so be careful with this approach. Personally, I prefer using ScriptBlock regardless, and just deal with the extra parameter definition and arguments.
Using the call & operator is optional when you are not executing a path rendered as a string. It can be omitted in the examples above, but is more useful like so:
& "C:\The\Program Path\Contains\spaces.exe"
& $programPathAsAVariable

Passing arguments to Start-Job scriptblock?

I'd like to setup a cmdlet to start and stop mysql, and I'm trying to do so with Start-Job. the I've got the following in my Powershell profile:
$mysqlpath = "C:\Program Files\MySQL\MySQL Server 5.5\bin"
Function Start-Mysql
{
Start-Job -ScriptBlock { & "$mysqlpath\mysqld.exe" }
}
The variable doesn't seem to be expanding in the job command however? I must be missing some sort of scoping rule. Could someone please advise? Thanks!
you have to use the -argumentlist parameter see get-help start-job :
start-job -ScriptBlock { & $args[0] } -ArgumentList #($mysqlpath )
note that in V3 you just have to use the prefix using: before your varname ex:
Start-Job -ScriptBlock { & "$using:mysqlpath\mysqld.exe" }
Loïc MICHEL's answer is correct, but if you find it becomes difficult to deal with remembering which positional argument is which within the ScriptBlock, I'd like to offer a trick using the param keyword. Within the ScriptBlock, begin the body with param like you would for an advanced function, and put your code after also as if it were a function:
Note: The ScriptBlock param name does not need to be the same in the ScriptBlock and current session, it can be the same or something totally different. The important thing is you match the correct argument positionally in the -ArgumentList.
Start-Job { param( $mysqlpath ) & "$mysqlpath\mysqld.exe" } -ArgumentList $mysqlpath
This works because a ScriptBlock is just an unnamed function, so you can define parameters in mostly the same way you can when defining a proper function. The arguments in -ArgumentList are passed to the ScriptBlock as positional arguments in the order provided, so by default the order the arguments are passed is the same order they will be bound to named parameters in.
While the $using: scope is syntactically easier to work with, this method gets you the best of all worlds here, as the $using: scope cannot be used within the current session. This is incredibly useful you have a ScriptBlock that needs to be able to execute in any context and it's complicated enough that referencing the arguments by index becomes difficult to manage. This approach allows you to name your parameters and works with any ScriptBlock in all execution contexts, whether it's Start-Job, Invoke-Command, powershell.exe, or executing a ScriptBlock with the call operator &.
This is one of (if not the) most portable solution if you want to use named variables instead of referencing $args[i] for every variable.

Execute a parameter passed into a powershell script as if it were a line in the script

I've got a wrapper powershell script that I'm hoping to use to automate a few things. It's pretty basic, and accepts a parameter that I want the script to run as if it were a line in the script. I absolutely cannot get it to work.
example:
param( [string[]] $p)
echo $p
# Adds the base cmdlets
Add-PSSnapin VMware.VimAutomation.Core
# Add the following if you want to do things with Update Manager
Add-PSSnapin VMware.VumAutomation
# This script adds some helper functions and sets the appearance. You can pick and choose parts of this file for a fully custom appearance.
. "C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-VIToolkitEnvironment.ps1"
$p
In the example above, I want $p to execute as if it were a line in the script. I know this isn't secure, and that's probably where the problem lies.
Here is how I try running the script and passing in a parameter for $p:
D:\ps\test>powershell -command "D:\ps\test\powershell_wrapper.ps1" 'Suspend-VM servername -Verbose -Confirm:$False'
How can I get my parameter of 'Suspend-VM servername -Verbose -Confirm:$False' to run inside my script? If I just include the value in the script instead of pass it in as a parameter it runs without any issues...
You can basically approach this two ways, depending on what your needs really are and how you want to structure your code.
Approach #1 - Invoke-Expression
Invoke-Expression basically allows you to treat a string like an expression and evaluate it. Consider the following trivial example:
Invoke-Expression '{"Hello World"}'
That will evaluate the string as if it were an expression typed in directly, and place the string "Hello World" on the pipeline. You could use that to take your string parameter and run it on-the-fly in your script.
Approach #2 - Using a ScriptBlock
PowerShell has a special data type called a ScriptBlock, where you can bind a script to a variable, and then invoke that script as part of your code. Again, here is a trivial example:
function Test-SB([ScriptBlock]$sb) {
$sb.Invoke()
}
Test-SB -sb {"Hello World"}
This example creates a function with a single parameter $sb that is of type ScriptBlock. Notice the parameter is bound to the actual chunk of code {"Hello World"}? That code is assigned to the $sb parameter, and then a call to the .Invoke method actually executes the code. You could adapt your code to take in a ScriptBlock and invoke it as part of your script.
Approach #3 - Updating your profile
OK, so I said there were two ways to approach it. There is actually a third... sort of... You could add the VMWare cmdlets to your $profile so they are always present and you don't need your wrapper to load in those libraries. Granted, this is a pretty big hammer - but it might make sense if this is the environment you are constantly working in. You could also just create a shortcut to PowerShell that runs a .ps1 on startup that includes those libraries and hangs around (this is what MS did with the SharePoint admin shell and several others). Take a look at this TechNet page to get more info on the $profile and if it can help you out:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb613488.aspx

Calling powershell script with many args from cmd

I have a simple powershell script. It takes two parameters. Both are part of methods (Get-ResourcePool) which take string arguments.
If I call the function definition in the Powershell script, like so:
functName CI *FMS
That works fine.
The function call in Powershell is (and like so because this script will be called from outside):
FuncName $($args[0], $args[1])
I try to call this from Powershell editor, where I have the snapins I need all installed, like so:
C:\Script.ps1 A "*s"
Where scrpt is the name of my .ps1 file. There is one function. This, however, fails with an error that the argument is null or empty.
Any ideas why?
EDIT:
The function signature is:
function RevertToSnapshot($otherarg, $wildcard)
I use $wildcard here:
$SearchString = [System.String]::Concat("*",
$VMWildcard)
Name $SearchString (Name is a parameter to get-vm in powercli).
This calling style:
FuncName $($args[0], $args[1])
Will result in just one argument passed to FuncName - a single array with two elements. You probably want:
FuncName $args[0] $args[1]
In general, with PowerShell you call cmdlets, functions, aliases using space separated arguments and no parens. Calling .NET methods is the one exception to this rule where you have to use parens and commas to separate arguments e.g:
[string]::Concat('ab','c')