Search code examples
powershellsyntaxvariable-declarationvariable-initialization

Why Does PowerShell Complain It Can't Retrieve a Variable?


I'm getting the following error message:

The variable '$dateTime' cannot be retrieved because it has not been set.

The error is generated by the line [DateTime]$dateTime in this code:

  [string]$registryEntry = Read-Registry -path $SCRIPT_KEY -name $key

   [DateTime]$dateTime 
   if ($null -ne $registryEntry) {
      $dateTime = [DateTime]$registryEntry
   }
   return $dateTime

But the error message makes no sense: I’m not trying to retrieve the $dateTime variable, I’m just declaring it.

I have to return a DateTime object, or $null if it’s undefined. FYI, the registry entry is the string “19 Jul 2023 05:52”

How do I make this work?

$psVersionTable:

Name                           Value
----                           -----
PSVersion                      7.3.6
PSEdition                      Core
GitCommitId                    7.3.6
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Solution

  • To add to Mathias R. Jessen's helpful answer:

    I’m not trying to retrieve the $dateTime variable, I’m just declaring it.

    PowerShell has no concept of mere variable declarations (the only exceptions are parameter variables - see the relevant section of about_Functions).

    You can only "declare" a variable by implicitly creating it via assigning a value to it. (If the variable already exists, it will be updated.)

    Since you want to use a type constraint, i.e. to limit the values the variable can hold to a given type, the value you assign must be of that type or convertible to it; in the simplest case:

    [datetime] $dateTime = 0
    

    Using [int] value 0 has the same effect as [datetime]::MinValue, as shown in Mathias' answer, given that 0 can be converted to the latter, due to PowerShell's flexible automatic type conversions (see this answer).

    If you either use no type constraint or you use a type constraint with a .NET reference type, you can use $null for initialization; e.g.:

    # No type constraint, same as [object]
    $newVar = $null
    
    # .NET reference-type type constraint
    [regex] $newVar = $null
    

    if $null is allowed as a value in a given situation, you can also use the following single-statement idiom for initializing a variable with varying values, so as to guarantee the variable's existence, based on using an if statement as an expression; it wouldn't work with [datetime], so I'm omitting the type constraint here:

    # Assigns the output from the `if` statement.
    $dateTime =
      if ($null -ne $registryEntry) {
        [datetime] $registryEntry
      }
    

    That is, the if statement's output becomes the value of $datetime, which means that if $null -ne $registryEntry is true, [datetime] $registryEntry is assigned, and $null otherwise.[1]

    Note that a PowerShell variable without a type constraint is free to not only be initialized with a value of an type (including $null), but to also later be updated with a value of any other type.

    As an aside:

    • Another surprising aspect of variable assignments in PowerShell is that they implicitly create a local variable with the given name, even if a variable by that name already exists, but was defined in an ancestral scope - see this answer.

    As for what you tried:

    In isolation, [DateTime]$dateTime does the following:

    • It is a type cast, i.e. it tries to convert an existing $dateTime variable to type [datetime] and outputs the result:

      • That is, you are trying to retrieve the value of variable $dateTime

      • If $dateTime does not exist yet...

        • ... and you have Set-StrictMode -Version 1 or higher in effect, you'll get the error you saw.

        • ... otherwise, given that referencing non-existent variables defaults to $null, your cast will be the equivalent of [datetime] $null, which fails with Cannot convert null to type "System.DateTime"

    • In the event that such a cast succeeds its result is sent to the success output stream, which - in the absence of assigning it to a variable or passing it to a command - prints to the display by default, due to PowerShell's implicit output behavior, explained in the bottom section of this answer.


    [1] Strictly speaking, it is the "Automation null" value that is assigned, which is an "enumerable null" that behaves like $null in expression contexts, and like an empty enumerable in enumeration contexts - see this answer for details.