I am using starship
to configure my prompt in Powershell
.
The starship
documentation supplies this code that is now added to Profile, so the propt is available on startup of PS
:
Invoke-Expression (&starship init powershell)
Now I am playing with module PSScriptAnalyzer
and it is Warning me not to use Invoke-Expression
.
On further reading I see that Invoke-Expression is a potential security risk.
Is it a security risk in the context of how I'm using it? If so then is there a specific alternative I can use in my Profile?
I've tried using & starship init powershell
in my Profile without success, as this is written to the host on start-up:
Invoke-Expression (& '/usr/local/bin/starship' init powershell --print-full-init | Out-String)
Is it a security risk in the context of how I'm using it?
Whether it is a security risk ultimately doesn't depend on whether or not you use Invoke-Expression
, but only on whether you trust the code being executed - and it's fair to assume that you do trust the code that comes with Starship.
While Invoke-Expression
(iex
) is generally to be avoided, the case at hand is a legitimate use:
& starship init powershell
outputs PowerShell source code, which - in order to take effect for prompt customization - must be dot-sourced, i.e. executed directly in the caller's scope (rather than in a child scope).
Passing code to Invoke-Expression
implicitly dot-sources it, and is the simplest way to do so for code that isn't already saved in a file (*.ps1
); in the latter case, you would use .
, the dot-sourcing operator.
The equivalent - but more verbose - approach that avoids Invoke-Expression
is to combine [scriptblock]::Create()
with the .
operator (which, like &
, the call operator, can also act on script blocks):
# Parse the PowerShell source code output by `starship init powershell`
# into a script block...
$scriptBlock = [scriptblock]::Create((& starship init powershell))
# ... and execute it dot-sourced
. $scriptBlock
As an aside, re why Invoke-Expression
is also used in the code output by starship init powershell
, as shown in your question, effectively resulting in a nested Invoke-Expression
invocation:
This indirection is used in order to produce single-line output from starship init
, given that passing multiple strings to Invoke-Expression
as an argument would break the call; e.g.:
# !! FAILS, because only a *single* string (though possibly multiline)
# !! may be passed.
Invoke-Expression ('1+2', '3+4')
# OK, via the pipeline: -> 3, 7
'1+2', '3+4' | Invoke-Expression
# OK, as a *single* *multiline* string
Invoke-Expression "1+2`n3+4"
The reason that multiline output from an external program turns into multiple strings is that PowerShell relays the lines in a program's output as a stream of separate strings, which become a string array when captured or passed as an argument; e.g.:
# Passes each output line (with trailing newline remove)
# separately through the pipeline:
# -> '[one]', '[two]'
sh -c 'echo one; echo two' | ForEach-Object { "[$_]" }
On Windows, use cmd /c 'echo one& echo two'
as input.
As shown above, passing multiple strings via the pipeline to Invoke-Expression
does work, so that the initialization code could be simplified as follows:
starship init powershell --print-full-init | Invoke-Expression
Even when using an argument the initialization could be simplified, by directly incorporating the Out-String
call:
Invoke-Expression (starship init powershell --print-full-init | Out-String)
If maximizing performance is the goal (though I doubt it will be noticeable in practice), use the -join
operator to join the output lines with newlines ("`n"
):
Invoke-Expression ((starship init powershell --print-full-init) -join "`n")