Instructions for many tools from the Linux world suggest setting an environment variable for a single invocation of a command like:
JEKYLL_ENV=production jekyll build
The equivalent in PowerShell is cumbersome:
try {
$oldValue = $env:JEKYLL_ENV
$env:JEKYLL_ENV = "production"
jekyll build
} finally {
$env:JEKYLL_ENV = $oldValue
}
I believe a similar syntax could work in PowerShell:
$env:JEKYLL_ENV="production" jekyll build
This would be a great usability enhancement. A lot of our tests rely on the current cumbersome method because of the lack of this feature.
It's easier to implement this as an argument to Start-Process
like
Start-Process main.exe -Environment @{ FOO = 'bar' }
The source code is in
This syntax also reminds user that starting a new process in Windows is more expensive (it took me a second to start a new PowerShell).
The idea is provided by #4671.
Start-Process : A parameter cannot be found that matches parameter name 'Environment'.
Just yesterday I came up with a proof of concept by abusing the CommandNotFoundHandler.
@johnjelinek: Adding an -Environment
parameter to Start-Process
is just a _proposal_ at this point.
@FranklinYu:
Even though Start-Process
too should support passing arbitrary environment variables, as proposed in #4761, it is not a substitute for @lzybkr's proposal:
Aside from being more verbose, Start-Process
-launched processes aren't connected to PowerShell's output streams, even when you use -Wait
and -NoNewWindow
, so you cannot directly capture or redirect / suppress output.
@lzybkr:
Love the idea; just to spell it out: it should work for _multiple_ env. variables too, as in POSIX-like shells:
# In a POSIX-like shell such as Bash
$ foo=1 bar=2 sh -c 'echo $foo $bar' # -> '1 2'
Presumed PS equivalent:
PS> $env:foo=1 $env:bar=2 sh -c 'echo $foo $bar' # -> '1 2'
Given that this a feature for _child processes_ (external programs), in the context of which only _environment_ variables can be meaningfully defined, perhaps even omitting the env:
part is an option - though perhaps that is conceptually too confusing:
# Implied `env:`?
PS> $foo=1 $bar=2 sh -c 'echo $foo $bar' # -> '1 2'
Given that these variables are by definition _command-scoped_ - only the target process sees them - I think that should be fine.
Hmm. That would be nice, but I feel like the syntax is a bit obtuse. I'd suggest perhaps something like this?
with @{PATH = $NewPath; JEKYLL_ENV = $jekyll} & $command $params
That would allow use of multiline syntax quite naturally for less lengthy lines and better readability:
with @{
PATH = $NewPath
JEKYLL_ENV = $jekyll
} & $command $params
It also has a direct tie-in to the existing &
operator for a bit better indication of what's actually happening.
EDIT: maybe withenv
as the keyword?
The hashtable syntax isn't horrible - that's what I ended up with here - invoked like:
Invoke-WithEnvironment @{ JEKYLL_ENV="production" } { jekyll build }
Would it make sense to simply add a -Environment @{}
parameter to Invoke-Command
?
Adding -Environment
to both Start-Process and Invoke-Command are both good options, I think. 馃檪
I suggest to discuss the parameters in #4671.
_Also_ making (see below), alongside Invoke-Command
support -Environment @{}
Start-Process
, as proposed in #4671, sounds like a good idea.
However, at least to me, this proposal is about a syntax that is:
(a) low on ceremony (terse)
(b) doesn't require a cmdlet call, i.e. is a _language feature_
Invoke-Command -Environment @{ ... } { ... }
fits neither bill.
@vexx32's proposal definitely meets (b); with respect to (a): while definitely more PowerShell-like, it still feels a bit verbose, especially for the - presumably common - case of defining a _single_ env. var.
If, by contrast, we go all in, we could _simply mimic POSIX syntax as-is_:
PS> foo=1 bar=2 sh -c 'echo $foo $bar' # -> '1 2'
While a tad obscure, you could argue that we're entering a different world here anyway - that of _external programs_ - where different rules already apply (such as --%
, how arrays are passed, ...).
The obscure syntax creates a discovery problem. Interactive, you can use a more terse syntax:
icm -env @{ foo=1; bar=2 } sh -c 'echo $foo $bar'
Given there is also an issue with PS effectively ignoring a native app's exit code, maybe this could be combined into a command like Start-NativeExecution
where one of the parameters would be -Environment
and, like in the PS build module, it throws on a non-zero exit code.
When invoking native apps you often want this "throw on non-zero exit code" functionality. I can't tell you how many times I've helped folks figure out why their build was passing when the log shows obvious errors.
Eh, I mean... that's kinda just duplicating what Start-Process could do with an extra parameter or two. I'm not sure how much sense it makes to have _two_ commands in that space at the moment? 馃
Start-Process
doesn't help with the test for $LASTEXITCODE and often in these CI/build/test scenarios you want a failed native app invocation to fail the build/test.
You could easily enhance Start-Process to accept a parameter (switch? int[]?) to throw on any non-zero or a predefined list of "bad" exit codes, though; that doesn't really mandate an entirely new command, I would think.
Note that &&
and ||
were specifically introduced for these scenarios, enabling you to write, for instance:
foo -bar || exit 1
# or
foo -bar || throw
Unfortunately, that currently doesn't actually work, because you're required to wrap the exit
/ throw
/ return
in $(...)
, which is both unexpected and cumbersome.
foo -bar || $(throw)
The ship hasn't sailed yet, fortunately, so I invite you to join the conversation at #10967.
_Update_: Oops! Missed the fact that this was already reviewed and decided against - see ; still, I'm hopeful that this will be reconsidered.
@SteveL-MSFT:
Interactive, you can use a more terse syntax:
I think it would be beneficial to provide something that is _robustly_ terse, and doesn't rely on aliases (or costly cmdlet calls, for that matter).
The obscure syntax creates a discovery problem
That could be remedied with a dedicated conceptual help topic covering invocation of native apps, which is definitely urgently needed even for the current capabilities - see https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152
I don't think the addition to Invoke-Command (or Start-Process) is mutually exclusive to something terse. If we wanted to explore that route, my preference is to use Bash syntax exactly so user would be able to cut and paste most examples (barring other issues we have with arg parsing/passing...) and have them just work in PowerShell.
my preference is to use Bash syntax exactly so user would be able to cut and paste most examples
I agree that this would be best; to recap what that looks like:
# Define command-scoped (child-process-only) $env:foo and $env:bar env. variables
foo=1 bar=2 sh -c 'echo $foo $bar' # -> '1 2'
I don't think the addition to Invoke-Command (or Start-Process) is mutually exclusive to something terse
Agreed, and #4671 already proposes that for Start-Process
(introducing an -Environment
parameter accepting a hashtable).
With Invoke-Command
we have to be careful, though, because there is no guarantee that a _child process_ is involved in whose environment the environment-variable definitions should - ephemerally - take effect.
I think it makes sense to always restrict the specified environment variables to _child processes_, which means that the _current_ process' environment is _not_ modified - that's how it (sensibly) works in POSIX-like shells.
Therefore, in the following invocation the Powershell code inside the -ScriptBlock
parameter would _not_ see $env:foo
, which invites confusion.
# $env:foo is NOT defined in the script block.
Invoke-Command -Environment @{ foo = 'bar' } -ScriptBlock { "`$env:foo is: $env:foo" }
In short: I think it's sufficient to offer the Bash-like syntax specifically for _external programs_ and otherwise enhance Start-Process
only.
A real life example where they use this concept in bash:
https://www.twilio.com/blog/2017/08/working-with-environment-variables-in-node-js.html
PORT=9999 node server.js
Twilio BlogEnvironment variables are a great way to configure parts of your Node.js application. Learn how to work with them and some tools that make your life easier.
First step was done in #10830.
Now we need to add new parameter - -Environment
.
Then we could resolve the issue.
But we have over 90 opened PRs so I stopped the work.
Most helpful comment
I agree that this would be best; to recap what that looks like:
Agreed, and #4671 already proposes that for
Start-Process
(introducing an-Environment
parameter accepting a hashtable).With
Invoke-Command
we have to be careful, though, because there is no guarantee that a _child process_ is involved in whose environment the environment-variable definitions should - ephemerally - take effect.I think it makes sense to always restrict the specified environment variables to _child processes_, which means that the _current_ process' environment is _not_ modified - that's how it (sensibly) works in POSIX-like shells.
Therefore, in the following invocation the Powershell code inside the
-ScriptBlock
parameter would _not_ see$env:foo
, which invites confusion.In short: I think it's sufficient to offer the Bash-like syntax specifically for _external programs_ and otherwise enhance
Start-Process
only.