2

I am running a batch file in a powershell script.

Powershell script(pw.ps1):

 # Powershell script

$ScriptPath = "C:\Users\Administrator\Documents\bh.bat"

$out = cmd.exe /c "$ScriptPath" 2>&1 | Out-String

$flg = $?

echo "Output: $out"

if ($flg) {
    echo "Worked"
} else {
    echo "Failed"
}

Batch File(bh.bat):

REM Batch File

@echo on

Setlocal EnableDelayedExpansion

schtasks /query /tn T1

if %errorlevel% NEQ 1 (
  echo "Task already scheduled"
) else (
  echo "Task not scheduled"
)

exit /b 0

Output of powershell script:

 PS C:\Users\Administrator\Documents> C:\Users\Administrator\Documents\pw.ps1
Output: 
C:\Users\Administrator\Documents>REM Batch File 

C:\Users\Administrator\Documents>Setlocal EnableDelayedExpansion 

C:\Users\Administrator\Documents>schtasks /query /tn T1 
cmd.exe : ERROR: The system cannot find the path specified.
At C:\Users\Administrator\Documents\pw.ps1:5 char:8
+ $out = cmd.exe /c "$ScriptPath" 2>&1 | Out-String
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (ERROR: The syst...path specified.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError


C:\Users\Administrator\Documents>if 1 NEQ 1 (echo "Task already scheduled" )  else (echo "Task not scheduled" ) 
"Task not scheduled"

C:\Users\Administrator\Documents>exit /b 0 

Failed

PS C:\Users\Administrator\Documents> 

My requirement is as follows:

  1. Powershell script should output "Failed" only if batch script fails with some errors such as syntax or runtime error.
  2. Powershell script should output "Worked" in two cases: task is found or not found. Currently Powershell script gives "Failed" as output if task is not found.

How can I check in the powershell script if the batch file ran successfully?

Abhay Gupta
  • 786
  • 12
  • 30
  • Why not use https://learn.microsoft.com/en-us/powershell/module/scheduledtasks/get-scheduledtask?view=win10-ps instead of using cmd for this? – Santiago Squarzon Mar 10 '21 at 18:57
  • I have added a small reproducer above. I need to perform the task query in batch file only. This is necessary in the code base that I am working in. – Abhay Gupta Mar 10 '21 at 19:06

2 Answers2

2

Change your batch file as follows:

@echo off
setlocal enabledelayedexpansion

schtasks /query /tn T1

:: Analyze the exit code (error level)
:: and map it onto a custom exit code.
:: Note that schtasks doesn't report *specific* error
:: exit codes: 1 means that *something* went wrong,
:: which we assume to be that the task doesn't exist.
if ERRORLEVEL 1 (
  echo Task not scheduled
  set ec=101
) else ( 
  echo Task already scheduled
  set ec=100
)

:: Getting here implies success.
:: Use the custom exit code to report the specific success status.
:: Note: If this batch file is aborted due a syntax error,
::       the caller will see exit code 255. 
exit /b %ec% 

Then invoke it from your PowerShell script as follows:

# Note: 
#  * No need to call via `cmd /c` in this case - you can invoke batch
#    files directly.
#  * Doing so has the added advantage of PowerShell *itself* reporting
#    a statement-terminating error if the batch file cannot b found,
#    which we handle via a try / catch statement.
try {

  $out = & $ScriptPath 2>&1 | Out-String

  # No need for `echo` (alias for `Write-Output`) - implicit output will do.
  "Output: $out"

  # Check the automatic $LASTEXITCODE variable for the exit code from the
  # most recently executed external program.
  # We infer success if we see one of the two custom exit codes.
  $ok = $LASTEXITCODE -in 100, 101

} catch {
  # This only happens if the batch file doesn't exist.
  $ok = $false
  $LASTEXITCODE = 2 # Simulated exit code: File not found.
}

if ($ok) { # task exists or doesn't exist.
  "Worked"
} else {   # something fundamental went wrong
  "Failed with exit code $LASTEXITCODE"
}

Note:

  • While a batch file can have syntax errors - which causes its execution to be aborted and the caller to see exit code 255 - any other error is reported via exit codes (error levels) - and it is up to each command / executable to properly set an exit code, with 0 by convention signaling success, and any nonzero value failure. Not all executables adhere to this convention:

    • Some always report 0, even when error conditions are encountered.
    • Some only ever report two exit codes: 0 for success, and 1 for any error - schtasks.exe falls into this category.
    • Some communicate specific outcomes with specific exit codes, allowing you to detect specific (error conditions).
    • Some, notably robocopy.exe, even use (of necessity) nonzero exit codes to communicate specific success states - this technique is also used in the code above.
  • If the only thing you need to know is whether the batch file exited abnormally, due to a syntax error, you can make do without the custom exit code and simply use
    $ok = $LASTEXITCODE -ne 255

  • Since your batch file sets a specific exit code explicitly with exit /b <n>, direct invocation from PowerShell (with &, the call operator) works fine, but note that
    cmd.exe /c call <batch-file> - note the call - is generally the most robust way to invoke a batch file - see this answer.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • @AbhayGupta, please see my update. I've also restored `2>&1 | Out-String` (I misunderstood the original intent). – mklement0 Mar 11 '21 at 13:11
0

I solved the issue by changing the batch file(bh.bat) as follows:

REM Batch File

@echo on

Setlocal EnableDelayedExpansion

schtasks /query /fo LIST | findstr TaskName | findstr T1 > nul

if %errorlevel% NEQ 1 (
  echo "Task already scheduled"
) else (
  echo "Task not scheduled"
)

exit /b 0

The powershell script remains the same.

Abhay Gupta
  • 786
  • 12
  • 30