How do I read a .env file from a .ps1 script?
Asked Answered
N

7

20

I have a .env file like this one:

TESTCASE_GROUP_SIZE=25
. . .

And I want to get its value (read it) into a .ps1 script. How can I do it?

Neuman answered 14/5, 2022 at 0:46 Comment(0)
G
37
get-content test.env | foreach {
    $name, $value = $_.split('=')
    set-content env:\$name $value
}

assuming you mean "set one environment variable per line in the file".

Germaun answered 14/5, 2022 at 0:55 Comment(6)
And then how do I call my variables?? Like TESTCASE_GROUP_SIZENeuman
You can use $env:TESTCASE_GROUP_SIZE , but environment variables are really so you can launch a program and it inherits them as its environment. If that's not what you're doing then they may be better in a hashtable rather than as individual variables.Germaun
You might want to enclose the body of the foreach in if ($_) { } to filter empty lines. Heck, you might as well filter the comments in the env file using patterns.Pedaias
If your values might have = in them, you'll want to add a second argument to split() so it doesn't toss them out: $_.split('=',2)Asteria
Worked for me in PS version 5.1 just had to drop the backslash on the set-content line: set-content env:$name $valueGrew
You could also use new-Variable -Name $name -Value $value if you want a local variable instead of an environment variableRainbolt
S
22

Polished version of @TessellatingHeckler's for future reference.

Get-Content .env | foreach {
  $name, $value = $_.split('=')
  if ([string]::IsNullOrWhiteSpace($name) || $name.Contains('#')) {
    continue
  }
  Set-Content env:\$name $value
}
Sparks answered 18/12, 2022 at 6:21 Comment(1)
very nice! I like that you care about comments too!Denice
M
7

Throwing my hat into the ring. Powershell's ConvertFrom-StringData will ignore comments and handle edge cases like multiple = signs.

Run notepad $PROFILE and copy it in to make it available any time you open the terminal.

<#
.SYNOPSIS
Imports variables from an ENV file

.EXAMPLE
# Basic usage
dotenv

.EXAMPLE
# Provide a path
dotenv path/to/env

.EXAMPLE
# See what the command will do before it runs
dotenv -whatif

.EXAMPLE
# Create regular vars instead of env vars
dotenv -type regular
#>
function Import-Env {
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('dotenv')]
    param(
        [ValidateNotNullOrEmpty()]
        [String] $Path = '.env',

        # Determines whether variables are environment variables or normal
        [ValidateSet('Environment', 'Regular')]
        [String] $Type = 'Environment'
    )
    $Env = Get-Content -raw $Path | ConvertFrom-StringData
    $Env.GetEnumerator() | Foreach-Object {
        $Name, $Value = $_.Name, $_.Value
        if ($PSCmdlet.ShouldProcess($Name, "Importing $Type Variable")) {
            switch ($Type) {
                'Environment' { Set-Content -Path "env:\$Name" -Value $Value }
                'Regular' { Set-Variable -Name $Name -Value $Value -Scope Script }
            }
        }
    }
}

Version 2 with Bash quoting rules by request of @YvesMartin

function Import-Env {
    [CmdletBinding(SupportsShouldProcess)]
    [Alias('dotenv')]
    param(
        [ValidateNotNullOrEmpty()]
        [String] $Path = '.env',

        # Determines whether variables are environment variables or normal
        [ValidateSet('Environment', 'Regular')]
        [String] $Type = 'Environment'
    )
    $Env = Get-Content -raw $Path | ConvertFrom-StringData
    $Env.GetEnumerator() | Foreach-Object {
        $Name, $Value  = $_.Name, $_.Value
        
        # Account for quote rules in Bash
        $StartQuote = [Regex]::Match($Value, "^('|`")")
        $EndQuote = [Regex]::Match($Value, "('|`")$")
        if ($StartQuote.Success -and -not $EndQuote.Success) {
            throw [System.IO.InvalidDataException] "Missing terminating quote $($StartQuote.Value) in '$Name': $Value"
        } elseif (-not $StartQuote.Success -and $EndQuote.Success) {
            throw [System.IO.InvalidDataException] "Missing starting quote $($EndQuote.Value) in '$Name': $Value"
        } elseif ($StartQuote.Value -ne $EndQuote.Value) {
            throw [System.IO.InvalidDataException] "Mismatched quotes in '$Name': $Value"
        } elseif ($StartQuote.Success -and $EndQuote.Success) {
            $Value = $Value -replace "^('|`")" -replace "('|`")$"  # Trim quotes
        }
        
        if ($PSCmdlet.ShouldProcess($Name, "Importing $Type Variable")) {
            switch ($Type) {
                'Environment' { Set-Content -Path "env:\$Name" -Value $Value }
                'Regular' { Set-Variable -Name $Name -Value $Value -Scope Script }
            }
        }
    }
}
Moderate answered 19/7, 2023 at 17:15 Comment(3)
Really good. In case of cross-platform dotenv file (for Linux), we can expect declaration like KEY="value1 value2" produces same result as original Powershell $Env:KEY="value1 value2" than means $Env:KEY container value1 value2 without double quotes. May you please propose a support for this?Schizopod
Found an implementation at github.com/stopthatastronaut/poshdotenv/blob/master/module/…Schizopod
@YvesMartin It's kind of a tall order to do this properly as Bash is evaluating the env file as source code but I did what I could. An easier approach would be to call $ExecutionContext.InvokeCommand.ExpandString() on the value so the quotes are evaluated to the raw string. However, doing that carries risk because it would then eval any Powershell code in the string. In the absence of a string parser, making sure the string is valid is a bit of effort.Moderate
P
1

I came up with this script:

get-content .env | foreach {
        $index = $_.IndexOf('=');
        $key = ''
        $value =''
        if ($index -ge 0) {
            $key = $_.Substring(0, $index)
            $value = $_.Substring($index + 1)
        }
        @{ $key = $value} 
    }
Peirce answered 24/2, 2023 at 17:34 Comment(0)
H
1

Made some additions to @jeiea's answer. Wrapped in a function that:

  • Let's you override the path of the .env file.
  • By default if the Dotenv file is not there the function returns and you would assume that env vars are being set through other means, useful for CI/CD pipelines, Docker, etc.
  • Added a param called $strict to make the function throw an error if the Dotenv file does not exist. Overriding the previous functionality.
  • Each line is only split on the first instance of a =.
  • Lines without a = are ignored.
  • Invalid variable names are skipped. If $strict is enabled then an error is thrown when invalid variable names are provided.
  • White space is trimmed from the beginning and end of variable names and values.
  • Quotes are stripped from values after any whitespace is striped.
  • Ensures that string boolean values are set to literal bool values.
  • Ensures that empty values and string 'null' values are set to literal $null values.
  • Support variable interpolation (i.e., referencing other variables)
  • Handle escape chars in values
  • Handle lines that start with export
function Read-Dotenv {
    param (
        [string]$EnvFile = '.env',  # Path to the .env file
        [bool]$Strict = $false      # Whether to throw an error if the file does not exist
    )

    # Check if the .env file exists
    if (-not (Test-Path $EnvFile)) {
        if ($Strict) {
            Throw "The Dotenv file does not exist at the path: $EnvFile"
        }
        return
    }

    # Hashtable to store environment variables
    $envVars = @{}

    # Function to resolve variable references within values
    function Resolve-Value {
        param (
            [string]$value,        # The value to resolve
            [hashtable]$envVars    # Hashtable containing the environment variables
        )

        $prevValue = $null
        while ($prevValue -ne $value) {
            $prevValue = $value

            # Resolve ${VAR} patterns
            $value = [regex]::Replace($value, '\$\{(\w+)\}', {
                param ($match)
                if ($envVars.ContainsKey($match.Groups[1].Value)) {
                    $envVars[$match.Groups[1].Value]
                } else {
                    $match.Value
                }
            })

            # Resolve $VAR patterns
            $value = [regex]::Replace($value, '\$(\w+)', {
                param ($match)
                if ($envVars.ContainsKey($match.Groups[1].Value)) {
                    $envVars[$match.Groups[1].Value]
                } else {
                    $match.Value
                }
            })
        }

        return $value
    }

    # Read and process the .env file line by line
    $content = Get-Content $EnvFile -Raw
    $lines = $content -split "`n"

    foreach ($line in $lines) {
        $line = $line.Trim()

        # Ignore empty lines and lines starting with '#'
        if ($line.StartsWith('#') -or [string]::IsNullOrWhiteSpace($line)) {
            continue
        }

        # Ignore 'export ' prefix
        if ($line.StartsWith('export ')) {
            $line = $line.Substring(7).Trim()
        }

        # Split by the first '=' only to separate name and value
        $splitIndex = $line.IndexOf('=')
        if ($splitIndex -eq -1) {
            continue
        }

        $name = $line.Substring(0, $splitIndex).Trim()
        $value = $line.Substring($splitIndex + 1).Trim()

        # Validate the variable name
        if ([string]::IsNullOrWhiteSpace($name) -or $name.Contains(' ')) {
            if ($Strict) {
                Throw "Invalid environment variable name: '$name' in line: '$line'"
            }
            continue
        }

        # Remove surrounding quotes from the value and handle escape characters
        $interpolate = $true
        if ($value.StartsWith('"') -and $value.EndsWith('"')) {
            $value = $value.Substring(1, $value.Length - 2) -replace '\"', '"'
        } elseif ($value.StartsWith("'") -and $value.EndsWith("'")) {
            $value = $value.Substring(1, $value.Length - 2) -replace "\\'", "'"
            $interpolate = $false
        }

        # Store the raw value in the hashtable
        $envVars[$name] = $value

        # Resolve and set the environment variable
        $resolvedValue = if ($interpolate) { Resolve-Value -value $value -envVars $envVars } else { $value }

        switch ($resolvedValue.ToLower()) {
            'true' { [System.Environment]::SetEnvironmentVariable($name, $true, "Process") }
            'false' { [System.Environment]::SetEnvironmentVariable($name, $false, "Process") }
            'null' { [System.Environment]::SetEnvironmentVariable($name, $null, "Process") }
            '' { [System.Environment]::SetEnvironmentVariable($name, $null, "Process") }
            default { [System.Environment]::SetEnvironmentVariable($name, $resolvedValue, "Process") }
        }
    }
}
Hosanna answered 6/8 at 13:35 Comment(0)
M
0

Polished and actually working version of what was said above:

Set-Variable EnvFileRelLoc -Option Constant -Value "../../../docker/.env";

Get-Content $EnvFileRelLoc | foreach {
$name, $value = [regex]::split($_, '(?<= |\w+)=(?= |\W+)') ;
if ([string]::IsNullOrWhiteSpace($name) -OR $name.Contains('#') -OR $name.Contains('=')) {
    #   do nothing
} else {
    Write-host "NAME: $name ; VALUE $value";
    Set-Content env:$name $value
}
}
Muscat answered 14/11, 2023 at 23:20 Comment(1)
Anyway to get this to work for a value that contains "==" in the string?Tletski
L
0

To make this easy to access:

  1. Open your Powershell profile (for example with notepad using notepad $PROFILE).
  2. Copy the following code (@jeiea's answer wrapped in a function):
function LoadEnvFile {
  Get-Content .env | foreach {
    $name, $value = $_.split('=')
    if ([string]::IsNullOrWhiteSpace($name) || $name.Contains('#')) {
      continue
    }
    Set-Content env:\$name $value
  }
}
  1. Once you save the profile and restart your shell, you can run ReadEnvFile anywhere to load an .env file in the same directory.

Feel free to swap the contents of the function with anyone else's answer if you like it more.

Lepidus answered 18/1 at 9:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.