Skip to main content

How to Detect if a Batch Script is Running Under PowerShell or CMD

With the rise of PowerShell as the primary shell for administrators, many Batch scripts are now launched from a PowerShell terminal rather than a traditional Command Prompt. While .bat files are designed for CMD, they can behave differently when called as a child process of PowerShell, especially regarding how special characters are escaped and how errors are piped. Detecting whether your script was "called" from PowerShell allows you to adjust your output or provide specific warnings for your users.

This guide will explain several methods for identifying the parent shell of your running Batch script.

Method 1: The PSModulePath Check (Quick Heuristic)

A quick way to detect a PowerShell environment is to check for PowerShell-specific content in the %PSModulePath% variable. This variable is set in PowerShell sessions and may have different content depending on the host.

Logic: Checking for PowerShell-Specific Environment

@echo off
setlocal

set "ParentShell=CMD"

:: PSModulePath is set by PowerShell and typically contains "WindowsPowerShell"
:: or "PowerShell" in the path. Check for these indicators.
if defined PSModulePath (
echo "%PSModulePath%" | findstr /i /c:"PowerShell" >nul 2>&1
if !errorlevel! equ 0 (
set "ParentShell=PowerShell"
)
)

echo [DETECTED] Parent shell: %ParentShell%

if "%ParentShell%"=="PowerShell" (
echo.
echo [NOTE] You are running this Batch script inside a PowerShell terminal.
echo Be aware that certain redirection and pipe behaviors may vary.
echo Environment variables set by this script will NOT persist in
echo your PowerShell session.
)

:: === Your main script logic goes here ===
echo.
echo Running main tasks...

pause
endlocal
warning

Reliability Note. The PSModulePath variable is now set globally on modern Windows 10/11 systems. This means it will almost always return true even in standard cmd.exe sessions, resulting in false positives. For high-confidence detection, use Method 2.

Method 2: Inspecting the Parent Process (Most Reliable)

The most accurate way is to check the process name of the parent process that launched your cmd.exe instance.

@echo off
setlocal

set "ParentShell=CMD"

:: Use PowerShell to find the grandparent process
:: The process tree is: powershell/pwsh (caller) -> cmd.exe (script) -> powershell.exe (this check)
:: So we must go up two levels from $PID to find the true host
powershell -NoProfile -Command "$p1 = (Get-CimInstance Win32_Process -Filter ('ProcessId='+[string]$PID)).ParentProcessId; $p2 = (Get-CimInstance Win32_Process -Filter ('ProcessId='+[string]$p1)).ParentProcessId; $n = (Get-Process -Id $p2 -ErrorAction SilentlyContinue).Name; if ($n -match 'powershell|pwsh') { exit 0 } else { exit 1 }" 2>nul

if %errorlevel% equ 0 (
set "ParentShell=PowerShell"
)

echo [DETECTED] Parent shell: %ParentShell%

if "%ParentShell%"=="PowerShell" (
echo [INFO] Script was launched from a PowerShell session.
) else (
echo [INFO] Script was launched from CMD or another host.
)

:: === Your main script logic goes here ===

pause
endlocal
info

Why this works. When PowerShell runs a .bat file, it spawns a cmd.exe child process. By walking up the process tree from the current cmd.exe to its parent, we can check whether the parent is powershell.exe (Windows PowerShell) or pwsh.exe (PowerShell 7+).

Why Detection Matters

When a Batch script runs inside PowerShell:

  1. Redirection: Piping output (e.g., myscript.bat | Select-String) follows PowerShell's object-based pipeline logic, which can behave differently from CMD's text-based streaming.
  2. Color Codes: If your script uses ANSI color codes, PowerShell 5.1 and PowerShell 7 interpret them differently than a standard legacy CMD window.
  3. Variable Scope: If your script is meant to set variables for the user's session, it won't work in PowerShell because child processes cannot export variables "up" to a parent PowerShell terminal.

Practical Use Case: Variable Scope Warning

If your script's purpose is to modify the user's PATH or set environment variables, detecting PowerShell allows you to warn the user proactively.

@echo off
setlocal

set "NewPath=C:\MyApp\bin"

:: Check if running under PowerShell (inspect process tree)
powershell -NoProfile -Command "$p1 = (Get-CimInstance Win32_Process -Filter ('ProcessId='+[string]$PID)).ParentProcessId; $p2 = (Get-CimInstance Win32_Process -Filter ('ProcessId='+[string]$p1)).ParentProcessId; $n = (Get-Process -Id $p2 -ErrorAction SilentlyContinue).Name; if ($n -match 'powershell|pwsh') { exit 0 } else { exit 1 }" 2>nul
if %errorlevel% equ 0 (
echo.
echo [WARNING] You are running this from PowerShell.
echo The PATH change below will NOT persist in your session.
echo.
echo To set this in PowerShell, run:
echo $env:PATH += ";%NewPath%"
echo.
)

:: Set the variable (works in CMD, ignored by PowerShell parent)
set "PATH=%PATH%;%NewPath%"
echo [OK] PATH updated for this CMD session.

endlocal

How to Avoid Common Errors

Wrong Way: Simple "where" checks

Checking where powershell.exe only tells you if PowerShell is installed on the computer; it doesn't tell you if the current script is running inside it.

Correct Way: Check the environment variables (Method 1) or the process tree (Method 2) of the active instance.

Problem: False Positives in IDEs

In editors like VS Code or PowerShell ISE, the integrated terminal IS a PowerShell session. Your script will correctly detect PowerShell even if you think you just clicked "Run." This is accurate behavior: the script is running under PowerShell in this context.

Problem: PowerShell 7 (pwsh.exe) vs Windows PowerShell (powershell.exe)

Method 2's parent process check must account for both powershell.exe (Windows PowerShell 5.1) and pwsh.exe (PowerShell 7+). The rewrite's regex -match 'powershell|pwsh' handles both.

Best Practices and Rules

1. Unified Output

If you detect PowerShell, consider removing cls (Clear Screen) commands, as PowerShell users often prefer to see the history of their commands above the script output.

2. Warn About Variable Scope

If your script's purpose is to set environment variables (like adding a bin folder to the PATH), detect the parent shell and warn the user. Provide the equivalent PowerShell command they should use instead (as shown in the Practical Use Case).

3. Handle Escaping

If your script accepts arguments, remember that PowerShell uses the backtick (`) for escaping while CMD uses the caret (^). If you detect a PowerShell host, you may want to sanitize input differently.

Conclusions

Detecting the parent shell is a subtle but important part of building "Console-Aware" automation. By knowing whether your code is executing in a legacy CMD environment or a modern PowerShell terminal, you can provide a smoother, more predictable experience for your users. Whether you use environment variable checks or precise process tree inspection, this environmental awareness ensures your scripts are professional and resilient.