her's a function that is similar to shell_exec but captures both stdout and stderr. It's much more complicated than using 2>&1, but it's independent of the shell and should work on windows, too. It uses proc_open with stream_select and non-blocking streams to read stdout and stderr concurrently. It is not guarantied that the lines appear in the correct order or that they are not intermingeled - but it did not happened when I tested it. So, here goes:
<?php
    function runExternal($cmd,&$code) {
        $descriptorspec = array(
            0 => array("pipe", "r"),  1 => array("pipe", "w"),  2 => array("pipe", "w") );
        
        $pipes= array();
        $process = proc_open($cmd, $descriptorspec, $pipes);
        
        $output= "";
        
        if (!is_resource($process)) return false;
        
        fclose($pipes[0]);
        
        stream_set_blocking($pipes[1],false);
        stream_set_blocking($pipes[2],false);
        
        $todo= array($pipes[1],$pipes[2]);
        
        while( true ) {
            $read= array(); 
            if( !feof($pipes[1]) ) $read[]= $pipes[1];
            if( !feof($pipes[2]) ) $read[]= $pipes[2];
            
            if (!$read) break;
            
            $ready= stream_select($read, $write=NULL, $ex= NULL, 2);
            
            if ($ready === false) {
                break; }
            
            foreach ($read as $r) {
                $s= fread($r,1024);
                $output.= $s;
            }
        }
        
        fclose($pipes[1]);
        fclose($pipes[2]);
        
        $code= proc_close($process);
        
        return $output;
    }
?>
here is how to use it:
<?php
$result= runExternal("ls -l some-file.txt",$code);
print "<pre>";
print $result;
print "</pre>\n";
print "<b>code: $code</b>\n";
?>
enjoy :-)