Alternative to $MyInvocation - powershell

When deploying PowerShell scripts from my RMM (NinjaOne), the scripts are called from a .bat (batch file).
Example:
#powershell -ExecutionPolicy Bypass -File "test.ps1" -stringParam "testing" -switchParam > "output.txt" 2>&1
The script I am calling requires PowerShell 7+, so I need to restart the script by calling pwsh with the current parameters. I planned to accomplish this via the following:
Invoke-Command { & pwsh -Command $MyInvocation.Line } -NoNewScope
Unfortunately, $MyInvocation.Line does not return the correct result when a PowerShell script is called from a batch file. What alternatives exist that would work in this scenario?
Notes:
I am unable to make changes to the .bat file.
$PSBoundParameters also does not return the expected result.
Testing Script (called from batch):
Param(
[string]$string,
[switch]$switch
)
if (!($PSVersionTable.PSVersion.Major -ge 7)) {
Write-Output "`n"
Write-Output 'Attempting to restart in PowerShell 7'
if(!$MyInvocation.Line) {
Write-Output 'Parameters not carried over - cannot restart'
Exit
} else { Write-Output $MyInvocation.Line }
Invoke-Command { & pwsh -Command $MyInvocation.Line } -NoNewScope # PowerShell 7
Exit
}
Write-Output 'Parameters carried over:'
Write-Output $PSBoundParameters
Write-Output "`nSuccessfully restarted"
Edit: I've discovered the reason for $MyInvocation / $PSBoundParameters not being set properly is due to the use of -File instead of -Command when my RMM provider calls the PowerShell script from the .bat file. I've suggested they implement this change to resolve the issue. Until they do, I am still looking for alternatives.

Leaving RMM providers out of the picture (whose involvement may or may not matter), the following test.ps1 content should work:
# Note: Place [CmdletBinding()] above param(...) to make
# the script an *advanced* one, which then prevents passing
# extra arguments that don't bind to declared parameters.
param(
[string] $stringParam,
[switch] $switchParam
)
# If invoked via powershell.exe, re-invoke via pwsh.exe
if ((Get-Process -Id $PID).Name -eq 'powershell') {
# $PSCommandPath is the current script's full file path,
# and #PSBoundParameters uses splatting to pass all
# arguments that were bound to declared parameters through.
# Any extra arguments, if present, are passed through with #args
pwsh -ExecutionPolicy Bypass -File $PSCommandPath #PSBoundParameters #args
exit $LASTEXITCODE
}
# Getting here means that the file is being executed by pwsh.exe
# Print the arguments received:
if ($PSBoundParameters.Count) {
"-- Bound parameters and their values:`n"
# !! Because $PSBoundParameters causes table-formatted
# !! output, synchronous output must be forced to work around a bug.
# !! See notes below.
$PSBoundParameters | Out-Host
}
if ($args) {
"`n-- Unbound (positional) arguments:`n"
$args
}
exit 0
As suggested in the code comments, place [CmdletBinding()] above the param(...) block in order to make the script an advanced one, in which case passing extra arguments (ones that don't bind to formally declared parameters) is actively prevented (and in which case $args isn't defined).
Caveat:
Note the need for piping $PSBoundParameters to Out-Host in order to force synchronous output of the (default) table-formatted representation of its value. The same would apply to outputting any other values that would result in implicit Format-Table formatting not based on predefined formatting data ($args, as an array whose elements are strings isn't affected). (Note that While Out-Host output normally isn't suitable for data output from inside a PowerShell session, it does write to an outside caller's stdout.)
This need stems from a very unfortunate output-timing bug, present in both Windows PowerShell and the current PowerShell (Core) version, 7.2.1; it is a variant manifestation of the behavior in detail in this answer and reported in GitHub issue #13985 and applies here because exit is called within 300 msecs. of initiating the implicitly table-formatted output; if you were to omit the final exit 0 statement, the problem wouldn't arise.
See also:
The automatic $PSCommandPath variable, reflecting the running script's full (absolute) path.
The automatic $PSBoundParameters variable, a dictionary containing all bound parameters and their values (arguments), and the automatic $args variable, containing all (remaining) positional arguments not bound to formally declared parameters.
Parameter splatting, in which referencing a hashtable (dictionary) or array variable with sigil # instead of $, passes the hashtable's entries / array's elements as individual arguments.
powershell.exe, the Windows PowerShell CLI; pwsh, the PowerShell (Core) 7+ CLI.
Other automatic variables used above, namely $PID (the current process' ID) and $LASTEXITCODE (the most recently executed external program's exit code).
As for what you tried:
$MyInvocation.Line isn't defined when a script is called via PowerShell's CLI; however, the automatic $PSCommandPath variable, reflecting the running script's full file path, is defined.
Even if $MyInvocation.Line were defined, it wouldn't enable robust pass-through of the original arguments, due to potential quoting issues and - when called from inside PowerShell - due to reflecting unexpanded arguments. (Also, the value would start with the executable / script name / path, which would have to be removed.)
While [Environment]::CommandLine does reflect the process command line of the CLI call (also starting with the executable name / path), the quoting issues apply there too.
Also, it is virtually pointless to use Invoke-Command for local invocations - see this answer.

I'd try putting a file called powershell.bat early on your path (at least, in a directory earlier on the path than the C:\Windows\System32\WindowsPowerShell\v1.0\; entry) and assemble the appropriate parameters (I've no idea of your required structure for $myinvocationline - no doubt it could be derived from the parameters delivered to powershell.bat).
My thinking is that this should override powershell, re-assemble the bits and deliver them to pwsh.

I put this line in test.ps1:
$MyInvocation | Format-List -Property *
Found this content in output.txt:
MyCommand : test.ps1
BoundParameters : {}
UnboundArguments : {-stringParam, testing, -switchParam}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 1
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName : D:\Temp\StackOverflow\71087897\test.ps1
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Runspace
DisplayScriptPosition :
Then tried this in test.ps1:
[string]$MySelf = $MyInvocation.InvocationName
Write-Host "###$MySelf###"
[string[]]$Params = $MyInvocation.UnboundArguments
foreach ($Param in $Params) {
Write-Host "Param: '$Param'"
}
And found this in output.txt:
###D:\Temp\StackOverflow\71087897\test.ps1###
Param: '-stringParam'
Param: 'testing'
Param: '-switchParam'
Then tried this in test.ps1:
[string]$Line = "$($MyInvocation.InvocationName) $($MyInvocation.UnboundArguments)"
Write-Host "$Line"
And found this in output.txt:
D:\Temp\StackOverflow\71087897\test.ps1 -stringParam testing -switchParam
Does this get you to where you need to be?

Related

Make Start-Process respect Verbose preference of parent script

Here are my scripts
Parent.ps1
[CmdletBinding(SupportsShouldProcess=$true)]
Param()
Write-Verbose 'Triggering Child Process...'
Start-Process PowerShell.exe '.\Child.ps1'
Child.ps1
[CmdletBinding(SupportsShouldProcess=$true)]
Param()
Write-Verbose 'Child Process Triggered.' # I want output from this line to be displayed
Write-Output 'Child Process Triggered.'
Start-Sleep 10
I'm calling the parent script as below
powershell Parent.ps1 -Verbose
Actual outpt:
VERBOSE: Triggering Child Process...
Child Process Triggered.
Desired Output:
VERBOSE: Triggering Child Process...
VERBOSE: Child Process Triggered.
Child Process Triggered.
If you really want to run .\Child.ps1 via another PowerShell instance, in a new window, asynchronously:
Start-Process PowerShell.exe "-c .\Child.ps1 -Verbose:`$$($VerbosePreference -eq 'Continue')"
Note the use of -c (-Command) to signal what PowerShell CLI parameter the command string is passed to, to distinguish it from -f (-File). While not strictly necessary, because -c is the default in Windows PowerShell (powershell.exe), it helps to clarify, especially given that PowerShell (Core) 7+ (pwsh) now defaults to -f.
When you invoke your Parent.ps1 script with -Verbose (-vb), PowerShell translates this switch to a script-scoped $VerbosePreference variable with value Continue.
To propagate a switch value - on or off - programmatically, you can follow the switch name with : and a Boolean, e.g. -Verbose:$true.
Caveat: While something like -Verbose:$false is typically the same as not passing the switch at all, there are exceptions, and this is one of them: -Verbose:$false explicitly overrides a caller's $VerbosePreference preference variable to deactivate verbose output - see this answer.
That said, this isn't a concern in your case, given that you're launching a new PowerShell instance, and there's no session-internal caller.
The above uses an expandable string to translate the value of $VerbosePreference into the appropriate Boolean; note that the subexpression ($(...)) is prefixed with `$, i.e. an escaped $ character to be retained verbatim, because stringifying a Boolean results in either True or False, so the $ prefix is needed to turn it back into a Boolean the way it needs to be represented as a literal in source code.
Note that if you were to invoke .\Child.ps1 directly from your parent script, it would automatically "inherit" the parent's $VerbosePreference value (it would see the same value by default, due to PowerShell's dynamic scoping).
A note on -c (-Command) vs. -f (-File) and PowerShell (Core) 7+:
For invoking a PowerShell script file (.ps1) via the CLI, it is generally sufficient and preferable for robust passing of verbatim arguments to use the -f (-File) parameter; -c (-Command) is only needed if you need the command string to be evaluated as PowerShell code.
In Windows PowerShell (powershell.exe), the -f parameter doesn't recognize Boolean argument values, unfortunately, which is why -c is used in the solution above. This limitation has been fixed in PowerShell (Core) 7+ (pwsh.exe).
See also:
Guidance on when to use -c (-Command) vs. -f (-File)
An overview of PowerShell's CLI; covers both editions.

What does hyphen/dash parameter mean to PowerShell?

I've found out that, if I pass only a dash to an argument of PowerShell 5.1 script on Windows 10, like this:
powershell.exe -File Test.ps1 -
I get a strange error message saying:
C:\path\Test.ps1 : Cannot process argument because the value of argument "name" is not valid. Change the value o f the "name" argument and run the operation again.
CategoryInfo : InvalidArgument: (:) [Test.ps1], PSArgumentException
FullyQualifiedErrorId : Argument,Test.ps1
The Test.ps1 is only:
echo "foo"
The actual problem I face though is that, when the script declares any mandatory parameter:
param (
[Parameter(Mandatory)]
$value
)
echo "foo"
Then executing the script the same way (with - argument) does nothing at all. No output. No error message. It just hangs for a few seconds. And then a control returns to a command prompt.
C:\path>powershell.exe -File Test.ps1 -
C:\path>_
What does the - mean to PowerShell (5.1)?
On the contrary, with PowerShell 2.0 on Windows 7, I get script usage in this case:
C:\path>powershell.exe -File Test.ps1 -
Test.ps1 [-value] <Object> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
C:\path>_
What makes sense (a missing mandatory parameter).
And without the mandatory parameter declaration, the script works (prints it output):
C:\path>powershell.exe -File Test.ps1 -
foo
C:\path>_
The behavior should be considered a bug - something that starts with - but isn't a valid parameter name should be passed as a positional argument rather than reporting an error.
The bug affects:
Windows PowerShell (as of v5.1.18362.145) - it is unclear if a fix will ever be made.
As you state, whether you (a) get the error message (... the value of argument "name" is not valid ...) or (b) the - is quietly ignored depends on whether your parameter has a parameter attribute such as [Parameter(Mandatory)][1] and/or your param() block has a [CmdletBinding()] attribute (if so, (b) applies).
PowerShell Core 6.x - that is, the problem will be fixed in v7 (current as of this writing: v7.0.0-preview.3); I don't know if 6.2.2, the stable version current as of this writing, will be fixed - we'll see what happens to the bug report on GitHub you've filed.
As for a workaround (works analogously in PowerShell Core):
Use -Command instead of -File.
While that changes the semantics of how the command line is parsed[2], in simple cases such as this one the difference won't matter:
C:\> powershell -Command ./Test.ps1 - # note the "./"
Note the ./, because using -Command (-c) makes PowerShell parse the arguments as if they were PowerShell code, and the usual restrictions re executing scripts by filename only apply (to prevent accidental execution of a file in the current directory, you need a path component to explicitly signal that intent, hence prefix ./ or .\ is needed).
If your script file path needed quoting, you'd have to use quoting and prepend &, the call operator; e.g.:
C:\> powershell -Command "& \"./Test.ps1\" -"
[1] Adding a [Parameter()] attribute to a declared parameter implicitly makes the enclosing script/function an advanced one, in which case different parsing rules apply. The [CmdletBinding[] attribute, which is applied to a param(...) block as a whole, explicitly marks a script / function as an advanced one.
[2] See this answer for the differences between how -File and -Command arguments are parsed.
This isn't an answer but I'm curious as well. If you've already figured it out I'd be interested in what you found. Otherwise, in case it helps, Powershell -h says everything after -file is the script and any arguments passed to it.
-File
Runs the specified script in the local scope ("dot-sourced"), so that the
functions and variables that the script creates are available in the
current session. Enter the script file path and any parameters.
File must be the last parameter in the command, because all characters
typed after the File parameter name are interpreted
as the script file path followed by the script parameters.
Tokenizer reads it in as a CommandArgument.
Powershell >> $Errors = $Null
Powershell >> [System.Management.Automation.PSParser]::Tokenize("powershell -file test.ps1 -", [ref]$Errors)[3]
Content : -
Type : CommandArgument
Start : 26
Length : 1
StartLine : 1
StartColumn : 27
EndLine : 1
EndColumn : 28
So it seems like the issue is further up the chain but I couldn't find a simple way to call the Parser functions to test.
I do see that there's a case that shouldn't occur where the error is swallowed and null returned which might cause it to just stop like it does in your example

Can't pass a script block as a parameter to powershell.exe via -Command

I'm trying this
$Global:commandBlock={
Start-Transcript -path $projectFolder\gruntLog.txt;
grunt $argList;
Stop-Transcript
}
$cmdProc=start-process powershell -ArgumentList ('-command `$Global:commandBlock') -WorkingDirectory $fwd -PassThru -NoNewWindow:$NoNewWindow
And keep getting $commandBlock : The term '$Global:commandBlock' is not recognized as the name of a cmdlet, function, script file, or operable program.
My guess was it has to do with scope. But making variable global didn't help. Adding -args $commandBlock like that:
-ArgumentList ('-command `$Global:commandBlock -args "-commandBlock:$commandBlock"')
-ArgumentList ('-command `$Global:commandBlock -args $commandBlock"')
didn't help
And I'm not sure that I escape variables correctly in the block, read this, but not sure how to apply to my script.
There's a few things which I think are keeping this from working. First, when you're using single quotes, ' you're instructing PowerShell to operate literally. This means that it won't expand variables. Not what you're looking for.
A better way to do this is to do it with an subexpression like this.
$Global:commandBlock={
'ham' >> C:\temp\test.txt
}
$cmdProc=start-process powershell -ArgumentList ("-command $($Global:commandBlock)") -PassThru -NoNewWindow:$NoNewWindow
This will give you the desired results.
Subexpressions are pretty sweet. It lets you embed a mini-scriptblock within a string, and it's then expanded out in the parent string.
"today's date is $(get-date), on system: $($env:COMPUTERNAME)"
today's date is 02/14/2017 11:50:49, on system: BEHEMOTH
There are two major issues (leaving the obvious mistake of attempting to reference a variable inside a single-quoted string aside):
Any argument you want to pass to a new powershell instance via -Command must be escaped in non-obvious ways if it contains " and/or \ chars, which is especially likely if you're passing a piece of PowerShell source code.
The escaping issue can generally be solved by Base64-encoding the source-code string and passing it via the -EncodedCommand parameter - see this answer of mine to a related question for how to do that, but a more concise alternative is presented below.
If the source code being passed references any variables that only exist in the calling session, the new instance won't see them.
The solution is not to reference session-specific variables in the source code being passed, but to pass their values as parameter values instead.
To solve the local-variable-not-seen-by-the-new-instance problem, we must rewrite the script block to accept parameters:
$scriptBlock={
param($projectFolder, $argList)
# For demonstration, simply *output* the parameter values.
"folder: [$projectFolder]; arguments: [$argList]"
}
Now we can apply the necessary escaping, using PetSerAl's sophisticated -replace expression from his comment on the question.
We can then invoke the resulting string with & {...} while passing it parameter values (I'm omitting the -WorkingDirectory and -PassThru parameters for brevity):
# Parameter values to pass.
$projectFolder = 'c:\temp'
$argList='-v -f'
Start-Process -NoNewWindow powershell -ArgumentList '-noprofile', '-command',
(('& {' + $scriptBlock.ToString() + '}') -replace '\"|\\(?=\\*("|$))', '\$&'),
"'$projectFolder'",
"'$argList'"
For an explanation of the regular expression, again see this answer.
Note how the variable values passed as parameters to the script block are enclosed in '...' inside a "..."-enclosed string in order to:
pass the values as a single parameter value.
protect them from another round of interpretation by PowerShell.
Note: If your variable values have embedded ' instances, you'll have to escape them as ''.
The above yields:
folder: [c:\temp]; arguments: [-v -f]
Alternative with a temporary, self-deleting script file:
Using -File with a script file has the advantage of being able to pass parameter values as literals, with no concern over additional interpretation of their contents.
Caveat: As of PowerShell Core v6-beta.3, there is a problem when passing parameter values that start with -: they are not bound as expected; see this GitHub issue.
To work around this problem, the sample script block below accesses only the first parameter by name, and relies on all remaining ones binding via the automatic $Args variable.
# Define the script block to be executed by the new PowerShell instance.
$scriptBlock={
param($projectFolder)
# For demonstration, simply *output* the parameter values.
"folder: [$projectFolder]; arguments: [$Args]"
}
# Parameter values to pass.
$projectFolder = 'c:\temp'
$argList='-v -f'
# Determine the temporary script path.
$tempScript = "$env:TEMP\temp-$PID.ps1"
# Create the script from the script block and append the self-removal command.
# Note that simply referencing the script-block variable inside `"..."`
# expands to the script block's *literal* content (excluding the enclosing {...})
"$scriptBlock; Remove-Item `$PSCommandPath" > $tempScript
# Now invoke the temporary script file, passing the arguments as literals.
Start-Process -NoNewWindow powershell -ArgumentList '-NoProfile', '-File', $tempScript,
$projectFolder,
$argList
Again, the above yields:
folder: [c:\temp]; arguments: [-v -f]
I've messed around with the syntax for passing args to a new powershell instance and have found the following works. So many variations fail without a good error message. Maybe it would work in your case?
$arg = "HAM"
$command = {param($ham) write-host $ham}
#please not its important to wrap your command
#in a further script block to stop it being processed to a string at execution
#The following would normally suffice "& $command $arg"
Start-Process powershell -ArgumentList "-noexit -command & {$command} $arg"
Also simply using the Invoke-Command gives you the -ArgumentList parameter to opperate against the given Command that you are missing with the standard powershell.exe parameters. This is probably a bit cleaner looking.
Start-Process powershell -ArgumentList "-noexit -command invoke-command -scriptblock {$command} -argumentlist $arg"
No need for any extra complex escaping or unwanted persisted variables. Just keep the script block in curly braces so it remains a script block on arrival in the new session. At least in this simple case...
If you have several string parameters that contain spaces. I found popping the string in a single parenthesis and separating with commas works well. You could also probably pass a predefined array as a single argument.
Start-Process powershell -ArgumentList "-noexit -command invoke-command -scriptblock {$command} -argumentlist '$arg1', '$arg2', '$arg3'"
Will this work:
$Global:commandBlock={
Start-Transcript -path $projectFolder\gruntLog.txt;
grunt $argList;
Stop-Transcript
}
& $Global:commandBlock

Max size of ScriptBlock / InitializationScript for Start-Job in PowerShell

When you start a new job with Start-Job, you can pass it a ScriptBlock and a InitializationScript for example:
Function FOO {
Write-Host "HEY"
}
Start-Job -ScriptBlock {FOO} -InitializationScript {
Function Foo { $function:FOO }
} | Wait-Job | Receive-Job
There seems to be a limit to the size of the initialization script you can pass, if it is too big then you get an error such as
[localhost] An error occurred while starting the background process. Error
reported: The filename or extension is too long.
+ CategoryInfo : OpenError: (localhost:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : -2147467259,PSSessionStateBroken
Behind the scenes, PowerShell is creating a new process and passing InitializationScript as a Base64 encoded command line parameter.
According to the Win32 CreateProcess() function, the max size of the command ine is 32,768 characters. So obviously if your Base64 encoded InitializationScript is getting near this size then you will probably get an error.
I haven't yet found a limit for the size of the ScriptBlock parameter. Can someone confirm that there is no limit?
I assume that there is no limit because it looks like the ScriptBlock is transmitted to the child process via standard input?
Your guess was correct.
PowerShell translates a Start-Job call into a PowerShell CLI call behind the scenes (to powershell.exe for Windows PowerShell, and to pwsh for PowerShell (Core) 7+), that is, it achieves parallelism via a child process that the calling PowerShell session communicates with, using the standard in- and output streams:
Because the -InitializationScript script block is translated to a Base64-encoded string representing the bytes of the UTF-16LE encoding of the block's string representation, which is passed to the CLI's -EncodedCommand parameter, its max. length is limited by the overall length limit of a process command line.
That limit is 32,766 characters (Unicode characters, not just bytes), because a terminating NUL character is required in the underlying WinAPI call (to quote from the CreateProcess() WinAPI function documentation you link to: "The maximum length of this string is 32,767 characters, including the Unicode terminating null character").
Note that the full path of the PowerShell executable is included in this limit, in double-quoted form (see below), and it really is the entire resulting command line that matters; therefore, given that PowerShell (Core) 7+ can be installed in any directory, its installation location has an effect on the effective limit, as does the length of the path of the current directory (see next point).
In Windows PowerShell, whose location is fixed, and whose CLI parameter values are of fixed length in the invocation (see below), this leaves 32,655 characters for the Base64-encoded string (32766 - 111 characters for the executable path and fixed parameters and the -EncodedCommand parameter name); while similar, no fixed number can be given for PowerShell (Core) 7+, due to differing install locations and the length of the -wd (working-directory) argument depending on the current location.
Base64-encoding the bytes of a UTF-16LE-encoded strings results in a ca. 2.67-fold increase in length, which*makes the **max. length of a script block passed to -InitializationScript 12,244 characters[1] for Windows PowerShell; for PowerShell (Core) 7+, it'll be slightly lower, depending on the installation location and the length of the current directory's path.
By contrast, the -ScriptBlock argument, i.e. the operation to perform in the background, is sent via stdin (the standard input stream) to the newly launched PowerShell process, and therefore has no length limit.
For instance, the following Start-Job call:
Start-Job -ScriptBlock { [Environment]::CommandLine } -InitializationScript { 'hi' > $null } |
Receive-Job -Wait -AutoRemoveJob
reveals that the background-job child process was launched as follows, when run from Windows PowerShell:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Version 5.1 -s -NoLogo -NoProfile -EncodedCommand IAAnAGgAaQAnACAAPgAgACQAbgB1AGwAbAAgAA==
As you can see, the -ScriptBlock argument's text is not present in the resulting command line (it was sent via stdin), whereas the -InitializationScript argument's is, as the Base64-encoded string passed to -EncodedCommand, which you can verify as follows:
# -> " 'hi' > $null ", i.e. the -InitializationScript argument, sans { and }
[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('IAAnAGgAaQAnACAAPgAgACQAbgB1AGwAbAAgAA=='))`)
As for the other parameters:
-s is short for -servermode, and it is an undocumented parameter whose sole purpose is to facilitate background jobs (communication with the calling process via its standard streams); see this answer for more information.
-Version 5.1 applies only to Windows PowerShell, and isn't strictly necessary.
-NoLogo is also not strictly necessary, because it is implied by the use of -EncodedCommand (as it would be with -Command and -File).
In PowerShell (Core) 7+, you'd also see a -wd (short for: -WorkingDirectory) parameter, because background jobs there now sensibly use the same working directory as the caller.
[1]
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('x' * 12244)).Length yields 32652, which is the closest you can come to the 32655 limit; an input length of 12245 yields 32656.
the script block actually have limit. and you can run command with scriptblock with 3 way :
$scriptblock = '
get-help
get-command
dir
get-help *command*
get-command *help*
'
iex $scriptblock
or use it :
$scriptblock = {
get-help
get-command
dir
get-help *command*
get-command *help*
}
Start-Process powershell.exe -ArgumentList "Command $scriptblock"
or use this :
Start-Process powershell {iex '
get-help
get-command
dir
get-help *command*
get-command *help*
'
}
the limit of script that can pass to powershell.exe is 12190 bytes.
but for script i never get limit from powershell more than 4000 line code.

Pass parameter from a batch file to a PowerShell script

In my batch file, I call the PowerShell script like this:
powershell.exe "& "G:\Karan\PowerShell_Scripts\START_DEV.ps1"
Now, I want to pass a string parameter to START_DEV.ps1. Let's say the parameter is w=Dev.
How can I do this?
Let's say you would like to pass the string Dev as a parameter, from your batch file:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 Dev"
put inside your powershell script head:
$w = $args[0] # $w would be set to "Dev"
This if you want to use the built-in variable $args. Otherwise:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 -Environment \"Dev\""
and inside your powershell script head:
param([string]$Environment)
This if you want a named parameter.
You might also be interested in returning the error level:
powershell -command "G:\Karan\PowerShell_Scripts\START_DEV.ps1 Dev; exit $LASTEXITCODE"
The error level will be available inside the batch file as %errorlevel%.
Assuming your script is something like the below snippet and named testargs.ps1
param ([string]$w)
Write-Output $w
You can call this at the commandline as:
PowerShell.Exe -File C:\scripts\testargs.ps1 "Test String"
This will print "Test String" (w/o quotes) at the console. "Test String" becomes the value of $w in the script.
When a script is loaded, any parameters that are passed are automatically loaded into a special variables $args. You can reference that in your script without first declaring it.
As an example, create a file called test.ps1 and simply have the variable $args on a line by itself. Invoking the script like this, generates the following output:
PowerShell.exe -File test.ps1 a b c "Easy as one, two, three"
a
b
c
Easy as one, two, three
As a general recommendation, when invoking a script by calling PowerShell directly I would suggest using the -File option rather than implicitly invoking it with the & - it can make the command line a bit cleaner, particularly if you need to deal with nested quotes.
Add the parameter declaration at the top of ps1 file
test.ps1
param(
# Our preferred encoding
[parameter(Mandatory=$false)]
[ValidateSet("UTF8","Unicode","UTF7","ASCII","UTF32","BigEndianUnicode")]
[string]$Encoding = "UTF8"
)
write ("Encoding : {0}" -f $Encoding)
Result
C:\temp> .\test.ps1 -Encoding ASCII
Encoding : ASCII
The answer from #Emiliano is excellent. You can also pass named parameters like so:
powershell.exe -Command 'G:\Karan\PowerShell_Scripts\START_DEV.ps1' -NamedParam1 "SomeDataA" -NamedParam2 "SomeData2"
Note the parameters are outside the command call, and you'll use:
[parameter(Mandatory=$false)]
[string]$NamedParam1,
[parameter(Mandatory=$false)]
[string]$NamedParam2