Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Bulk Office Protection Application

4.83/5 (4 votes)
15 Oct 2010CPOL4 min read 27.8K   848  
Adds another layer of security to your Word or Excel documents. Can search for and bulk process documents to add a protection password or change your protection password.

Image 1

Introduction

This project is an upgrade of the Bulk Word Protection Utility posted here. Most of it's been re-written or re-factored since that article. It's a C# application used to search for and loop through all Word or Excel files and change protection settings in bulk.

Of course, the real usefulness is that you can bulk reset properties of Word documents fairly easily. For now, it just works with document protection, but you could mass modify any number of document properties with only a few more lines of code and some more options in the GUI.

Background

If you're not familiar with Word document protection, some info is available here: http://office.microsoft.com/en-us/word/CH010397751033.aspx.

Using the Code

There is a FileSearchClass class, a ProcessDocuments class, and some UI code behind the OfficeAppProtectionForm form.

On the OfficeAppProtectionForm form, there are five regions of code:

  • Form Events: Contains the InitializeComponent and selects a document type on load.
  • Click Events: All [control]_Click events.
  • UI Events: Miscellaneous UI events. Many call other methods on the form.
  • BackgroundWorker Events: Contains the _DoWork, _ProgressChanged, and _RunWorkerCompleted events for the two BackgroundWorker processes.
  • Private Methods: Just a few grunts to do my UI bidding.

I'll spare you the mundane UI code and get into the BackgroundWorkers and classes. First up, the xSearchFolderBackground BackgroundWorker. I will link a separate article just for this class. It's kind of cool by my standards since it estimates the number of sub-directories, accepts a BackgroundWorker from the calling form, and sends updates back to it, and you can have a somewhat accurate ProgressBar.

When you click the xSelectDirectory button and choose a directory to search, the selected directory and the search pattern for the selected document type get appended together and passed to the xSearchFolderBackground.RunWorkerAsync method. Then, the xSearchFolderBackground_DoWork event, which is now running on the BackgroundWorker's thread, creates a new FileSearchClass, sends the BackgroundWorker to the class, splits apart the directory and search pattern, and starts the search. I'll show the details of the FileSearchClass in the other article. Here's the code for the click event and the BackgroundWorker events:

C#
private void xSelectDirectory_Click(object sender, EventArgs e)
// search the selected directory for the file type chosen
{
    this.DoubleBuffered = true;
    if (this.xFolderBrowserDialog.ShowDialog() == DialogResult.OK)
    {
        // if the background worker is not already doing stuff, kick it off
        if (xSearchFolderBackground.IsBusy != true)
        {
            if (this.xFilesListBox.Items.Count > 0)
                if (MessageBox.Show("Would you like to clear the already " + 
                        "added filenames before searching?", 
                        "Files Already Listed", 
                        MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    this.xFilesListBox.Items.Clear();
                }

            string searchPattern = string.Empty;
            if (this.xDocTypeComboBox.Text == "Word Documents")
                searchPattern = "*.doc";
            else
                searchPattern = "*.xls";

            this.xDocProgressBar.Visible = true;
            ChangeControlStatus(false);
            this.Refresh();
            xSearchFolderBackground.RunWorkerAsync(
              this.xFolderBrowserDialog.SelectedPath + 
              "|" + searchPattern);
        }
    }
}
private void xSearchFolderBackground_DoWork(object sender, DoWorkEventArgs e)
/// create a FileSearchClass and pass the directory and search pattern
/// to the search method.
{
    string[] args = (e.Argument as string).Split("|".ToCharArray());
    string directoryToSearch = args[0];
    string searchPattern = args[1];

    FileSearchClass searchClass = new FileSearchClass();
    searchClass.bw = (BackgroundWorker)sender;
    searchClass.Search(directoryToSearch, searchPattern);
}

private void xSearchFolderBackground_ProgressChanged(object sender, 
                                     ProgressChangedEventArgs e)
// whenever the background process progress changes,
// update the progress bar on the form
{
    if (this.xDocProgressBar.Maximum >= e.ProgressPercentage)
    {
        this.xDocProgressBar.Value = e.ProgressPercentage;
    }
    else
    {
        //debug...shouldn't be greater than 100% progress!
    }

    if (e.UserState != null && e.UserState is FileSearchClass.UserState)
    {
        FileSearchClass.UserState state = (FileSearchClass.UserState)e.UserState;
        if (state.currentOperation == FileSearchClass.Operations.Estimating)
            this.xStatusLbl.Text = 
              string.Format("Status: {0}", state.estimatingMessage);
        else if (state.currentOperation == FileSearchClass.Operations.Searching)
        {
            ///***
            ///I commented this section out.
            ///The refresh of the label is so fast that
            ///it won't let you click the Cancel button!
            ///***
            // so the directory path doesn't flow off the edge of the form:
            //if (state.directoryInProcess.Length > 80)
            //    state.directoryInProcess = 
            //        state.directoryInProcess.Substring(0, 56) + 
            //        "..." + 
            //        state.directoryInProcess.Substring(
            //           state.directoryInProcess.Length - 20, 20);
            //this.xStatusLbl.Text = string.Format("Status: {0}",
            //                                state.directoryInProcess);
            this.xStatusLbl.Text = string.Format("Status: {0}", 
                                                 state.searchingMessage);
            //this.xStatusLbl.Refresh();

            if (state.foundFileName != string.Empty)
            {
                this.xFilesListBox.Items.Add(state.foundFileName);
                this.xFilesListBox.Refresh();
            }
        }
    }
}

private void xSearchFolderBackground_RunWorkerCompleted(object sender, 
                                     RunWorkerCompletedEventArgs e)
{
    if (!(e.Error == null))
    {
        MessageBox.Show(e.Error.Message, "Error", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    else
    {
        // re-enable controls on form
        ChangeControlStatus(true);

        // update the number of filenames selected
        UpdateNumListboxItems();
        this.xStatusLbl.Text = string.Format("Status: {0}", "Search complete!");
    }
}

The ProcessDocuments class does all of the heavy lifting when bulk processing the documents. The xGoButton click event gathers all the UI information, sends it to a new ProcessDocuments class, and passes the class to the xProcessDocsBackground.RunWorkerAsync method:

C#
private void xGoButton_Click(object sender, EventArgs e)
// Starts/cancels document processing or cancels the search process.
{
    // if the user hit the button while it says
    // "Cancel", cancel the background worker
    if (this.xGoButton.Text == "Cancel" && 
            xProcessDocsBackground.WorkerSupportsCancellation && 
            xProcessDocsBackground.IsBusy == true)
    {
        xProcessDocsBackground.CancelAsync();
        ChangeControlStatus(true);
        return;
    }

    // if the user hit the button while it says
    // "Cancel", cancel the background worker
    if (this.xGoButton.Text == "Cancel" && 
          xSearchFolderBackground.WorkerSupportsCancellation && 
          xSearchFolderBackground.IsBusy == true)
    {
        xSearchFolderBackground.CancelAsync();
        ChangeControlStatus(true);
        return;
    }

    // build the list of filenames
    ArrayList fileNames = new ArrayList();
    fileNames.AddRange(this.xFilesListBox.Items);
    // build the list of passwords
    ArrayList userPasswords = new ArrayList();

    if (this.xUnprotectPW.Text.Trim() == string.Empty)
    {
        userPasswords.AddRange(this.xUnprotectPW.Items);
    }
    else
    {
        userPasswords.Add(this.xUnprotectPW.Text.Trim());
        userPasswords.AddRange(this.xUnprotectPW.Items);
    }

    // get WdProtectionType chosen
    WdProtectionType userWdProtectionType;
    if (xNoProtectionRB.Checked)
        userWdProtectionType = WdProtectionType.wdNoProtection;
    else if (xTrackedChangesRB.Checked)
        userWdProtectionType = WdProtectionType.wdAllowOnlyRevisions;
    else if (xReadOnlyRB.Checked)
        userWdProtectionType = WdProtectionType.wdAllowOnlyReading;
    else if (xFillingFormsRB.Checked)
        userWdProtectionType = WdProtectionType.wdAllowOnlyFormFields;
    else
        userWdProtectionType = WdProtectionType.wdAllowOnlyReading;

    // create a class to hold data needed to pass to the background worker
    ProcessDocuments newDocInfoClass = new ProcessDocuments
    {
        fileNames = fileNames.ToArray(typeof(string)) as string[],
        reprotectPassword = this.xProtectPW.Text,
        passwords = userPasswords.ToArray(typeof(string)) as string[],
        suggReadOnly = this.xReadOnlyCB.Checked,
        wordProtectionType = userWdProtectionType,
        bw = xProcessDocsBackground,
        wordDocsToProcess = this.xDocTypeComboBox.Text.Contains("Word"),
        xlDocsToProcess = this.xDocTypeComboBox.Text.Contains("Excel"),
        removeXlProtection = this.xExcelNoProtectionRB.Checked
    };

    // if the background worker is not already doing stuff, kick it off
    if (xProcessDocsBackground.IsBusy != true)
    {
        xProcessDocsBackground.RunWorkerAsync(newDocInfoClass);
        ChangeControlStatus(false);
    }
}

private void xProcessDocsBackground_DoWork(object sender, DoWorkEventArgs e)
// process all documents in the list in the background
{
    ProcessDocuments passedDocInfo = e.Argument as ProcessDocuments;
    passedDocInfo.ProcessOfficeDocs();
}

In the ProcessDocuments class, I check to see if there are Word documents to process or Excel documents to process, and run either ProcessWordDocs() or ProcessExcelDocs().

Those methods start an instance of either Word or Excel, opens each document in the list, tries every password in the list to unprotect the document (if necessary), and re-protects it according to the user's settings.

During processing, after every 10 documents processed, the code checks to see if more than 50% of the documents are failing (wrong password, incorrect permissions, etc.), and asks the user if they want to stop.

C#
private void ProcessWordDocs()
// Processes all Word documents
{
    if (_bw != null && _bw.WorkerReportsProgress)
    {
        // report progress back to form after every document has been processed
        _bw.ReportProgress(_bwPercentComplete, "Opening Word...");
    }

    // create an instance of MS Word and a reference to a Document object
    Word.Application wordApp = new Word.Application();
    Word.Document doc;

    // string to hold the names of every filename where an error occurred
    string errorFileNames = null;

    // string to hold the names of every filename which was processed successfully
    string filesSavedSuccessfully = null;

    // number of files where an error occurred
    double numErrorFiles = 0;

    // number of files successfully saved
    double numFilesSaved = 0;

    // total number of files processed, should be the same
    // as the number of files in the list box
    double numFilesProcessed = 0;

    // counter will be reset after every 10 files processed
    int counter = 0;

    // for every item in the files list box, process the filename specified
    for (int i = 0; i < _numFiles; i++)
    {
        object filename = fileNames[i];
        if (_bw != null && _bw.WorkerReportsProgress)
        {
            // report progress back to form after every document has been processed
            _bwPercentComplete = (int)Math.Round((numFilesProcessed / _numFiles) * 100);
            _bw.ReportProgress(_bwPercentComplete, "Processing '" + 
                               filename + "'");
        }

        try
        {
            // open the specified Word document
            doc = wordApp.Documents.Open(ref filename, ref missing, 
                    ref objFalse, ref missing, ref missing, 
                    ref missing, ref missing, ref missing, ref missing, 
                    ref missing, ref missing, ref objFalse, ref missing, 
                    ref missing, ref missing, ref missing);

            // if the document is protected,
            // try every password in the list to unprotect it
            if (doc.ProtectionType != Word.WdProtectionType.wdNoProtection)
            {
                try
                {
                    // first, try to unprotect it with an empty password
                    object blankPassword = string.Empty;

                    doc.Unprotect(ref blankPassword);
                }
                catch { } // drop through errors to try the entered password

                // loop through every password in the list and try to unprotect it
                for (int j = 0; j < _passwords.GetLength(0); j++)
                {
                    try
                    {
                        object unprotectPassword = _passwords[j];
                        doc.Unprotect(ref unprotectPassword);
                    }
                    catch { } // drop through errors and try the next password
                }
            }

            // if the document is unprotected, re-protect it according to user settings
            if (doc.ProtectionType == Word.WdProtectionType.wdNoProtection)
            {
                // protect the document according to the selected protection type
                doc.Protect(wordProtectionType, ref objFalse, 
                            ref _reprotectPassword, ref missing, ref missing);

                /// I couldn't find a way to overwrite the file
                /// without error, so I create a temporary filename,
                /// save the new document, delete the original,
                // and rename the temporary file with the original filename
                object tempFilename = filename.ToString() + ".temp";

                try
                {
                    // save the doc with the temporary filename and close the document
                    doc.SaveAs(ref tempFilename, ref missing, ref missing, ref missing, 
                               ref objFalse, ref missing, ref _suggReadOnly,
                               ref missing, ref missing, ref missing, 
                               ref missing, ref missing, ref missing, 
                               ref missing, ref missing, ref missing);

                    // close the document so you can delete the original file
                    ((Word._Document)doc).Close(ref objFalse, ref missing, ref missing);

                    // delete the original file
                    System.IO.File.Delete(filename.ToString());

                    // rename the temporary file with
                    // the original filename using the Move method
                    System.IO.File.Move(tempFilename.ToString(), filename.ToString());

                    // increment counters and store filename
                    // to be output into a log file later
                    numFilesSaved = numFilesSaved + 1;
                    numFilesProcessed = numFilesProcessed + 1;
                    filesSavedSuccessfully = filesSavedSuccessfully + filename + 
                                             System.Environment.NewLine;
                }
                catch (Exception)
                // there was a problem somewhere
                // on the save, delete, or rename operations
                {
                    // log the filename to be output into
                    // the error log later and increment the counters
                    errorFileNames = errorFileNames + filename + 
                                     System.Environment.NewLine;
                    numFilesProcessed = numFilesProcessed + 1;
                    numErrorFiles = numErrorFiles + 1;
                    MessageBox.Show("Could not save the document: " + filename);
                }
            }
            else
            /// couldn't unprotect the document, so log the filename to be output into
            /// the error log later and increment the counters
            {
                errorFileNames = errorFileNames + filename + 
                                 System.Environment.NewLine;
                numFilesProcessed = numFilesProcessed + 1;
                numErrorFiles = numErrorFiles + 1;

                // now close the document
                ((Word._Document)doc).Close(ref objFalse, ref missing, ref missing);
            }
        }
        catch (Exception) // exception while opening file
        {
            // log the filename to be output into the error
            // log later and increment the counters
            errorFileNames = errorFileNames + filename + System.Environment.NewLine;
            numFilesProcessed = numFilesProcessed + 1;
            numErrorFiles = numErrorFiles + 1;
            MessageBox.Show("Could not save the document: " + filename);
        }

        // check to see if the user hit the Cancel button
        if (_bw != null && _bw.CancellationPending)
        {
            // close Word
            ((Word._Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
            // write logs out to disk
            WriteLogs(errorFileNames, filesSavedSuccessfully, 
                      numErrorFiles, numFilesSaved);
            //bail out
            return;
        }

        if (_bw != null && _bw.WorkerReportsProgress)
        {
            // report progress back to form after every document has been processed
            _bwPercentComplete = (int)Math.Round((numFilesProcessed / _numFiles) * 100);
            _bw.ReportProgress(_bwPercentComplete);
        }

        /// at this point, the document's been saved
        /// or has errored out. either way it's been processed,
        /// so increment the number of documents processed
        counter = counter + 1;

        //After every 10 files saved, if 50% or more 
        //of the operations are failing, prompt user for action
        if (counter == 10)
        {
            double percentFailed = (numErrorFiles / numFilesProcessed);

            if (percentFailed >= 0.5)
            {
                if (MessageBox.Show(numErrorFiles + 
                    " operations have failed out of " + 
                    numFilesProcessed + ". Would you like to quit?", 
                    "Operations Failed", MessageBoxButtons.YesNo, 
                    MessageBoxIcon.Information) == DialogResult.Yes)
                {
                    // close Word
                    ((Word._Application)wordApp).Quit(ref objFalse, 
                                        ref missing, ref missing);
                    // write logs out to disk
                    WriteLogs(errorFileNames, filesSavedSuccessfully, 
                              numErrorFiles, numFilesSaved);
                    // bail out
                    return;
                }
            }
            // reset the counter for the next batch of 10
            counter = 0;
        }
    }

    // We've processed all of the files, so quit Word
    ((Word._Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
    // write logs out to disk
    WriteLogs(errorFileNames, filesSavedSuccessfully, numErrorFiles, numFilesSaved);
}

private void ProcessExcelDocs()
// Processes all Excel documents
{
    if (_bw != null && _bw.WorkerReportsProgress)
    {
        // report progress back to form after every document has been processed
        _bw.ReportProgress(_bwPercentComplete, "Opening Excel...");
    }

    // create an instance of MS Word and a reference to a Document object 
    Excel.Application xlApp = new Excel.Application();
    Excel.Workbook xlWorkbook;
    xlApp.DisplayAlerts = true;
    xlApp.DisplayInfoWindow = true;

    // string to hold the names of every filename where an error occurred
    string errorFileNames = null;

    // string to hold the names of every filename which was processed successfully
    string filesSavedSuccessfully = null;

    // number of files where an error occurred
    double numErrorFiles = 0;

    // number of files successfully saved
    double numFilesSaved = 0;

    // total number of files processed, should be the same
    // as the number of files in the list box
    double numFilesProcessed = 0;

    // counter will be reset after every 10 files processed
    int counter = 0;

    // for every item in the files list box, process the filename specified
    for (int i = 0; i < _numFiles; i++)
    {
        string filename = fileNames[i];
        if (_bw != null && _bw.WorkerReportsProgress)
        {
            // report progress back to form after every document has been processed
            _bwPercentComplete = (int)Math.Round((numFilesProcessed / _numFiles) * 100);
            _bw.ReportProgress(_bwPercentComplete, "Processing '" + 
                               filename + "'");
        }
        try
        {
            // open the specified Excel document
            xlWorkbook = xlApp.Workbooks.Open(filename, missing, objFalse, missing, 
                           missing, missing, objTrue, missing, missing, missing, 
                           missing, missing, missing, missing, missing);

            // loop through each worksheet in the excel document and unprotect it
            foreach (Excel.Worksheet xlWorksheet in xlWorkbook.Sheets)
            {
                if (xlWorksheet.ProtectContents == true)
                {
                    // loop through every password
                    // in the list box and try to unprotect it
                    for (int j = 0; j < _passwords.GetLength(0); j++)
                    {
                        try
                        {
                            xlWorksheet.Unprotect(_passwords[j]);
                        }
                        catch { } // drop through errors and try the next password
                    }
                }
            }

            bool noErrors = true;
            foreach (Excel.Worksheet xlWorksheet in xlWorkbook.Sheets)
            {
                // if the file is unprotected and the user wants to protect it, do so.
                if (xlWorksheet.ProtectContents == false)
                {
                    if (!_removeXlProtection)
                    {
                        // protect the document according to the selected protection type

                        xlWorksheet.Protect(_reprotectPassword, objTrue, objTrue, 
                            objTrue, missing, objFalse, objFalse, objFalse, 
                            objFalse, objFalse, objFalse, objFalse,
                            objFalse, objFalse, objFalse, objFalse);
                    }
                }
                else
                //store the filename for an error report and break for the next file
                {
                    errorFileNames = errorFileNames + filename + 
                                       System.Environment.NewLine;
                    numFilesProcessed = numFilesProcessed + 1;
                    numErrorFiles = numErrorFiles + 1;

                    // now close the document
                    xlWorkbook.Close(objFalse, missing, objFalse);
                    noErrors = false;
                    break;
                }
            }

            if (noErrors == true)
            {
                /// I couldn't find a way to overwrite the file without error,
                /// so I create a temporary filename, save the new document,
                /// delete the original, and rename the temporary file with
                /// the original filename
                object tempFilename = filename + ".temp";

                try
                {
                    // save the doc with the temporary filename and close the document
                    xlWorkbook.SaveAs(tempFilename, missing, missing, missing,
                            _suggReadOnly, missing, Excel.XlSaveAsAccessMode.xlNoChange, 
                            missing, missing, missing, missing, missing);

                    // close the workbook so you can delete the original file
                    xlWorkbook.Close(objFalse, missing, objFalse);

                    // delete the original file
                    System.IO.File.Delete(filename.ToString());

                    // rename the temporary file with
                    // the original filename using the Move method
                    System.IO.File.Move(tempFilename.ToString(), filename.ToString());

                    // increment counters and store filename
                    // to be output into a log file later
                    numFilesSaved = numFilesSaved + 1;
                    numFilesProcessed = numFilesProcessed + 1;
                    filesSavedSuccessfully = filesSavedSuccessfully + 
                                             filename + System.Environment.NewLine;
                }
                catch (Exception)
                // there was a problem somewhere on the save, delete, or rename operations
                {
                    // log the filename to be output into the error log
                    // later and increment the counters
                    errorFileNames = errorFileNames + filename + 
                                     System.Environment.NewLine;
                    numFilesProcessed = numFilesProcessed + 1;
                    numErrorFiles = numErrorFiles + 1;
                    MessageBox.Show("Could not save the document: " + filename);
                }
            }
        }
        catch (Exception) // exception while opening file
        {
            // log the filename to be output into
            // the error log later and increment the counters
            errorFileNames = errorFileNames + filename + System.Environment.NewLine;
            numFilesProcessed = numFilesProcessed + 1;
            numErrorFiles = numErrorFiles + 1;

            MessageBox.Show("Could not save the document: " + filename);
        }

        // check to see if the user hit the Cancel button
        if (_bw != null && _bw.CancellationPending)
        {
            // close Excel
            try
            {
                xlApp.Workbooks.Close();
                xlApp.Quit();
            }
            catch (Exception) { }

            KillExcel();

            // write logs out to disk
            WriteLogs(errorFileNames, filesSavedSuccessfully, 
                      numErrorFiles, numFilesSaved);
            //bail out
            return;
        }

        if (_bw != null && _bw.WorkerReportsProgress)
        {
            // report progress back to form after every document has been processed
            _bwPercentComplete = (int)Math.Round((numFilesProcessed / _numFiles) * 100);
            _bw.ReportProgress(_bwPercentComplete);
        }

        /// at this point, the document's been saved or has errored out. either way it's
        /// been processed, so increment the number of documents processed
        counter = counter + 1;

        //After every 10 files saved, if 50% or more 
        //of the operations are failing, prompt user for action
        if (counter == 10)
        {
            double percentFailed = (numErrorFiles / numFilesProcessed);
            if (percentFailed >= 0.5)
            {
                if (MessageBox.Show(numErrorFiles + 
                     " operations have failed out of " + 
                     numFilesProcessed + ". Would you like to quit?", 
                     "Failing Operations", MessageBoxButtons.YesNo,
                     MessageBoxIcon.Information) == DialogResult.Yes)
                {
                    // close Excel
                    try
                    {
                        xlApp.Workbooks.Close();
                        xlApp.Quit();
                    }
                    catch (Exception) { }
                    
                    KillExcel();

                    // write logs out to disk
                    WriteLogs(errorFileNames, filesSavedSuccessfully, 
                              numErrorFiles, numFilesSaved);

                    // bail out
                    return;
                }
            }
            // reset the counter for the next batch of 10
            counter = 0;
        }
    }
    // We've processed all of the files, so quit Excel
    try
    {
        xlApp.Workbooks.Close();
        xlApp.Quit();
    }
    catch (Exception) { }

    KillExcel(); 

    // write logs out to disk
    WriteLogs(errorFileNames, filesSavedSuccessfully, numErrorFiles, numFilesSaved);
}

I ran in to a problem closing Excel which took me quite a while to figure out. Basically, you have to jump through a bunch of hoops to get Excel to release all of its COM resources, and it won't close until everything has been properly released.

So after hours of trying to make sure everything was closed properly, I finally just strong-armed Excel into closing by finding the instance of Excel.exe running in the Task Manager and closing the process with a blank MainWindowTitle. Regular instances of Excel running have something in the MainWindowTitle, so it will leave other open instances of Excel alone:

C#
private void KillExcel()
/// kills the instance of "Excel" process in which the Window title is empty.
/// I had to do this because I couldn't get the Excel COM resources to release properly.
{
    foreach (Process xlProcess in Process.GetProcessesByName("Excel"))
    {
        //when started programmatically the window title
        //is empty, won't kill other Excel instances
        if (xlProcess.MainWindowTitle == string.Empty)
            xlProcess.Kill();
    }
}

Then finally, the logs get written to the C:\ directory and are launched automatically:

C#
private void WriteLogs(string errorFileNames, string filesSavedSuccessfully, 
                       double numErrorFiles, double numFilesSaved)
// Write log files out to disk and opens them
{
    // error log and protection log filenames
    const string errorsFileDir = "c:\\errors.log";
    const string reportFileDir = "c:\\successes.log";

    System.IO.FileStream fileStream;

    // Now we create the log reports
    try // to write the summary log file to disk
    {
        fileStream = System.IO.File.Create(reportFileDir);
        System.IO.StreamWriter objWriter;
        objWriter = new System.IO.StreamWriter(fileStream);
        objWriter.Write(filesSavedSuccessfully + 
          "______________________________________________" +

        System.Environment.NewLine + numFilesSaved.ToString() + 
               " file(s) processed successfully.");
        objWriter.Close();
        MessageBox.Show("Success log created at: " + reportFileDir, 
                        "Log Written", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
    catch (Exception)
    {
        MessageBox.Show("Could not create report log at: " + reportFileDir,
                        "Log Written", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
    try // to write the error log file to disk
    {
        fileStream = System.IO.File.Create(errorsFileDir);
        System.IO.StreamWriter objWriter;
        objWriter = new System.IO.StreamWriter(fileStream);
        objWriter.Write(errorFileNames + 
          "______________________________________________" +

        System.Environment.NewLine + numErrorFiles.ToString() + " problem file(s).");
        objWriter.Close();
        MessageBox.Show("Error log created at: " + errorsFileDir, 
                        "Log Written", MessageBoxButtons.OK, 
                        MessageBoxIcon.Information);
    }
    catch (Exception)
    {
        MessageBox.Show("Could not create error log at: " + errorsFileDir,
                        "Log Written", MessageBoxButtons.OK, 
                        MessageBoxIcon.Information);
    }

    // open the summary log

    ProcessStartInfo procStartInfo = new ProcessStartInfo();
    procStartInfo.FileName = reportFileDir;
    Process.Start(procStartInfo);

    // open the error log
    procStartInfo.FileName = errorsFileDir;
    Process.Start(procStartInfo);
}

Points of Interest

Like I said, you could modify this application to include other document properties to mass modify. The document subject, author, keywords, revision number, or any other document properties which are editable.

The search class is very reusable, and you can find out more about it here. It doesn't search inside ZIP files, and doesn't include shortcuts, so the number of files you find won't match exactly what your OS will find.

History

My inspiration for the method to access the Word properties came partly from here. Other than that, I've taken bits and pieces of information from the Web as I needed, mostly just concepts though and not actual code.

My previous article, Bulk Word Processing Utility, obviously could only do Word documents, and the search functionality froze the whole program. I've also done a bunch of re-factoring and performance tuning, and now it should be a bit faster and more resilient.

The program is now compatible with Office 2010.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)