Powershell Start-Process to start Powershell session and pass local variables - powershell

Is there a way to use the Powershell Start-Process cmdlet to start a new Powershell session and pass a scriptblock with local variables (once of which will be an array)?
Example:
$Array = #(1,2,3,4)
$String = "This is string number"
$Scriptblock = {$Array | ForEach-Object {Write-Host $String $_}}
Start-Process Powershell -ArgumentList "$Scriptblock"
Thanks.

I'm pretty sure there's no direct way to pass variables from one PowerShell session to another. The best you can do is some workaround, like declaring the variables in the code you pass in -ArgumentList, interpolating the values in the calling session. How you interpolate the variables into the declarations in -ArgumentList depends on what types of variables. For an array and a string you could do something like this:
$command = '<contents of your scriptblock without the curly braces>'
Start-Process powershell -ArgumentList ("`$Array = echo $Array; `$String = '$String';" + $command)

I was able to get this to work by joining the array with "/" to create a string and entering the scriptblock into another .ps1 script with appropriate parameters and splitting the joined string back to an array within the second script and using
Start-Process Powershell -ArgumentList "&C:\script.ps1 $JoinedArray $String"
Ugly, but it's the only way I could get it to work. Thanks for all the replies.

You could wrap the contents of your script block in a function, and then call the function from the ArgumentList and pass in the variables as parameters to the function, as I do on this post.
$ScriptBlock = {
function Test([string]$someParameter)
{
# Use $someParameter to do something...
}
}
# Run the script block and pass in parameters.
$myString = "Hello"
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('$myString')}"

The command line options for PowerShell.exe say that you should be able to pass arguments when using a script block by adding -args:
PowerShell.exe -Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] }
However when I try to do that I get the following error:
-args : The term '-args' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
I added $MyInvocation | fl to the script block to see what was happening, and it looks like the -args is just appended to the deserialized commands in the script block (hence the error since -args is not a valid command). I also tried using GetNewClosure() and $Using:VariableName but those only appear to work when the script block is invoked (as opposed to this where we are using it to serialize/deserialize the commands).
The I was able to get it to work by wrapping it in a function like deadlydog's answer.
$var = "this is a test"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host "hello world: $message"
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"AdminTasks('$var')" -Verb runAs #-WindowStyle Hidden
#Output:
MyCommand :
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host hello world: $message
}
AdminTasks('this is a test')
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 1
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName :
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Runspace
DisplayScriptPosition :
hello world: this is a test
Wrapping it in a script block and using $args[0] or $args[1] also works, just be aware that you many need to wrap the $var0 or $var1 in quotes if there are issues when it is deserialized and use `$ to prevent the $sb from being replaced with "" since that variable doesn't exist in the caller's scope:
$var0 = "hello"
$var1 = "world"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
$sb = {
write-host $args[0] $args[1]
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"& `$sb $var0 $var1"

If you want to pass objects that are serializable, but are not strings, I wrote up a solution: Is there a way to pass serializable objects to a PowerShell script with start-process?

Related

How to pass a variable to new console window in Powershell

I want to pass a variable to a new console, but I don't know how.
$server = "server_name"
Start-Process Powershell {
$host.ui.RawUI.WindowTitle = “Get-Process”
Invoke-Command -ComputerName $server -ScriptBlock {
Get-Process
}
cmd /c pause
}
Error message:
Invoke-Command : Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again
Start-Process only accepts (one or more) strings as arguments, not a script block ({ ... }).
While a script block is accepted, it is simply stringified, which results in its verbatim content being passed as the argument (sans { and }), which means that $server is retained as-is (not expanded), and the child process that runs your command doesn't have a variable by that name defined, causing Invoke-Command to fail due not receiving a value for -ComputerName.
Therefore, in order to incorporate variable values from the caller's scope, you must use string interpolation, using an expandable (double-quoted) string ("...") that encodes all arguments:[1]
$server = "server_name"
# Parameters -FilePath and -ArgumentList are positionally implied.
# For the resulting powershell.exe call, -Command is implied.
Start-Process powershell "
$host.ui.RawUI.WindowTitle = 'Get-Process'
Invoke-Command -ComputerName $server -ScriptBlock { Get-Process }
pause
"
A computer name ($server, in your case) doesn't contain spaces, but any variable values that do would require embedded enclosing quoting inside the overall "..." string, such as \`"$someVar\`" (`" escapes an " inside a "..." string in PowerShell, and the \ is additionally needed to escape the resulting verbatim " for the PowerShell CLI, powershell.exe).
For full robustness, additionally enclose the entire string value (implied -Command argument) in embedded "..." quoting ("`"...`"").
You can make this a bit easier by using the here-string form of an expandable string (#"<newline>...<newline>"#), inside of which you don't need to escape " chars.
Example of a fully robust call:
$someVar = 'A value with spaces'
Start-Process powershell #"
-NoProfile -Command "
# Echo the value of $someVar
Write-Output \"$someVar\"
pause
"
"#
Note the use of -NoProfile before -Command, which suppresses loading of the profile files, which can speed up the call and makes for a more predictable execution environment.
[1] Technically, -ArgumentList accepts an array of arguments, and while passing the pass-through arguments individually may be conceptually preferable, a long-standing bug unfortunately makes it better to encode all arguments in a single string - see this answer.
if you want to use param-
info
param([type]$p1 = , [type]$p2 = , ...)
or:
info
param(
[string]$server
)
Write-Host $a
./Test.ps1 "your server name"

powershell : pipe get-content to ps1 file with parameters

I'm trying to write a script which uses the powershell cmdlet get-content tail and inserts the new lines into the sql server table. i can't get the syntax to pipe the tail to the sqlinsert.ps1 file that handles the table insert.
i'm looking for help on how to pipe "get-content tail" to a sqlinsert.ps1 file to do a sql database insert statement using the following :
$startTime = get-date
Write-Host "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt"
get-content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
# % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
in the sqlinsert.ps1 :
param ([string]$stmp, [string]$method, [string]$msg )
$Connection = New-Object System.Data.SQLClient.SQLConnection
$Connection.ConnectionString = "server='$serverName';database='$databaseName';User ID = $uid; Password = $pwd;"
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$sql = "insert into [tbl_iiserrors] (errstamp, method, msg) values (#stmp , #method, #msg) "
.
.
.
error i get:
& : The term 'sqlinsert.ps1' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is
correct and try again. At C:\Temp\ob\iislog\tst_tail.ps1:3 char:95
... Mdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $ ...
~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (sqlinsert.ps1:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
Suggestion [3,General]: The command sqlinsert.ps1 was not found, but
does exist in the current location. Windows PowerShell does not load
commands from the current location by default. If you trust this
command, instead type: ".\sqlinsert.ps1". See "get-help
about_Command_Precedence" for more details.
The sqlinsert.ps1 works when i run it from powershell command :
PS c:\temp> .\sqlinsert -stmp 2020-11-20 00:00:00 -method 'eek' -msg 'uh hello'
In order to bind pipeline input to a parameter, you need to decorate it with a [Parameter] attribute and specify that it accepts pipeline input, like this:
param (
[string]$stmp,
[string]$method,
[Parameter(ValueFromPipeline = $true)]
[string]$msg
)
See the about_Functions_Advanced_Parameters help file for more details about how to modify the behavior of parameters
By design, for security reasons, PowerShell requires you to signal the intent to execute a script located in the current directory explicitly, using a path - .\sqlinsert.ps1 - rather than a mere file name - sqlinsert.ps1; that is what the suggestion following the error message is trying to tell you.
Note that you only need &, the call operator, if the script path is quoted and/or contains variable references - and .\sqlinsert.ps1 doesn't require quoting.
You can only use the automatic $_ variable, which represents the current input object from the pipeline inside a script block ({ ... }), such as one passed to the ForEach-Object cmdlet, which invokes that block for each object received via the pipeline.
Re the content of your script: Inside expandable strings ("..."), you cannot use # to refer to variables to be expanded (interpolated); use regular, $-prefixed variable references or $(...), the subexpression operator to embed expressions; also, it looks like you're inserting string values into the SQL table, so you'll have to enclose the expanded variable values in embedded '...'
$startTime = get-date
Get-Content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -Tail 1 -Wait |
ForEach-Object {
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
}
The alternative to using a ForEach-Object call is to modify your script to directly receive its -msg argument from the pipeline, as shown in Mathias' answer, in which case you must omit the -msg $_ argument from your script call:
Get-Content ... |
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error"

Printing the argument in powershell

May I know, why the string argument is not printed in the following powershell script?
function Get-Name ( [string] $Username ) {
echo "user : $Username"
}
PS C:\> .\Get-Name.ps1 -username "test"
PS C:\>
The script file Get-Name.ps1 only defines the function Get-Name, it doesn't execute it.
Use the dot-source operator (.) to have it define the function in the calling scope, and then you can execute the function itself:
PS C:\> . .\Get-Name.ps1
PS C:\> Get-Name -Username test
user : test
Alternatively, remove the function Get-Name { and } part of the script as pointed out by Lee_Dailey, at which point the script file becomes a parameterized function in itself and you can then do:
PS C:\> .\Get-Name.ps1 -Username test
user : test
Please see the about_Scripts help file, especially the section about script scope and dot sourcing
the problem is that you define a function, not a callable script. [grin] this will work ...
Param ([string] $Username)
echo "user : $Username"
here's an example of calling the above ...
. .\Func_Get-Name.ps1 -username 'tutu'
output ...
user : tutu

Passing parameters to a PowerShell job [duplicate]

This question already has an answer here:
Parenthesis Powershell functions
(1 answer)
Closed 7 years ago.
I've been toying around with this dang parameter passing to powershell jobs.
I need to get two variables in the script calling the job, into the job. First I tried using -ArgumentList, and then using $args[0] and $args[1] in the -ScriptBlock that I provided.
function Job-Test([string]$foo, [string]$bar){
Start-Job -ScriptBlock {#need to use the two args in here
} -Name "Test" -ArgumentList $foo, $bar
}
However I realized that -ArgumentList gives these as parameters to -FilePath, so I moved the code in the scriptblock into its own script that required two parameters, and then pointed -FilePath at this script.
function Job-Test([string]$foo, [string]$bar){
$myArray = #($foo,$bar)
Start-Job -FilePath .\Prog\august\jobScript.ps1 -Name 'Test' -ArgumentList $myArray
}
#\Prog\august\jobScript.ps1 :
Param(
[array]$foo
)
#use $foo[0] and $foo[1] here
Still not working. I tried putting the info into an array and then passing only one parameter but still to know avail.
When I say no avail, I am getting the data that I need however it all seems to be compressed into the first element.
For example say I passed in the name of a file as $foo and it's path as $bar, for each method I tried, I would get args[0] as "filename path" and args[1] would be empty.
ie:
function Job-Test([string]$foo, [string]$bar){
$myArray = #($foo,$bar)
Start-Job -FilePath .\Prog\august\jobScript.ps1 -Name 'Test' -ArgumentList $myArray
}
Then I called:
$foo = "hello.txt"
$bar = "c:\users\world"
Job-Test($foo,$bar)
I had jobScript.ps1 simply Out-File the two variables to a log on separate lines and it looked like this:
log.txt:
hello.txt c:\users\world
#(empty line)
where it should have been:
hello.txt
c:\users\world
you don't need to call the function like you would in java. just append the two variables to the end of the function call Job-Test $foo $bar

Use hashtable as parameter when splatting?

I am trying to use Start-Job to launch a new Powershell script. The new script has several parameters (some optional, some not), so I want to make a hashtable and splat them. However, one of these parameters is itself a hash table. I am trying to start the job like this:
$MyStringParam = "string1"
$MyHashParam = #{}
$MyHashParam.Add("Key1", "hashvalue1")
$arguments = #{MyStringParam=$MyStringParam;MyHashParam=$MyHashParam}
Start-Job -Name "MyJob" -ScriptBlock ([scriptblock]::create("C:\myscript.ps1 $(&{$args} #arguments)"))
Whereupon I get this error in the new job:
Cannot process argument transformation on parameter 'arguments'.
Cannot convert the "System.Collections.Hashtable" value of
type "System.String" to type "System.Collections.Hashtable".
Looks like it's treating the value I want to pass in as a hash table as a string. For the life of me I can't figure out how to get around this. Can anyone help?
You will need to pass the variable into the scriptblock as a parameter of the scriptblock, and then splat that parameter to your second script. Something like this should work for you:
Start-Job -Name "MyJob" -ScriptBlock {Param($PassedArgs);& "C:\myscript.ps1" #PassedArgs} -ArgumentList $Arguments
I created the following script and saved it to C:\Temp\TestScript.ps1
Param(
[String]$InString,
[HashTable]$InHash
)
ForEach($Key in $InHash.keys){
[pscustomobject]#{'String'=$InString;'HashKey'=$Key;'HashValue'=$InHash[$Key]}
}
I then ran the following:
$MyString = "Hello World"
$MyHash = #{}
$MyHash.Add("Green","Apple")
$MyHash.Add("Yellow","Banana")
$MyHash.Add("Purple","Grapes")
$Arguments = #{'InString'=$MyString;'InHash'=$MyHash}
$MyJob = Start-Job -scriptblock {Param($MyArgs);& "C:\Temp\testscript.ps1" #MyArgs} -Name "MyJob" -ArgumentList $Arguments | Wait-Job | Receive-Job
Remove-Job -Name 'MyJob'
$MyJob | Select * -ExcludeProperty RunspaceId | Format-Table
It produced the expected results:
String HashKey HashValue
------ ------- ---------
Hello World Yellow Banana
Hello World Green Apple
Hello World Purple Grapes
The process of running the job will add a RunspaceId property to any objects returned, that is why I had to exclude that.
Rather than
[scriptblock]::create("C:\myscript.ps1 $(&{$args} #arguments)")
does this work?
[scriptblock]::create("C:\myscript.ps1 $(&{$args}) #arguments")
I.e. move the splat outside of the $()