问题
I have a small "dev.bat" batch file on my PATH which I run to switch to my development project directory in W:\
. This works fine from CMD but not when run from PowerShell (or PWSH).
I have no other problems running .bat files from PowerShell.
PS C:\> type C:\dev.bat
W:
CD W:\dev
PS C:\> dev.bat
me@computer C:\
> W:
me@computer W:\dev
> CD W:\dev
PS C:\> echo "Why did dev.bat not change directory??"
Why did dev.bat not change directory??
PS C:\> W:
PS W:\>
No, cmd /c dev.bat
makes no difference.
回答1:
When run from PowerShell, batch files invariably run in a (cmd.exe
) child process[1], given that PowerShell itself doesn't understand the batch language.
Changing the working directory in a child process is limited to that child process (and its own children), and has no effect on the calling process; a child process cannot change the calling process' working directory.
Your only option is to:
- have your batch file echo (print) the desired working directory
- capture that path in PowerShell and pass it to
Set-Location
If you don't want to change your batch file, use the following workaround:
Set-Location -LiteralPath (cmd /c 'dev.bat >NUL && cd')
# Or if you want to use the 'cd' alias for Set-Location and
# are confident that path never has "[" characters in it (so that
# it can't be mistaken for a wildcard expression):
cd (cmd /c 'dev.bat >NUL && cd')
If batch files needn't be involved at all, and you just want a convenient way to create custom functions that change to a predefined location (working directory), place the following function in your $PROFILE
file:
# Helper function to place in $PROFILE, which generates custom quick-cd
# functions, based on a function name and target directory path.
function New-QuickCD ($Name, $LiteralPath) {
$funcDef = @"
function global:$Name { Push-Location -LiteralPath "$LiteralPath" } # quick-CD function
"@
Invoke-Expression $funcDef # define in current session too
$funcDef >> $PROFILE # append to $PROFILE
}
Note:
The generated functions use
Push-Location
rather thanSet-Location
to enable easy returning to the previous location withPop-Location
(popd
).For convenience, generated functions are also defined in the current session via
Invoke-Expression
[2] on creation, so you don't have to reload (dot-source)$PROFILE
or open a new session before you can call the newly generated function.Blindly appending to
$PROFILE
with>>
means that if you redefine a function, the new definition will take effect, but the obsolete previous one will linger in the file, requiring manual cleanup; the comment# quick-CD function
placed after each generated function is meant to facilitate that - see the bottom section for a more sophisticated version ofNew-QuickCD
that updates old definitions in place.You can make the function more robust and convenient in a variety of ways: making the parameters mandatory, verifying the path's existence (by default), resolving the path to an absolute one - again, see the bottom section.
E.g., to create a function named dev
that switches to W:\dev
, you'd then call:
# Generate function 'dev', which switches to 'W:\dev',
# append it to your $PROFILE file, and also define it in this session:
New-QuickCD dev W:\dev
# Call it:
dev # changes the current location to W:\dev; use 'popd' to return.
More robust, flexible New-QuickCD
function:
It improves on the above version as follows:
- It makes the parameters mandatory.
- It verifies the existence of the target directory path.
- It defines the functions with support for a
-PrintOnly
switch that merely prints the function's target directory, without changing to it. - It resolves a relative path to an absolute one first, so that you can run
New-QuickCD foo .
to define a function that switches to the absolute path of the current location. - When you redefine a function, the previous definition is automatically updated:
- In order to enable this functionality
$PROFILE
is rewritten as a whole, using the>
redirection operator. - To remove functions, you must still edit
$PROFILE
manually.
- In order to enable this functionality
- It comes with comment-based help; run
help New-QuickCD -Examples
, for instance.
function New-QuickCD {
<#
.SYNOPSIS
Creates a custom quick-CD function.
.DESCRIPTION
Creates a custom quick-CD function and appends it your $PROFILE file.
Such a function changes to a fixed location (directory) stored inside the
function, specified at creation time to allow for quickly changing to
frequently used directories using a short name.
For convenience, a newly created function is also defined for the running
session (not just for all future sessions).
The quick-CD functions use Push-Location to change location, which
enables you to easily return to the previously active location with
Pop-Location (popd).
To determine what location a given quick-CD function *would* change to,
invoke it with the -PrintOnly switch.
.PARAMETER FunctionName
The name of the quick-CD function to define.
.PARAMETER DirectoryPath
The literal path of the directory the quick-CD function should change to.
If given a relative path, it is resolved to an absolute one first.
For convenience, you may specify a *file* path, in which case that file's
parent path is used.
.NOTES
Your $PROFILE file is recreated every time you use this function, using the
> redirection operator, so as to support updating functions in place.
To *remove* a quick-CD function, edit $PROFILE manually.
.EXAMPLE
New-QuickCD dev W:\dev
Adds a 'dev' function to $PROFILE, which on invocation changes the current
location to W:\dev
* Call just 'dev' to change to W:\dev. Use popd to return to the previous
location.
* Call 'dev -PrintOnly' to print what location function 'dev' *would*
change to.
.EXAMPLE
New-QuickCD proj .
Adds a 'proj' function to $PROFILE, which on invocation changes to the
the location that is current at the time of calling New-QuickCd.
#>
param(
[Parameter(Mandatory)] [string] $FunctionName,
[Parameter(Mandatory)] [string] $DirectoryPath
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
# Resolve the path to a full path. Fail if it doesn't exist.
$fullPath = (Resolve-Path -ErrorAction Stop -LiteralPath $DirectoryPath).Path
# As a courtesy, if the path is a *file*, we use its parent path instead.
if (Test-Path -PathType Leaf $fullPath) {
$fullPath = [IO.Path]::GetDirectoryName($fullPath)
}
# Define a comment that identifies the functions we add to $PROFILE as
# quick-CD functions.
$idComment = '<# quick-CD function generated with New-QuickCD #>'
# Generate the new function's source code...
# * on a *single line*, which enables easy filtering when updating $PROFILE below
# * with a distinctive comment at the end of the line that identifies the
# function as a quick-CD function.
# * with the global: scope specifier, which makes it easier to call the
# same definition with Invok-Expression to make the function available in the
# current session too.
$newFuncDef = @"
$idComment function global:$FunctionName { param([switch] `$PrintOnly) if (`$PrintOnly) { "$fullPath" } else { Push-Location -LiteralPath "$fullPath" } }
"@
# ... define it in the current session (doing this *before* updating $PROFILE ensures early exit if the function name is invalid)
Invoke-Expression $newFuncDef
# ... and update $PROFILE:
# Get the current content of $PROFILE
[string] $currentProfileContent = if (Test-Path -LiteralPath $PROFILE) { Get-Content -Raw -LiteralPath $PROFILE }
# Try to replace an existing definition.
$newProfileContent = $currentProfileContent -replace ('(?m)^{0} function global:{1} .+$' -f [regex]::Escape($idComment), [regex]::Escape($FunctionName)), $newFuncDef
if (-not $currentProfileContent -or $newProfileContent -ceq $currentProfileContent) { # Profile didn't exist or nothing was replaced -> we must append the new definition.
$newProfileContent = $newProfileContent.TrimEnd() + [Environment]::NewLine * 2 + $newFuncDef
}
# Write the file.
$newProfileContent > $PROFILE
}
[1] By contrast, batch files run in-process when invoked from cmd.exe
, analogous to how PowerShell runs its *.ps1
scripts in-process.
POSIX-like shells such as Bash, on the other hand, by default run their scripts in a child process, except when sourcing is used (.
, source
)
[2] While this is a safe use of Invoke-Expression
, it should generally be avoided.
回答2:
@mkelement is correct: there is no simple way to do this from a .bat file on your path - that is old school. The proper PowerShell way is to create an alias to a function which does what you want.
Borrowing from this answer my solution is:
Step 1: Create a reusable function to make an alias:
PS> echo 'function myAlias {
$g=[guid]::NewGuid();
$alias = $args[0]; $commands = $args[1];
echo "function G$g { $commands }; New-Alias -Force $alias G$g">>$profile
};'>>$profile
Re-start powershell (to load the above function) and then define your dev
shortcut as follows:
Step 2: Create a dev
shortcut/alias which gets you where you want to be:
PS> myAlias dev "Set-Location W:\dev"
Step 3: Happily use dev
PS C:\> dev
PS W:\dev>
来源:https://stackoverflow.com/questions/55707051/batch-file-to-change-directory-run-from-within-powershell-does-nothing