Passing variables and object to start-job - powershell

I know that a script block executed via start-job cannot see the variables outside of the script block. To pass variables in you use the -arguments paramater. So why does this work (from MS article):
Start-Job -ScriptBlock { Get-Process -Name $args } -ArgumentList "powershell"
But this does not:
Start-Job -ScriptBlock { Get-aduser $args } -ArgumentList "samaccountname"
When I run it and receive the job I get the following error:
Cannot convert 'samaccountname' to the type 'Microsoft.ActiveDirectory.Management.ADUser' required by parameter 'Identity'. Specified method is not supported.
This same syntax works though outside of running it via start-job:
Get-aduser "samaccountname"
This last command is to demonstrate to you that the syntax is correct in the start-job script block. So why does the command expect an ADUser object when executed via start-job wheras outside of the script block it will accept a string?
I need to be able to execute the command via start-job

All credit here goes to Abraham in the comments above. He provided the answer but for whatever reason didn't submit it as an answer, so I am providing this to help others.
The issue is that -Arguments is always an array, even if you only provide one value. In my example where it didn't work with the second command (the get-Aduser one) is because I was passing an array to a paramater which expected a string. Changing the code to args[0] fixed this.
The first command worked because it's paramater accepts a string or an array.
NOTE: Passing complex objects to Start-job is a different story and has it's own issues, I posed this as a different question here

Related

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

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

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.

How to provide options to script blocks?

I'm trying to learn about script blocks at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks?view=powershell-6.
I want to send a script block to an event registration with a command-line option to the registration command, like this:
$Block = {
Param($option)
Write-Host "The option was $option"
if ($option==ABC) {
Write-Host "ABC was specified"
}
}
But when I test this by appending
Invoke-Command -ScriptBlock $Block -option ABC
I get the error
Invoke-Command : A parameter cannot be found that matches parameter name 'option'.
When in doubt, read the documentation:
-ArgumentList
Supplies the values of local variables in the command. The variables in the command are replaced by these values before the command is run on the remote computer. Enter the values in a comma-separated list. Values are associated with variables in the order that they are listed. The alias for ArgumentList is "Args".
The values in ArgumentList can be actual values, such as "1024", or they can be references to local variables, such as "$max".
To use local variables in a command, use the following command format:
{param($<name1>[, $<name2>]...) <command-with-local-variables>} -ArgumentList <value> -or- <local-variable>
The "param" keyword lists the local variables that are used in the command. The ArgumentList parameter supplies the values of the variables, in the order that they are listed.
The first element from the -ArgumentList array becomes the value of the first named parameter in the scriptblock, the second array element becomes the value of the second named parameter, and so on.
Basically, your statement should look like this:
Invoke-Command -ScriptBlock $Block -ArgumentList 'ABC'
Also, the syntax of the comparison operation in your scriptblock is wrong. The equality comparison operator in PowerShell is -eq, not ==.
Remember that you can always check the documentation: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-6
$Block = {Param($option)
Write-Host "The option was $option"
if ($option -eq "ABC") // -eq instead of ==, string in quotations
{Write-Host "ABC was specified"}
}
Invoke-Command -ScriptBlock $Block -ArgumentList "ABC"
If there are more arguments you can list them in order. Using script blocks isn't something you want to do very often. It's better to create a function (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions?view=powershell-6)
You can use the call operator on scriptblocks too, and just provide the arguments after a space or -option:
& $block abc
The option was abc
ABC was specified
And you can later assign the scriptblock to a function. That's what a function is.
$function:myfunc = $block

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.

Powershell invoke-command with -NoMachineProfile how to get passed args

The Question
Does anyone know of a method to get the same data as $MyInvocation? Strong preference is using only built in stuff and not 3rd party add on software to accomplish this.
The Problem
When you create a session with -NoMachineProfile $MyInvocation is not populated. I was hoping there was some other way to get the information just as there is with [System.Environment]::Username is for $env:USERNAME.
I am not able to change the way things are invoked. Please do not suggest removing -NoMachineProfile as a solution since that is not available as an option.
I do not want to rely on $args because that is post-processing. Namely quotes, variables and other passed arguments are no longer identical to what they were when they were passed.
Background
$MyInvocation.line contains the exact command with 0 parsing provided.
e.g.
SomeScript.ps1
write-host ("Invoked line: "+$MyInvocation.line)
.\somescript.ps1 foo bar "foo and bar"
yields the following output
Invoked line: .\somescript.ps1 foo bar "foo and bar"
quotes and all, it means it is capable of being cut and pasted and exactly run the same way with 0 user translation.
-NoMachineProfile invocation
$SE_ADMIN_CREDENTIALS = Get-Credential
$PS_SESSION_OPTIONS = New-PSSessionOption -NoMachineProfile
$arrayRemoteTarget = "thehostname"
$strScriptToRun = ".\somescript.ps1"
$StrArguments = "arg1,arg2,arg3"
$icmCommand = "Invoke-Command -Credential `$SE_ADMIN_CREDENTIALS -SessionOption `$PS_SESSION_OPTIONS -ComputerName `$arrayRemoteTarget -FilePath `$strScriptToRun -ArgumentList (`$strArguments).Split("","")"
Invoke-Expression $icmCommand
-sessionoption (new-pssessionoption -nomachineprofile)
is another way to do that.
As for passing arguments, i believe
$using:variable
is what you're looking for
e.g.
$listing = "*"
invoke-command -computername $computer -scriptblock {cd /; ls $using:listing}
i hope that makes sense.
otherwise you could use "arg[0]" or "args[0]" in the file to run script... should work...
One final note, your quotes in quotes, is likely messing up your script, i'd just replace split("","") with split(',')