| crashrpt |
A crash reporting system for Windows applications

# Using crprober.exe Tool

Since v1.2.0, CrashRpt provides a tool named crprober.exe that can be used for automating crash report processing. To run the tool, go to `bin` directory and type crprober.exe in the command line.

Note:
  If you run the precompiled tool from `bin` folder and do not compile it yourself, you might experience some problems if Visual C++ 2010 Redistributable files are not installed on your machine. There may be error messages like 'The application configuration is incorrect' and so on. If you do not have Visual C++ 2010 Redistributable on your machine and do not want to install it, please compile the solution yourself as described in Compiling CrashRpt.

Note that crprober.exe shouldn't be distributed to end users. They just won't be able to use it without symbol files.

The tool can be used to open error report files generated by any version of CrashRpt. However it works the best with reports created by version 1.1 and higher.

Typically an error report file contains an associated MD5 hash file. The crprober tool can perform integrity check using the MD5 hash file. When you receive a report using HTTP connection, the MD5 hash can be retrieved from 'md5' script argument. When you receive a report using E-mail, the hash is attached to the mail message. For more information, see Sending Error Reports.

The tool extracts information about the error report (its GUID, version of CrashRpt that generated the report, creation time in UTC format), the information about the application that sent the report (name, version, path to executable file, the list of loaded modules), information about the operating system (name, version, service pack, CPU info), and information about the exception (exception address, exception code, stack trace). It presents the extracted information in text form (creates a text file or writes to the terminal).

The error report can't be processed properly without symbol files (*.PDB) generated by Visual Studio for your application. You typically store the symbol files locally when preparing to software release. For more information, see Preparing to Software Release.

The crprober.exe is a console tool. It accepts several command line arguments. The arguments are listed in the table below:

Name | Description
/? | Prints the usage help.
/f <input_file> | Required. Absolute or relative path to input ZIP file name.
/fmd5 <md5_file_or_dir> | Optional. Path to .md5 file containing MD5 hash for the <input_file> or directory name where to search for the .md5 file. If this parameter is omitted, the .md5 file is searched in the directory where <input_file> is located.
/o <out_file_or_dir> | Optional. Output file name or directory name. Or use empty name "" to direct output to terminal. If this parameter is omitted, output is not generated.
/sym <sym_search_dirs> | Optional. Symbol files search directory or list of directories separated with semicolon. If this parameter is omitted, symbol files are searched using the default search sequence.
/ext <extract_dir> | Optional. Specifies the directory where to extract all files contained in error report. If this parameter is omitted, files are not extracted.
/get <table_id> <column_id> <row_id> | Optional. Specifies the table ID, column ID and row index of the property to retrieve. If this parameter specified, the property is written to the output file or to terminal, as defined by /o parameter. For the list of properties you can retrieve, see Using CrashRptProbe API.

The crprober tool can return one of the following return codes:

Return Code | Meaning
0 | OK.
1 | Unexpected error.
2 | Invalid command line argument. Ensure you pass correct parameters.
3 | Integrity check failed (MD5 hash mismatch)
4 | Error extracting file. Ensure the extraction path is correct.

## Examples of Use

The following example processes the 'error_report.zip' file and writes output to the terminal. Symbol files are assumed to be located in 'D:\Symbol Files' directory.

    crprober.exe /f error_report.zip /o "" /sym "D:\Symbol Files"

The following example opens 'error_report.zip' file located in the current directory, performs integrity check using 'error_report.zip.md5' file and writes output to the 'error_report.zip.txt' file. Symbol files are assumed to be located in 'D:\Symbol Files' or in 'D:\MyApp\sym' directory.

    crprober.exe /f error_report.zip /md5 error_report.zip.md5 /o error_report.zip.txt /sym "D:\Symbol Files;D:\MyApp\sym"

The following example retrieves the application name from the error report and writes it to terminal:

    crprober.exe /f error_report.zip /o "" /sym "D:\Symbol Files;D:\MyApp\sym" /get XmlDescMisc AppName 0

The following example retrieves the count of rows in the MdmpModules table and writes it to terminal:

    crprober.exe /f error_report.zip /o "" /sym "D:\Symbol Files;D:\MyApp\sym" /get MdmpModules RowCount 0

## Real-Life Usage Scenario

Let's assume you receive error reports over E-mail. To automate error reports extraction from E-mail attachments, you may use some extension of your mail program. For example, if you use Mozilla Thunderbird, you can use Attachment Extractor extension. You can configure the extension to determine the error report messages on the fly and extract email attachments (ZIP and MD5 files) to some folder.

When you have ZIP and MD5 files stored in the directory, you may want to process them all at once. You may want to process reports from the latest version of your application, ignoring all others. You may want to group similar error reports, for example, by stack trace of the exception thread. You may want to copy processed reports to some other directory, leaving not processed reports where they are. You also may want to move corrupted error reports to some folder, to move error reports not having exception information to another folder, and error reports not having symbols loaded for any stack frame to the third folder. You can automate this task using a batch (BAT) script.

Below an example batch script is presented (you can find the script in processing\scripts\postprocess.bat file).

- The script recursively searches INPUT_DIR folder and processes files matching the pattern defined by INPUT_FILE_PATTERN variable. For example, "*.zip" pattern means to process all ZIP files in a directory.
- The script accepts only reports having application name as defined by ACCEPTABLE_APPNAME variable. Keep this empty to accept all appnames.
- The script accepts only reports having application version as defined by ACCEPTABLE_APPVERSION variable. Keep this empty to accept all app versions.
- The script searches for symbol files in SYM_SEARCH_DIRS. This may be semicolon-separated list of directories.
- The script saves processed reports and output text files to SAVE_RESULTS_TO_DIR directory. It groups error reports by similar stack traces, placing them to a single subfolder. Error reports not having exception information are placed into NoExceptionInfo subfolder, and error reports not having symbols loaded for any stack trace are placed to NoSymbolsLoaded subfolder.
- The script moves corrupted error reports to the folder defined by SAVE_INVALID_REPORTS_TO_DIR parameter.

    @echo off
    rem Process a group of ZIP error report files

    set INPUT_DIR="E:\ErrorReports"
    set INPUT_FILE_PATTERN="*.zip"
    set ACCEPTABLE_APPNAME="CrashRpt Tests"
    set ACCEPTABLE_APPVERSION="1.2.0"
    set SYM_SEARCH_DIRS="D:\Projects\CrashRpt\CrashRptSaved\1.2.0"
    set SAVE_RESULTS_TO_DIR="valid_reports\"
    set SAVE_INVALID_REPORTS_TO_DIR="invalid_reports\"
    set CRPROBER_PATH="D:\Projects\CrashRpt\bin\crprober.exe"

    mkdir %SAVE_RESULTS_TO_DIR%

    for /R %INPUT_DIR% %%f in ( %INPUT_FILE_PATTERN% ) do call :process_report "%%f"
    erase temp.txt
    exit

    :process_report

      echo Processing file: %1

      if %ACCEPTABLE_APPNAME%=="" goto appname_ok
      rem Get application name from the crash report file and write it to "temp.txt"
      %CRPROBER_PATH% /f %1 /o "temp.txt" /get XmlDescMisc AppName 0
      if not %errorlevel%==0 goto failed

      set /p app_name=<temp.txt
      if "%app_name%"==%ACCEPTABLE_APPNAME% goto appname_ok
      goto done

    :appname_ok

      if %ACCEPTABLE_APPVERSION%=="" goto appversion_ok
      rem Get application version from the crash report file and write it to "temp.txt"
      %CRPROBER_PATH% /f %1 /o "temp.txt" /get XmlDescMisc AppVersion 0
      if not %errorlevel%==0 goto failed

      set /p app_version=<temp.txt
      if "%app_version%"==%ACCEPTABLE_APPVERSION% goto appversion_ok

      goto done

    :appversion_ok

      set stack_md5=NoExceptionInfo
      %CRPROBER_PATH% /f %1 /o "temp.txt" /sym %SYM_SEARCH_DIRS%  /get MdmpMisc ExceptionThreadStackMD5 0
      if not %errorlevel%==0 goto save_results
      set /p stack_md5=<temp.txt
      erase temp.txt

      if %stack_md5%=="" set stack_md5=NoSymbolsLoaded

    :save_results

      mkdir %SAVE_RESULTS_TO_DIR%%stack_md5%

      rem Process report and write results to text file
      %CRPROBER_PATH% /f %1 /o %1.txt /sym %SYM_SEARCH_DIRS%
      echo Return code=%errorlevel%
      if not %errorlevel%==0 goto failed

    :ok
      move %1 %SAVE_RESULTS_TO_DIR%%stack_md5%
      move %1.txt %SAVE_RESULTS_TO_DIR%%stack_md5%
      goto done

    :failed

     mkdir %SAVE_INVALID_REPORTS_TO_DIR%
     move %1 %SAVE_INVALID_REPORTS_TO_DIR%

    :done

    rem Return from subroutine
    goto :eof

When the script finishes, you will have similar error reports groupped in subfolders. The question is what group to start to analyze first? What groups contain the reports of errors occurring most often? The following simple Python script (you can find the script in processing\scripts\basic_stats.py file) can help you to answer this question by getting the count of .txt files in each subfolder and outputting the results into the text file.

    # This script calculates how many error reports are in each subdirectory
    # and how many error reports are in total.
    # Edit in_dir and out_file parameters as you need.

    import os

    in_dir = "D:/Projects/CrashRpt/valid_reports"
    out_file = "stats.txt"

    f = open(out_file, "w")

    def get_txt_file_count(dirname):
       count = 0
       for root, dirs, files in os.walk(dirname, True):
         for file in files:
            if file[-4:] != ".txt":
                continue;
            count += 1
         break;
       return count

    multimap = dict()
    for root, dirs, files in os.walk(in_dir):
       for dir in dirs:
          dir_name = os.path.join(root, dir)
          report_count_in_dir = get_txt_file_count(dir_name)
          if report_count_in_dir in multimap.keys():
             multimap[report_count_in_dir].append(dir)
          else:
             multimap[report_count_in_dir] = [dir]

    ordered_list = list(multimap.keys())
    ordered_list.sort()
    ordered_list.reverse()

    total_count = 0
    total_groups = 0
    for count in ordered_list:
      total_groups += len(multimap[count]);
      total_count += count * len(multimap[count])

    f.write("Total %d reports (100%%) in %d directories\n"%(total_count, total_groups))

    n = 1
    for key in ordered_list:
      for dir in multimap[key]:
       percent = key/total_count*100
       f.write("%d. %d reports (%0.1f%%) in '%s'\n"%(n, key, percent, dir))
       n = n+1

    f.close()

The expected script output:

    207 error reports in total
    105 error reports in 'subdir1'
    50 error reports in 'subdir2'
    30 error reports in 'subdir3'
    20 error reports in 'subdir4'
    2 error reports in 'subdir5'

So, the 'subdir1' contains the reports about an error reported the most often. And the 'subdir5' contains the reports about an error reported the most rare.