Powershell: Should we detect/Deny using $_ as a user defined variable?

Created on 4 May 2017  路  60Comments  路  Source: PowerShell/PowerShell

This works but it seems like terrible practice - should we detect/deny using $_ as a user defined variable?
(At PSCONF.EU and a customer's colleague is doing this in his code)

foreach ($_ in gps | where {$_.name -like "*SS"})
{
$_.name
}

This also works:

$_ = "test"
write-host -ForegroundColor red "Thiis is a $_ "

Breaking-Change Committee-Reviewed Issue-Discussion Resolution-Won't Fix WG-Language

Most helpful comment

You'll likely see this in other functions and modules as well. @proxb's very popular PoshRsJob also looks like it uses $_.

I can understand wanting to dissuade this use when it doesn't make sense, but if you remove the ability to use it, you'll make some popular tools far less usable. Using $_ in a scriptblock as the current item is common and expected behavior, it would be sad to lose this.

Thanks for the heads up on this @nightroman - no idea if we can still change their minds, but this would have been completely off the radar for me (and I suspect others).

Cheers!

All 60 comments

Happen to know why they were doing it? Does not seem to serve any purpose other than potentially creating a hell of a confusion.

It should be read-only. Otherwise it could cause confusion and is there any benefit to being able to use it? I wouldn't think so.

Since it's doable, but bad practice, maybe a PSScriptAnalyzer rule instead? What about the risk of breaking code that's already out in the wild?

My understanding was that it was a beginner so it was just bad practice.

Chances are that if they are setting $_ or $PSITem, they either already have every brittle code or it might already be broken code.

3061 is a related issue for _all_ automatic variables.

There are 28 in total that _shouldn't_ be writable, but currently are.

As for $_ / $PSItem specifically (I agree that it shouldn't be assignable - or perhaps just in this _one_ scenario):

The need to specify a variable explicitly in the foreach _statement_ allows for a more descriptive name than the implicit $_ can provide in the ForEach-Object case, but I can see how someone would choose $_ - either by analogy with or due to confusion with ForEach-Object.

It is the built-in foreach _alias_ that has sown perennial confusion between the ForEach-Object _cmdlet_ and the foreach _language statement_.

It may be okay if the failure happens during parsing stage. As long as a previously working script doesn't fail half way through. And I suppose scripts breaking should be a risk when upgrading PS. I agree that these should be read-only, and if you listened to people like me nothing would ever move forward! 馃槢
I also think that if they are going to be read-only then in this case they should be read-only regardless of strict mode. Thoughts?

We discussed aliases previously in #2000 and PowerShell PG agreed that aliases useful only in interactive mode and we need to write RFC originally relying on using (yes! - it is in parsing time). Welcome to https://github.com/PowerShell/PowerShell-RFC/!

I've just submitted #3830, which, if implemented, would be a way of:

  • mitigating the foreach / ForEach-Object confusion and allowing their peaceful coexistence,
  • while still allowing the advisable prevention of _explicit_ assignments to automatic variables such as $_ .

@AdilHindistan

Happen to know why they were doing it? Does not seem to serve any purpose other than potentially creating a hell of a confusion.

I often convert ForEeach-Object to foreach loop both ways. It is convenient to use the same variable $_ and keep the processing blocks not changed. To me, it is not confusion but rather clarity, when I see $_ in my code I know it is the current loop/pipeline variable, no matter what surrounds it.

@nightroman

I do understand the appeal of the consistent use of $_, but, I think it is generally problematic to allow assigning to automatic variables (although in this particular case, making the variable read-only would probably be too much of a breaking change).

The solution, I think, is to let PowerShell define $_ _implicitly_, as it does for ForEach-Object, if no loop variable is specified, as I've proposed in #3830.

Applied to the command in the initial post:

Instead of:

foreach ($_ in gps | where {$.name -like "*SS"}) { $_.name }

we could write:

# Not defining a loop variable ($var in ...) could implicitly define $_
foreach (gps | where {$.name -like "*SS"}) { $_.name }

@PowerShell/powershell-committee discussed this and agree that the parser will enforce not allowing writing to $_/$psitem

Set-Variable can write to the variable.

@iSazonov I think the intent here is to address common usage where the user isn't aware that the variable they are using could be clobbered by PowerShell. Set-Variable doesn't seem like it would fall under this, but I'm not opposed to special casing it in that cmdlet.

@PowerShell/powershell-committee discussed this and agree that the parser will enforce not allowing writing to $_/$psitem

Built-in cmdlets like Select-Object, Sort-Object, Group-Object,
Format-Table support script block expressions based on the predefined
conventional variable $_. They introduced some sort of practice of
defining filters and custom expressions via script blocks with $_.
Such expressions may be used for built-in cmdlets and in other
user function parameters customising their behaviour.

Using such expression script blocks now is straightforward, we set $_
to the target object and invoke a script block which uses $_:

    $_ = <...>
    if (& $ExpressionUsingDollarUnder) {
        ...
    }
    else {
        ...
    }

If $_ cannot be assigned in the future then such a way will not be possible.
What is the suggested alternative of invoking custom expressions with $_?

Let summarize my concerns:

  • Read only $_ will break existing code for sure.
  • Expressions with arguments and parameters will work but they will not be universal, e.g. I will not be able to use them as is in my code and in built-in cmdlets that require expressions with $_.
  • On moving code between pipelines (ForEach-Object) and foreach loops I will have to rename variables.

Besides, expressions with arguments or parameters are not that pretty:

    # now we ask a user to provide neat familiar expressions with $_
    Get-MySomething ... -Property {$_.X}, {$_.Y + $_.Z}

    # what we will have to ask a user to code, some mouthful expressions
    Get-MySomething ... -Property {$args[0].X}, {$args[0].Y + $args[0].Z}
    Get-MySomething ... -Property {param($something) $something.X}, {param($something) $something.Y + $something.Z}

@nightroman:

I hear you re breaking existing code, but it sounds like the committee has decided to pay that price.
Based on what I said the in the other thread, a lexical strict mode with opt-in to the read-only behavior might be an alternative, but we don't have that, at least yet.
(And we may be past the point where the powers that be pay attention.)

Expressions, unlike the contexts where $_ is automatically defined, have no unambiguous _single_ parameter: you can pass any number of parameters to a script block, and then you need to distinguish between them. (And, of course, you can reference any number of preexisting variables in them).

Unless I'm misinterpreting your example, Get-MySomething ... -Property {$_.X}, {$_.Y + $_.Z} will _not_ have to change, because $_ will continue to _implicitly_ refer the current pipeline object, such as when using Select-Object with calculated properties, or, more generally, when using script-block parameters ad-hoc; e.g., when passing a script block to the -NewName parameter of the Rename-Item cmdlet:

... | Rename-Item -NewName { $_.Name + '.bak' }  -WhatIf

As before, $_ _implicitly_ refers to the current pipeline object.

In case you were thinking of referring to a $_ explicitly defined _before_ invoking the pipeline: note how that would clash with the script-block parameter feature.

@mklement0

There is no pipeline in my example. Here is some hypothetical code:

    function Get-MySomething($Property) {
        $_ = @{x=1; y=2; z=3}
        if ($Property) {
            foreach($block in $Property) {
                & $block
            }
        }
        else {
            $_
        }
    }

    # default: @{x=1; y=2; z=3}
    Get-MySomething

    # custom: @(1, 5)
    Get-MySomething -Property {$_.X}, {$_.Y + $_.Z}

It's a contrived example just to show that we do not always have a pipeline.
But we may have some "current target object(s)" and let users do something
optional with it. These optional expressions, filters, actions, loggers, formatters,
etc. a user defines as script blocks with $_ because this is a familiar "PowerShell way".

All in all, I think assignable $_ is a tool. When used properly it is a useful tool.

It's not just my code is going to be broken. I remember I saw some code here and there.
@RamblingCookieMonster, are you using $_ as a parameter in Invoke-Parallel?

Yeah - I'd echo the suggestion of adding a PSScriptAnalyzer rule. This will absolutely break existing code - in some cases that code might be unnecessary and poorly thought out, in other cases, it might be integral to user experience. In both of those cases, presumably you would slow adoption of newer PowerShell for anyone using $_

Also, yep, Invoke-Parallel adds that to the scriptblock it builds, to allow running things like 1..10 | Invoke-Parallel {"Something parallel where $_ is the current item in the pipeline"}. This would be a breaking change for a number of existing tools and scripts in the environments I've worked in, and I suspect others.

Cheers!

You'll likely see this in other functions and modules as well. @proxb's very popular PoshRsJob also looks like it uses $_.

I can understand wanting to dissuade this use when it doesn't make sense, but if you remove the ability to use it, you'll make some popular tools far less usable. Using $_ in a scriptblock as the current item is common and expected behavior, it would be sad to lose this.

Thanks for the heads up on this @nightroman - no idea if we can still change their minds, but this would have been completely off the radar for me (and I suspect others).

Cheers!

@RamblingCookieMonster:

If I understand correctly, you're saying there are tools that, for _technical reasons_, need to _emulate_ the _automatic_ definition of $_ by _relaying_ the current pipeline object.

In other words: this is a technical necessity that, by virtue of being an _emulation_, doesn't change the _semantics_ of $_.

If that's the case - and there is no better way to solve this problem - it definitely sounds like preserving the ability to write to $_ is important, and how merely implementing a PSScriptAnalyzer rule is the best approach.

@nightroman:

Your example does _not_ fall into that category, however: it seeks to _extend_ the automatic semantics of $_ to a _convention_, which I think is ill-advised.

While there is no pipeline _input_ in your example (you could argue that a function invocation by itself is a pipeline of length 1), any function/cmdlet _can_ be called in a _later_ stage of a pipeline, and seeing something like Get-MySomething -Property {$_.X}, {$_.Y + $_.Z}:

  • invites confusion with the script-block parameter syntax, as stated: seeing $_ in a script block that is part of a pipeline will make readers expect $_ to refer to the current pipeline object - understandably so.

  • declaring a function that assumes the _caller_ knows about the convention to use $_, which refers to a _custom_ $_ defined _inside the function_ seems like a bad idea.

To use your example: What would you think people will expect $_ to refer to in the following use of your function?

1, 2 | % { Get-MySomething -Property {$_.X}, {$_.Y + $_.Z} }

Given the discussion above, it seems to me important to keep allowing write to $_ and maybe other automatic variables as well.
@SteveL-MSFT can the committee reconsider the decision that was made earlier?

@mklement0

Your example does not fall into that category, however: it seeks to extend
the automatic semantics of $_ to a convention, which I think is ill-advised.

Ill-advised or not, it's another topic. The example shows conceptual
possibilities that are going to be taken away. Please note that I emphasized
that the example was contrived.

Another conceptual example using $_. It implements some sort of maybe chain
expressions

class Maybe {
    $value
    Maybe($value) {
        $this.value = $value
    }
    [Maybe] map($fn) {
        $_ = $this.value
        if ($null -eq $_) {
            $result = $null
        }
        else {
            $result = & $fn
        }
        return [Maybe]::new($result)
    }
}

# (1 + 7) / 2 - 2 = 2
$r = [Maybe]::new(1).map({$_ + 7}).map({$_ / 2}).map({$_ - 2})
$r.Value

# ($null + 7) / 2 - 2 = $null
$r = [Maybe]::new($null).map({$_ + 7}).map({$_ / 2}).map({$_ - 2})
$r.Value

# $null in the middle = $null
$r = [Maybe]::new(1).map({$_ + 7}).map({$null}).map({$_ - 2})
$r.Value

Thanks to $_ = $this.value input expressions are

    .map({$_ + 7}).map({$_ / 2}).map({$_ - 2})

instead of

    .map({$args[0] + 7}).map({$args[0] / 2}).map({$args[0] - 2})

This Maybe example may be ill-advised, too. This is not the point. The point is,
if we want user expressions to be defined as blocks with $_ then we can do
this easily because we can assign $_.

@mklement0

To use your example: What would you think people will expect $_ to refer to in the following use of your function? 1, 2 | % { Get-MySomething -Property {$_.X}, {$_.Y + $_.Z} }

This was not my example, you made something else. I explicitly said that there was no pipeline in the example. And if it was and if it was a real code then I would perhaps design it differently. But it is another topic.

What you presented based on my example is something like

1, 2 | % { Select-Object -Property {$_.Name}, {$_.Version} -InputObject $Host }

We can write some strange code without my example, too.

@daxian-dbw

Given the discussion above, it seems to me important to keep allowing write to $_ and maybe other automatic variables as well.

Some automatic variables are proven to be troublesome. I collected some cases here:
https://github.com/nightroman/PowerShellTraps/tree/master/Basic/Automatic-variables

$args and $input look like good candidates for parser errors on
assignments or using as parameters. Even this may break something but the
impact will be much less than denying assignment of $_.

Unlike the above troublesome variables, $_ is proven to be useful when
assigned in proper scenarios. And it is difficult to replace with another
variable because this would break some de facto standard practices.

@nightroman

Let me try to distill my concern:

Never design a function that cannot be used as _part_ of a pipeline (even if it itself doesn't accept pipeline _input_).
You don't control how people use your function and it's hard to anticipate all legitimate use cases.

In the examples that @RamblingCookieMonster gave, the custom use of $_ is an implementation detail that is hidden from the user and uses the exact same semantics as automatic $_.

By contrast, your personal, convention-based extension to the $_ semantics can clash with the built-in semantics, as I've tried to demonstrate.

Since $_ may need to remain assignable to support the implementation-detail scenarios, you're obviously free to continue to use it with your custom semantics, but I personally hope that you won't _publish_ any functionality based on it.

Convention-based semantic practice using $_ is introduced by PowerShell
itself. It is used in event handler script blocks. I.e. by the convention,
event handlers use $_ as event arguments.

By another similar PowerShell convention, $_ as a predefined variable also
works for [Action[X, Y]]. Example:

# script block uses conventional $_
$myAction = {Write-Host "`$_ = $_"}

# engaging this block in a supported standard PowerShell scenario
$action = [Action[object, int]] $myAction
$action.Invoke($null, 42)

# reusing this block in our own scenario (can do, $_ is assignable)
$_ = 100
& $myAction

Output

$_ = 42
$_ = 100

In other words, conventional $_ is not unusual in PowerShell.

Another convention is $_ as an error record in catch blocks. A lot of error
handling code is written with $_ = error in mind. But what if an error record
comes not from a catch block directly? Then one rewrites such a code using a
different variable (probably a good practice). But in reality one may just use
the assignable variable $_, $_ = <error record> and use the traditional error
handling code $_. Here is the example from psake:
https://github.com/psake/psake/blob/9ace6b4ebb7fd8121f8fe8da83c6227741fad09c/psake.psm1#L695

By _convention_ I mean that both the caller and callee must _choose_ to observe shared rules in order for the code to function as intended.

This is distinct from PowerShell's _automatic_ definition of $_, which is _guaranteed by the engine_ to work, in the relevant scenarios.


As for your examples:

The _psake_ example falls into the emulating-the-standard-semantics-of-$_ category, which, as we've seen, appears to be needed at times. It is absolutely _not_ needed in the linked function, however, as direct use of the $ErrorRecord pipeline-binding parameter variable is the right thing to do - no need for $_ at all.

In the script-block example, it is telling that PowerShell's _native_ mechanism for invoking a script block - via & (or .) - does _not_ define $_ in your scenario, and that you had to find an obscure .NET detour to make it happen.

As stated, in the _general_ script-block invocation scenario, there is no unambiguous _single_ parameter that should be named $_ - and PowerShell doesn't do so, presumably for that reason.
The general-purpose way of passing arguments to script blocks is via $Args or param() blocks may not be as concise as $_, but it is the correct - flexible and extensible - mechanism to use.

I suggest we leave it at that, at least for the larger audience, lest we generate what others may perceive as (more) noise.
If you want to continue this conversation, feel free to get in touch via my profile.

Thanks for great discussion. In all scenarios I see that left var $_ can be replaced by custom variable.

Yes, it can. I afraid the discussion became too long and hid the main issue. The read only $_ is a breaking change. For various reasons existing scripts use $_ as a left variable.

I guess we can reduce the breaking change impact by:

  1. Add rule in ScriptAnalyzer today. Need open new Issue there. @nightroman Could you please make this? https://github.com/PowerShell/PSScriptAnalyzer/issues/712
  2. Consider using a strict mode/ our compatibility module. /cc @DarwinJS. #3061

@iSazonov

  1. is already covered by this PSScriptAnalyzer issue.

With respect to a strict mode, 2. is part of #3061

We should be have explicit rule for "$_/$psitem and $input should be read only". For rest ps variables - "It is recommended to be read only".

Are we talking about PSSA rules or parser errors?

If it is about rules then I would warn about assignments to all automatic variables. It's a clear message to think twice and this is good.

If it is about parser or runtime errors then I would not do this because it is a breaking change. Some real known problem cases were closed "Won't fix" because fixing would be potential breaking changes. Is assignment to automatic variables a problem? It is not quite clear. I tried to explain that it is not the problem. It is a useful tool. If this was not convincing, never mind. But please consider to not break existing code if this is not for fixing known problems.

The two aspects are _independent_ of each other:

  • It sounds like the consensus is that detecting assigning to automatic variables is at least worth a _warning_, which a PSScriptAnalyzer ruler would cover. The caveat is that not everyone may use an editor with integrated PSScriptAnalyzer support.

  • Separately, the question is which assignments to automatic variables should be _prevented by the engine_, ideally during the _parsing_ stage:

    • Clearly, as we've learned in this discussion, preventing assignments to $_ _will_ break existing code - both code that assigns to $_ as an _implementation detail_ out of _technical necessity_ as well as code that uses $_ with _custom semantics_.

    • If the decision is made _not_ to break existing code (which sounds like the right thing to do), disallowing assigning to $_ _in the future_ requires a _lexical_ strict-mode opt-in mechanism - which we don't have yet: see https://github.com/PowerShell/PowerShell-RFC/blob/master/1-Draft/RFC0003-Lexical-Strict-Mode.md

The only interdependence is that the wording of PSScriptAnalyzer messages would ideally distinguish between _potentially ill-advised use_ and _uses that will break the code_, which obviously requires knowledge of what uses the engine itself will prevent.

Preventing assignment to special variables _not only_ breaks existing code, it cripples our ability to do DSL-like things in PowerShell in the future.

The notion that anyone _other than the PowerShell team_ who uses the built-in variable names is somehow doing it incorrectly and should use different variable names is simply preposterous. Good developers take advantage of what users have already learned, and that includes automatic variable names.

Whenever someone writes a script/function/cmdlet that deals with streaming input, pipelines, or events ... and accepts script blocks ... variables like $_ and $this should be supported in those scriptblocks, and the author needs to be able to set them before invoking them.

Not only would we not be able to write anything that _looks like_ ForEach-Object or Where-Object (or at least, we won't be allowed to do it in a PowerShell script), we also would not be able to write anything that _looks like_ an event handler, or a property, or ...

@Jaykul Could you please add code examples to show the broken scenarious?

Out of curiosity, what cases are you going to resolve or improve by making read-only $_?

The very first example in this thread looks fine to me and probably to the author:

foreach ($_ in gps | where {$_.name -like "*SS"}) { $_.name }

There is nothing wrong with it. There are some advantages in using $_ as the foreach loop variable.

It is true that the variable $_ is quite busy in PowerShell. But such is the PowerShell design.
One has to understand the context of $_ and its meaning in this context.

Here is some pure PowerShell without "reusing" $_.
$_ is used three times and each time it is different.

"some input" | %{
    "pipeline item: $_"
    switch ($Host.Name) { ConsoleHost { "switch item: $_" }}
    try { throw "some error" } catch { "error message: $_" }
}

Output

pipeline item: some input
switch item: ConsoleHost
error message: some error

If I understand correctly, you're saying there are tools that, for technical reasons, need to emulate the automatic definition of $_ by relaying the current pipeline object.

In other words: this is a technical necessity that, by virtue of being an emulation, doesn't change the semantics of $_ .

Example with PoshRsJob is just a denial because it uses a _nonpublic_ API.

All examples above cons "$_ read only" use either bad coding style or tricky code - just that we want to exclude "terrible practice" - it is the original idea of the issue. This is always bad if a language forces to use a bad coding style or tricky code. Now PowerShell is open source and we can enhance it all things that customers need and let them write clear and clean code.

Currently we have three point:

  1. Add rules in PSScriptAnalyzer (Implement now)
    1.1 Recommending rule for all special variables.
    1.2 Mandatory rule for $_/$PSItem/$Input
  2. Force read only by strict mode (Implement at 6.0)
  3. Force read only at parse time. (Implement at 6.1)

Found one more use of $_ in my utility script Rename-FarFile-.ps1.

The current API lets users specify a new name like

Rename-FarFile-.ps1 { $_.LastWriteTime.ToString('_yyMMdd_HHmmss_') + $_.Name }

where $_ is the current file in the Far Manager panel (an orthodox console based file manager).

This is not a conceptual example but a tool in use, with the API designed in a familiar PowerShell way. Besides, used script blocks may be reused without changes for something else, e.g. Get-Item ... | Rename-Item -NewName { $_.LastWriteTime.ToString('_yyMMdd_HHmmss_') + $_.Name }.

Given $_ is going to be read only, what would be the recommended adjustment from the
PowerShell committee?

Since it's may not always be obvious who acts in what capacity: I'm just participating in this discussion; I have no say in what will or will not be implemented.

Given $_ is going to be read only, what would be the recommended adjustment from the
PowerShell committee?

Leaving the question of whether your specific use of $_ is advisable (we've discussed this), a simple way to bind any object to $_ without needing it to be assignable is to use ForEach-Object:

$sb = { "[$_]" }
$CustomUnderscoreValue = Get-Date '2017-01-01'
ForEach-Object -InputObject $CustomUnderscoreValue $sb

The above yields string [01/01/2017 00:00:00].

Note the use of -InputObject, which ensures that the object is passed as-is, without enumeration.

@Jaykul:

Having never written or studied a PowerShell DSL, I'm asking innocently: Can you give us a quick example of where writing a DSL would be severely hampered / rendered impossible if $_ (and $this, ...) were read-only?

If the consensus ends up being that we do need to keep at least some automatic variables writable (we already know of at least 1: $LASTEXITCODE), it would certainly help to also provide _stringent guidelines_ for when and how to use them.

@mklement0

a simple way to bind any object to $_ without needing it to be assignable is to use ForEach-Object ...

I am sorry, but this is ugly and not simple at all.

@iSazonov

It would be nice to get something from the committee. The committee is about to withdraw a useful tool. Why it is a bad tool for the committee is not explained, fine. But at least what is going to be recommended instead?

Example with PoshRsJob is just a denial because it uses a nonpublic API.

@iSazonov - is it an undocumented API though? I think it's well accepted that one should not write to an automatic variable, but there are cases where best practices mandate using it - without it, PoshRSJob and Invoke-Parallel would rely on ugly assumptions and avoid a common learnedpractice - that $_ within a scriptblock refers to the current item. Any similar function that uses a scriptblock as a parameter and somehow sets $_ in that scriptblock would likely be broken if $_ becomes read only.

As one of the authors (well... borrower from @proxb) I could certainly change the code but... You'll likely find a large swath of existing PowerShell automation using PoshRSJob, Invoke-Parallel, and many other runspace-based-parallelization variations, a number of which allow for using $_.
As a consumer of these functions, I would be a bit miffed at this, as it would be an unnecessary (IMHO) restriction that now forces me to either (1) not update to PowerShell 6+, or (2), add additional refactoring on top of whatever else we need to refactor to use PowerShell 6+. From my perspective, adding a road block to adoption would not be good.

Cheers!

Note that we have had occasional requests to make public the API that allows setting $_ when invoking a script block.

That seems like a good idea to do now, I suppose it was years ago but it just never bubbled up to seem important enough.

Assuming that API was public, maybe that's good enough for new scripts, but it does mean scripts won't be portable to Windows PowerShell.

May I propose a compromise scoped approach?

$_ is read only if it is created as automatic by PowerShell in the current scope.

That is, this is an error, in the current scope $_ is automatic

... | ForEach-Object {
    $_ = ... # error
}

But this is not an error, $_ is not automatic in a new scope:

... | ForEach-Object {
    # a new scope, user own stuff
    & {
        $_ = ... # fine
    }
}

That is how it is now:

1 | ForEach-Object {
    # $_ exists
    Get-Variable _ -Scope 0
    & {
        # $_ does not exist
        Get-Variable _ -Scope 0
    }
}

Output

Name                           Value
----                           -----
_                              1
Get-Variable : Cannot find a variable with the name '_'.
At C:\....ps1:5 char:3
+         Get-Variable _ -Scope 0
+         ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (_:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

The above should prevent writing to PowerShell automatic variables and this is good.
At the same time, this allows using of own variables when this is needed by design.
Such a change is still breaking but chances are much lower and with easy
workarounds (new scopes).

Another example of using $_ is the Find-Ast function in PowerShellEditorServices. A couple of things to note:

  1. It doesn't use the method described in this thread. But, if I had known about it at the time I would have considered it for the 3.0 compatible portion if it let me avoid recreating the ScriptBlock in the module scope.

  2. For PS 4.0+ it uses an existing public API that will not be broken by the proposed change. ScriptBlock.InvokeWithContext pulls _, input, and this out of variablesToDefine and passes them directly to InvokeWithPipe.

All of that said, I don't see why this needs to be more than a PSSA warning. I think that making them read only is unnecessary and potentially harmful for little gain. And blocking it completely with a parse exception just seems way over the top.

Considering all of the discussion above, i'd like to vote for a PSSA warning only. The argument that this will not be shown in non PSSA editors, holds. Yet I don't see which threat is mitigated by restricting use of $_. I think many devs like Posh for its flexibility. Let's keep that flexibility.

I am not PowerShell/powershell-committee Team member so I can only give my understanding of the conclusion.

For personal using - Writing to $_ is bad practice in most cases and we do not wish to encourage such practices. In most cases, there are native workarounds that @mklement0 has demonstrated more than once. (Some may continue to use Windows PowerShell only but over time I'm sure they'll like PowerShell Core too)

For tools - There are tools/modules that use custom $_ . Sometimes it's also just a bad style. Sometimes this is forced to be used because there are no native alternatives. _In any case_, we want to preserve backward compatibility as much as possible. To do that, we can use a strict mode, lexical strict mode and Interop-Module.

For "special things" - Right way is to use native things. Currently PowerShell is Open Source and Community is free to implement natively things that have long been expected - Concurrency, DSL
(Did you know we already have a pilot implementation in #3169?), also we have plans to enhance classes and finally, @lzybkr proposal "to make public the API that allows setting $_ when invoking a script block."
No doubts, all the popular packages mentioned above and not mentioned at all will have many benefits if such fundamental things are implemented natively in PowerShell Core. We don't want to force you to port the modules - they must continue to work, but their new versions will have these new benefits. We very much hope that you will actively participate in this work starting now.

As discussed on Twitter, putting this back up for review given the feedback so far.

My 2 cents as a PowerShell user:
I definitely discourage re-purposing internal variables such as $_. If some 3rd party PowerShell script would start to break in a new version of Windows PowerShell then I'd be very upset but if I had to check my scripts and their 3rd party dependencies anyway when swiching to PowerShell Core then I would not mind it as it would be expected to have some breaking changes. The only risk would be if my testing would not invoke this 'naughty' code path so that I would not spot it. I guess most users would check portability only very loosely by testing if they can still import their modules and maybe executing it once through the main execution path.

To compare it to other scripting languages: Matlab allows re-purposing of variables defined by it such as pi and even the imaginary variable i but it can easily lead to unintended behaviour when using e.g. i as a loop variable.

Before RTM we should review all breaking changes and try to fix it in interop module.

We just spoke amongst @PowerShell/powershell-committee, and we've decided to go with your feedback:

  • make no changes in the engine to avoid breaking anyone
  • VS Code (or other PS Editor Services-enabled editors) will be the dominant editing platform for 6.0, and so we can create a useful rule that throws whenever someone assigns a value to $_ (or any of the others that @mklement0 enumerated)

Is there a tracking Issue in VS Code repo?

For PSScriptAnalyzer https://github.com/PowerShell/PSScriptAnalyzer/issues/712

No VSCode issue needed. Once ScriptAnalyzer rule is created, it'll be used by EditorServices and automatically lights up in VSCode (and any other editor integrated with EditorServices)

Was this page helpful?
0 / 5 - 0 ratings