Skip to content

What is the right way to implement deprecation in PowerShell? #11674

Closed

Description

Follow-up from #11662 (comment):

Context:

Note: Deprecation in the context of PowerShell is always "soft" deprecation in the sense that, given the commitment to backward compatibility, features are never removed, so that old code continues to function. Instead, deprecation means discouraging use of a given feature, for various reasons: having been superseded by a superior alternative, general obsoleteness, security concerns.

Deprecation can be implemented in the following, potentially complementary ways:

  • Documentation warnings: Clearly state that a given cmdlet / one of its parameters is deprecated and should not be used in new code.

  • Design-time warnings: Implement a PSScriptAnalyzer (PSSA) warning that is ideally also surfaced in Visual Studio Code, via the PowerShell extension's integrated PowerShell Editor Services (PSES).
    This is currently mostly not implemented (see details below).

  • Runtime warnings:
    Currently, a warning (to stream 3) is emitted when a deprecated feature is called.

The latter are currently implemented by a repurposing of the general System.ObsoleteAttribute attribute, whose purpose is to provide compile-time warnings (and optionally errors) about obsolete features: given that PowerShell code isn't compiled, the PowerShell engine explicitly surfaces Obsolete attributes on cmdlets or their parameters (but seemingly not SDK classes with the attribute) with a PowerShell warning at runtime.

Examples of features currently deprecated this way are Send-MailMessage and the -Raw parameter of the Format-Hex cmdlet; e.g.:

PS> 'foo' | Format-Hex -Raw
WARNING: Parameter 'Raw' is obsolete. Raw parameter is deprecated.
... # regular output

3>$null or -WarningAction Ignore can be used to suppress the warning, but old code written pre-deprecation will show the warning.

As an aside: The combination of the boilerplate part of the warning (Parameter 'Raw' is obsolete) with the attribute-specific message (Raw parameter is deprecated) could use some revising.


Relevant questions are:

  • Should deprecation be signaled at runtime at all?

    • If so, is the current approach the right one?
  • Is it sufficient to warn at design time, knowing that not every user will see those warnings, given that they may use an editor other than Visual Studio Code and not install and routinely run Invoke-ScriptAnalyzer after installing the PSScriptAnalyzer module?

    • As of this writing, PSSA (warning) rules seems to comprise just one relating to one specific deprecated feature, AvoidUsingDeprecatedManifestFields, whereas several more deprecated features exist - full list of rules is here.

    • Perhaps a more promising approach is to let PSSA directly surface Obsolete attributes encountered in code, if technically feasible.

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Jan 24, 2020
  2. SeeminglyScience commented on Jan 24, 2020

    @SeeminglyScience
    Contributor

    Thanks Michael Klement (@mklement0)! Copying my comment here so I can mark the one in the other thread as off topic.

    While I agree that adding an ObsoleteAttribute would be annoying and probably not what we want here, I strongly disagree that it would be a breaking change.

    If adding ObsoleteAttribute to anything is a breaking change, then that attribute is pointless.

    It definitely can break things (I know there is a difference between something breaking the change contract and a change technically breaking a thing, but bear with me):

    function Test-Something {
        [CmdletBinding()]
        param()
        end {
            if ($theWorldIsEnding) {
                $PSCmdlet.WriteWarning('some bad stuff is happening')
            }
        }
    }

    This is part of why I don't really like the warning system at all. Most of the things I would want to warn about are better off as a non-terminating error anyway.

    If something is writing a warning, I'm either silencing it, or throwing on it. If I'm throwing on it with -WarningAction Stop, then adding ObsoleteAttribute will cause my script to always throw.

    I know that if it's considered a breaking change than that makes the attribute useless, but that's how it feels currently. Personally I think ObsoleteAttribute should be moved to a design time warning only, maybe with error: true being the exception. That's how C# works as well, anything already built keeps on chugging but you're gonna know about it if you try to use it in something new.

    The whole idea of attributes is that they behave as additive metadata.

    That's true up until the point where something reads that metadata and changes behavior based on it. Adding Mandatory to a ParameterAttribute for example is for sure a breaking change.

  3. SeeminglyScience commented on Jan 24, 2020

    @SeeminglyScience
    Contributor

    Simply emitting a new warning message can also break systems that detect success/failure based on stdout/err.

    For example, script based application detection methods in SCCM. The SCCM client will invoke a PowerShell script and use stdout/err to determine if the subject application was installed correctly. Here's a table (from About custom script detection methods):

    Zero exit code
    STDOUT STDERR Script result Application detection state
    Empty Empty Success Not installed
    Empty Not empty Failure Unknown
    Not empty Empty Success Installed
    Not empty Not empty Success Installed
    Non-zero exit code
    STDOUT STDERR Script result Application detection state
    Empty Empty Failure Unknown
    Empty Not empty Failure Unknown
    Not empty Empty Failure Unknown
    Not empty Not empty Failure Unknown

    So since warning counts as stdout, if a detection method uses a command that has been deprecated, all of those applications will start reporting as if they are already installed.

  4. iSazonov commented on Jan 24, 2020

    @iSazonov
    Collaborator

    GitHub search results:

    • -WarningAction - 8472
    • -ErrorAction - 400857

    I still believe that the transfer of the system to a new version involves testing. I don’t think anyone can easily jump from 5.1 version to 7.0.

    To migrate a large system like SCCM MSFT team and consumers must test all used scripts.
    I guess we need a special label for already tested scripts and engine could report still not tested scripts.

    For warning messages we could play with preference level to ignore such messages in scripts and show in console session.

  5. SeeminglyScience commented on Jan 24, 2020

    @SeeminglyScience
    Contributor

    GitHub search results:

    • -WarningAction - 8472
    • -ErrorAction - 400857

    That's honestly way more than I expected to see. I would have guessed like low 300s.

    I still believe that the transfer of the system to a new version involves testing. I don’t think anyone can easily jump from 5.1 version to 7.0.

    To migrate a large system like SCCM MSFT team and consumers must test all used scripts.

    Well yeah for sure. I've supported many breaking changes in the past and will continue to do so - when the benefit outweighs the risk.

    You could argue (and I'd find it hard to disagree) that the risk is pretty small for adding a warning. That said, it's often discussed as zero risk, like it's an interactive only change. The risk is definitely higher than zero, and in my opinion higher than should be acceptable for the simple act of marking something as obsolete. I'm mainly suggesting that a design time warning should be sufficient.

  6. dragonwolf83 commented on Jan 25, 2020

    @dragonwolf83

    A new Obsolete or Deprecate stream could be added that is only updated by the System.Obsolete attribute. That prevents breaking changes with anyone using -WarningAction and separates out code changes from scripting logic.

    The SCCM scenario is not likely an issue. Warning and Verbose are not redirected to standard out by default so it should not be breaking their detection logic. You can redirect those streams individually or redirect all streams to standard out, but then Verbose would break you way before anything else does. Anyone redirecting those streams likely is already handling error detection properly.

  7. SeeminglyScience commented on Jan 25, 2020

    @SeeminglyScience
    Contributor

    A new Obsolete or Deprecate stream could be added that is only updated by the System.Obsolete attribute. That prevents breaking changes with anyone using -WarningAction and separates out code changes from scripting logic.

    That's a cool idea, but a lot of work. Problems with that approach aside, they're realistically not going to do that.

    The SCCM scenario is not likely an issue. Warning and Verbose are not redirected to standard out by default so it should not be breaking their detection logic. You can redirect those streams individually or redirect all streams to standard out, but then Verbose would break you way before anything else does. Anyone redirecting those streams likely is already handling error detection properly.

    When I say stdout, I'm not referring to PowerShell's output stream. I'm referring to the console process standard output. Anything that writes to the console window and isn't classified as an error message is written to standard output.

    Open up cmd.exe and run this

    powershell -nop -noni -c "Write-Warning visible" > test.log

    In the file will be "WARNING: visible".

  8. dragonwolf83 commented on Jan 25, 2020

    @dragonwolf83

    When I say stdout, I'm not referring to PowerShell's output stream.

    I was too, but I see $WarningPreference defaults to Continue so it is redirecting by default to standard out but Verbose output is not unless you specify it.

    Another option is to just write to the console only and not implement as a full stream. I don't believe the old Write-Host from before PS 5 wrote to standard out since the whole issue was that it wrote to the console directly. If that is the case, surfacing that logic shouldn't be too hard for this special case.

  9. SeeminglyScience commented on Jan 25, 2020

    @SeeminglyScience
    Contributor

    Another option is to just write to the console only and not implement as a full stream. I don't believe the old Write-Host from before PS 5 wrote to standard out since the whole issue was that it wrote to the console directly. If that is the case, surfacing that logic shouldn't be too hard for this special case.

    The issue was that it wasn't going through a PowerShell controlled stream. It did still write to stdout though.

  10. mklement0 commented on Jan 25, 2020

    @mklement0
    ContributorAuthor

    To illustrate that with a quick example: The following cmd.exe command has no output, because not just Write-Host, but [Console]::WriteLine() goes to stdout too:

    C:\>pwsh -nop -c "write-host hi; [console]::writeline('ho')" > NUL

    As an aside: That all streams, including the error stream, go to stdout is, of course, a problem, but changing that would be a breaking change: #7989 (comment)

    I'm mainly suggesting that a design time warning should be sufficient.

    I agree; in order to implement that we'd have to do the following (I can't speak to the technical feasibility):

    • Make only PSSA, not the PS engine look for Obsolete attributes and automatically surface them as warnings.

      • If this is technically feasible, it would obviate the need for individual rules for deprecated features, and the need for playing catch-up with the source code.
      • Conversely, it would mean that you can only provide the ability to turn all deprecation warnings on or off as a group.
    • Make PSES and therefore Visual Studio Code (with the PowerShell extension) surface all deprecation warnings by default.

    • Through announcements and documentation, make it clear that the best PowerShell development experience is offered by Visual Studio Code (along the line of this, but not just from the perspective of obsolescent technology) and that use of a different editor should be complemented with integrating Invoke-ScriptAnalyzer into the development workflow.

  11. iSazonov commented on Jan 25, 2020

    @iSazonov
    Collaborator

    I see the discussion is only about"Runtime warnings".

    I think that the SCCM example is not successful, because SCCM starts an external process. Sometimes it seems to me that it has no design at all :-). Since PowerShell merges everything into one output stream, this will always be a problem, regardless of whether there are warnings. This mechanism is, by definition, completely unreliable.
    Opposite look SC Orchestrator. It doesn't use external process, it hosts PowerShell and as result has not the issue.

    Now we should look how deprecation is used. We have 2 main scenario - user console session and application session (I mean all without user interaction).
    As user I'd accept informational messages that a cmdlet or feature will be removed from future version as non-secure (Invoke-Expression) or non-supported (Send-MailMessage).
    But it is important that the message offers a replacement (link to docs) otherwise it will useless and annoying. User will switch to proposed alternative and will do not get warnings.

    For applications the warnings are pointless. Main concern in the discussion is that warnings can break something. I believe we are thinking not in right direction because besides this minimal risk there are many, many changes that represent a real risk.
    Running blindly any script, module or application on new PowerShell version is dangerous and may be destructively. Users should test their scripts in sandbox before run in live business environment.
    Right direction is that we should protect users and enforce them to test scripts.
    Proposal is to introduce PowerShell adoption mode to prevent users from running non-tested scripts.

  12. mklement0 commented on Jan 25, 2020

    @mklement0
    ContributorAuthor

    Since PowerShell merges everything into one output stream, this will always be a problem, regardless of whether there are warnings. This mechanism is, by definition, completely unreliable.

    This is a separate problem, which can be tackled via the planned separate PowerShell executable for not loading $PROFILE by default - see #8072.
    The right behavior is to send all streams other than the success output stream to stderr, which is the only other native stream available.

    it hosts PowerShell

    This is not just about SCCM. It is unrealistic and unreasonable to expect all external (build, CI / CD, ....) tooling to host PowerShell, so the problem will persist, even with the above fix in place.

    Aside from that, even a host that checks streams separately could be thrown off by the appearance of new warnings.

    user console session

    Agreed, in interactively submitted individual commands (as opposed to scripts) that print to the host it would be helpful for the engine to surface deprecation warnings, along with helpful links.

    Proposal is to introduce PowerShell adoption mode to prevent users from running non-tested scripts.

    Interesting - I suggest you create a new issue for that.

    But if I interpret your comments correctly, you agree that there is no need for runtime deprecation warnings for unattended (update: and captured / redirected) execution, correct?

  13. iSazonov commented on Jan 25, 2020

    @iSazonov
    Collaborator

    But if I interpret your comments correctly, you agree that there is no need for runtime deprecation warnings for unattended execution, correct?

    I find it useful for users in interactive session and pointless for scenarios like task scheduler (here we could only write a log event and telemetry if we need).

  14. changed the title [-]Is using the System.Obsolete attribute the right way to implement deprecation in PowerShell?[/-] [+]What is the right way to implement deprecation in PowerShell?[/+] on Jan 25, 2020
  15. SeeminglyScience commented on Jan 25, 2020

    @SeeminglyScience
    Contributor

    One option would be to only write the warning when CommandOrigin is Runspace and -NonInteractive was not specified. That would eliminate the majority of risk.

  16. 31 remaining items

  17. SteveL-MSFT commented on Aug 19, 2020

    @SteveL-MSFT
    Member

    @PowerShell/powershell-committee discussed this today. Although no conclusion, we did agree on a few things:

    • We should have Deprecated Feature flags like we have Experimental Feature flags. If a Deprecated Feature is disabled, then it should throw an exception (instead of being hidden).
    • We should have warnings and telemetry when a Deprecated Feature is used, this will help determine if a Deprecated feature can ever be removed (default is to never remove a feature as we don't have usage data today)
    • For .NET APIs that are obsolete, we can also prevent usage if disallowed if called by scripts

    We believe this feature requires a RFC to be authored

  18. iSazonov commented on Aug 20, 2020

    @iSazonov
    Collaborator

    We could do not complicate and simply continue to use Experimental Features but have "Deprecate" in an experimental feature name.

  19. added
    Committee-ReviewedPS-Committee has reviewed this and made a decision
    and removed
    Review - CommitteeThe PR/Issue needs a review from the PowerShell Committee
    on Aug 26, 2020
  20. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  21. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  22. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  23. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

  24. xtqqczze commented on Oct 7, 2025

    @xtqqczze
    Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Committee-ReviewedPS-Committee has reviewed this and made a decisionIssue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-No ActivityIssue has had no activity for 6 months or more

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions