{"id":52203,"date":"2009-10-21T00:01:00","date_gmt":"2009-10-21T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2009\/10\/21\/hey-scripting-guy-part-1-how-can-i-update-many-office-word-documents-at-once\/"},"modified":"2009-10-21T00:01:00","modified_gmt":"2009-10-21T00:01:00","slug":"hey-scripting-guy-part-1-how-can-i-update-many-office-word-documents-at-once","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-part-1-how-can-i-update-many-office-word-documents-at-once\/","title":{"rendered":"Hey, Scripting Guy! Part 1: How Can I Update Many Office Word Documents at Once?"},"content":{"rendered":"<p><!-- AddThis Button BEGIN --><a class=\"addthis_button\" href=\"http:\/\/www.addthis.com\/bookmark.php?v=250&amp;pub=scriptingguys\"><img decoding=\"async\" alt=\"Bookmark and Share\" src=\"http:\/\/s7.addthis.com\/static\/btn\/v2\/lg-share-en.gif\" width=\"125\" height=\"16\"><\/a>  <\/p>\n<p>(<strong>Editor&#8217;s note<\/strong>: This is part 1 of a two-part article originally intended for <em>TechNet Magazine<\/em>. Part 2 will be published tomorrow.)&nbsp;<br>&nbsp;<\/p>\n<p class=\"MsoNormal\">Little clouds of dust rise from the ground like miniature tornados carrying powdery sand that drifts along lazily on a nearly still breeze. The tires that support the ancient bus grind well-worn rocks and produce more powdery sand that readily fills any void created by previous traffic. Hushed voices leave words that hang in the air like the soft dust that clings to anything moist. The approach to Saqqara follows an ancient route along winding canals, date palms, and camels. The short ride from bustling Cairo scrolls me back thousands of years as I enter the temple complex of Djoser. Across the complex, rising with the seeming force of eternity stands the step pyramid of Djoser. I hold my breath as I stand in wonderment at the massive structure that has remained for more than 3,000 years. As I close my eyes, a breeze seems to pick up and for a few moments I am transported back to the time when this complex was new and served the impressive gateway for which it had been designed. I stand as one more point on a vast time continuum. <\/p>\n<p class=\"MsoNormal\">Few things seem to remain for multiple millennia, and those that do gain traction from the current milieu will not emerge from the world of information technology. Documents are often out of date before the virtual ink dries on the screen of the laptop. Web sites come and go, and migrate from URL to URL. Product names change, and so do the names of features that persist from version to version. In a rapidly changing world, how can you keep your documentation up to date? Using Microsoft Office Word, you can use the find-and-replace tool to change all instances of a word or phrase with another word or phrase of your choosing. But what do you do when you have hundreds of Word documents that need to be inspected and updated? This is where scripting comes in.<\/p>\n<p class=\"MsoNormal\">The complete ReplaceWordsLogResults.ps1 script is seen here. <\/p>\n<p class=\"CodeBlockScreenedHead\"><strong>ReplaceWordsLogResults.ps1<\/strong><\/p>\n<p class=\"CodeBlockScreened\"><span><font><font face=\"Lucida Sans Typewriter\"># &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br># NAME: ReplaceWordsLogResults.ps1<br># AUTHOR: ed wilson, Microsoft<br># DATE: 8\/11\/2009<br>#<br># KEYWORDS: Word.Application<br>#<br># COMMENTS: This script replaces a word in a document file<br># with another word or phrase. <br>#<br># TNM<br># &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;<br>Param(<br><span>&nbsp;<\/span>$path = &#8220;C:datafso&#8221;<br>)#end param<\/p>\n<p># *** Functions here ***<br>Function Get-WordSelection([string]$file,[boolean]$visible)<br>{<br><span>&nbsp;<\/span>$script:Word = New-Object -ComObject word.application<br><span>&nbsp;<\/span>$Word.Visible = $visible<br><span>&nbsp;<\/span>$script:doc = $Word.Documents.Open($file)<br><span>&nbsp;<\/span>$script:selection = $Word.Selection<br><span>&nbsp;<\/span>} #end function Get-WordSelection <br><span>&nbsp;<\/span><br><span>&nbsp;<\/span>Function New-LogObject ($document, $replaced)<br><span>&nbsp;<\/span>{<br><span>&nbsp;<\/span>$logObject = New-Object psobject <br><span>&nbsp;<\/span>$logObject | Add-Member -MemberType noteProperty -Name &#8220;document&#8221; -Value $document<br><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;replaced&#8221; -Value { $rtn }<br><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;date&#8221; -value { get-date -uformat &#8220;%Y\/%m\/%d&#8221; }<br><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;user&#8221; -value { $env:username }<br><span>&nbsp;<\/span>$Script:doc.Save()<br><span>&nbsp;<\/span>$Script:doc.close()<br><span>&nbsp;<\/span>$logObject <br><span>&nbsp;<\/span>} # end function new-logobject<\/p>\n<p># *** Entry to Script ***<\/p>\n<p>$files = Get-ChildItem -Path $path -Include *.doc,*.docx -Recurse<br>$wordHash = @{&#8220;misspelled&#8221; = &#8220;spelled incorrectly&#8221; ; &#8220;done&#8221;=&#8221;finished&#8221;}<br>$logfile = &#8220;ReplaceResults.csv&#8221;<br>$i=0<br>$ReplaceAll = 2<br>$FindContinue = 1<br>$MatchCase = $False<br>$MatchWholeWord = $True<br>$MatchWildcards = $False<br>$MatchSoundsLike = $False<br>$MatchAllWordForms = $False<br>$Forward = $True<br>$Wrap = $FindContinue<br>$Format = $False<\/p>\n<p>$files | ForEach-Object {<br><span>&nbsp; <\/span>$file = $_.fullname<br><span>&nbsp; <\/span>$i ++<br><span>&nbsp; <\/span>write-progress -activity &#8220;Searching For Word documents&#8221; `<br><span>&nbsp;<\/span>-status &#8220;Progress:&#8221; -percentcomplete ($i\/$files.count*100)<br><span>&nbsp;<\/span><br><span>&nbsp;<\/span>Get-WordSelection -file $_.fullname -visible $false<br><span>&nbsp;<\/span><br><span>&nbsp;<\/span>foreach($FindText in $wordHash.keys)<br><span>&nbsp;<\/span>{<br><span>&nbsp; <\/span>$rtn = $Script:Selection.Find.Execute($FindText,$MatchCase,<br><span>&nbsp;&nbsp; <\/span>$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,<br><span>&nbsp;&nbsp; <\/span>$MatchAllWordForms,$Forward,$Wrap,$Format,<br><span>&nbsp;&nbsp; <\/span>$wordHash.$FindText,$ReplaceAll) <br><span>&nbsp;<\/span>} #end foreach findtext<br><span>&nbsp;<\/span><br><span>&nbsp;<\/span>New-LogObject -document $file -replaced $rtn<br>} | Export-Csv -Path (join-path -Path $path -ChildPath $logfile) -NoTypeInformation<\/p>\n<p>$Script:word.quit()<\/p>\n<p><\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The first thing the ReplaceWordsLogResults.ps1 script does is create a command-line parameter named <b>path<\/b> to allow you to specify the directory to search for Word documents. The <b>Param<\/b> keyword is used to create the parameter. To use the parameter from the Windows PowerShell console, you specify the name of the script and the path you wish to search. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">ReplaceWordsLogResults.ps1 &ndash;path c:mydirectory<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <b>param<\/b> statement is shown here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">Param(<br><span>&nbsp;<\/span>$path = &#8220;C:datafso&#8221;<br>)#end param<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The first function that is created is the <b>Get-WordSelection<\/b> function. It receives two inputs when it is called from within the script. The first parameter is the <b>file<\/b> parameter and the second parameter is the Boolean <b>visible<\/b> parameter. The <b>file<\/b> parameter contains the path to a Word document that will be processed. The <b>visible<\/b> parameter is used to indicate whether the Word document will be visible while it is being scanned. The variables that are created inside the <b>Get-WordSelection<\/b> are marked with the script scope. This means the variables will be available anywhere within the ReplaceWordsLogResults.ps1 script. The <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb148369.aspx\"><font face=\"Segoe\">Word.Application object<\/font><\/a> is the main object that is used when automating Microsoft Word. It is used to obtain the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb211902.aspx\"><font face=\"Segoe\">Documents collection object<\/font><\/a> to open the Word document. It will also be used to exit the Word application after all the Word documents have been processed. This portion of the <b>Get-WordSelection<\/b> function is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">Function Get-WordSelection([string]$file,[boolean]$visible)<br>{<br><span>&nbsp;<\/span>$script:Word = New-Object -ComObject word.application<br><span>&nbsp;<\/span>$Word.Visible = $visible<br><span>&nbsp;<\/span>$script:doc = $Word.Documents.Open($file)<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">To work with the text inside the Word document, you will need to obtain a <b>Selection<\/b> object. The easiest way to obtain a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb178879.aspx\">Selection object<\/a> is to query the <b>selection<\/b> property from the Word application object. The returned <b>Selection<\/b> object will need to be used outside the <b>Get-WordSelection<\/b> function. The variable that is created to hold the <b>Selection<\/b> object is therefore created with a script-level scope. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$script:selection = $Word.Selection<br><span>&nbsp;<\/span>} #end function Get-WordSelection <\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">Because the ReplaceWordsLogResults.ps1 script will need to create a log that is used to track the progress of the changes made to the Word documents in the target folder, a function named <b>New-LogObject<\/b> is created. It receives two parameters: the path to the document being scanned and a Boolean value that indicates if changes were made to it. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>Function New-LogObject ($document, $replaced)<br><span>&nbsp;<\/span>{<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <b>log<\/b> object that is created is an instance of a PSObject. The <b>New-Object<\/b> cmdlet is used to create the object. The returned object is stored in the variable <b>$logObject<\/b>: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject = New-Object psobject <\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">Four properties are added to the <b>$logObject<\/b>. The <b>Add-Member<\/b> cmdlet is used to add the members to the object. The first two properties are passed to the <b>New-LogObject<\/b> function when it is called. The <b>Date<\/b> property is created by using the <b>Get-Date<\/b> cmdlet, and the user name is pulled from the environmental drive. This is seen here:<\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject | Add-Member -MemberType noteProperty -Name &#8220;document&#8221; -Value $document<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;replaced&#8221; -Value { $rtn }<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;date&#8221; -value { get-date -uformat &#8220;%Y\/%m\/%d&#8221; }<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject | Add-Member -MemberType scriptProperty -Name &#8220;user&#8221; -value { $env:username }<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The Word document is saved and closed by using the appropriate methods from the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb211897.aspx\"><font face=\"Segoe\">Word Document object<\/font><\/a>. The newly created object is then emitted back to the calling script. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$Script:doc.Save()<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$Script:doc.close()<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$logObject<\/p>\n<p><\/font><\/span><\/p>\n<p>&lt;<\/p>\n<p>p style=&#8221;MARGIN: 4pt 0in 7pt; BACKGROUND: #d9d9d9; mso-background-themecolor: background1; mso-background-themeshade: 217&#8243; class=&#8221;CodeBlock&#8221;&gt;<span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>} # end function new-logobject<\/p>\n<p><\/font><\/span><\/p>\n<p><br>Join us tomorrow for part 2, the conclusion to this post about updating many Word documents at once. In the meantime, follow us on <a title=\"Twitter\" href=\"http:\/\/www.twitter.com\/scriptingguys\/\"><span><font face=\"Segoe\">Twitter<\/font><\/span><\/a> or <a title=\"Facebook\" href=\"http:\/\/www.facebook.com\/home.php?#\/group.php?gid=5901799452\"><span><font face=\"Segoe\">Facebook<\/font><\/span><\/a>. If you have any questions, send e-mail to us at <a href=\"http:\/\/blogs.technet.commailto:scripter@microsoft.com\"><span><font face=\"Segoe\">scripter@microsoft.com<\/font><\/span><\/a> or post your questions on the <a href=\"http:\/\/social.technet.microsoft.com\/Forums\/en\/ITCG\/threads\/\">Official Scripting Guys Forum<\/a>. See you on Monday. Peace!<\/p>\n<p><b><span>Ed Wilson and Craig Liebendorfer, Scripting Guys<\/p>\n<p><\/span><\/b><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>(Editor&#8217;s note: This is part 1 of a two-part article originally intended for TechNet Magazine. Part 2 will be published tomorrow.)&nbsp;&nbsp; Little clouds of dust rise from the ground like miniature tornados carrying powdery sand that drifts along lazily on a nearly still breeze. The tires that support the ancient bus grind well-worn rocks and [&hellip;]<\/p>\n","protected":false},"author":595,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[84,49,3,45],"class_list":["post-52203","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-microsoft-word","tag-office","tag-scripting-guy","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>(Editor&#8217;s note: This is part 1 of a two-part article originally intended for TechNet Magazine. Part 2 will be published tomorrow.)&nbsp;&nbsp; Little clouds of dust rise from the ground like miniature tornados carrying powdery sand that drifts along lazily on a nearly still breeze. The tires that support the ancient bus grind well-worn rocks and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52203","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/595"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=52203"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52203\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=52203"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=52203"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=52203"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}