1

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)

whytheq
  • 34,466
  • 65
  • 172
  • 267
  • What is the output of the commands `uname -a` and `$PSVersionTable.PSVersion.ToString()`? – lit Jul 01 '23 at 11:42

1 Answers1

2

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")
mklement0
  • 382,024
  • 64
  • 607
  • 775