Summary: Microsoft Scripting Guy, Ed Wilson, shows how to use Windows PowerShell to automatically complete the Microsoft Word built-in properties.
Microsoft Scripting Guy, Ed Wilson, is here. Registration for the Charlotte PowerShell Saturday event to be held on September 15, 2012 is underway. (Incidentally, that is the day after my birthday. The speakers dinner at the Scripting Wife’s and my house, will actually be a Scripting Guy birthday party). We have all speakers (except for one) identified, and you can see the speakers and the tracks on the website. If you are going to be anywhere near Charlotte, North Carolina in the United States on September 15, 2012, you should make plans to attend. With three tracks and over a dozen Microsoft PFEs, Microsoft MVPs, and community leaders speaking, it will be an event too good to pass up. I myself, am doing two or three presentations on the beginner and the advanced tracks. It will be cool. Here is the link to register for this awesome PowerShell event.
Use PowerShell to add to Word metadata
Note This week I have been talking about finding files and working with the associated built-in properties of those files (Microsoft Word). On Monday, I wrote Use PowerShell to Explore Nested Directories and Files. on Tuesday, I wrote Use PowerShell to Help Find all of Your Images. And on Wednesday, we began our deep dive into the Microsoft Word automation model when I wrote Find All Word Documents that Contain a Specific Phrase, which was followed up with Use PowerShell to Find Specific Word Built-in Properties on Thursday.
When I figured out how to find specific Microsoft Word documents by using the Microsoft Word built-in properties, I thought it would be a useful technique. Potentially, it could be quicker, and more accurate to use these built-in properties than to try to use regular expressions to search Word documents for specific word patterns. It is easier to search for specific words in specific Microsoft Word styles, but all of my documents do not always use standard Microsoft Word styles. Therefore, that technique does not work so well.
If, on the other hand, I have accurately populated the built-in properties in a Microsoft Word document, I know I can search for them via the technique I developed in yesterday’s Hey, Scripting Guy! Blog. In a previous blog, How Can I List All the Properties of a Microsoft Word Document?, I talked about manually adding values to the Microsoft Word built-in properties. Today I want to talk more about adding values to the Microsoft Word built-in properties.
To be useful as a document management technique, I need to have my script figure out what to add. This can involve lots of regular expressions and other things. Based on my writing of this series, I have decided to modify my Microsoft Word template: the title goes in the Title style, the summary goes in as a Subtitle style; and I use Heading 9 for my tags. But, of course, although this will help in the future, it does not do much for me today. I do not want to overly complicate the script today because my main purpose is to illustrate the complicated task of actually writing to a Microsoft Word built-in property.
Yesterday when I was messing around with my script, I also noticed that because all my Data directory was copied from my backup that is stored on my SAN at home, the file creation dates are all messed up. This includes the Content created built-in property as well as the Date last saved built-in property. In addition, the actual file time stamps, Date created, Date modified, and Date accessed are similarly unreliable.
Note I created a function in the Use PowerShell to Modify File Access Time Stamps blog post. By using that function, it is easy to change the file time stamps. That will be the topic for tomorrow’s Weekend Scripter blog posting.
Writing to Microsoft Word built-in properties via PowerShell
MSDN details the built-in properties for Microsoft Word. Today, I want to assign a value to the Comments built-in property.
The first part of the Set-SpecificDocumentProperties script appears similar to the script from yesterday’s Hey, Scripting Guy! Blog. The difference is two new variables. The thing to keep in mind here is that the $AryProperties and the $newValue are specified as an [array], but they are actually singletons. The reason for this is because the SetProperty method used to write the values back to the BuiltInDocumentProperties collection must receive an array. Other than that, this code is relatively straightforward.
$path = "C:fso", [array]$include = @("HSG*.doc*","WES*.doc*"))
[array]$AryProperties = "Comments"
[array]$newValue = "Scripting Guy blog"
$application = New-Object -ComObject word.application
$application.Visible = $false
$binding = "System.Reflection.BindingFlags" -as [type]
$docs = Get-childitem -path $Path -Recurse -Include $include
Now I use the foreach statement to walk through the collection of documents retrieved by the Get-ChildItem cmdlet. Inside the script block for the command, the first thing I do is open the document and store the returned Document object in the $document variable. Next I retrieve the BuiltInDocumentProperties object, and I store it in the $builtinProperties variable. Next I use the GetType method to return the BuiltInDocumentProperties type, and I store that in the $builtinPropertiesType variable. I could also use [system.__ComObject] like I did yesterday, but I thought I would show you a different technique that is perhaps a bit more readable. Here is the code.
Foreach($doc in $docs)
$document = $application.documents.open($doc.fullname)
$BuiltinProperties = $document.BuiltInDocumentProperties
$builtinPropertiesType = $builtinProperties.GetType()
Once again, (just like in yesterday’s script), I use the Try / Catch commands to attempt to write new values for the properties. If an exception occurs, a blue string displays that states the script was unable to set a value for the property.
In the Try script block, the first thing I do is get the built-in property and assign it to the $BuiltInProperty variable. To do this, I use the InvokeMember method on the item with the GetProperty binding. I also include the $builtinProperties variable that contains the BuiltInProperties collection. I store the returned property object in the $BuiltInProperty variable. Next I use the GetType method to return the data type, and I store that in the $BuiltInPropertyType variable. These two lines of code are shown here (the first line is really long and wraps).
$BuiltInProperty = $builtinPropertiesType.invokemember("item",$binding::GetProperty,$null,$BuiltinProperties,$AryProperties)
$BuiltInPropertyType = $BuiltInProperty.GetType()
I now call the SetProperty binding for the value by using code that is similar to the previous line of code. Once again, the new value must be supplied in an array.
Inside the loop, it is now time to close the document, release the COM objects, and remove the variables. This code is shown here.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($BuiltinProperties) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
Remove-Variable -Name document, BuiltinProperties
When the script finishes looping through the documents, the final cleanup occurs. This code is shown here.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
The default option of the Close method is to save the Word Document. You can use the wdSaveChanges value from the WdSaveOptions enumeration as well. MSDN documents the WdSaveOptions enumeration, but it is easy to use Windows PowerShell to find this information by using Get-Member –static on the variable that contains the enumeration type. What is really weird is that the interop requires that the save option is passed by reference. This is the reason for the [ref] type constraint in front of the $saveOption variable.
I uploaded the complete Set-SpecificDocumentProperties.ps1 script to the Scripting Guys Script Repository When you download the zip file, make sure to unblock the file prior to attempting to run the script or else the script execution policy will prohibit the script from running. For more information about this, refer to the following Scripting Wife blog.
Join me tomorrow for the Weekend Scripter when I will talk about parsing file names and creating DateTime objects based on the file name. It is cool.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy