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

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.

Related

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.

PowerShell - Sorry, we couldn't find Microsoft.PowerShell.Core\FileSystem::

I'm trying to modify the script created by Boe Prox that combines multiple CSV files to one Excel workbook to run on a network share.
When I run it locally, the script executes great and combines multiple .csv files into one Excel workbook.
Clear-Host
$OutputFile = "ePortalMonthlyReport.xlsx"
$ChildDir = "C:\MonthlyReport\*.csv"
cd "C:\MonthlyReport\"
echo "Combining .csv files into Excel workbook"
. C:\PowerShell\ConvertCSVtoExcel.ps1
Get-ChildItem $ChildDir | ConvertCSVtoExcel -output $OutputFile
echo " "
But when I modify it to run from a network share with the following changes:
Clear-Host
# Variables
$OutputFile = "ePortalMonthlyReport.xlsx"
$NetworkDir = "\\sqltest2\dev_ePortal\Monthly_Report"
$ChildDir = "\\sqltest2\dev_ePortal\Monthly_Report\*.csv"
cd "\\sqltest2\dev_ePortal\Monthly_Report"
echo "Combining .csv files into Excel workbook"
. $NetworkDir\ConvertCSVtoExcel.ps1
Get-ChildItem $ChildDir | ConvertCSVtoExcel -output $OutputFile
echo " "
I am getting an error where it looks like it using the network path twice and I am not sure why:
Combining .csv files into Excel workbook
Converting \sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv
naming worksheet 001_StatsByCounty
--done
opening csv Microsoft.PowerShell.Core\FileSystem::\sqltest2\dev_ePortal\Monthly_Report\\sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv) in excel in temp workbook
Sorry, we couldn't find Microsoft.PowerShell.Core\FileSystem::\sqltest2\dev_ePortal\Monthly_Report\\sqltest2\dev_ePortal\Monthly_Report\001_StatsByCounty.csv. Is it possible it was moved, renamed or deleted?
Anyone have any thoughts on resolving this issue?
Thanks,
Because in the script it uses the following regex:
[regex]$regex = "^\w\:\\"
which matches a path beginning with a driveletter, e.g. c:\data\file.csv will match and data\file.csv will not. It uses this because (apparently) Excel needs a complete path, so if the file path does not match, it will add the current directory to the front of it:
#Open the CSV file in Excel, must be converted into complete path if no already done
If ($regex.ismatch($input)) {
$tempcsv = $excel.Workbooks.Open($input)
}
ElseIf ($regex.ismatch("$($input.fullname)")) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
}
Else {
$tempcsv = $excel.Workbooks.Open("$($pwd)\$input")
}
Your file paths will be \\server\share\data\file.csv and it doesn't see a drive letter, so it hits the last option and jams $pwd - an automatic variable of the current working directory - onto the beginning of the file path.
You might get away if you edit his script and change the regex to:
[regex]$regex = "^\w\:\\|^\\\\"
which will match a path beginning with \\ as OK to use without changing it, as well.
Or maybe edit the last option (~ line 111) to say ...Open("$($input.fullname)") as well, like the second option does.
Much of the issues are caused in almost every instance where the script calls $pwd rather than $PSScriptRoot. Replace all instances with a quick find and replace.
$pwd looks like:
PS Microsoft.PowerShell.Core\FileSystem::\\foo\bar
$PSScriptRoot looks like:
\\foo\bar
The second part i fixed for myself is what #TessellatingHeckler pointed out. I took a longer approach.
It's not the most efficient way...but to me it is clear.
[regex]$regex = "^\w\:\\"
[regex]$regex2 = "^\\\\"
$test = 0
If ($regex.ismatch($input) -and $test -eq 0 ) {
$tempcsv = $excel.Workbooks.Open($input)
$test = 1 }
If ($regex.ismatch("$($input.fullname)") -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
$test = 1}
If ($regex2.ismatch($input) -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open($input)
$test = 1 }
If ($regex2.ismatch("$($input.fullname)") -and $test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($input.fullname)")
$test = 1}
If ($test -eq 0) {
$tempcsv = $excel.Workbooks.Open("$($PSScriptRoot)\$input")
$test = 0 }

Get-Help format is different when calling it in a script

I am wondering why my PowerShell get-help outputs like the following image when using a script I've written. The script's purpose is to display get-help information when selecting a function from an array.
#Run this file in the same directory as the Functions file.
#this function validates user input
function getInput
{
do
{
$input = Read-Host "`n>Enter function # to see its description"
}until(([int]$input -gt 0) -and ([int]$input -le $flist.count))
$input
}
#include the script we want
. "$PSScriptRoot\functions.ps1"
#This operates on a loop. After viewing your help info, press key and you will be prompted to choose another function.
$quit = 0
while(!$quit){
#get all functions
$f = #(get-content functions.ps1 | where-object { $_.StartsWith("function", "CurrentCultureIgnoreCase") -and (-not $_.Contains("#")); $c++} | sort-object)
"There are " + $f.count + " functions!"
#split on ' ', get second word (function name), add to array
$flist = #{}
$i = 0
foreach($line in $f){
$temp = $line.split(' ')
$temp[1]
$i++
$flist.add($i, $temp[1])
}
#print, order ascending
$flist.GetEnumerator() | sort -Property name
#accept user input
$input = getInput
#get-help about the chosen function
"Get-Help " + $flist[[int]$input]
Get-Help Add-ADGrouptoLocalGroup | format-list
#Get-Help $flist[[int]$input] -full
Get-Command $flist[[int]$input] -syntax
Pause
}
The target script $PSScriptRoot\Functions.ps1 has a bunch of functions in it. What my script is doing is this:
List all functions found within a target file.
Put their name in an indexed array
Prompt user for which function to get-help on, at a given index
Print get-help and get-syntax on the selected function
Each function has the <#.SYNOPSIS .DESCRIPTION ... etc #> comment block in it (You can see the function's details--from the function's comment help-block--in the provided image). If I run get-help on the function within the target script, it appears to be formatted normally--but that's not the case when using script I've written.
What is really bothering me is the #{Text = 'stuff'} formatting, etc. Thanks ahead of time!
You're piping the output of get-help through format-list. This "overrides" the default formatting PS does on the PSCustomObject (in PS 3.0 at least) that get-help creates. You should be able to just invoke get-help by itself and not pipe it. If that doesn't work, then pipe it through out-default.
See help about_format for more details.

Reload the path in 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.

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