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

Bulk Word Protection Utility

4.67/5 (3 votes)
10 Apr 2009CPOL3 min read 36.8K   1.3K  
Can be used to bulk search for and unprotect or protect Word documents. Can also be used to bulk change passwords for Word document protection.

Image 1

Introduction

In a previous life, all of our Quality documents were Word documents which needed to be password protected to "Filling In Forms Only" using Word's document protection functionality. I was tasked with changing this password...for all 500+ documents on the server. So, I wrote a C# application to loop through all .doc(x) files and do this for me :p.

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.

It would probably be possible to do this with Excel documents as well, but I've never had the need to check it out...yet.

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

I wrote this in C# simply because I started out with VB.NET and wanted to force myself to get more familiar with C#. The code could easily be translated to VB.NET.

Almost all of my code is right behind the form except some startup checking in Main and a DocInfoClass used to pass information from the form to the BackgroundWorker as an argument. When doing the recursive search, the FileSearch method looks in the directory selected, and the DirSearch method is the recursive part.

The bulk of the intelligence is in the ProcessDocsBackground_DoWork sub which is called by the "Go" button. The "Go" button code handles both kicking off the background thread to process documents and also canceling the background worker.

C#
private void xGoButton_Click(object sender, EventArgs e)
{
    // if the user hit the button while it says "Cancel",
    // cancel the background worker
    if (this.xGoButton.Text == "Cancel" && 
        ProcessDocsBackground.WorkerSupportsCancellation)
    {
        ProcessDocsBackground.CancelAsync();
        ChangeControlStatus(true);
        return;
    }
    // create a class to hold data needed to pass to the background worker
    DocInfoClass newDocInfoClass = new DocInfoClass
    {
        fileNames = this.xFilesListBox,
        numFiles = this.xFilesListBox.Items.Count,
        numPasswords = this.xUnprotectPW.Items.Count,
        unprotectPassword = this.xUnprotectPW.Text,
        reprotectPassword = this.xProtectPW.Text,
        passwords = this.xUnprotectPW, 
        suggReadOnly = this.xReadOnlyCB.Checked
    };
    
    // if the background worker is not already doing stuff, kick it off
    if (ProcessDocsBackground.IsBusy != true)
    {
        this.xDocProgressBar.Visible = true;
        ProcessDocsBackground.RunWorkerAsync(newDocInfoClass);
        ChangeControlStatus(false);
    }
}

The ProcessDocsBackgroun_DoWork sub creates an instance of MS Word, 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.

Finally, it deletes the original document and replaces it with the new unprotected version, and spits out a log file for successful documents and problematic documents.

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 ProcessDocsBackground_DoWork(object sender, DoWorkEventArgs e)
    // process all documents in the list in the background
{
    DocInfoClass passedDocInfo = e.Argument as DocInfoClass;
    // create an instance of MS Word and a reference to a Document object
    Microsoft.Office.Interop.Word.Application wordApp = 
                 new Microsoft.Office.Interop.Word.Application();
    Microsoft.Office.Interop.Word.Document doc;
    // setup properties for document
    object unprotectPassword = passedDocInfo.unprotectPassword;
    object reprotectPassword = passedDocInfo.reprotectPassword;
    object missing = System.Reflection.Missing.Value;
    object objReadOnly = (object)passedDocInfo.suggReadOnly;
    int numFiles = passedDocInfo.numFiles;
    object empty = string.Empty;
    object objFalse = false;
    object objTrue = 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++)
    {
        object filename = passedDocInfo.fileNames.Items[i];
        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 != WdProtectionType.wdNoProtection)
            {
                if (this.xUnprotectPW.Items.Count == 0)
                /// there's no items in the password listbox,
                /// so try a blank password or whatever
                /// was entered into the password text box
                {
                    try
                    {
                        // first, try to unprotect it with an empty password
                        unprotectPassword = string.Empty;
                        doc.Unprotect(ref unprotectPassword);
                    }
                    catch { } // drop through errors to try the entered password
                    try
                    // try to unprotect it with whatever is in the password text box
                    {
                        unprotectPassword = passedDocInfo.unprotectPassword;
                        doc.Unprotect(ref unprotectPassword);
                    }
                    catch { } // drop through errors
                }
                else
                /// there were passwords in the list box, so try blank, whatever
                /// was entered in the text box, and each password in the list
                {
                    try
                    {
                        // first, try to unprotect it with an empty password
                        unprotectPassword = string.Empty;
                        doc.Unprotect(ref unprotectPassword);
                    }
                    catch { } // drop through errors to try the entered password
                    try
                    // try to unprotect it with whatever is in the password text box
                    {
                        unprotectPassword = passedDocInfo.unprotectPassword;
                        doc.Unprotect(ref unprotectPassword);
                    }
                    catch { } // drop through errors
                    // loop through every password in the list box and try to unprotect it
                    for (int j = 0; j < passedDocInfo.numPasswords; j++)
                    {
                        try
                        {
                            unprotectPassword = passedDocInfo.passwords.Items[j].ToString();
                            doc.Unprotect(ref unprotectPassword);
                        }
                        catch { } // drop through errors and try the next password
                    }
                }
            }
            /// see if the document is unprotected, if not, store the filename for
            /// an error report and move to the next file
            if (doc.ProtectionType != WdProtectionType.wdNoProtection)
            {
                errorFileNames = errorFileNames + filename + System.Environment.NewLine;
                numFilesProcessed = numFilesProcessed + 1;
                numErrorFiles = numErrorFiles + 1;
                //MessageBox.Show("Could not unprotect the document: " + filename);
                // now close the document
                ((_Document)doc).Close(ref objFalse, ref missing, ref missing);
            }
            else
            /// the file is unprotected, so see what kind of protection the user wants
            /// to put back on the file and protect it
            /// accordingly with the password provided
            {
                if (xNoProtectionRB.Checked)
                {
                    doc.Protect(WdProtectionType.wdNoProtection, 
                        ref objFalse, ref reprotectPassword, 
                        ref missing, ref missing);
                }
                else if (xTrackedChangesRB.Checked)
                {
                    doc.Protect(WdProtectionType.wdAllowOnlyRevisions, 
                        ref objFalse, ref reprotectPassword, 
                        ref missing, ref missing);
                }
                else if (xReadOnlyRB.Checked)
                {
                    doc.Protect(WdProtectionType.wdAllowOnlyReading, 
                        ref objFalse, ref reprotectPassword, ref missing, ref missing);
                }
                else if (xFillingFormsRB.Checked)
                {
                    doc.Protect(WdProtectionType.wdAllowOnlyFormFields, 
                        ref objFalse, ref reprotectPassword, ref missing, ref missing);
                }
                else
                {
                    doc.Protect(WdProtectionType.wdAllowOnlyReading, 
                        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 objReadOnly,
                        ref missing, ref missing, ref missing, ref missing, 
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing);
                    ((_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);
                }
            }
        }
        catch (Exception)
        { }// allow errors to drop through. maybe the file wouldn't open for some reason...
        // check to see if the user hit the Cancel button
        if (ProcessDocsBackground.CancellationPending)
        {
            // close Word
            ((_Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
            // write logs out to disk
            WriteLogs(errorFileNames, filesSavedSuccessfully, 
                      numErrorFiles, numFilesSaved);
            /// set the cancelled property to true.
            /// I check this property inside the
            /// RunWorkerCompleted event
            e.Cancel = true;
            //bail out
            return;
        }
        // report progress back to form after every document has been processed
        int percentCompleted = (int)Math.Round((numFilesProcessed / numFiles)*100);
        ProcessDocsBackground.ReportProgress((int)percentCompleted);
        /// 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)
            {
                DialogResult bailOut = DialogResult;
                if (MessageBox.Show(numErrorFiles + 
                    " operations have failed out of " + 
                    numFilesProcessed + 
                    ". Would you like to quit?", 
                    "Protection Wizard", MessageBoxButtons.YesNo,
                    MessageBoxIcon.Information) == DialogResult.Yes)
                {
                    // close Word
                    ((_Application)wordApp).Quit(ref objFalse, 
                                                 ref missing, ref missing);
                    // write logs out to disk
                    WriteLogs(errorFileNames, filesSavedSuccessfully, 
                              numErrorFiles, numFilesSaved);
                    /// set the cancelled property to true.
                    /// I check this property inside the
                    /// RunWorkerCompleted event
                    e.Cancel = true;
                    // bail out
                    return;
                }
            }
            // reset the counter for the next batch of 10
            counter = 0;
        }
    }
    // We've processed all of the files, so quit Word
    ((_Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
    // write logs out to disk
    WriteLogs(errorFileNames, filesSavedSuccessfully, numErrorFiles, numFilesSaved);
}

The rest of the code is pretty self-explanatory and a lot of GUI code. About 20% of my code is comments, but if you have any questions, let me know.

Points of interest

It was fun figuring out how the BackgroundWorker is used. And, getting it to take data from the form's thread and pass back data to the progress bar was interesting and challenging the first time you do it.

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.

History

I've made a bunch of updates to it over the past few months, but this is the first release to the public. When I was searching for a tool to do this, I couldn't find anything, so I thought it might be worthwhile for someone out there (I hope). I figured it wouldn't take too long to throw an article up here and let someone reap the fruits of my labor (toil).

If anybody knows of any other (free) tool that does this for you, please let me know, I'd be interested to see what they do.

License

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