Search code examples
powershelldebuggingvscode-debugger

Hashtable key depends on where function is defined


I'm encountering the strangest issue with powershell code.

I have 2 files, A.ps1 and B.ps1. B contains very simple code that maps environment to days of the week, and then uses the current environment to get the correct days:

$Location\A.ps1 # import Detect-Environment
$currentEnv = Detect-Environment

# 0 as Sunday, 6 as Saturday
$activatedEnvsAndWeekdays = @{
    [CurrentEnv]::LOCAL = @(0, 1, 2, 3, 4, 5, 6)
    [CurrentEnv]::DEV   = @(0, 1, 2, 3, 4, 5, 6)
    [CurrentEnv]::UAT   = @()
    [CurrentEnv]::PROD  = @(1)
}

$activatedWeekdayNums = $activatedEnvsAndWeekdays[$currentEnv]

A.ps1 contains:

enum CurrentEnv {
    LOCAL
    DEV
    UAT
    PROD
}

function Detect-CinchyEnvironment {
    $hostName = [System.Net.Dns]::GetHostName()
    switch -Wildcard ($hostName) {
        '*CINCHYPRD*' { [CurrentEnv]::PROD }
        '*CINCHYUAT*' { [CurrentEnv]::UAT }
        '*CINCHYDEV*' { [CurrentEnv]::DEV }
        Default { [CurrentEnv]::LOCAL }
    }
}

Since my current env is [CurrentEnv]::LOCAL, $activatedWeekdayNums should be the list @(0, 1, 2, 3, 4, 5, 6). But it's not! It's $null. After much digging, I discovered why.

When I define and call Detect-Environment from within my file, the code works fine. But when I define Detect-Environment in another file called A.ps1 and then import it with . $Location\A.ps1, it doesn't work, even though $currentEnv says it's equal to [CurrentEnv]::LOCAL. Indeed if I compare the variables obtained by equating$currentEnv1 (local function) to $currentEnv2 (imported function), $currentEnv2 -eq $currentEnv return True. But the result with $activatedEnvsAndWeekdays is different. Totally confused.

Any help is appreciated! Happy to provide more context/code.

EDIT: $currentEnv1.GetType() is equal to:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     CurrentEnv                               System.Enum

$currentEnv2.GetType() is equal to the same thing:

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     CurrentEnv                               System.Enum

EDIT2: To add more info, even if I do $activatedEnvsAndWeekdays[[CurrentCinchyEnv]::LOCAL] so I'm not using $currentEnv at all, it doesn't return anything when Detect-Environment and enum CurrentCinchyEnv are imported.


Solution

  • Posting this answer as was requested in comments. For anyone wondering how to reproduce this bug (?), here is a minimal reproducible example.

    • script1.ps1
    enum TestEnum {
        Test
    }
    
    $key = . .\script2.ps1
    $map = @{ [TestEnum]::Test = 'hello' }
    $map[$key]
    
    • script2.ps1
    [TestEnum]::Test
    

    If we attempt to run script1.ps1 in a clean PS session we should get the right value hello, however, any subsequent update to script1.ps1 (has to be the .ps1 defining the enum) will result in PowerShell emitting a new type with the same name:

    PS /> [AppDomain]::CurrentDomain.GetAssemblies().GetTypes() |
        Where-Object Name -EQ TestEnum
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     TestEnum                                 System.Enum
    True     True     TestEnum                                 System.Enum
    

    And a newer assembly version:

    PS /> [AppDomain]::CurrentDomain.GetAssemblies().GetTypes() |
        Where-Object Name -EQ TestEnum | ForEach-Object AssemblyQualifiedName
    
    TestEnum, PowerShell Class Assembly, Version=1.0.0.2, Culture=neutral, PublicKeyToken=null
    TestEnum, PowerShell Class Assembly, Version=1.0.0.3, Culture=neutral, PublicKeyToken=null
    

    It's worth noting that this would only affect us while working on our code but the bug should not be reproducible on a clean session, in other words, if you wish to use reference type keys on your hashtable and can bear with having to restart your PowerShell session on each update of your code then it should be fine during runtime.

    As a personal recommendation, I would use strings as the hashtable keys to avoid this.

    As workaround, casting the type should fix the issue without a need to restart the session:

    $map[[TestEnum] $keyEnum]