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.
private void xGoButton_Click(object sender, EventArgs e)
{
if (this.xGoButton.Text == "Cancel" &&
ProcessDocsBackground.WorkerSupportsCancellation)
{
ProcessDocsBackground.CancelAsync();
ChangeControlStatus(true);
return;
}
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 (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.
private void ProcessDocsBackground_DoWork(object sender, DoWorkEventArgs e)
{
DocInfoClass passedDocInfo = e.Argument as DocInfoClass;
Microsoft.Office.Interop.Word.Application wordApp =
new Microsoft.Office.Interop.Word.Application();
Microsoft.Office.Interop.Word.Document doc;
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 errorFileNames = null;
string filesSavedSuccessfully = null;
double numErrorFiles = 0;
double numFilesSaved = 0;
double numFilesProcessed = 0;
int counter = 0;
for (int i = 0; i < numFiles; i++)
{
object filename = passedDocInfo.fileNames.Items[i];
try
{
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 (doc.ProtectionType != WdProtectionType.wdNoProtection)
{
if (this.xUnprotectPW.Items.Count == 0)
{
try
{
unprotectPassword = string.Empty;
doc.Unprotect(ref unprotectPassword);
}
catch { }
try
{
unprotectPassword = passedDocInfo.unprotectPassword;
doc.Unprotect(ref unprotectPassword);
}
catch { }
}
else
{
try
{
unprotectPassword = string.Empty;
doc.Unprotect(ref unprotectPassword);
}
catch { }
try
{
unprotectPassword = passedDocInfo.unprotectPassword;
doc.Unprotect(ref unprotectPassword);
}
catch { }
for (int j = 0; j < passedDocInfo.numPasswords; j++)
{
try
{
unprotectPassword = passedDocInfo.passwords.Items[j].ToString();
doc.Unprotect(ref unprotectPassword);
}
catch { }
}
}
}
if (doc.ProtectionType != WdProtectionType.wdNoProtection)
{
errorFileNames = errorFileNames + filename + System.Environment.NewLine;
numFilesProcessed = numFilesProcessed + 1;
numErrorFiles = numErrorFiles + 1;
((_Document)doc).Close(ref objFalse, ref missing, ref missing);
}
else
{
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);
}
object tempFilename = filename.ToString() + ".temp";
try
{
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);
System.IO.File.Delete(filename.ToString());
System.IO.File.Move(tempFilename.ToString(), filename.ToString());
numFilesSaved = numFilesSaved + 1;
numFilesProcessed = numFilesProcessed + 1;
filesSavedSuccessfully = filesSavedSuccessfully +
filename + System.Environment.NewLine;
}
catch (Exception)
{
errorFileNames = errorFileNames + filename +
System.Environment.NewLine;
numFilesProcessed = numFilesProcessed + 1;
numErrorFiles = numErrorFiles + 1;
MessageBox.Show("Could not save the document: " + filename);
}
}
}
catch (Exception)
{ }
if (ProcessDocsBackground.CancellationPending)
{
((_Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
WriteLogs(errorFileNames, filesSavedSuccessfully,
numErrorFiles, numFilesSaved);
e.Cancel = true;
return;
}
int percentCompleted = (int)Math.Round((numFilesProcessed / numFiles)*100);
ProcessDocsBackground.ReportProgress((int)percentCompleted);
counter = counter + 1;
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)
{
((_Application)wordApp).Quit(ref objFalse,
ref missing, ref missing);
WriteLogs(errorFileNames, filesSavedSuccessfully,
numErrorFiles, numFilesSaved);
e.Cancel = true;
return;
}
}
counter = 0;
}
}
((_Application)wordApp).Quit(ref objFalse, ref missing, ref missing);
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.