Back to Home

How I fought with Microsoft Word!

Word · php · doc · COM · OLE

How I fought with Microsoft Word!

    I would like to tell one story that happened to me. In my opinion, it is quite interesting, and can help someone with a similar problem. I must say right away - this is my first post on the hub :-) It turned out to be a bit long, so I made the main conclusions separately - at the end of the article.

    So, the task:
    There is a portal to which Microsoft Word documents are downloaded in Doc format. Before publishing to the public, they must be processed. How exactly is not important, so for simplification we take the following algorithm:
    1. Create a new document.
    2. Paste data from source (insert file).
    3. Save the received file instead of the original.


    I will say right away: an additional server with Win2k3 + Office + Apache + PHP5 on board is engaged in Doc files. Perhaps, in the comments, I will find the answer, how can I perform similar manipulations on FreeBSD :-)

    Answer to the first question: Why is this processing necessary?
    Through trial and error, it was concluded that this is the minimum sequence of actions that must be performed for all documents.
    There are two reasons for this:
    • Firstly, when you insert a file, only its contents are inserted (text, formatting, pictures), and the macros are left overboard. This allows you to get rid of malicious code that can get into your Normal.dot :-) Of course, if you need to save macros, this option will not work, but viruses are not needed for most tasks .
    • Secondly, if the source file was locked for writing, then you cannot change it. A new document will not inherit this lock.


    The answer to the second question: How was the processing of documents implemented?
    Through trial and error several generations of processors have been made. The fact is that, in the beginning, a normally working program started to generate errors after a while. As a result, processing was stopped. And when stops became too frequent, you had to admit the failure of the previous decision and invent a new one.

    So.
    First generation: “mainscript.exe”
    This script ran Word with command-line options. One of them is the name of the file being processed, and the second is the name of the processing macro. After 2 minutes, the program killed the process it started. The script itself was run through CGI.
    Of the minuses, it can be noted:
    1. the program worked exactly 2 minutes
    2. frequent freezes and programs and Word. As a result, a bunch of hanging processes winword.exe and mainscript.exe.


    Second generation: simple php - COM / OLE
    If the first generation was implemented before me, then the second is my add-ons over the first. A simple script was written that implements the same actions from a macro. One script was replaced by another. Locally, everything worked perfectly. Processing the average file took 15 seconds (instead of 2 minutes). The script is transferred to the server. Testing. The same 15 seconds. We start the work, and ...
    Winwards fall, hang, slow down ... Exceptions constantly appear. In addition, php scripts hang for a time exceeding max_execution_time and sag only if they throw the running winword.exe process. The reasons are not clear, Google leads to an articlein which Microsoft warns that the office is not intended for server applications and its behavior in my situation is unpredictable. It was also not recommended to run more than one winword process there. No sooner said than

    done:
    Third generation: monopolization of access to winword.exe
    Another processing request checks if Word is already running or not. This check can be done through the database or pid-file. I did through the DB. The value of the flag field increases with each request. If there were a lot of requests, it means that the busy process is hung, and then the killword.exe magic program starts. It stupidly kills all processes called winword.exe.

    It seems that everything works fine, but over time, the run time of the script has increased, and more often you need to run killword. Permanent Exception ...

    Fourth generation: Release of the handler. A
    painstaking search for the causes of errors gave a very interesting result. After opening / saving the file, control returns to the php script, but at the same time the COM server remains busy for some time. And if at this moment we send the following command to the COM server, then we will fly to Exception . After that, Word can be killed and started all over again.

    The problem was solved by simply copying to a temporary folder. It turned out that Word scans the folder for temporary files, or something else. Over time, the number of files in the source folder increased and the script began to crash.

    In general, the last scheme of work turned out to be the most efficient. Sometimes errors pop up, such as “out of memory”, but this is quite rare and is still a secret :-)

    So: the DOC
    handler operation scheme. The handler consists of two files: tmanager.php and handler.php
    1. tmanger.php
    This is a daemon that works constantly (or periodically starts through the task scheduler). Every n seconds, he checks the table in the database for the next request for processing (the so-called queue).
    If there is an application, we perform the following actions:
    1. Delete all files from temporary folders (including% SYSTEM_ROOT% \ TEMP)
    2. Kill all word processes (killword.exe)
    3. Copy source file to temporary folder
    4. Run handler.php (via curl)
    5. Copy the resulting file to a shared folder
    6. Clean up temporary files and unnecessary processes
    7. Delete an entry from the database queue


    2. handler.php
    This is the handler itself. In a separate file, it is taken out in case of a hang. If after some time it does not return the result, then tmanager.php will start killword.
    here is its simplified code:
    1. try {  
    2.   $doc = new COM("Word.Document");
    3.   $doc->Range->InsertFile('E:\\tmp\\tmp1.doc');
    4.  
    5.     // Здесь код для обработки файла
    6.  
    7.   $doc->SaveAs('E:\\tmp\\tmp2.doc');
    8.   $doc = null;
    9. } catch (Exception $e) {
    10.   print '3';
    11.   $doc = null;
    12.   die();
    13. }
    14. print 0;
    15. ?>
    * This source code was highlighted with Source Code Highlighter.


    The tmanager.php script passes the handler.php file to the script for processing. The latter processes it and creates a new file. In case handler.php hangs, tmanager.php just kills winword.

    conclusions
    1. Do not open the files sent to you directly, do the file insertion.
    2. Do not run multiple Winword.exe processes - they may conflict.
    3. Do not open or save files in a folder with a large number of files - Word will try to scan these files and at the same time “slow down”.
    4. Separate the process working with COM from the main process. This will allow you to take some actions when the first hangs.


    Update: Found a link to an article about server automation. Since the last reading, she became in Russian and grew a little.

    Read Next