This summer I wrote about a function I developed called Out-MSWord. The function was discussed in my Practical PowerShell column which was published in the free e-Journal Windows Administration in RealTime put out by RealTime Publishers. The original was published in Issue #17 if you are interested. The function accepted pipelined input and created a Microsoft Word document. Naturally, you need to have Microsoft Word installed in order for this to work.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
PS C:\> get-service | out-MSWord
The function accepted a number of parameters so you could control font name, size, color, append, and more. The function was written for PowerShell v1.0 but also worked on PowerShell v2.0. However, I was revisiting the function and realize there were places I could tweak, such as adding additional error handing. I also realized that if rewrote this for PowerShell v2.0, I could create an advanced function and take advantage of cmdletbinding, advanced parameters and help.
1: Function Out-MSWord {
2: #requires -version 2.0
3:
4: <#
5: .Synopsis
6: Accept pipelined input from a PowerShell expression and write output to a Microsoft Word document.
7: .Description
8: Pipe cmdlet output to this function and create a MS Word document. You must have Microsoft Word installed.
9: This has been tested with Microsoft Word 2007. If you don't specify a filename, Microsoft Word will open
10: upon completion and you can manually save the document. You can specify a filename along with optional
11: parameters -append or -noclobber.
12:
13: The -noclobber parameter will not overwrite any existing files. Otherwise, if you specify a file and it
14: already exists, it will be overwritten.
15:
16: The -Tee parameter will write piped input to the console as well as send it to the Word file.
17:
18: You can specify font size, name and color for the document body and footer. The defaults are 8pt Consolas.
19: You will get best results using a fixed width font like Courier New or Lucida Console. Page numbering is
20: automatically defined for the lower right corner of each page. If you don't specify a footer it defaults to
21: Printed [datetime].
22:
23: .Parameter Path
24: The path and filename for the new Microsoft Word document.
25: .Parameter InputObject
26: Pipelined objects passed to this function.
27: .Parameter Tee
28: Display objects in the console and write to the Word document.
29: .Parameter Append
30: Append data to the Microsoft Word document
31: .Parameter NoClobber
32: Don't overwrite the Microsoft Word document if it already exists.
33: .Parameter Font
34: Font name to use in the document body. Default is Consolas.
35: .Parameter FontSize
36: Font size to use in the document body. Default is 8. This parameter has an alias of Size.
37: .Parameter FontColor
38: Font color to use in the document body. Valid choices are Auto,Black,Blue,Green,Red,Teal,Violet, and Yellow.
39: The default is Auto. This paramater has an alias of Color.
40: .Parameter FooterText
41: Text to use for the document footer. The default is Printed [datetime]. This parameter has an alias
42: of Footer.
43: .Parameter FooterFont
44: Font to use for the document footer. Default is Consolas.
45: .Parameter FooterSize
46: Font size to use for the document footer. Default is 8.
47:
48: .Example
49: get-service | where {$_.status -eq "running"} | Out-MSWord
50:
51: This will create a Microsoft Word document with a list of all running processes.
52: .Example
53: gwmi win32_bios | ow -path "c:\files\my3.doc" -noclobber
54:
55: Save the output from Get-WMIObject for the Win32_Bios class to a file called c:\files\my3.doc. If the file
56: already exists it will not be overwritten.
57: .Example
58: get-eventlog -list | ow c:\test\log.docx -tee
59:
60: Take the outpout from Get-Eventlog and not only save it to the specified file but also display the results
61: in the console. This example assumes you are using the alias ow for the Out-MSWord function.
62: .Example
63: get-process | sort workingset -descending | where {$_.workingset -lt 100mb -and $_.workingset -gt 25mb} | ow c:\files\ps.doc -fontcolor yellow -font "Lucida Console" -fontsize 10.5 -append
64:
65: This expression will create a Word document of processes with a workingset of between 25MB and 100MB. The
66: results will be appended to the file, ps.doc, written in a yellow Lucida Console 10.5 font. This example
67: assumes you are using the ow alias for this function.
68: .Example
69: @((gwmi win32_operatingsystem),(gwmi win32_bios),(gwmi win32_computersystem)) | ow -footertext ("{0} captured: {1}" -f $env:computername,(get-date))
70:
71: Take the output from the three Get-WMIObject expressions and write output to all of them to a Microsoft Word
72: document. A custom footer will be included that shows the computername and the current datetime.
73: .Example
74: $file="c:\reports\eventreport.doc"
75: C:\PS>"Eventlog report for $env:computername" | Out-MSWord $file -size 14
76: C:\PS>$events=get-eventlog System -newest 100 | group EntryType
77: C:\PS>for ($i=0;$i -lt $events.count;$i++) {
78: switch ($events[$i].name) {
79: "Information" {
80: Out-MSWord $file -input "Information Events" -size 10 -append
81: $events[$i].group | format-list TimeWritten,EventID,Source,Message |
82: Out-MSWord $file -color Green -append
83: }
84: "SuccessAudit" {
85: Out-MSWord $file -input "Success Audit Events" -size 10 -append
86: $events[$i].group | format-list TimeWritten,EventID,Source,Message |
87: Out-MSWord $file -color Green -append
88: }
89: "Error" {
90: Out-MSWord $file -input "Error Events" -size 10 -append
91: $events[$i].group | format-list TimeWritten,EventID,Source,Message |
92: Out-MSWord $file -color Red -append
93: }
94: "FailureAudit" {
95: Out-MSWord $file -input "Failure Audit Events" -size 10 -append
96: $events[$i].group | format-list TimeWritten,EventID,Source,Message |
97: Out-MSWord $file -color Red -append
98: }
99: "Warning" {
100: Out-MSWord $file -input "Warning Events" -size 10 -append
101: $events[$i].group | format-list TimeWritten,EventID,Source,Message |
102: Out-MSWord $file -color Yellow -append
103: }
104: } #end Switch
105: } #end foreach
106:
107: In this example a formatted Microsoft Word document is created that list eventlog information for the local
108: computer. Event log information is grouped by EntryType and then group is processed by using a For statement
109: and a Switch construct. Event information is written to the Microsoft Word document in a corresponding font
110: color. This example also shows how to insert text into the document by explicitly using the -InputObject
111: parameter.
112:
113: .Inputs
114: Accepts objects as pipelined input.
115: .Outputs
116: Microsoft Word document object
117: .Link
118: Out-Printer
119: Out-File
120: .Notes
121: NAME: Out-MSWord
122: VERSION: 2.0
123: AUTHOR: Jeffery Hicks
124: LASTEDIT: September 18, 2009
125:
126:
127: #>
128:
129: [CmdletBinding()]
130:
131: #define the parameters
132: param (
133: [Parameter(ValueFromPipeline=$False,Position=0,Mandatory=$False,HelpMessage="The filename and path for the saved Word document.")]
134: [ValidateScript({Test-Path (split-path $_)})]
135: [String]$Path,
136:
137: [Parameter(ValueFromPipeline=$True,Position=1,Mandatory=$True,HelpMessage="Pipelined input.")]
138: [object[]]$InputObject,
139:
140: [Parameter(ValueFromPipeline=$False,Mandatory=$False, HelpMessage="If specified, write to the console and the Word document.")]
141: [switch]$Tee,
142:
143: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="If specified, append to output write to the Word document.")]
144: [switch]$Append,
145:
146: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="If specified, don't overwrite the existing Word document specified by `$path.")]
147: [switch]$NoClobber,
148:
149: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font family to use for the document body.")]
150: [string]$Font="Consolas",
151:
152: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font size to use for the document body.")]
153: [Alias("Size")]
154: [double]$FontSize=8,
155:
156: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font color to use for the document body.")]
157: [ValidateSet("Auto","Black","Blue","Green","Red","Teal","Violet","Yellow")]
158: [Alias("Color")]
159: [string]$FontColor="Auto",
160:
161: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="Text to use for the footer.")]
162: [Alias("Footer")]
163: [string]$FooterText=("printed {0}" -f (Get-Date)),
164:
165: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font to use for the document footer.")]
166: [string]$FooterFont="Consolas",
167:
168: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font size to use for the document footer.")]
169: [double]$FooterSize=8
170:
171: ) #end param definition
172:
173:
174: BEGIN{
175:
176: #define some MS Word variables
177: $wdSeekMainDocument = 0
178: $wdSeekPrimaryFooter = 4
179: $wdSeekPrimaryHeader = 1
180: $wdAlignPageNumberCenter = 1
181: $wdAlignPageNumberInside = 3
182: $wdAlignPageNumberLeft = 0
183: $wdAlignPageNumberOutside = 4
184: $wdAlignPageNumberRight = 2
185: $wdStory = 6
186:
187: #define color values
188: $Auto = 0
189: $Black = 1
190: $Blue = 16711680
191: $Green = 32768
192: $Red = 255
193: $Teal = 8421376
194: $Violet = 8388736
195: $Yellow = 32896
196:
197: # #select font color
198: switch ($fontcolor) {
199: "Auto" {$color=$Auto}
200: "Black" {$color=$Black }
201: "Blue" {$color=$Blue }
202: "Green" {$color=$Green }
203: "Red" {$color=$Red }
204: "Teal" {$color=$Teal }
205: "Violet" {$color=$Violet }
206: "Yellow" {$color=$Yellow }
207:
208: default {
209: #this line should never get called since there is data validation with the fontcolor parameter, but I'll leave it in
210: #just in case
211: Write-Warning "Invalid color choice: $fontcolor. Using Default. Valid color choices are: Black,Blue,Green,Red,Teal,Violet and Yellow."
212: $color=$wdAuto}
213: } #end Switch
214:
215: $ErrorActionPreference="SilentlyContinue"
216:
217: Trap {
218: Write-Warning "There was an error. Make sure you have Microsoft Word installed and that you are specifying valid filename or path."
219: Break
220: }
221: #create the MS Word COM object
222: $word=New-Object -ComObject "Word.Application" -ea Stop
223:
224: #get document if -append
225: if ($append)
226: {
227: #verify file exists and if so, open it
228: if (Test-Path $path)
229: {
230: $doc=$word.documents.open($path)
231: $blnNewFile=$False
232:
233: }
234: else
235: {
236: #you asked to append to a file that doesn't exist so create a new one
237: $doc=$word.Documents.add()
238: $blnNewfile=$True
239: }
240: }
241: else
242: {
243: #create a new document
244: $doc=$word.Documents.add()
245: $blnNewFile=$True
246:
247: }
248:
249: $selection=$word.Selection
250:
251: #get the footer
252: $doc.ActiveWindow.ActivePane.view.SeekView=$wdSeekPrimaryFooter
253:
254: #set the footer
255: $selection.HeaderFooter.Range.Text=$footerText
256:
257: #add page numbering
258: $selection.HeaderFooter.PageNumbers.Add($wdAlignPageNumberRight) | Out-Null
259:
260: #get the footer and format font
261: $footers=$doc.Sections.Last.Footers
262:
263: foreach ($footer in $footers) {
264: if ($footer.exists) {
265: $footer.range.font.name=$footerfont
266: $footer.range.font.size=$footersize
267: }
268: } #end Foreach
269:
270: #return focus to main document
271: $doc.ActiveWindow.ActivePane.view.SeekView=$wdSeekMainDocument
272:
273: #initialize an array to hold incoming objects
274: $data=@()
275:
276: } #end BEGIN scriptblock
277:
278: PROCESS {
279:
280: #save incoming objects to a variable
281: if ($InputObject) {
282: $data+=$InputObject
283: }
284: else
285: {
286: $data+=$_
287: }
288:
289: #write piped object if -Tee
290: if ($tee)
291: {
292: write $_
293: }
294:
295: } #end PROCESS scriptblock
296:
297: END {
298:
299: #convert data to string
300: $text=$data | Out-String
301:
302: #only write to the file if $text exists
303: if ($text) {
304: if (!$blnNewFile) #the file has been opened before
305: {
306: #jump to the end
307: $selection.Endkey($wdStory) | Out-Null
308: #insert blank lines
309: $selection.TypeParagraph()
310: $selection.TypeParagraph()
311:
312: } #end if !$blnNewFile
313:
314: #set font and paragraph settings
315: $selection.font.name=$font
316: $selection.font.size=$fontsize
317: $selection.font.color=$color
318: $selection.paragraphFormat.SpaceBefore = 0
319: $selection.paragraphFormat.SpaceAfter = 0
320:
321: #add the text to the document
322: $selection.TypeText($text.Trim())
323:
324: if ($path)
325: {
326: #don't overwrite if -noclobber was specified
327: if ($noclobber)
328: {
329: #does file exist?
330: if (Test-path $path)
331: {
332: Write-Warning "-NoClobber specified and $path exists"
333: #show the document so you can review and/or save
334: $word.visible=$True
335: } #end if item exists
336: else
337: {
338: $doc.SaveAs([ref]$path)
339: $word.quit()
340: }
341: } #end if $noclobber
342: else
343: {
344: $doc.SaveAs([ref]$path)
345: $word.quit()
346: }
347: } #end if $path
348: else #no filepath
349: {
350: #jump to the beginning of the document
351: $selection.Homekey($wdStory) | Out-Null
352:
353: #show the document so you can review and/or save
354: $word.visible=$True
355: }
356: } #end if $text
357: else
358: {
359: Write-Warning "No data to write."
360: #turn off alerts
361: $word.displayAlerts=0
362: #close without saving
363: $doc.close([ref]$Word.WdSaveOptions.wdDoNotSaveChanges)
364:
365: #exit Microsoft Word
366: $word.quit()
367: }
368: } #end END scriptblock
369:
370: } #end Function
371:
372: #create an alias
373: Set-Alias ow Out-MSWord
374: Get-Alias ow
375:
The information at the beginning is used by Get-Help to present help information just as you get with a cmdlet. That’s the great thing in PowerShell v2; you can write an advanced function that behaves essentially like a cmdlet.
If you use –full you will get full parameter help and a number of examples. I love that I can now incorparate documentation directly into the script or function.
One thing I was able to accomplish was move some of the error handling to the parameters. In PowerShell v2 you can add validation tests. I’ve added one to verify the specified path value is valid and one to check the value passed for the font color. In the first version I used a Switch construct to verify and write a warning message if an invalid value was passed., Now I can use parameter validation.
1: [Parameter(ValueFromPipeline=$False,Mandatory=$False,HelpMessage="The font color to use for the document body.")]
2: [ValidateSet("Auto","Black","Blue","Green","Red","Teal","Violet","Yellow")]
3: [Alias("Color")]
4: [string]$FontColor="Auto",
You’ll notice I also defined an alias for this parameter. Read the help topic about_Functions_Advanced_Parameters to learn more.
My script file also defines an alias, ow, for the Out-MSWord function. Be sure to look at the help examples to see how to use this function.
Download
and save it as a .ps1 file. You’ll need to dot source the script to load it into your session or script or add it to your profile.
If you find a use for this in a production environment, I’d love to hear about it.