Is it possible to get the caller's directory in PowerShell script when calling from CMD or batch file? - powershell

Let's say I type this in CMD from C:\source:
powershell.exe Set-ExecutionPolicy RemoteSigned -File C:\test\test.ps1
In test.ps1 I try to get C:\source as directory without success.
$script_folder = $PSScriptRoot
$myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$myDir
$PSScriptRoot
Both $myDir and $PSScriptRoot returns C:\test\ instead of C:\source.

You could use $PWD which is the Automatic variable for Present Working Directory. When you open PowerShell it should continue to use the same working directory.
From about_automatic_variables
$PWD
Contains a path object that represents the full path of the current directory.
Also MS-DOS is an Operating System which cannot run PowerShell. This is different from cmd.exe aka Command Prompt in Windows.

The automatic variables you are using are information about the script invocation. The location from which the command to launch the script was initiated is part of the environment.
$PWD contains information about the present working directory (nod to posix pwd command). Specifically, $PWD.Path.
Per the about_automatic_variables page (or Get-Help about_automatic_variables), $PSScriptRoot, $PSCommandPath, are properties of $MyInvocation.
See here for an example of using Split-Path -Path $($Global:$MyInvocation.MyCommand.Path) to get the current path.
Recommend a test script:
# TestInvocationAndPWDPaths.ps1
function Test-MyInvocation {
$MyInvocation
}
function Test-PWD {
$PWD
}
'$MyInvocation from script:'
$MyInvocation
'$MyInvocation from function:'
Test-MyInvocation
'$PWD from script:'
$PWD
'$PWD from function'
Test-PWD
Has interesting results. Running this from powershell console, and from ISE, and from command prompt will show you the differences in $MyInvocation.

$MyInvocation.PSScriptRoot gives you the caller scripts folder.
When the caller is command line, this will return $null.
You should be able to use these two facts.
Just want to add that it is a general pitfall in powershell to use $pwd/get-location inside psm1 functions.
Instead inject the full paths as parameters.

Related

How to execute powershell code in current directory from terminal?

I run a powershell command like this
powershell.exe -File C:\GitHub\project\test\run.ps1 "foo bar"
And my current directory is already C:\GitHub\project\test. How can I make the -File argument just start from the current directory?
Thanks
Simply omit the directory path altogether (powershell.exe -File run.ps1 "foo bar") or prefix the script file name with .\ (powershell.exe -File .\run.ps1 "foo bar") in order to run a script located in the current directory.
Note that in cases where you need to use -Command rather than -File, the .\ prefix is required, because PowerShell then treats the CLI arguments as PowerShell code, and for security reasons PowerShell doesn't permit running scripts in the current directory by file name only - see this answer.
See also: about_PowerShell_exe, the documentation of the Windows PowerShell CLI (for PowerShell [Core] v6+, whose executable file name is pwsh, the relevant topic is about_pwsh.)

Powershell file download issue

I'm trying to download the PuTTY executable using PowerShell, but could not get the file on temp path.
My script:
$Url = "https://the.earth.li/~sgtatham/putty/latest/x86/putty.exe"
$Path = "C:%homepath%\AppData\Local\Temp\putty.exe"
$Wc = New-Object System.Net.WebClient
$Wc.DownloadFileAsync($Url,$Path)
I am executing following command via CMD:
powershell.exe "-ExecutionPolicy" "RemoteSigned" "-file" "test.ps1"
You have two problems, both of which need to be corrected for your script to have a chance of working.
The command for executing a Powershell script from within CMD.EXE should not have the arguments quoted:
powershell.exe -ExecutionPolicy RemoteSigned -file test.ps1
To expand a system environment variable from within powershell, you do not surround it with % as you do in CMD. See http://ss64.com/ps/syntax-env.html for more information; assuming that the environment variable HOMEPATH exists, you would reference it in Powershell as $env:homepath, not %homepath%.
The %VAR% form is not used in powershell, this is only used in CMD. In PowerShell you need to use $env:VAR instead.
You can run Get-ChildItem Env: to get a list of all the Environmental Variables you can use.
For your script try this:
$Path = "$env:USERPROFILE\AppData\Local\Temp\putty.exe"
I've used USERPROFILE instead of HOMEPATH as this includes the drive letter so your script will still work if a different letter is used.

powershell will not keep commands in function call

I am trying to call a new powershell environment from powershell that sets a path location and imports a module...
at first glance it appears to do this, however... when trying to run any of the functions in the module (psm1 and ps1 files) it cannot find the module.
it appears to be dropping everything apart form the set location
heres my code
function NewEnv($path)
{
$new = powershell -NoExit -command "&{cpath $path}"
}
function cpath($path)
{
cd $path;
PGCheck $path
Import-Module ".\myModule"
get-Module
}
Found the answer
Powershell -NoExit -command "&{cd $path; PGCheck $path; Import-Module .\mymodule}"

PowerShell: Run command from script's directory

I have a PowerShell script that does some stuff using the script’s current directory. So when inside that directory, running .\script.ps1 works correctly.
Now I want to call that script from a different directory without changing the referencing directory of the script. So I want to call ..\..\dir\script.ps1 and still want that script to behave as it was called from inside its directory.
How do I do that, or how do I modify a script so it can run from any directory?
Do you mean you want the script's own path so you can reference a file next to the script? Try this:
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "My directory is $dir"
You can get a lot of info from $MyInvocation and its properties.
If you want to reference a file in the current working directory, you can use Resolve-Path or Get-ChildItem:
$filepath = Resolve-Path "somefile.txt"
EDIT (based on comment from OP):
# temporarily change to the correct folder
Push-Location $dir
# do stuff, call ant, etc
# now back to previous directory
Pop-Location
There's probably other ways of achieving something similar using Invoke-Command as well.
There are answers with big number of votes, but when I read your question, I thought you wanted to know the directory where the script is, not that where the script is running. You can get the information with powershell's auto variables
$PSScriptRoot # the directory where the script exists, not the
# target directory the script is running in
$PSCommandPath # the full path of the script
For example, I have a $profile script that finds a Visual Studio solution file and starts it. I wanted to store the full path, once a solution file is started. But I wanted to save the file where the original script exists. So I used $PsScriptRoot.
If you're calling native apps, you need to worry about [Environment]::CurrentDirectory not about PowerShell's $PWD current directory. For various reasons, PowerShell does not set the process' current working directory when you Set-Location or Push-Location, so you need to make sure you do so if you're running applications (or cmdlets) that expect it to be set.
In a script, you can do this:
$CWD = [Environment]::CurrentDirectory
Push-Location $MyInvocation.MyCommand.Path
[Environment]::CurrentDirectory = $PWD
## Your script code calling a native executable
Pop-Location
# Consider whether you really want to set it back:
# What if another runspace has set it in-between calls?
[Environment]::CurrentDirectory = $CWD
There's no foolproof alternative to this. Many of us put a line in our prompt function to set [Environment]::CurrentDirectory ... but that doesn't help you when you're changing the location within a script.
Two notes about the reason why this is not set by PowerShell automatically:
PowerShell can be multi-threaded. You can have multiple Runspaces (see RunspacePool, and the PSThreadJob module) running simultaneously withinin a single process. Each runspace has it's own $PWD present working directory, but there's only one process, and only one Environment.
Even when you're single-threaded, $PWD isn't always a legal CurrentDirectory (you might CD into the registry provider for instance).
If you want to put it into your prompt (which would only run in the main runspace, single-threaded), you need to use:
[Environment]::CurrentDirectory = Get-Location -PSProvider FileSystem
This would work fine.
Push-Location $PSScriptRoot
Write-Host CurrentDirectory $CurDir
I often used the following code to import a module which sit under the same directory as the running script. It will first get the directory from which powershell is running
$currentPath=Split-Path ((Get-Variable
MyInvocation -Scope
0).Value).MyCommand.Path
import-module "$currentPath\sqlps.ps1"
I made a one-liner out of #JohnL's solution:
$MyInvocation.MyCommand.Path | Split-Path | Push-Location
Well I was looking for solution for this for a while, without any scripts just from CLI. This is how I do it xD:
Navigate to folder from which you want to run script (important thing is that you have tab completions)
..\..\dir
Now surround location with double quotes, and inside them add cd, so we could invoke another instance of powershell.
"cd ..\..\dir"
Add another command to run script separated by ;, with is a command separator in powershell
"cd ..\..\dir\; script.ps1"
Finally Run it with another instance of powershell
start powershell "cd..\..\dir\; script.ps1"
This will open new powershell window, go to ..\..\dir, run script.ps1 and close window.
Note that ";" just separates commands, like you typed them one by one, if first fails second will run and next after, and next after... If you wanna keep new powershell window open you add -noexit in passed command . Note that I first navigate to desired folder so I could use tab completions (you couldn't in double quotes).
start powershell "-noexit cd..\..\dir\; script.ps1"
Use double quotes "" so you could pass directories with spaces in names e.g.,
start powershell "-noexit cd '..\..\my dir'; script.ps1"

How to reload user profile from script file in PowerShell

I want to reload my user profile from a script file. I thought that dot sourcing it from within the script file would do the trick, but it doesn't work:
# file.ps1
. $PROFILE
However, it does work if I dot source it from PowerShell's interpreter.
Why do I want to do this?
I run this script every time I update my profile and want to test it, so I'd like to avoid having to restart PowerShell to refresh the environment.
If you want to globally refresh your profile from a script, you will have to run that script "dot-sourced".
When you run your script, all the profile script runs in a "script" scope and will not modify your "global" scope.
In order for a script to modify your global scope, it needs to be "dot-source" or preceded with a period.
. ./yourrestartscript.ps1
where you have your profile script "dot-sourced" inside of "yourrestartscript.ps1". What you are actually doing is telling "yourrestartscript" to run in the current scope and inside that script, you are telling the $profile script to run in the script's scope. Since the script's scope is the global scope, any variables set or commands in your profile will happen in the global scope.
That doesn't buy you much advantage over running
. $profile
So, the approach that you marked as the answer may work inside the Powershell command prompt, but it doesn't work inside PowerShell ISE (which, to me, provides a superior PowerShell session) and probably won't work right in other PowerShell environments.
Here's a script that I have been using for a while, and it has worked very well for me in every environment. I simply put this function into my Profile.ps1 at ~\Documents\WindowsPowerShell, and whenever I want to reload my profile, I dot-source the function, i.e.
. Reload-Profile
Here's the function:
function Reload-Profile {
#(
$Profile.AllUsersAllHosts,
$Profile.AllUsersCurrentHost,
$Profile.CurrentUserAllHosts,
$Profile.CurrentUserCurrentHost
) | % {
if(Test-Path $_){
Write-Verbose "Running $_"
. $_
}
}
}
& $profile
works to reload the profile.
If your profile sets aliases or executes imports which fail then you will see errors because they were already set in the previous loading of the profile.
Why are you trying to do this?
Because it is likely to create duplicates (appends to $env:path) and problems with setting constant/readonly objects causing errors.
There was a thread on this topic recently on microsoft.public.windows.powershell.
If you are trying to reset the state of the session there is no way to do this, even using an inner scope ($host.EnterNestedPrompt()) because of the ability to set variables/aliases/... at "all scope".
I found this workaround:
#some-script.ps1
#restart profile (open new powershell session)
cmd.exe /c start powershell.exe -c { Set-Location $PWD } -NoExit
Stop-Process -Id $PID
A more elaborated version:
#publish.ps1
# Copy profile files to PowerShell user profile folder and restart PowerShell
# to reflect changes. Try to start from .lnk in the Start Menu or
# fallback to cmd.exe.
# We try the .lnk first because it can have environmental data attached
# to it like fonts, colors, etc.
[System.Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
$dest = Split-Path $PROFILE -Parent
Copy-Item "*.ps1" $dest -Confirm -Exclude "publish.ps1"
# 1) Get .lnk to PowerShell
# Locale's Start Menu name?...
$SM = [System.Environment+SpecialFolder]::StartMenu
$CurrentUserStartMenuPath = $([System.Environment]::GetFolderPath($SM))
$StartMenuName = Split-Path $CurrentUserStartMenuPath -Leaf
# Common Start Menu path?...
$CAD = [System.Environment+SpecialFolder]::CommonApplicationData
$allUsersPath = Split-Path $([System.Environment]::GetFolderPath($CAD)) -Parent
$AllUsersStartMenuPath = Join-Path $allUsersPath $StartMenuName
$PSLnkPath = #(Get-ChildItem $AllUsersStartMenuPath, $CurrentUserStartMenuPath `
-Recurse -Include "Windows PowerShell.lnk")
# 2) Restart...
# Is PowerShell available in PATH?
if ( Get-Command "powershell.exe" -ErrorAction SilentlyContinue ) {
if ($PSLnkPath) {
$pi = New-Object "System.Diagnostics.ProcessStartInfo"
$pi.FileName = $PSLnkPath[0]
$pi.UseShellExecute = $true
# See "powershell -help" for info on -Command
$pi.Arguments = "-NoExit -Command Set-Location $PWD"
[System.Diagnostics.Process]::Start($pi)
}
else {
# See "powershell -help" for info on -Command
cmd.exe /c start powershell.exe -Command { Set-Location $PWD } -NoExit
}
}
else {
Write-Host -ForegroundColor RED "Powershell not available in PATH."
}
# Let's clean up after ourselves...
Stop-Process -Id $PID
This is only a refinement of the two line script in guillermooo's answer above, which did not get the new PowerShell window into the correct directory for me. I believe this is because $PWD is evaluated in the new PowerShell window's context, which is not the value we want set-location to process.
function Restart-Ps {
$cline = "`"/c start powershell.exe -noexit -c `"Set-Location '{0}'" -f $PWD.path
cmd $cline
Stop-Process -Id $PID
}
By rights it shouldn't work, as the command line it spits out is malformed, but it seems to do the job and that's good enough for me.
since I stumbled onto this several years later, I thought to add that you can use the invocation operator: & to load your profile with the default variable to your profile: $profile.
so, if your session somehow fails to load your profile (happens to me with cmder/conemu) just type:
& $profile
I used this to troubleshoot what profile was taking forever to load.
Start Run:
powershell_ise -noprofile
Then i ran this:
function Reload-Profile {
#(
$Profile.AllUsersAllHosts,
$Profile.AllUsersCurrentHost,
$Profile.CurrentUserAllHosts,
$Profile.CurrentUserCurrentHost
) | % {
if(Test-Path $_){
Write-Verbose "Running $_"
$measure = Measure-Command {. $_}
"$($measure.TotalSeconds) for $_"
}
}
}
. Reload-Profile
Thank you #Winston Fassett for getting me closer to finding my issue.
Pseudo Alias (simulate keys)
If you just want a function to work like an alias in the console, just simulate the key presses to get around having to use the dot source.
# when "reload" is typed in the terminal, the profile is reloaded
# use sendkeys to send the enter key to the terminal
function reload {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait(". $")
[System.Windows.Forms.SendKeys]::SendWait("PROFILE")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
}
screenshot of it working