代码之家  ›  专栏  ›  技术社区  ›  to StackOverflow

在CMD批处理文件中,我可以确定它是否从powershell运行吗?

  •  8
  • to StackOverflow  · 技术社区  · 7 年前

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

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

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

    这大致相当于VisualStudio安装的“开发者命令提示符”快捷方式,它设置运行VS实用程序所需的环境变量。

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

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

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

    有什么方法可以在批处理文件中检测到吗 MyFile.cmd 它是从PowerShell调用的,这样我就可以,例如,向用户显示警告?这需要在没有任何第三方实用程序的情况下完成。

    4 回复  |  直到 7 年前
        1
  •  8
  •   mklement0    7 年前

    Your own answer is robust 虽然由于需要运行PowerShell进程,它的速度通常很慢,但通过 优化用于确定调用shell的PowerShell命令 :

    @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
    

    注意,我添加了一个进程名称检查 pwsh 同时,使解决方案与PowerShell一起工作 核心


    更快的替代方案-虽然不太可靠

    基于以下假设 ,即 :

    系统 PSModulePath 是在注册表中持久定义的(不是 用户特定

    该解决方案依赖于检测网络中是否存在用户特定路径

    @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 :

    窗口 .

    命令提示符 从PowerShell发射 ,上述解决方案将拒绝运行,即使它们应该运行,因为 PetSerAl
    虽然下面的解决方案本身也没有检测到这种情况,但它仍然会打开一个设置了环境变量的可用(尽管是新的)窗口。

    @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,但在 新窗口,基于调用用户更喜欢在PowerShell中工作的假设。
    exit 呼叫关闭窗口。

        2
  •  3
  •   to StackOverflow    7 年前

    正如乔·科克(Joe Cocker)常说的“我靠朋友们的一点帮助过日子”。

    Lieven Keersmaekers ,他的评论让我找到了以下解决方案:

    @echo off
    setlocal
    CALL :GETPARENT PARENT
    IF /I "%PARENT%" == "powershell.exe" GOTO :ISPOWERSHELL
    endlocal
    
    echo Not running from Powershell 
    SET MyEnvVariable=MyValue
    
    GOTO :EOF
    
    :GETPARENT
    SET CMD=$processes = gwmi win32_process; $me = $processes ^| where {$_.ProcessId -eq $pid}; $parent = $processes ^| where {$_.ProcessId -eq $me.ParentProcessId} ; $grandParent = $processes ^| where {$_.ProcessId -eq $parent.ParentProcessId}; $greatGrandParent = $processes ^| where {$_.ProcessId -eq $grandParent.ParentProcessId}; Write-Output $greatGrandParent.Name
    
    for /f "tokens=*" %%i in ('powershell -command "%CMD%"') do SET %1=%%i
    
    GOTO :EOF
    
    :ISPOWERSHELL
    echo.
    echo ERROR: This batch file may not be run from a PowerShell prompt
    echo.
    cmd /c "exit 1"
    GOTO :EOF
    
        3
  •  2
  •   beatcracker    7 年前

    RefreshEnv.cmd Make refreshenv.bat error if powershell.exe is being used .

    beatcracker/detect-batch-subshell . 这是它的副本,以防万一。


    仅当直接从交互式命令处理器会话调用时才会运行的脚本

    脚本将检测它是否从非交互式会话运行( cmd.exe /c detect-batch-subshell.cmd

    非交互式shell包括PowerShell/PowerShell ISE、Explorer等。。。基本上,任何试图通过在单独的 cmd.exe 例如。

    命令提示符

    依赖关系

    1. 命令提示符
    2. detect-batch-subshell.cmd

    输出:

    > detect-batch-subshell.cmd
    
    Running interactively in cmd.exe session.
    

    例子:

    1. 打开 powershell.exe
    2. detect-batch-subshell.cmd

    输出:

    PS > detect-batch-subshell.cmd
    
    detect-batch-subshell.cmd only works if run directly from cmd.exe!
    

    代码

    • detect-batch-subshell.cmd
    @echo off
    
    setlocal EnableDelayedExpansion
    
    :: Dequote path to command processor and this script path
    set ScriptPath=%~0
    set CmdPath=%COMSPEC:"=%
    
    :: Get command processor filename and filename with extension
    for %%c in (!CmdPath!) do (
        set CmdExeName=%%~nxc
        set CmdName=%%~nc
    )
    
    :: Get this process' PID
    :: Adapted from: http://www.dostips.com/forum/viewtopic.php?p=22675#p22675
    set "uid="
    for /l %%i in (1 1 128) do (
        set /a "bit=!random!&1"
        set "uid=!uid!!bit!"
    )
    
    for /f "tokens=2 delims==" %%i in (
        'wmic Process WHERE "Name='!CmdExeName!' AND CommandLine LIKE '%%!uid!%%'" GET ParentProcessID /value'
    ) do (
        rem Get commandline of parent
        for /f "tokens=1,2,*" %%j in (
            'wmic Process WHERE "Handle='%%i'" GET CommandLine /value'
        ) do (
    
            rem Strip extra CR's from wmic output
            rem http://www.dostips.com/forum/viewtopic.php?t=4266
            for /f "delims=" %%x in ("%%l") do (
                rem Dequote path to batch file, if any (3rd argument)
                set ParentScriptPath=%%x
                set ParentScriptPath=!ParentScriptPath:"=!
            )
    
            rem Get parent process path
            for /f "tokens=2 delims==" %%y in ("%%j") do (
                rem Dequote parent path
                set ParentPath=%%y
                set ParentPath=!ParentPath:"=!
    
                rem Handle different invocations: C:\Windows\system32\cmd.exe , cmd.exe , cmd
                for %%p in (!CmdPath! !CmdExeName! !CmdName!) do (
                    if !ParentPath!==%%p set IsCmdParent=1
                )
    
                rem Check if we're running in cmd.exe with /c switch and this script path as argument
                if !IsCmdParent!==1 if %%k==/c if "!ParentScriptPath!"=="%ScriptPath%" set IsExternal=1
            )
        )
    )
    
    if !IsExternal!==1 (
        echo %~nx0 only works if run directly from !CmdExeName!^^!
        exit 1
    ) else (
         echo Running interactively in !CmdExeName! session.
     )
    
    endlocal
    
        4
  •  2
  •   sst    7 年前

    beatcracker 我认为最好不要假设可以用来启动批处理脚本的外部shell,例如,通过bashshell运行批处理文件时也会出现问题。

    CMD WMI ,执行时间非常快。

    @echo off
    call :IsInvokedInternally && (
        echo Script is launched from an interactive CMD shell or from another batch script.
    ) || (
        echo Script is invoked by an external App. [PowerShell, BASH, Explorer, CMD /C, ...]
    )
    exit /b
    
    :IsInvokedInternally
    setlocal EnableDelayedExpansion
    :: Getting substrings from the special variable CMDCMDLINE,
    :: will modify the actual Command Line value of the CMD Process!
    :: So it should be saved in to another variable before applying substring operations.
    :: Removing consecutive double quotes eg. %systemRoot%\system32\cmd.exe /c ""script.bat""
    set "SavedCmdLine=!cmdcmdline!"
    set "SavedCmdLine=!SavedCmdLine:""="!"
    set /a "DoLoop=1, IsExternal=0"
    set "IsCommand="
    for %%A in (!SavedCmdLine!) do if defined DoLoop (
        if not defined IsCommand (
            REM Searching for /C switch, everything after that, is CMD commands
            if /i "%%A"=="/C" (
                set "IsCommand=1"
            ) else if /i "%%A"=="/K" (
                REM Invoking the script with /K switch creates an interactive CMD session
                REM So it will be considered an internal invocatoin
                set "DoLoop="
            )
        ) else (
            REM Only check the first command token to see if it references this script
            set "DoLoop="
    
            REM Turning delayed expansion off to prevent corruption of file paths
            REM which may contain the Exclamation Point (!)
            REM It is safe to do a SETLOCAL here because the we have disabled the Loop,
            REM and the routine will be terminated afterwards.
            setlocal DisableDelayedExpansion
            if /i "%%~fA"=="%~f0" (
                set "IsExternal=1"
            ) else if /i "%%~fA"=="%~dpn0" (
                set "IsExternal=1"
            )
        )
    )
    :: A non-zero ErrorLevel means the script is not launched from within CMD.
    exit /b %IsExternal%
    

    它检查用于启动的命令行 指令 指令 /C script.bat 非CMD shell通常使用它来启动批处理脚本。

    @ 指令

    cmd /c @MyScript.bat & AdditionalCommands