In a CMD batch file, can I determine if it was run from powershell?

  • A+
Category:Languages

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 

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

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 

This can be confusing for users who switch between CMD and 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.

 


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 

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

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


Much faster alternative - though less robust:

The solution below relies on the following assumption, which is true in a default installation:

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

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 

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

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.

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 

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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: