在CMD批处理文件中,我可以确定它是否是从Powershell运行的吗? [英] In a CMD batch file, can I determine if it was run from powershell?

查看:213
本文介绍了在CMD批处理文件中,我可以确定它是否是从Powershell运行的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Windows批处理文件,其目的是设置一些环境变量,例如

I have a Windows batch file whose purpose is to set some environment variables, e.g.

=== MyFile.cmd ===
SET MyEnvVariable=MyValue

用户可以先执行此操作,然后再执行需要环境变量的工作,例如:

Users can run this prior to doing work that needs the environment variable, e.g.:

C:\> MyFile.cmd
C:\> echo "%MyEnvVariable%"    <-- outputs "MyValue"
C:\> ... do work that needs the environment variable

这大致等效于Visual Studio安装的开发人员命令提示符"快捷方式,该快捷方式设置了运行VS实用程序所需的环境变量.

This is roughly equivalent to the "Developer command prompt" shortcuts installed by Visual Studio, which set environment variables needed to run VS utilities.

但是,如果用户碰巧打开了Powershell提示,则环境变量当然不会传播回Powershell:

However if a user happens to have a Powershell prompt open, the environment variable is of course not propagated back to Powershell:

PS C:\> MyFile.cmd
PS C:\> Write-Output "${env:MyEnvVariable}"  # Outputs an empty string

这对于在CMD和PowerShell之间切换的用户可能会造成混淆.

This can be confusing for users who switch between CMD and PowerShell.

有没有一种方法可以在批处理文件MyFile.cmd中检测到它是从PowerShell调用的,因此可以向用户显示警告?这需要在没有任何第三方工具的情况下完成.

Is there a way I can detect in my batch file MyFile.cmd that it was called from PowerShell, so that I can, for example, display a warning to the user? This needs to be done without any 3rd party utility.

推荐答案

您自己的答案是可靠的通常由于需要运行PowerShell进程而变慢,可以通过优化用于确定调用外壳程序的PowerShell命令来显着提高它的速度:

Your own answer is robust and while it is generally slow due to needing to run a PowerShell process, it can be made significantly faster by optimizing the PowerShell command used to determine the calling shell:

@echo off
setlocal
CALL :GETPARENT PARENT
IF /I "%PARENT%" == "powershell" GOTO :ISPOWERSHELL
IF /I "%PARENT%" == "pwsh" GOTO :ISPOWERSHELL
endlocal

echo Not running from Powershell 
SET MyEnvVariable=MyValue

GOTO :EOF

:GETPARENT
SET "PSCMD=$ppid=$pid;while($i++ -lt 3 -and ($ppid=(Get-CimInstance Win32_Process -Filter ('ProcessID='+$ppid)).ParentProcessId)) {}; (Get-Process -EA Ignore -ID $ppid).Name"

for /f "tokens=*" %%i in ('powershell -noprofile -command "%PSCMD%"') do SET %1=%%i

GOTO :EOF

:ISPOWERSHELL
echo. >&2
echo ERROR: This batch file may not be run from a PowerShell prompt >&2
echo. >&2
exit /b 1

在我的计算机上,运行速度大约提高了3-4倍(YMMV),但仍然需要将近1秒钟.

On my machine, this runs about 3 - 4 times faster (YMMV) - but still takes almost 1 second.

请注意,我还添加了对进程名称pwsh的检查,以使解决方案也可以与PowerShell Core 一起使用.

Note that I've added a check for process name pwsh as well, so as to make the solution work with PowerShell Core too.

更快的替代方法-尽管不那么可靠:

下面的解决方案基于以下假设,在默认安装中为 true :

注册表中仅持久定义了一个名为PSModulePath system 环境变量(而不是 user-specific 一个).

Only a system environment variable named PSModulePath is persistently defined in the registry (not also a user-specific one).

该解决方案依赖于检测PSModulePath中用户特定路径的存在,PowerShell在启动时会自动添加该路径.

The solution relies on detecting the presence of a user-specific path in PSModulePath, which PowerShell automatically adds when it starts.

@echo off
echo %PSModulePath% | findstr %USERPROFILE% >NUL
IF %ERRORLEVEL% EQU 0 goto :ISPOWERSHELL

echo Not running from Powershell 
SET MyEnvVariable=MyValue

GOTO :EOF

:ISPOWERSHELL
echo. >&2
echo ERROR: This batch file may not be run from a PowerShell prompt >&2
echo. >&2
exit /b 1


按需启动新的cmd.exe控制台窗口的替代方法:


Alternative approach for launching a new cmd.exe console window on demand:

在以前的方法的基础上,以下变体只需 在检测到正在从PowerShell中运行时在新的cmd.exe窗口中重新调用该批处理文件.

Building on the previous approach, the following variant simply re-invokes the batch file in a new cmd.exe window on detecting that it is being run from PowerShell.

这不仅为用户带来方便,而且还减轻了上面的解决方案产生误报的问题:在从 PowerShell 启动的交互式cmd.exe会话中运行时,以上 PetSerAl 指出,解决方案即使可以运行也将拒绝运行.
尽管下面的解决方案本身也无法检测到这种情况,但它仍会打开一个设置了环境变量的可用窗口-尽管是新窗口.

This is not only more convenient for the user, it also mitigates the problem of the solutions above yielding false positives: When run from an interactive cmd.exe session that was launched from PowerShell, the above solutions will refuse to run, even though they should, as PetSerAl points out.
While the solution below also doesn't detect this case per se, it still opens a useable - albeit new - window with the environment variables set.

@echo off
REM # Unless already being reinvoked via cmd.exe, see if the batch
REM # file is being run from PowerShell.
IF NOT %1.==_isNew. echo %PSModulePath% | findstr %USERPROFILE% >NUL
REM # If so, RE-INVOKE this batch file in a NEW cmd.exe console WINDOW.
IF NOT %1.==_isNew. IF %ERRORLEVEL% EQU 0 start "With Environment" "%~f0" _isNew & goto :EOF

echo Running from cmd.exe, setting environment variables...

REM # Set environment variables.
SET MyEnvVariable=MyValue

REM # If the batch file had to be reinvoked because it was run from PowerShell,
REM # but you want the user to retain the PowerShell experience,
REM # restart PowerShell now, after definining the env. variables.
IF %1.==_isNew. powershell.exe

GOTO :EOF

在设置了所有环境变量之后,请注意最后一个IF语句如何也重新调用PowerShell,但是会在 same 新窗口中基于调用用户喜欢在PowerShell中工作的假设.
新的PowerShell会话将看到新定义的环境变量,但请注意,您需要连续两次exit调用 来关闭窗口.

After setting all environment variables, note how the last IF statement, also re-invokes PowerShell, but in the same new window, based on the assumption that the calling user prefers working in PowerShell.
The new PowerShell session will then see newly defined environment variables, though note that you'll need two successive exit calls to close the window.

这篇关于在CMD批处理文件中,我可以确定它是否是从Powershell运行的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆