web-dev-qa-db-ja.com

CMDバッチファイルで、それがpowershellから実行されたかどうかを確認できますか?

一部の環境変数を設定することを目的としたWindowsバッチファイルがあります。

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

ユーザーは、環境変数を必要とする作業を行う前にこれを実行できます。例:

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

これは、VSユーティリティを実行するために必要な環境変数を設定するVisual Studioによってインストールされる「開発者コマンドプロンプト」ショートカットとほぼ同じです。

ただし、ユーザーがたまたまPowershellプロンプトを開いている場合、環境変数はPowershellに反映されません。

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

これは、CMDとPowerShellを切り替えるユーザーを混乱させる可能性があります。

バッチファイルで検出できる方法はありますかMyFile.cmd PowerShellから呼び出されたため、たとえばユーザーに警告を表示できますか?これは、サードパーティのユーティリティなしで行う必要があります。

12
Joe

ジョー・コッカーがよく言ったように「私は友達から少し助けてもらいました」。

この場合、 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
Joe

Chocolateyの RefreshEnv.cmd スクリプトに対して次のようなことを行いました: powershell.exeが使用されている場合は、refreshenv.batエラーを作成します

私のソリューションは、無関係な理由で使用されなくなったわけではありませんが、このリポジトリで利用可能です beatcracker/detect-batch-subshel​​l 。これは念のためコピーです。


対話型のコマンドプロセッサセッションから直接呼び出された場合にのみ実行されるスクリプト

スクリプトは、非インタラクティブセッション(cmd.exe /c detect-batch-subshell.cmd)から実行されているかどうかを検出し、適切なエラーメッセージを表示します。

非対話型シェルには、PowerShell/PowerShell ISE、エクスプローラーなどが含まれます。基本的には、個別のcmd.exeインスタンスでスクリプトを実行してスクリプトを実行しようとするものすべてです。

Hovewer、PowerShell/PowerShell ISEからcmd.exeセッションにドロップし、そこでスクリプトを実行すると機能します。

依存関係

  • wmic.exe -Windowsに付属XP Professional以上。

例:

  1. cmd.exeを開く
  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
2
beatcracker

beatcracker からの回答のように、バッチスクリプトの起動に使用できる外部シェルについては想定しない方がよいと思います。たとえば、バッチファイルを実行するときに問題が発生する可能性もあります。 bashシェル。

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%

CMDシェルの起動に使用されたコマンドラインをチェックして、スクリプトがCMD内から、またはコマンドラインシグネチャ/C script.batを使用して外部アプリによって起動されたかどうかを確認します。非CMDシェルがバッチスクリプトを起動するために使用します。

何らかの理由で外部起動検出をバイパスする必要がある場合、たとえば、定義された変数を利用するために追加のコマンドを使用してスクリプトを手動で起動する場合、@CMD コマンドライン:

cmd /c @MyScript.bat & AdditionalCommands
1
sst