Reload the path in PowerShell - powershell

If I have an instance of PowerShell ISE running and I install something that modifies the PATH or I modify it in any way outside of PowerShell then I need to restart PowerShell for it to see the updated PATH variable.
Is there a way to reload the path from within PowerShell without restarting it?

Just to bring Rob's comment to light:
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")

Try getting the machine path and assigning it to the session's path.
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")

Easiest way, use Chocolatey (freeware). It works for both CMD and PowerShell. Then you will be able to reload PATH (with variable expansion) with a simple command:
refreshenv
Installation from cmd (requires administrator rights):
#"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
Example usage:
> SET JAVA_HOME=c:/java/jdk6
> SET PATH=%JAVA_HOME%/bin
> ECHO %PATH%
c:/java/jdk6/bin
> SET JAVA_HOME=c:/java/jdk8
> refreshenv
Refreshing environment variables from registry for cmd.exe. Please wait...Finished..
> echo %PATH%
c:/java/jdk8/bin

Based on mpen's answer, here is a PowerShell function:
function refresh-path {
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") +
";" +
[System.Environment]::GetEnvironmentVariable("Path","User")
}
Then just call refresh-path.

Just to add to other answers, you can make sure you don't add superfluous joins by filtering in case the user has an empty path.
$env:Path=(
[System.Environment]::GetEnvironmentVariable("Path","Machine"),
[System.Environment]::GetEnvironmentVariable("Path","User")
) -match '.' -join ';'
Or, more usefully, if you're running a script that adds to a different or multiple environment variables, use a function to reset them all
function resetEnv {
Set-Item `
-Path (('Env:', $args[0]) -join '') `
-Value ((
[System.Environment]::GetEnvironmentVariable($args[0], "Machine"),
[System.Environment]::GetEnvironmentVariable($args[0], "User")
) -match '.' -join ';')
}
resetEnv Path
resetEnv AppPath

If your path contains environment variables that weren't defined at the start of the session, you'll want to expand those too:
$env:Path = [System.Environment]::ExpandEnvironmentVariables([System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"))
For me this was useful after installing NVM which defines and adds %NVM_HOME% to the path.
To take this to its logical conclusion you could use this recursive function to expand instead:
function Expand-EnvironmentVariablesRecursively($unexpanded) {
$previous = ''
$expanded = $unexpanded
while($previous -ne $expanded) {
$previous = $expanded
$expanded = [System.Environment]::ExpandEnvironmentVariables($previous)
}
return $expanded
}
And then use:
$env:Path = Expand-EnvironmentVariablesRecursively([System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"))
I've opened an issue to add this solution into refreshenv from Chocolatey.

Related

How to run Powershell script in VS Code using Powershell Integrated Console

I have this little script, which works fine in PowerShell (outside of VS Code):
$a = #{Portfolio = "CALoan"; Folder = "S:\Data\{yymmdd}"; Filename = "LN{yymmdd}.txt"}
$l = #($a)
$l | ForEach-Object -Process {
$p = ($_.Folder + '\' + $_.Filename).Replace("{yymmdd}", "190911")
if (Test-Path $p) {
[pscustomobject] #{Portfolio = $_.Portfolio; Path = $p; CreateTime = (Get-ChildItem $p).CreationTime}
} else {
[pscustomobject] #{Portfolio = $_.Portfolio; Path = $p; CreateTime = "not found"}
}
} | Out-GridView
However, when viewing the script in the VSCode editor, if I just right-click and choose Run Code (using the Code Runner extension), I get tons of errors like this:
PS C:\Users\me> $l | ForEach-Object -Process {
Missing closing '}' in statement block or type definition.
At line:0 char:0
PS C:\Users\me> $p = ($_.Folder + '\' + $_.Filename).Replace("{yymmdd}", "190911")
PS C:\Users\me> if (Test-Path $p) {
Missing closing '}' in statement block or type definition.
At line:0 char:0
PS C:\Users\me> [pscustomobject] #{Portfolio = $_.Portfolio; Path = $p; CreateTime = (Get-ChildItem $p).CreationTime}
Portfolio Path CreateTime
--------- ---- ----------
\ {2/28/2019 12:41:46 PM, 2/20/2019 12:32:15 PM, 1/24/2019 3:54:50 PM, 3/15/2019 1:46:40 PM...}
PS C:\Users\me> } else {
At line:1 char:1
+ } else {
+ ~
Unexpected token '}' in expression or statement.
At line:1 char:8
+ } else {
+ ~
Missing closing '}' in statement block or type definition.
PS C:\Users\me> [pscustomobject] #{Portfolio = $_.Portfolio; Path = $p; CreateTime = "not found"}
Portfolio Path CreateTime
--------- ---- ----------
\ not found
PS C:\Users\me> }
At line:1 char:1
+ }
+ ~
Unexpected token '}' in expression or statement.
PS C:\Users\me> } | Out-GridView
At line:1 char:1
+ } | Out-GridView
+ ~
Unexpected token '}' in expression or statement.
At line:1 char:3
+ } | Out-GridView
+ ~
An empty pipe element is not allowed.
PS C:\Users\me>
It's as if the integrated terminal is executing one line at a time rather than sending the whole script to PowerShell. What is the right way to do this?
BTW If I start a new PowerShell terminal within VS Code, it works as expected (also using Code Runner). So, what's up with the PowerShell Integrated Console and Code Runner?
I can't speak to the Code Runner extension, but you can bypass the problem by installing the PowerShell extension, which is invaluable for both editing and running PowerShell code in Visual Studio Code.
It allows you to run selected code reliably and faster (because no intermediate script file and external PowerShell process is involved - see below) by:
either pressing F8
or right-clicking the selected text and clicking Run Selection.
Caveat:
By default, code you run via the PowerShell extension executes in the same PowerShell session, so that subsequent invocations can be affected by previous ones; e.g., if you highlight a line containing (++$i) and run it repeatedly, the value of $i keeps incrementing.
Turning on setting Create Temporary Integrated Console (via File > Preferences > Settings or Ctrl+,) can be used to change that, so that every invocation creates a new, temporary session to run in.

Exit and Continue script (for example to take PATH environment change effect) [duplicate]

If I have an instance of PowerShell ISE running and I install something that modifies the PATH or I modify it in any way outside of PowerShell then I need to restart PowerShell for it to see the updated PATH variable.
Is there a way to reload the path from within PowerShell without restarting it?
Just to bring Rob's comment to light:
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Try getting the machine path and assigning it to the session's path.
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
Easiest way, use Chocolatey (freeware). It works for both CMD and PowerShell. Then you will be able to reload PATH (with variable expansion) with a simple command:
refreshenv
Installation from cmd (requires administrator rights):
#"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
Example usage:
> SET JAVA_HOME=c:/java/jdk6
> SET PATH=%JAVA_HOME%/bin
> ECHO %PATH%
c:/java/jdk6/bin
> SET JAVA_HOME=c:/java/jdk8
> refreshenv
Refreshing environment variables from registry for cmd.exe. Please wait...Finished..
> echo %PATH%
c:/java/jdk8/bin
Based on mpen's answer, here is a PowerShell function:
function refresh-path {
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") +
";" +
[System.Environment]::GetEnvironmentVariable("Path","User")
}
Then just call refresh-path.
Just to add to other answers, you can make sure you don't add superfluous joins by filtering in case the user has an empty path.
$env:Path=(
[System.Environment]::GetEnvironmentVariable("Path","Machine"),
[System.Environment]::GetEnvironmentVariable("Path","User")
) -match '.' -join ';'
Or, more usefully, if you're running a script that adds to a different or multiple environment variables, use a function to reset them all
function resetEnv {
Set-Item `
-Path (('Env:', $args[0]) -join '') `
-Value ((
[System.Environment]::GetEnvironmentVariable($args[0], "Machine"),
[System.Environment]::GetEnvironmentVariable($args[0], "User")
) -match '.' -join ';')
}
resetEnv Path
resetEnv AppPath
If your path contains environment variables that weren't defined at the start of the session, you'll want to expand those too:
$env:Path = [System.Environment]::ExpandEnvironmentVariables([System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"))
For me this was useful after installing NVM which defines and adds %NVM_HOME% to the path.
To take this to its logical conclusion you could use this recursive function to expand instead:
function Expand-EnvironmentVariablesRecursively($unexpanded) {
$previous = ''
$expanded = $unexpanded
while($previous -ne $expanded) {
$previous = $expanded
$expanded = [System.Environment]::ExpandEnvironmentVariables($previous)
}
return $expanded
}
And then use:
$env:Path = Expand-EnvironmentVariablesRecursively([System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User"))
I've opened an issue to add this solution into refreshenv from Chocolatey.

how to make powershell shorten the long directory before ">"

for example, if I set an alias in profile.ps1
$ws='C:\Users\Jack\folder0\folder1\folder2'
After I cd to the workspace locationcd $ws
It shows as below
PS C:\Users\Jack\folder0\folder1\folder2\>
Now, I'm wondering if there is a way to let it show as below or similar
PS $ws>
It's my first time ask questions on StackOverflow. if there is anything unsuitable, please give me some advice.
You can modify the prompt-function to do whatever you want. If you only want to check a single variable, you can do this:
$ws = "c:\users\frode"
function prompt {
$CurrentLocation = $executionContext.SessionState.Path.CurrentLocation.Path
if($CurrentLocation -like "$ws*") {
$CurrentLocation = $CurrentLocation -replace [regex]::Escape($ws), '$ws'
}
"PS $($CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
}
Output:
PS C:\Users> cd .\frode
PS $ws> cd .\Desktop
PS $ws\Desktop>
If you need to support multiple variables, you can store the paths in a hashtable and check that or use Get-Variable to search through variables that contains a valid path. Remember to exclude ex $PWD which is always your current location.

Piping from a variable instead of file in Powershell

Is ther any way in Powershell to pipe in from an virable instead of a file?
There are commands that I need to pipe into another command, right now that is done by first creating a file with the additional commands, and then piping that file into the original command. Code looks somehting like this now:
$val = "*some command*" + "`r`n" + "*some command*" + "`r`n" + "*some command*"
New-Item -name Commands.txt -type "file" -value $val
$command = #'
db2cmd.exe /C '*custom db2 command* < \Commands.txt > \Output.xml'
'#
Invoke-Expression -Command:$command
So instead of creating that file, can I somehow just pipe in $val insatead of Commands.txt?
Try this
$val = #("*some command*1","*some command2*","*some command3*")
$val | % { db2cmd.exe /C $_ > \Output.xml }
You should be able to pipe in from $val provided you use Write-Output or its shorthand echo, but it may also be worth trying passing the commands directly on the command line. Try this (and if it doesn't work I can delete the answer):
PS C:\> filter db2cmd() { $_ | db2cmd.exe ($args -replace '(\\*)"','$1$1\"') }
PS C:\> $val = #"
>> *custom db2 command*
>> *some command*
>> *some command*
>> *some command*
>> "#
>>
PS C:\> db2cmd /C $val > \Output.xml
What happens here is that Windows executables receive their command line from a single string. If you run them from cmd.exe you cannot pass newlines in the argument string, but Powershell doesn't have that restriction so with many programs you can actually pass multiple lines as a single argument. I don't know db2cmd.exe so it might not work here.
The strange bit of string replacement is to handle any double quotes in the arguments: Powershell doesn't quote them and the quoting rules expected by most exe files are a bit bizarre.
The only limitation here would be that $val must not exceed about 32,600 characters and cannot contain nulls. Any other restrictions (such as whether non-ascii unicode characters work) would depend on the application.
Failing that:
echo $val | db2cmd.exe /C '*custom db2 command*' > \Output.xml
may work, or you can use it in combination with the filter I defined at the top:
echo $val | db2cmd /C '*custom db2 command*' > \Output.xml

How can I get the current PowerShell executing file?

Note: PowerShell 1.0
I'd like to get the current executing PowerShell file name. That is, if I start my session like this:
powershell.exe .\myfile.ps1
I'd like to get the string ".\myfile.ps1" (or something like that). EDIT: "myfile.ps1" is preferable.
Any ideas?
I've tried to summarize the various answers here, updated for PowerShell 5:
If you're only using PowerShell 3 or higher, use $PSCommandPath
If want compatibility with older versions, insert the shim:
if ($PSCommandPath -eq $null) { function GetPSCommandPath() { return $MyInvocation.PSCommandPath; } $PSCommandPath = GetPSCommandPath }
This adds $PSCommandPath if it doesn't already exist.
The shim code can be executed anywhere (top-level or inside a function), though $PSCommandPath variable is subject to normal scoping rules (eg, if you put the shim in a function, the variable is scoped to that function only).
Details
There's 4 different methods used in various answers, so I wrote this script to demonstrate each (plus $PSCommandPath):
function PSCommandPath() { return $PSCommandPath }
function ScriptName() { return $MyInvocation.ScriptName }
function MyCommandName() { return $MyInvocation.MyCommand.Name }
function MyCommandDefinition() {
# Begin of MyCommandDefinition()
# Note: ouput of this script shows the contents of this function, not the execution result
return $MyInvocation.MyCommand.Definition
# End of MyCommandDefinition()
}
function MyInvocationPSCommandPath() { return $MyInvocation.PSCommandPath }
Write-Host ""
Write-Host "PSVersion: $($PSVersionTable.PSVersion)"
Write-Host ""
Write-Host "`$PSCommandPath:"
Write-Host " * Direct: $PSCommandPath"
Write-Host " * Function: $(PSCommandPath)"
Write-Host ""
Write-Host "`$MyInvocation.ScriptName:"
Write-Host " * Direct: $($MyInvocation.ScriptName)"
Write-Host " * Function: $(ScriptName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Name:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Name)"
Write-Host " * Function: $(MyCommandName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Definition:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Definition)"
Write-Host " * Function: $(MyCommandDefinition)"
Write-Host ""
Write-Host "`$MyInvocation.PSCommandPath:"
Write-Host " * Direct: $($MyInvocation.PSCommandPath)"
Write-Host " * Function: $(MyInvocationPSCommandPath)"
Write-Host ""
Output:
PS C:\> .\Test\test.ps1
PSVersion: 5.1.19035.1
$PSCommandPath:
* Direct: C:\Test\test.ps1
* Function: C:\Test\test.ps1
$MyInvocation.ScriptName:
* Direct:
* Function: C:\Test\test.ps1
$MyInvocation.MyCommand.Name:
* Direct: test.ps1
* Function: MyCommandName
$MyInvocation.MyCommand.Definition:
* Direct: C:\Test\test.ps1
* Function:
# Begin of MyCommandDefinition()
# Note this is the contents of the MyCommandDefinition() function, not the execution results
return $MyInvocation.MyCommand.Definition;
# End of MyCommandDefinition()
$MyInvocation.PSCommandPath:
* Direct:
* Function: C:\Test\test.ps1
Notes:
Executed from C:\, but actual script is C:\Test\test.ps1.
No method tells you the passed invocation path (.\Test\test.ps1)
$PSCommandPath is the only reliable way, but was introduced in PowerShell 3
For versions prior to 3, no single method works both inside and outside of a function
While the current Answer is right in most cases, there are certain situations that it will not give you the correct answer. If you use inside your script functions then:
$MyInvocation.MyCommand.Name
Returns the name of the function instead name of the name of the script.
function test {
$MyInvocation.MyCommand.Name
}
Will give you "test" no matter how your script is named.
The right command for getting the script name is always
$MyInvocation.ScriptName
this returns the full path of the script you are executing. If you need just the script filename than this code should help you:
split-path $MyInvocation.PSCommandPath -Leaf
If you only want the filename (not the full path) use this:
$ScriptName = $MyInvocation.MyCommand.Name
Try the following
$path = $MyInvocation.MyCommand.Definition
This may not give you the actual path typed in but it will give you a valid path to the file.
beware:
Unlike the $PSScriptRoot and $PSCommandPath automatic variables, the
PSScriptRoot and PSCommandPath properties of the $MyInvocation automatic
variable contain information about the invoker or calling script, not the
current script.
e.g.
PS C:\Users\S_ms\OneDrive\Documents> C:\Users\SP_ms\OneDrive\Documents\DPM ...
=!C:\Users\S_ms\OneDrive\Documents\DPM.ps1
...where DPM.ps1 contains
Write-Host ("="+($MyInvocation.PSCommandPath)+"!"+$PSCommandPath)
If you are looking for the current directory in which the script is being executed, you can try this one:
$fullPathIncFileName = $MyInvocation.MyCommand.Definition
$currentScriptName = $MyInvocation.MyCommand.Name
$currentExecutingPath = $fullPathIncFileName.Replace($currentScriptName, "")
Write-Host $currentExecutingPath
As noted in previous responses, using "$MyInvocation" is subject to scoping issues and doesn't necessarily provide consistent data (return value vs. direct access value). I've found that the "cleanest" (most consistent) method for getting script info like script path, name, parms, command line, etc. regardless of scope (in main or subsequent/nested function calls) is to use "Get-Variable" on "MyInvocation"...
# Get the MyInvocation variable at script level
# Can be done anywhere within a script
$ScriptInvocation = (Get-Variable MyInvocation -Scope Script).Value
# Get the full path to the script
$ScriptPath = $ScriptInvocation.MyCommand.Path
# Get the directory of the script
$ScriptDirectory = Split-Path $ScriptPath
# Get the script name
# Yes, could get via Split-Path, but this is "simpler" since this is the default return value
$ScriptName = $ScriptInvocation.MyCommand.Name
# Get the invocation path (relative to $PWD)
# #GregMac, this addresses your second point
$InvocationPath = ScriptInvocation.InvocationName
So, you can get the same info as $PSCommandPath, but a whole lot more in the deal. Not sure, but it looks like "Get-Variable" was not available until PS3 so not a lot of help for really old (not updated) systems.
There are also some interesting aspects when using "-Scope" as you can backtrack to get the names, etc. of the calling function(s). 0=current, 1=parent, etc.
Hope this is somewhat helpful.
Ref, https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-variable
I would argue that there is a better method, by setting the scope of the variable $MyInvocation.MyCommand.Path:
ex> $script:MyInvocation.MyCommand.Name
This method works in all circumstances of invocation:
EX:
Somescript.ps1
function printme () {
"In function:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
}
"Main:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
" "
printme
exit
OUTPUT:
PS> powershell C:\temp\test.ps1
Main:
MyInvocation.ScriptName:
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: test.ps1
In function:
MyInvocation.ScriptName: C:\temp\test.ps1
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: printme
Notice how the above accepted answer does NOT return a value when called from Main. Also, note that the above accepted answer returns the full path when the question requested the script name only. The scoped variable works in all places.
Also, if you did want the full path, then you would just call:
$script:MyInvocation.MyCommand.Path
A short demonstration of #gregmac's (excellent and detailed) answer, which essentially recommends $PSCommandPath as the only reliable command to return the currently running script where Powershell 3.0 and above is used.
Here I show returning either the full path or just the file name.
Test.ps1:
'Direct:'
$PSCommandPath # Full Path
Split-Path -Path $PSCommandPath -Leaf # File Name only
function main () {
''
'Within a function:'
$PSCommandPath
Split-Path -Path $PSCommandPath -Leaf
}
main
Output:
PS> .\Test.ps1
Direct:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Within a function:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Did some testing with the following script, on both PS 2 and PS 4 and had the same result. I hope this helps people.
$PSVersionTable.PSVersion
function PSscript {
$PSscript = Get-Item $MyInvocation.ScriptName
Return $PSscript
}
""
$PSscript = PSscript
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
""
$PSscript = Get-Item $MyInvocation.InvocationName
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
Results -
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
This can works on most powershell versions:
(& { $MyInvocation.ScriptName; })
This can work for Scheduled Job
Get-ScheduledJob |? Name -Match 'JOBNAMETAG' |% Command