{"id":52193,"date":"2009-10-22T00:01:00","date_gmt":"2009-10-22T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2009\/10\/22\/hey-scripting-guy-part-2-how-can-i-update-many-office-word-documents-at-once\/"},"modified":"2009-10-22T00:01:00","modified_gmt":"2009-10-22T00:01:00","slug":"hey-scripting-guy-part-2-how-can-i-update-many-office-word-documents-at-once","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-part-2-how-can-i-update-many-office-word-documents-at-once\/","title":{"rendered":"Hey, Scripting Guy! Part 2: 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 2 of a two-part article originally intended for <em>TechNet Magazine<\/em>. Part 1 was published <a title=\"yesterday\" href=\"http:\/\/blogs.technet.com\/heyscriptingguy\/archive\/2009\/10\/21\/hey-scripting-guy-october-21-2009.aspx\">yesterday<\/a>.)&nbsp;<\/p>\n<p class=\"MsoNormal\"><br>Now we arrive at the entry point to the script. The first thing to do in the entry point of the script is to create a collection of all the .doc and docx files in the folder indicated by the <b>$path<\/b> variable. The <b>Get-ChildItem<\/b> cmdlet is used to gather the collection of files. The <b>Recurse<\/b> parameter is used to tell the <b>Get-ChildItem<\/b> cmdlet to retrieve all files in the path that match the <b>Include<\/b> filter. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$files = Get-ChildItem -Path $path -Include *.doc,*.docx &ndash;Recurse<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The ReplaceWordsLogResults.ps1 script can be used to make nomenclature changes in a collection of documents. It can also be used to automatically correct misspelled words and to improve the grammar of documents. Such a candidate for improvement is seen here:<\/p>\n<p class=\"Fig-Graphic\"><img decoding=\"async\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/october\/hey1021\/tnm-10-09-01.jpg\" width=\"600\" height=\"417\"><a href=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/october\/hey1021\/tnm-10-09-01.jpg\"><\/a><\/p>\n<p class=\"MsoNormal\"><br>There is no way to completely automate the grammar checker and the spelling checker from within Microsoft Word. There is a find-and-replace method that can be used to perform the same service. To allow the script to work with multiple words, a hash table is created. Each element of the hash table is made up of a key and a value. Each element in the hash table is separated from other elements by a semicolon. The hashtable is stored in the variable <b>$wordHash<\/b> and is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$wordHash = @{&#8220;misspelled&#8221; = &#8220;spelled incorrectly&#8221; ; &#8220;done&#8221;=&#8221;finished&#8221;}<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The log file name is stored in the <b>$logfile<\/b> variable. The log will be a comma-separated value file so that it can easily be opened in Microsoft Excel. A counter variable, <b>$i<\/b>, will be used to keep track of the progress as the script walks through the collection of files. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$logfile = &#8220;ReplaceResults.csv&#8221;<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$i=0<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb257583.aspx\"><font face=\"Segoe\">Find object<\/font><\/a> is used to perform the find-and-replace operation in the Word document. The find operation represented by the <b>find<\/b> object begins when it is called by the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/bb179352.aspx\"><font face=\"Segoe\">Find.Execute method<\/font><\/a> of the <b>find<\/b> object. The <b>execute<\/b> method takes a large number of parameters. The easiest way to deal with complicated methods such as the <b>execute<\/b> method is to create a collection of variables that contain the desired parameters. This makes the method call easier to read and also makes the script easier to modify. The variables that hold the preferences for the <b>execute<\/b> method are seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$ReplaceAll = 2<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$FindContinue = 1<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$MatchCase = $False<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$MatchWholeWord = $True<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$MatchWildcards = $False<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$MatchSoundsLike = $False<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$MatchAllWordForms = $False<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$Forward = $True<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$Wrap = $FindContinue<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$Format = $False<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The collection of Word documents that were obtained by the <b>Get-ChildItem<\/b> cmdlet is piped to the <b>ForEach-Object<\/b> cmdlet. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$files | ForEach-Object {<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <b>$file<\/b> variable is used to store the complete path to each Word document that comes across the pipeline. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp; <\/span>$file = $_.fullname<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <b>$i<\/b> counter variable is incremented by one as seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp; <\/span>$i ++<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">Next the <b>Write-Progress<\/b> cmdlet is used to display a progress bar. The progress bar shown in the following image is the one displayed by the Windows PowerShell ISE on Windows PowerShell 2.0. The progress bar gives a visual indicator that the script is running and is continuing to process Word documents. This is the only visual indicator that the script is running or has completed:<\/p>\n<p class=\"Fig-Graphic\"><img decoding=\"async\" title=\"Image of progress bar\" alt=\"Image of progress bar\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/october\/hey1021\/tnm-10-09-02.jpg\" width=\"430\" height=\"300\"><\/p>\n<p class=\"Fig-Graphic\">The code that calls the <b>Write-Progress<\/b> cmdlet is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp; <\/span>write-progress -activity &#8220;Searching For Word documents&#8221; <\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>-status &#8220;Progress:&#8221; -percentcomplete ($i\/$files.count*100)<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The <b>Get-WordSelection<\/b> function is used to create the <b>Word application<\/b> object and to create the <b>selection<\/b> object. The <b>Get-WordSelection<\/b> function accepts two parameters, the path to the Word document, and whether or not to make the Word application visible while processing the files. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>Get-WordSelection -file $_.fullname -visible $false<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">Now it is time to walk through the hash table that contains the words and their substitute values. The <b>keys<\/b> property is used to obtain a collection of all the keys in the hash table. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>foreach($FindText in $wordHash.keys)<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>{<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">Armed with a specific set of keys and values, it is time to search the Word document for specific words. The words that are sought are stored in the keys of the hash table. The substitute word is the value associated with the <b>key<\/b> value. To retrieve the value associated with a particular key, the value is retrieved by querying the specific key. Each key in the collection of keys from the hash table is queried. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp; <\/span>$rtn = $Script:Selection.Find.Execute($FindText,$MatchCase,<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;&nbsp; <\/span>$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;&nbsp; <\/span>$MatchAllWordForms,$Forward,$Wrap,$Format,<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;&nbsp; <\/span>$wordHash.$FindText,$ReplaceAll) <\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>} #end foreach findtext<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The modified Word document is seen here:<\/p>\n<p class=\"Fig-Graphic\"><img decoding=\"async\" title=\"Image of modified Word document\" alt=\"Image of modified Word document\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/october\/hey1021\/tnm-10-09-03.jpg\" width=\"600\" height=\"417\"><\/p>\n<p class=\"Fig-Graphic\">&nbsp;<\/p>\n<p class=\"MsoNormal\">After the <b>Execute<\/b> method from the <b>Find<\/b> object has been called and all the replacements that are stored in the hash table have been made, it is time to call the <b>New-LogObject<\/b> function. This function accepts two parameters: the path to the Word document and the Boolean return value that was captured from the <b>Execute<\/b> method. This is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>New-LogObject -document $file -replaced $rtn<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">One reason to pipe the collection of Word documents to the <b>ForEach-Object<\/b> cmdlet is that it makes it easy to create a CSV file from the logging object that is returned from the <b>New-LogObject<\/b> function. A new logging object is created for each Word document that is processed. The <b>Export-Csv<\/b> cmdlet includes the properties on the first line of the CSV file and the values on the second line. There is no <b>append<\/b> parameter for the <b>Export-Csv <\/b>cmdlet. The <b>Export-Csv<\/b> cmdlet is designed to handle piped data, and each logging object is therefore piped to the <b>Export-Csv<\/b> cmdlet without incurring extra property descriptions. This command is seen here: <\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">} | Export-Csv -Path (join-path -Path $path -ChildPath $logfile) -NoTypeInformation<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"CodeBlock\"><span><font face=\"Lucida Sans Typewriter\">$Script:word.quit()<\/p>\n<p><\/font><\/span><\/p>\n<p class=\"MsoNormal\">The completed log is seen here:<\/p>\n<p class=\"Fig-Graphic\"><img decoding=\"async\" title=\"Image of completed log\" alt=\"Image of completed log\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/october\/hey1021\/tnm-10-09-04.jpg\" width=\"600\" height=\"417\"><\/p>\n<p class=\"MsoNormal\"><br>Though I do not think this script will stand the test of time, it should make it easier for you to cope with many of the changes that take place on a daily basis in the workplace. Whether you use the ReplaceWordsLogResults.ps1 script to fix common typographical errors or standard letter transpositions, or to update documents with the latest and greatest naming conventions, you are sure to find a use for it. <\/p>\n<p class=\"MsoNormal\">For more tools, tips, and tricks to help you surf the tides of change, head over to the <a href=\"http:\/\/technet.microsoft.com\/scriptcenter\/\">TechNet Script Center<\/a> and check out our daily Hey, Scripting Guy! Blog posts. <\/p>\n<p><b><span>Ed Wilson and Craig Liebendorfer, Scripting Guys<\/span><\/b><\/p>\n<p class=\"MsoNormal\"><b><span><\/p>\n<p><\/span><\/b>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>(Editor&#8217;s note: This is part 2 of a two-part article originally intended for TechNet Magazine. Part 1 was published yesterday.)&nbsp; Now we arrive at the entry point to the script. The first thing to do in the entry point of the script is to create a collection of all the .doc and docx files in [&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-52193","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 2 of a two-part article originally intended for TechNet Magazine. Part 1 was published yesterday.)&nbsp; Now we arrive at the entry point to the script. The first thing to do in the entry point of the script is to create a collection of all the .doc and docx files in [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52193","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=52193"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52193\/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=52193"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=52193"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=52193"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}