8000 Provide simple one-time setting of environment variable · Issue #3316 · PowerShell/PowerShell · GitHub
[go: up one dir, main page]

Skip to content

Provide simple one-time setting of environment variable #3316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
lzybkr opened this issue Mar 13, 2017 · 53 comments
Open

Provide simple one-time setting of environment variable #3316

lzybkr opened this issue Mar 13, 2017 · 53 comments
Labels
Issue-Enhancement the issue is more of a feature request than a bug KeepOpen The bot will ignore these and not auto-close WG-Language parser, language semantics WG-NeedsReview Needs a review by the labeled Working Group

Comments

@lzybkr
Copy link
Contributor
lzybkr commented Mar 13, 2017

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
@lzybkr lzybkr added WG-Language parser, language semantics Issue-Enhancement the issue is more of a feature request than a bug labels Mar 13, 2017
@andyleejordan
Copy link
Member

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.

@FranklinYu
Copy link
FranklinYu commented Nov 29, 2018

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

/// <summary>
/// This class implements the Start-process command.
/// </summary>
[Cmdlet(VerbsLifecycle.Start, "Process", DefaultParameterSetName = "Default", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=135261")]
[OutputType(typeof(Process))]
public sealed class StartProcessCommand : PSCmdlet, IDisposable

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.

@johnjelinek
Copy link

Start-Process : A parameter cannot be found that matches parameter name 'Environment'.

@lzybkr
Copy link
Contributor Author
lzybkr commented Oct 3, 2019

Just yesterday I came up with a proof of concept by abusing the CommandNotFoundHandler.

@mklement0
Copy link
Contributor
mklement0 commented Oct 3, 2019

@johnjelinek: Adding an -Environment parameter to Start-Process is just a proposal at this point [update: implemented in v7.4+].

@FranklinYu:

Even though Start-Process too should support passing arbitrary environment variables, as proposed in #4671, 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.

@vexx32
Copy link
Collaborator
vexx32 commented Oct 3, 2019

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?

@lzybkr
Copy link
Contributor Author
lzybkr commented Oct 3, 2019

The hashtable syntax isn't horrible - that's what I ended up with here - invoked like:

Invoke-WithEnvironment @{ JEKYLL_ENV="production" } { jekyll build }

@SteveL-MSFT
Copy link
Member

Would it make sense to simply add a -Environment @{} parameter to Invoke-Command?

@vexx32
Copy link
Collaborator
vexx32 commented Oct 3, 2019

Adding -Environment to both Start-Process and Invoke-Command are both good options, I think. 🙂

@SteveL-MSFT SteveL-MSFT added this to the vNext-Consider milestone Oct 3, 2019
@SteveL-MSFT SteveL-MSFT added the Hacktoberfest Potential candidate to participate in Hacktoberfest label Oct 3, 2019
@iSazonov
Copy link
Collaborator
iSazonov commented Oct 4, 2019

I suggest to discuss the parameters in #4671.

@mklement0
Copy link
Contributor
mklement0 commented Oct 4, 2019

Also making Invoke-Command support -Environment @{} (see below), alongside 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, ...).

@SteveL-MSFT
Copy link
Member

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'

@rkeithhill
Copy link
Collaborator
rkeithhill commented Nov 23, 2019

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.

@vexx32
Copy link
Collaborator
vexx32 commented Nov 23, 2019

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? 🤔

@rkeithhill
Copy link
Collaborator

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.

@vexx32
Copy link
Collaborator
vexx32 commented Nov 23, 2019

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.

@mklement0
Copy link
Contributor
mklement0 commented Nov 24, 2019

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; still, I'm hopeful that this will be reconsidered.

@mklement0
Copy link
Contributor
mklement0 commented Nov 24, 2019

@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 MicrosoftDocs/PowerShell-Docs#5152

@SteveL-MSFT
Copy link
Member

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.

@mklement0
Copy link
Contributor

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.

@TylerLeonhardt
Copy link
Member
TylerLeonhardt commented Jun 14, 2020

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 Blog
Environment 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.

@iSazonov
Copy link
Collaborator

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.

@microsoft-github-policy-service microsoft-github-policy-service bot added Resolution-No Activity Issue has had no activity for 6 months or more labels Nov 16, 2023
@Jackenmen
Copy link

This is a feature request, not a bug report, I don't think it can really become stale.

@rkeithhill
Copy link
Collaborator

Not just a feature request, but a reasonably high "upvoted" feature request.

@dkaszews
Copy link
Contributor

Good fucking job, bot

@rkeithhill
Copy link
Collaborator

I keep running across documentation (from Microsoft BTW), that assumes this syntax will just work.

image

@SeeminglyScience SeeminglyScience added the KeepOpen The bot will ignore these and not auto-close label Dec 5, 2023
@foxx1337
Copy link

Rather than introduce a syntax addition, I wonder if for most usage a smaller change would be sufficient:

start-process ls -environment @{LS_COLORS='..."}

I think it would be more useful for Invoke-Expression. So probably for all the cmdlets with the Expression, Command or Process nouns, if not others too.

@mklement0
Copy link
Contributor
mklement0 commented Jan 22, 2024

@foxx1337:

  • Invoke-Expression should generally be avoided. Aside from that, it merely accepts in string form what you can execute directly, so this wouldn't help anyway.

  • Start-Process by now does have an -Environment parameter, which - while a welcome addition - is not a solution to the problem at hand, as previously explained.

  • Conceivably, Invoke-Command could be enhanced to support an -Environment parameter too, in a manner that does provide stream integration, given that it is invariably synchronous. However:

    • Unlike Start-Process, Invoke-Command's focus isn't on calling external programs, i.e. not on creating child processes.

    • Something like
      Invoke-Command -Environment @{ LS_COLORS='...' } { foo ... }
      is still a lot of ceremony compared to
      LS_COLORS='...' foo ...

@dkaszews
Copy link
Contributor

Personally I don't mind a bit of ceremony. I'll take readability over obscure terseness any day of the week.

@jibbers42
Copy link
jibbers42 commented Jan 23, 2024

I'd prefer something quick to bang out on the command line and easy to remember without much fanfare. If I only had intentions of using the feature in scripts then I wouldn't much care how verbose it was.

@totkeks
Copy link
totkeks commented Apr 8, 2024

Was thinking about converting some bash scripts to PowerShell to improve their readability, but running a typical make command without that requires a lot of boilerplate.

What is the status and/or consensus on this one - will it come to PowerShell?

@SteveL-MSFT SteveL-MSFT added the WG-NeedsReview Needs a review by the labeled Working Group label Apr 29, 2024
@github-project-automation github-project-automation bot moved this to In progress in Shell Aug 28, 2024
@mpawelski
Copy link

If there are no technical limitations or syntax conflicts I would really vote for making bash-like syntax just work in Powershell.

In my experience most of the shell examples in any documentation are in (ba)sh, sometimes even on Microsoft docs. Luckily 80% of the time these examples works in Powershell just fine, 19% of other examples doesn't work because:

  • command is multi-line and has \ but pwsh requires `
  • command has one-time env variable (this issue) and pwsh not.
  • other small syntax differences, like command has arguments that contains @ { } characters
  • other bash-specific features.

I would be great to decrease these chances of having to manually edit a command that you've copied, just because you use less popular shell. It would be a huge usability win for new and long-time users of Powershell.

@MatejKafka
Copy link
Contributor
MatejKafka commented Sep 19, 2024

@mpawelski

If there are no technical limitations

Afaict, one of the blocking issues is that environment variables are global for the whole process, and PowerShell now supports thread jobs, meaning that there may be multiple commands running at once inside a single process – if you set an environment variable in multiple thread jobs, it will cause conflict, and re-architecting PowerShell to have separate env vars for each job would be quite hard.

However, I agree that even with this caveat, it would be worth it to add some form of support for this, since most users do not use thread jobs, and for native commands, the implementation may avoid the issue alltogether. Above, I proposed to use a fish-style env command to implement this without ambiguity (since x=y is a valid function name in PowerShell)):

env TEST=1 cmd /c 'echo TEST=%TEST%'

This syntax is not compatible with Bash, but you only need to prefix the expression with env to make it work and PowerShell could have an error message to suggest the env command if you try to execute something that looks like Bash-style env var assignment.

Proof of concept: https://github.com/MatejKafka/powershell-profile/blob/master/CustomModules/ScopedEnvironment/ScopedEnvironment.psm1

@mklement0
Copy link
Contributor
mklement0 commented Sep 19, 2024
  • Environment variables are primarily used for inter-process communication, so that limiting the proposed POSIX-shell-like syntax to calls to external (native) programs (child processes) makes sense to me.

    • In other words: an attempt to do something like FOO=1 Get-ChildItem should result in an error.
    • This also avoids the threads issue (assuming that the variables are truly only set in the environment of the child process, which is the goal).
  • On Unix-like platforms, the POSIX-mandated /usr/bin/env utility already works the way your custom function does, e.g.:

     env TEST=1 sh -c 'echo $TEST'
    
  • As for x=y being ambiguous, given that it is a valid function name in PowerShell:

    • I think a defensible way to resolve this is to tell users: use & to disambiguate in the unlikely event hat x=y is truly the name of a command.

In summary:

  • I think that implementing the POSIX-shell syntax in PowerShell too - for external programs only - is the way to go, and, within the constraints discussed above, I see no reasons that would make that infeasible.

  • The suboptimal alternative is to provide a built-in env command on Windows (to complement /usr/bin/env on Unix), as a cmdlet with env as its alias.

    • This cmdlet [see below] should probably also implement the -i option of /usr/bin/env.
    • Also, it should ideally only modify (pre-set) the callee's environment, not also the caller's (with the need for subsequent restoration, which is what your proof-of-concept does of technical necessity).

@dkaszews
Copy link
Contributor

I think going with Unix syntax x=y foo is a bad idea:

  1. Inconsistency with all variables set with $x = y
  2. Inconsistency with environment variables set with $env:x = y
  3. Inconsistency with no spaces required around =, which is a common pitfall with Unix beginners writing x = y and getting "x is not a command", plus the spaces may be added by existing formatters
  4. No possibility to set non-literals, as x=$y foo would be interpreted as a string "x=$y" foo and not a command, while & x=$y foo would invoke command "x=$y" instead of foo

I would recommend instead implementing a env command for Windows, or, even better, do it the PowerShell way with Invoke-Command -Environment @{ x = y } 'foo' which solves all of the problems above.

@mklement0
Copy link
Contributor
mklement0 commented Sep 20, 2024

Number 4. is a non-issue, given that, e.g. Write-Output FOO=$HOME already works (argument-parsing mode); also, given that new syntax would be introduced, we'd have control over how to implement this.

Numbers 1. - 3. are fair points, but:

There are two major drivers for this feature:

  • Convenience and concision

  • Compatibility with POSIX shells, given the abundance of installation and sample command lines for the latter out there, as @mpawelski has pointed out.

Ensuring the latter is what makes the inconsistencies you mention unavoidable, just like we've already introduced other inconsistencies for the sake of compatibility with POSIX shells (automatic globbing (wildcard expansion) and tilde (~) expansion when calling external programs).


As for the env solution:

It occurred to me that implementing env (on Windows) as a PowerShell command is not an option, because env will have to behave like any external (native) program with respect to its command-line parsing (not treating , as an array constructor, expanding ~, assuming it becomes a stable feature, passing -- through, ...)

Thus, there are only two options:

  • Ship an actual env.exe binary on Windows.

  • Bend the rules for an env command, which could then even be implemented as a keyword.

Neither option is great:

  • Shipping env.exe has the advantage of not needing any changes in PowerShell itself, but it would be a first in terms of shipping an additional executable, and that executable would have to be discoverable via $env:PATH; this is currently not a problem, but only due to the questionable practice of PowerShell prepending its $PSHOME value to $env:PATH on startup - which was done for historical reasons that no longer apply (see Pwsh corrupts PATH environment variable, breaking nested invocations on everything that isn't x86-64 #24090 (comment)).

  • Implementing env as a keyword would be awkward and (given that we wouldn't want platform-conditional keywords) preempt /usr/bin/env on Unix-like platforms. If implemented as a function / cmdlet (on Windows only), treating it like an external program in terms of invocation syntax is awkward as well.

@alexchandel
Copy link

7+ years and this is still impossible. pwsh is not a shell replacement without this basic feature. Why not even an Environment flag?

@mpawelski
Copy link

Just for reference, two other non-posix shell that use key=value command bash-inspired syntax:

Having such syntax in Powershell, even just for native commands, would be great

@dkaszews
Copy link
Contributor

I have been looking into implementing vim-test/vim-test#832 and it would actually be nice to have some shell-agnostic syntax. For now it's set var='value' in cmd, var='value' in POSIX shells and $env:var = 'value' in pwsh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Issue-Enhancement the issue is more of a feature request than a bug KeepOpen The bot will ignore these and not auto-close WG-Language parser, language semantics WG-NeedsReview Needs a review by the labeled Working Group
Projects
Status: In progress
Development

No branches or pull requests

0