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.
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.
CodePudding user response:
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
.
CodePudding user response:
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?
CodePudding user response:
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
# 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 bound arguments through.
pwsh -ExecutionPolicy Bypass -File $PSCommandPath @PSBoundParameters
exit $LASTEXITCODE
}
# Getting here means that the file is being executed by pwsh.exe
# Print the bound parameters.
$PSBoundParameters
exit 0
The above assumes that only arguments for formally declared parameters are passed; if you do want to support extra arguments, append @args
to the pwsh -ExecutionPolicy Bypass -File $PSCommandPath @PSBoundParameters
line above.
Alternatively, as suggested in the code comments, place [CmdletBinding()]
above the param(...)
block, which makes the script an advanced one, in which case passing extra arguments is actively prevented (and in which case $args
isn't defined).
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.
- Even if
Also, it is virtually pointless to use
Invoke-Command
for local invocations - see this answer.