Skip to content

Get-Uptime -Since ignores an explicit $false value #25015

Closed

Description

@surfingoldelephant

Prerequisites

Steps to reproduce

(Get-Uptime).GetType().Name               # TimeSpan
(Get-Uptime -Since).GetType().Name        # DateTime
(Get-Uptime -Since:$false).GetType().Name # DateTime, should be TimeSpan

Expected behavior

# TimeSpan
# DateTime
# TimeSpan

Actual behavior

# TimeSpan
# DateTime
# DateTime

Context

  • Get-Uptime emits a [timespan] object by default or [datetime] with -Since.
  • If -Since is explicitly provided a $false value (-Since:$false/hash table splatting), the type is unexpectedly [datetime].
    • -Since/-Since:$true and -Since:$false result in identical behavior.
  • Get-Uptime uses the selected parameter set to determine output type, which is tied to the presence of the -Since switch, not its value.

switch (ParameterSetName)
{
case TimespanParameterSet:
// return TimeSpan of time since the system started up
WriteObject(uptime);
break;
case SinceParameterSet:
// return Datetime when the system started up
WriteObject(DateTime.Now.Subtract(uptime));
break;
}

Despite it being uncommon to specify an explicit $false switch (and arguably against its intention), I believe the current Get-Uptime behavior still amounts to a bug.

  • Specifying $false is permitted by the language and the Get-Uptime result subverts reasonable user expectation.
  • There are valid use cases for this (e.g., dynamically retrieving the boolean from another source).
  • Other cmdlets with parameter sets tied to a switch respect an explicit $false value (e.g., 'foo' | Select-String f -Raw:$false)
# Simulate dynamic retrieval of the boolean.
$getDateTimeOutput = $false

Get-Uptime -Since:$getDateTimeOutput
# Or...
$params = @{ Since = $getDateTimeOutput }
Get-Uptime @params

# Get-Uptime still unexpectedly emits [datetime] output.

Environment data

Name                           Value
----                           -----
PSVersion                      7.6.0-preview.2
PSEdition                      Core
GitCommitId                    7.6.0-preview.2
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0, 5.0, 5.1…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Activity

  1. changed the title [-]`Get-Uptime -Since` ignores an explict `$false` value[/-] [+]`Get-Uptime -Since` ignores an explicit `$false` value[/+] on Feb 14, 2025
  2. mklement0 commented on Feb 14, 2025

    @mklement0
    Contributor

    Good find and good sleuthing.

    As an aside, re "arguably against its intention":

    Details

    As you've already noted, splatting switch parameters with a value of $false results in the equivalent of -SomeSwitch:$false.

    There is one case where this syntax is required, namely if you want to override a preference variable ad hoc, notably with -Confirm:$false

    To support the latter case, distinguishing in the command body between the switch having been omitted and having been specified but with a value of $false is necessary.

    Unfortunately, the property name .IsPresent is confusing, as it reflects the effective value rather than the presence of the switch.
    (Arguably, .IsPresent should have been designed to truly reflect only the presence, alongside a .Value property that reflects the effective value.)

    It is also debatable how parameter-set selection should work in the face of -SomeSwitch:$false if -SomeSwitch belongs to a distinct parameter set.
    Arguably, unlike the current behavior, treating -SomeSwitch:$false as if -SomeSwitch had not been specified for the purposes of parameter-set selection would be the more sensible default, given that the preference-variable override-like use case is less common.

    $splat = @{ Flag = $false }
    & { [CmdletBinding()]param([Parameter(ParameterSetName='FlagSet')] [switch] $Flag) $Flag; $pscmdlet.ParameterSetName } @splat
    # Current behavior: parameter set 'Flagset' *is* selected, despite value $false
  3. MartinGC94 commented on Feb 14, 2025

    @MartinGC94
    Contributor

    It's actually quite common that switch parameters that are explicitly set to false don't work as expected. It's usually whenever the Switch is used to specify the parameter set. Here's a few examples:

    Get-WindowsPackage -Online:$false # Crashes the whole process
    ls C:\ | where Name -EQ:$false Windows # False is silently ignored
    Get-TimeZone -ListAvailable:$false # False is silently ignored
    Get-Random -InputObject (1..10) -Shuffle:$false # False is silently ignored
    

    Here's a snippet to find commands that probably have this issue:

    foreach ($Command in Get-Command -CommandType Cmdlet, Function)
    {
        foreach ($Parameter in $Command.Parameters.Values)
        {
            if ($Parameter.SwitchParameter -and $Parameter.ParameterSets.values.IsMandatory -contains $true)
            {
                $Command
                break
            }
        }
    }
    
    # And the output:
    CommandType     Name                                               Version    Source
    -----------     ----                                               -------    ------
    Function        help                                                          
    Cmdlet          ConvertFrom-Csv                                    7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Get-Help                                           7.6.0.2    Microsoft.PowerShell.Core
    Cmdlet          Get-Module                                         7.6.0.2    Microsoft.PowerShell.Core
    Cmdlet          Get-Process                                        7.0.0.0    Microsoft.PowerShell.Management
    Cmdlet          Get-Random                                         7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Get-SecureRandom                                   7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Get-TimeZone                                       7.0.0.0    Microsoft.PowerShell.Management
    Cmdlet          Import-Csv                                         7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Import-Module                                      7.6.0.2    Microsoft.PowerShell.Core
    Cmdlet          Invoke-RestMethod                                  7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Invoke-WebRequest                                  7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          New-PSSession                                      7.6.0.2    Microsoft.PowerShell.Core
    Cmdlet          Select-String                                      7.0.0.0    Microsoft.PowerShell.Utility
    Cmdlet          Set-StrictMode                                     7.6.0.2    Microsoft.PowerShell.Core
    Cmdlet          Split-Path                                         7.0.0.0    Microsoft.PowerShell.Management
    Cmdlet          Test-Connection                                    7.0.0.0    Microsoft.PowerShell.Management
    Cmdlet          Where-Object                                       7.6.0.2    Microsoft.PowerShell.Core
    

    Note that the parameter info is only available for imported modules so the snippet won't find problematic commands if their module hasn't been imported yet.

    Ideally people would stop using switches to control parametersets and instead use actual values. For example Dism commands could assume Online AKA the current system by default and only work on images if a path has been specified. I understand why they didn't do this though. If you are working on an image you don't want to accidentally target your own machine because you forgot to specify a path.

  4. mklement0 commented on Feb 14, 2025

    @mklement0
    Contributor

    Great analysis, MartinGC94; given these results, it sounds like my musing in my previous comment about treating
    -SomeFlag:$false as if -SomeFlag had not been specified with respect to parameter-set selection would actually fix quite a few bugs - including the one at hand - rather than constituting an unacceptable breaking change.

  5. added
    WG-Cmdletsgeneral cmdlet issues
    Up-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributors
    on Feb 15, 2025
  6. mklement0 commented on Feb 15, 2025

    @mklement0
    Contributor

    Ilya (@iSazonov), since I assume that the Up-for-Grabs designation only applies to the immediate fix to be applied to Get-UpTime, I've spun out my suggestion to tackle this problem systemically into a separate feature request / bug report:

    Implementing the latter would implicitly fix the bug at hand, as well as the bugs that @MartinGC94 mentioned.
    (where Name -EQ:$false Windows is a special case, whose behavior would change from quietly ignoring the $false value to reporting an error due to the effective absence of an operator).

  7. iSazonov commented on Feb 15, 2025

    @iSazonov
    Collaborator

    Up-for-Grabs - we should fix all cmdlets in the repo if they implement a switch in wrong way.


    As for declaring this behavior incorrect in general, I cannot agree.
    Firstly, the switch was originally implemented as having three states, similar to a nullable boolean. It can be really useful. Let's look at an enum-based parameter. It can switch the set, have multiple possible values, and may have a default value. If there are only two possible values, then this is practically the same as a 3-state switch. We consider this to be useful, just as we do not remove the nullable boolean from C#. Then why should we limit users to the switch options?

    As discussed earlier, it would be enough to add some good examples to the documentation.

  8. mklement0 commented on Feb 15, 2025

    @mklement0
    Contributor

    Ilya (@iSazonov), I don't fully understand your comment, but note that the three-states argument is a separate issue that is incidental to the linked proposal; with the implementation of the latter, the ability to distinguish the three states will not go away and, conversely, requires custom logic already (the 3 states being: switch passed without argument, switch passed with argument $false, switch omitted).

    By contrast, the proposal would make parameter-set selection work the way most users - including the authors of many existing built-in cmdlets - intuitively expect.

    Implementing the proposal not only obviates the need to fix - many - of the existing cmdlets individually, it would also prevent authors of future cmdlets from experiencing the same pitfall.

    I suggest voicing your concerns regarding the linked proposal there, not here.

  9. surfingoldelephant commented on Feb 16, 2025

    @surfingoldelephant
    Author

    It's actually quite common that switch parameters that are explicitly set to false don't work as expected. It's usually whenever the Switch is used to specify the parameter set.

    Agreed. I'm guilty of this, also. Nowadays, I often try to rework exclusive switches into a single, enum/set-based parameter and branch off that instead of the parameter set.

    Here's a snippet to find commands that probably have this issue

    Thank you. This revealed 8 additional commands that exhibit the issue. See this comment.

  10. surfingoldelephant commented on Feb 16, 2025

    @surfingoldelephant
    Author

    I've spun out my suggestion to tackle this problem systemically into a separate feature request / bug report:

    Thank you for submitting this. I agree, it would be great if we can avoid modifying the individual commands.

    In the event the proposal is rejected, there's a question of how to handle the commands individually.

    In the case at hand with Get-Uptime, the fix is very simple, but that is probably not the case with some of the others. It seems a case-by-case evaluation in separate issues will be required, though only if the systematic fix is rejected.

  11. moved this from Queue to In-Progress-Issues in Cmdlets Working Groupon Oct 1, 2025
  12. added
    Issue-BugIssue has been identified as a bug in the product
    WG-ReviewedA Working Group has reviewed this and made a recommendation
    and removed
    Needs-TriageThe issue is new and needs to be triaged by a work group.
    on Oct 1, 2025
  13. moved this from In-Progress-Issues to Reviewed in Cmdlets Working Groupon Oct 1, 2025
  14. SteveL-MSFT commented on Oct 1, 2025

    @SteveL-MSFT
    Member

    @PowerShell/wg-powershell-cmdlets agrees this is a bug

  15. logiclrd commented on Oct 3, 2025

    @logiclrd
    Contributor

    Maybe this is a non-issue or has a trivial fix, but on the surface, it seems to me (a developer with no prior experience in the PowerShell codebase 🙂) that there's a fundamental problem. The problem arises from the fact that the presence of -Since selects a different ParameterSet, and that ParameterSet is documented as returning DateTime. Is it okay for the cmdlet to just return a different type than the one that it looks like it's supposed to be returning?

    In practice, the cmdlet returning a different type than it's supposed to (per its [OutputType] attribute) seems to work...

  16. mklement0 commented on Oct 4, 2025

    @mklement0
    Contributor

    Jonathan Gilbert (@logiclrd):

    The OutputType attribute value is only a documentation note. It's not derived from the function code or compared to the actual function output. As such, the value might be inaccurate.

  17. MartinGC94 commented on Oct 4, 2025

    @MartinGC94
    Contributor

    The OutputType attribute value is only a documentation note.

    This is slightly misleading. The OutputType lets the type inference know what types it can expect which is important for tab completion: Get-Something | Where <Tab> without an attribute to tell PowerShell what type(s) to expect it can't provide any useful completions. I have created a docs issue to point out this flaw in the documentation: MicrosoftDocs/PowerShell-Docs#12414

  18. logiclrd commented on Oct 4, 2025

    @logiclrd
    Contributor

    The OutputType attribute value is only a documentation note.

    This is slightly misleading. The OutputType lets the type inference know what types it can expect which is important for tab completion

    Hmm, interesting. Get-Uptime is attributed [Cmdlet(DefaultParameterSet = "Timespan")] with [Parameter(ParameterSetName = "Since")] on the Since switch. And, it has two [OutputType] attributes mapping "Timespan" to typeof(TimeSpan) and "Since" to typeof(DateTime).

    If I run Get-Uptime on its own, it returns a TimeSpan. And, with no switches, the default ParameterSet of "Timespan" should be selected. This all lines up.

    But if I type Get-Uptime | Where and start pressing tab, it lists the fields of DateTime. It's treating it as though the "Since" ParameterSet were selected.

    What's going on? Is this its own bug??

  19. MartinGC94 commented on Oct 4, 2025

    @MartinGC94
    Contributor

    Get-Uptime | Where <Tab> lists the properties for both DateTime and TimeSpan. This is because the implementation simply adds all the listed outputtypes without bothering to figure out which ones to add. This is a known limitation in the current implementation: https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs#L1459-L1462

    I did look into fixing this at one point, but it wasn't as straightforward as I had hoped and I didn't care enough to dig more into it.

    GitHub
    PowerShell for every system! Contribute to PowerShell/PowerShell development by creating an account on GitHub.
  20. logiclrd commented on Oct 4, 2025

    @logiclrd
    Contributor

    You replied in the middle of me writing this reply 🙂 I had started out with this observation, aligned with what you wrote:

    On closer inspection, it's just taking the union of all [OutputType] specifications and not paying any attention to parameter sets. I misunderstood, and inferred a link that wasn't there between parameter sets and the completion in Where. 🙂

    So, the real issue is that if you have a cmdlet Qux that has, say, parameter sets "A" and "B", and "A" has a SwitchParameter in it called Foo and "B" has, say, a string parameter called Bar, then you can't do Qux -Foo:$false -Bar "Eenp", because the presence of -Foo, even with explicit value $false, will require parameter set "A" while the presence of -Bar will require parameter set "B", and it can't be both at the same time.

    And this doesn't seem to be something that really comprehensively be "fixed". A narrow fix could explicitly test for the constant value $false and passed to a switch and ignore that switch for the purposes of selecting the parameter set. But that would only fix it for the case where someone were explicitly typing My-Cmdlet -Something:$false at the command-line, which is, I suspect, basically never a use case. The place where this kind of inference would be more useful is if you're forwarding a switch on as in:

    function Foo
    {
      param ( [Switch] $Bar )
      Get-Uptime -Since:$Bar
    }
    
    # or
    
    function Eenp
    {
      param ( [Switch] $Since )
      Get-Uptime @PSBoundParameters
    }
    

    (This is a bad example because it only has one parameter -- a better example would use a cmdlet that has parameters that can only be used in one parameter set or another.)

    In this case, it cannot statically determine the parameter set; the selection depends on the runtime values. But, the actual implementation of parameter set selection does not use the values, does it? My understanding is that a given statement's parameter set is static to the structure of the statement, regardless of the values provided, and if that's the case, then ignoring switches that have false values for the purposes of parameter set selection is simply not possible.

  21. microsoft-github-policy-service commented on Oct 9, 2025

    @microsoft-github-policy-service
    Contributor

    📣 Hey @surfingoldelephant, how did we do? We would love to hear your feedback with the link below! 🗣️

    🔗 https://aka.ms/PSRepoFeedback

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

    Issue-BugIssue has been identified as a bug in the productUp-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributorsWG-Cmdletsgeneral cmdlet issuesWG-ReviewedA Working Group has reviewed this and made a recommendation

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions