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. Well 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 via the web site. 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 PFE, Microsoft MVP, and community leaders speaking it will be an event too good to pass up. I, myself, am doing two or three presentations on both the beginner and the advanced track. 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 talked about 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 on Use PowerShell to Find Specific Word Built-in Properties Thursday.
Once 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 – and therefore that technique does not work so well. If, on the other hand, I have populated accurately the built-in properties on a Microsoft Word document, I know I can search for them via the technique I developed in yesterday’s Hey Scripting Guy! Blog article. In a previous, Hey Scripting Guy! Blog article I talked about manually adding values to the Microsoft Word built-in properties. Today I want to talk about adding values to the Microsoft Word built-in properties. To be useful, as a document management technique, I will need to be able to have my script figure out what to add. This can involve lots of regular expressions, and other things. Based upon my writing of this series, I have decided to modify my Microsoft Word template: the title goes in the Title style, and the summary goes as a subtitle style; and I use the Heading 9 for my tags. But, of course, while this will help in the future, it does not do much for me today. I do not want to complicate overly the script for today because my main purpose is to illustrate the complicated task of actually writing to a Microsoft Word built-in property. I also noticed, yesterday, when I was messing around with my script, that because all of my data directory was copied from my backup stored on my SAN I have at home, then 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 timestamps, 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. Using that function, it is easy to change the file time stamps. That will be the topic for tomorrows 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 article. The difference is two new variables. The thing to keep in mind here is that the $AryProperties and the $newValue are both 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.
Param(
$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 cmdlets. Inside the scriptblock 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 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 stating 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 datatype and I store that in the $BuiltInPropertyType variable. These two lines of code appear 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 using code that is similar to the previous line of code. Once again, the new value must be supplied in an array.
$BuiltInPropertyType.invokemember("value",$binding::SetProperty,$null,$BuiltInProperty,$newValue)}
Inside the loop it is now time to close the document, and release a the COM objects and remove the variables. This code appears here.
$document.close()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($BuiltinProperties) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
Remove-Variable -Name document, BuiltinProperties
}
Once the script finishes looping through the documents, the final cleanup occurs. This code appears here.
$application.quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
The default option of the close method is to save o 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 as well by using Get-Member –static on the variable that contains the enumeration type. The thing that 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 on this, refer to the following Scripting Wife article.
Join me tomorrow for the Weekend Scripter when I will talking about parsing file names, and creating datetime objects based upon 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 scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy