Environment Variables in PowerShell

Posted by : on

Category : powershell   scripts   environment


Introduction

If you want to have a variable that is available across sessions, that is, in every powershell windows, you need to set an environment variable. You can set environment variables for every users on your machine (system wide), which requires administrator access, or you can set an environment variable for your user only, that will be available on each new powershell or dos sessions, that doesn’t require administrator privileges.


Changing or Adding Environment Variables

Technically, when you add an environment value, it will be added in the Registry under these

  • USER
    HKEY_CURRENT_USER \ Environment
  • SYSTEM
    HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ Environment

All the usual ways to edit the registry are valid, and in PowerShell, you can set an environment variable using the function [System.Environment]::SetEnvironmentVariable .

    [System.Environment]::SetEnvironmentVariable($Name,$Value,[System.EnvironmentVariableTarget]::User)

Using the following possible scopes:

    > [Enum]::GetNames([System.EnvironmentVariableTarget])

    Process
    User
    Machine

Publishing Environement Variables Changes

However, note that modifications to the environment variables do not result in immediate change. For example, if you start another PowerShell session, or another Command Prompt after making the changeanges, the environment variables will reflect the previous (not the current) values. The changes do not take effect until you log off and then log back on.

To effect these changes without having to log off, broadcast a WM_SETTINGCHANGE message to all windows in the system, so that any interested applications (such as Windows Explorer, Program Manager, Task Manager, Control Panel, and so forth) can perform an update.

For example, the following function should propagate the changes to the environment variables used in your PowerShell session:

    function Publish-EnvironmentChanges
    {

        <#
        .SYNOPSIS
            Simulates like the Windows UI : sends a WM_SETTINGCHANGE broadcast to all Windows notifying them of the change to settings so they can refresh their config and you can do it too!
        .DESCRIPTION
            Simulates like the Windows UI : sends a WM_SETTINGCHANGE broadcast to all Windows notifying them of the change to settings so they can refresh their config and you can do it too!
            
        .PARAMETER Timeout 
           Timeout
        .PARAMETER Flags
            SMTO_ABORTIFHUNG 0x0002
            The function returns without waiting for the time-out period to elapse if the receiving thread appears to not respond or "hangs."
            SMTO_BLOCK 0x0001
            Prevents the calling thread from processing any other requests until the function returns.
            SMTO_NORMAL0x0000
            The calling thread is not prevented from processing other requests while waiting for the function to return.
            SMTO_NOTIMEOUTIFNOTHUNG 0x0008
            The function does not enforce the time-out period as long as the receiving thread is processing messages.
            SMTO_ERRORONEXIT 0x0020
            The function should return 0 if the receiving window is destroyed or its owning thread dies while the message is being processed.
        #>

        [CmdletBinding(SupportsShouldProcess)]
        param (
            [Parameter(Mandatory = $false, Position=0)]
            [int]$Timeout = 1000,
            [Parameter(Mandatory = $false, Position=1)]
            [int]$Flags = 2 # SMTO_ABORTIFHUNG: return if receiving thread does not respond (hangs)
        )
        $TypeAdded = $True
        try{
            [WinAPI.RegAnnounce]$test
        }catch{
            $TypeAdded = $False
            Write-Verbose "WinAPI.RegAnnounce not declared..."
        }

        $Result = $True
        $funcDef = @'

            [DllImport("user32.dll", SetLastError = true, CharSet=CharSet.Auto)]

             public static extern IntPtr SendMessageTimeout (
                IntPtr     hWnd,
                uint       msg,
                UIntPtr    wParam,
                string     lParam,
                uint       fuFlags,
                uint       uTimeout,
            out UIntPtr    lpdwResult
             );

    '@

        if($TypeAdded -eq $False){
             Write-Verbose "ADDING WinAPI.RegAnnounce"
            $funcRef = add-type -namespace WinAPI -name RegAnnounce -memberDefinition $funcDef
        }
        
        try{
            $HWND_BROADCAST   = [intPtr] 0xFFFF
            $WM_SETTINGCHANGE = 0x001A  # Same as WM_WININICHANGE
            $fuFlags          = $Flags  
            $timeOutMs        = $Timeout  # Timeout in milli seconds
            $res              = [uIntPtr]::zero

            # If the function succeeds, this value is non-zero.
            $funcVal = [WinAPI.RegAnnounce]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::zero, "Environment", $fuFlags, $timeOutMs, [ref] $res);

            if ($funcVal -eq 0) {
               throw "SendMessageTimeout did not succeed, res= $res"
            }
            else {
               write-Verbose "Message sent"
               return $True
            }
        }
        catch{
            $Result = $False
            Write-Error $_
        }
        return $Result
    }

Set-EnvironmentVariable

Wrapping things up, to set an environment variable, this helper function will be useful :

    Function Set-EnvironmentVariable{
        [CmdletBinding(SupportsShouldProcess)]
            param(
            [parameter(mandatory=$true, Position=0)]
            [String]$Name,
            [parameter(mandatory=$true, Position=1)]
            [String]$Value,
            [parameter(mandatory=$false)]
            [ValidateSet('User', 'Machine', 'Session', 'UserSession')]
            [String]$Scope='UserSession'
            )
        switch($Scope.ToLower())
        {
            { 'session','usersession' -eq $_ } 
            { 
                $CurrentSetting=( Get-ChildItem -Path env: -Recurse | % -process { if($_.Name -eq $Name) {$_.Value} })
             
                if(($CurrentSetting -eq $null) -Or ($CurrentSetting -ne $null -And $CurrentSetting.Value -ne $Value)){
                    Write-Verbose "Environment Setting $Name is not set or has a different value, changing to $Value"
                    $TempPSDrive = $(get-date -Format "temp\hhh-\mmmm-\sss")
                    new-psdrive -name $TempPSDrive -PsProvider Environment -Root env:| Out-null
                    $NewValPath=( "$TempPSDrive" + ":\$Name")
                    Remove-Item -Path $NewValPath -Force -ErrorAction Ignore | Out-null
                    New-Item -Path $NewValPath -Value $Value -Force -ErrorAction Ignore | Out-null
                    Remove-PSDrive $TempPSDrive -Force | Out-null
                }
            }
            { 'user','usersession' -eq $_ } 
            { 
                Write-Verbose "Setting $Name --> $Value [User]"
                [System.Environment]::SetEnvironmentVariable($Name,$Value,[System.EnvironmentVariableTarget]::User)
            }
            { 'machine' -eq $_ }
            {  
                Write-Verbose "Setting $Name --> $Value [Machine]"
                [System.Environment]::SetEnvironmentVariable($Name,$Value,[System.EnvironmentVariableTarget]::Machine)
            }
        }
        Publish-EnvironmentChanges
}



About Guillaume Plante
Guillaume Plante

A developper with a passion for technology, music, astronomy and art. Coding range: hardware/drivers, security, ai,. c/c++, powershell

Email : guillaumeplante.qc@gmail.com

Website : https://arsscriptum.ddns.net

Useful Links