# Copyright © 2009, Microsoft Corporation. All rights reserved. # Functions # # Build-RTFData: Takes a Header and Body and wraps it into a string representing the complete file # Create-RTFHeader: Builds an RTF header with overridable default values # Create-RTFURL: Builds an RTF URL with a link and a link name # Insert-RTFURLs: Uses [URL] tag to convert URLs in text # Escape-RTF: Converts international characters to RTF text by optionally adding backslashes etc. # Escape-RTFProcess: Helper Function for Escape-RTF # Add-RTFText: Insert paragraph text into the RTF # Create-RTFList: Takes a list of objects and formats it into an RTF bulleted list, with optional # HeaderText, OrderBy, and LocalizationTbl parameters # Create-RTFSubItem: Helper function for Create-RTFList # File-ToHexLines: Creates a string output of a file in hexidecimal. Optional ByteWidth parameter # specifies number of bytes per line. # # Example: # # $header = Create-RTFHeader -CharSet 'ansi' # $body = Create-RTFList $myProducts 'ProductName' -HeaderText 'Product List' # $body += Add-RTFText $blocktext # $output = Build-RTFData $header $body function Build-RTFData([string]$RTFHeader, [string]$RTFBody) { [string]$FullText = '{' + $Header + "`r`n" + $RTFBody + "`r`n" + '}' return $FullText } function Create-RTFHeader { Param( $CharSet = 'ansi', $MajorVersion = '1', $CodePage = '1252', $DefaultFont = '0', $From = 'Text', $HTMLVer = '1', $Language = '1033', $Language_EA = '1033', $Language_ME = '1033', $Generator = 'Powershell Script RTFGEN 0.1' ) #RTF Tag / Version [string]$Header = '\rtf' + $MajorVersion #Character Set switch ($CharSet.ToLower()) { 'ansi' {$Header += '\ansi\ansicpg' + $CodePage } 'mac' {$Header += '\mac\ansicpg' + $CodePage } 'pc' {$Header += '\pc\ansicpg' + $CodePage } 'pca' {$Header += '\pca\ansicpg' + $CodePage } 'fbidis' {$Header += '\fbdis' } default {$Header += '\ansi\ansicpg' + $CodePage } } #RTF Source if($From) { switch ($From.ToLower()) { 'text' {$Header += '\fromtext' } 'html' {$Header += '\fromhtml' + $HTMLVer } default {} } } #Language Info $Header += '\deflang' + $Language $Header += '\deflangfe' + $Language_EA $Header += '\adeflang' + $Language_ME #Font Block $Header += "`r`n" + '{\fonttbl{\f0\fnil\fcharset0 Segoe UI;}{\f1\fnil\fcharset0 Segoe UI;}{\f2\fnil\fcharset0 Calibri;}{\f3\fnil\fcharset2 Symbol;}}' #Generator $Header += "`r`n" + '{\*\generator ' + $Generator + ' ;}' return $Header } # Escape-RTF can handle strings and arrays of strings. Can be used as a Pipe-Line. # In addition to Escaping illegal characters Escape-RTF supports Bold, Italic and Underline # thrugh [b][/b],[i][/i] and [u][/u] patterns in the text function Escape-RTF { param([string]$Source) Begin { } Process { if($_) { Escape-RTFProcess $_ } } End { if($Source) { $Source | Foreach-Object { Escape-RTFProcess $_ } } } } function Create-RTFURL([string]$URL, [string]$URLText) { $output = '{\field{\*\fldinst{HYPERLINK "' + $URL + '"}}{\fldrslt{\ul\cf1 ' + $URLText + '}}}' return $output } function Insert-RTFURLs { param( [string]$InString, [string]$Start = '[URL]', [string]$End = '[/URL]' ) [String[]]$arrOutput = @() [int]$index = $InString.IndexOf($Start) + $Start.Length While ($index -ne $Start.Length - 1) { [string]$StartString = $InString.Substring(0, ($index - $Start.Length) ) [string]$EndString = $InString.Substring(($Instring.IndexOf($End, $index) + $End.Length), ($InString.Length - ($inString.IndexOf($End, $index) + $End.Length)) ) [string]$BetweenText = $InString.Substring($index, $InString.IndexOf($End, $index) - $index) [string]$Insert = Create-RTFURL $BetweenText $BetweenText $InString = $StartString + $Insert + $EndString $index = $InString.IndexOf($Start, $index) + $Start.Length } return $inString } function Escape-RTFProcess([string]$inString) { if ($inString -match '^[a-z0-9\-/ ]*$') { return $inString } $Output = new-object system.text.stringbuilder $arrSource = [Char[]]$inString foreach ($character in $arrSource) { if ($character -gt 127) { $Output.append('\u') | Out-Null $Output.append([uint32]$character) | Out-Null $Output.append('?') | Out-Null } elseif (('\', '{', '}') -eq $character) { $Output.append('\') | Out-Null $Output.append($character) | Out-Null } else { $Output.append($character) | Out-Null } } [string]$OutString = $Output.ToString() $OutString = Insert-RTFURLs $OutString if ($OutString -match '\[.*\]') { $OutString = $OutString.Replace('[b]', '\b ' ) $OutString = $OutString.Replace('[B]', '\b ' ) $OutString = $OutString.Replace('[/b]', '\b0 ' ) $OutString = $OutString.Replace('[/B]', '\b0 ' ) $OutString = $OutString.Replace('[u]', '\ul ' ) $OutString = $OutString.Replace('[U]', '\ul ' ) $OutString = $OutString.Replace('[/u]', '\ul0 ' ) $OutString = $OutString.Replace('[/U]', '\ul0 ' ) $OutString = $OutString.Replace('[i]', '\i ' ) $OutString = $OutString.Replace('[i]', '\i ' ) $OutString = $OutString.Replace('[/i]', '\i0 ' ) $OutString = $OutString.Replace('[/I]', '\i0 ' ) } return $OutString } function Add-RTFText { param( [string]$Source, [int]$Fontsize = 9, [switch]$IgnoreNewLines = $false, [switch]$bold = $false, [switch]$italic = $false ) $RTFFontSize = $Fontsize * 2 [string]$Output = '' [string]$boldstart = '' [string]$boldend = '' [string]$italstart = '' [string]$italend = '' if($bold) { [string]$boldstart = '\b' [string]$boldend = '\b0' } if($italic) { [string]$italstart = '\i' [string]$italend = '\i0' } if($IgnoreNewLines) { $Output += '\pard' + $italstart + $boldstart + '\f0\fs' + $RTFFontSize + ' ' + (Escape-RTF $string.trim()) + $boldend + $italend + '\par' } else { $arrSource = $Source.Split("`n") foreach( $string in $arrSource) { $Output += '\pard' + $italstart + $boldstart + '\f0\fs' + $RTFFontSize + ' ' + (Escape-RTF $string.trim()) + $boldend + $italend + '\par' } } return $Output } # Create-RTFList # This function builds a 2-Level RTF formatted list from an array of objects and NoteProperty fields. # A Note Property has a Name and Value... which are handled different ways by the function # # $KeyName: This is the Name of the NoteProperty in each object which will comprise the first # level of the bulleted list. The Key Name is never displayed, however its value is. # # $HeaderText: If this is entered, a header at the top of the RTF list is displayed # # $OrderBy: This array is an in order list of the note-property names that will be displated. Any # NoteProperties not listed in this array will not be displayed, so it can be used to # both order the output and exclude unwanted data. If not specified all NoteProperties # will be displayed in no specific order as data is stored in a Hash Table # # $LocalizationTbl: Use this hash table to change the display value of NoteProperty names to a localized # value. Any omissions will simply result in the display of a non-localized name. function Create-RTFList { param ( [array]$objArray, [string]$KeyName, [string]$HeaderText = $null, [string[]]$OrderBy = $null, [hashtable]$LocalizationTbl = $null ) if ($objArray -eq $null) { return } [string]$myData = '' if(!([System.String]::IsNullOrEmpty($HeaderText))) { $myData += '\viewkind4\uc1\pard\sl276\slmult1\lang9\f0\fs36 ' + (Escape-RTF $HeaderText) + '\par' + "`r`n" } foreach($object in $objArray) { $ItemName = '' [hashtable]$ListItems = @{} # Gather Data [array]$properties = Get-Member -InputObject $object -MemberType NoteProperty if ($properties -ne $null) { foreach($property in $properties) { if($property.Name -ieq $KeyName) { $ItemName = $object.$($property.Name) } else { $ListItems.Add($property.Name, $object.$($property.Name)) } } } else { break } # Output Header $myData += "\pard\sl240\slmult1\b\f1\fs20 " + (Escape-RTF $ItemName) + '\par' + "`r`n" #Formatting at begining of list $myData += "\pard{\*\pn\pnlvlblt\pnf3\pnindent0{\pntxtb\'B7}}\fi-360\li720\sl240\slmult1\b0\f2" # Output List: -OrderBy Work Flow if($OrderBy -ne $null) { foreach($entry in $OrderBy) { if($ListItems.ContainsKey($entry)) { [string]$ListItemName = $ListItems.Keys | Where-Object { $_ -ieq $entry } if($LocalizationTbl -ne $null) { if($LocalizationTbl.Item($entry) -ne $null) { $ListItemName = $LocalizationTbl.Item($entry) } } # Only do something if the key has a name if(!([System.String]::IsNullOrEmpty($ListItemName))) { $myData += Create-RTFSubItem $ListItemName $ListItems[$entry] } } } } # Output List: Non -OrderBy Work Flow else { $enumList = $ListItems.GetEnumerator() foreach ($item in $enumList) { [string]$ListItemName = $item.Key if($LocalizationTbl -ne $null) { if($LocalizationTbl.Item($item.Key) -ne $null) { $ListItemName = $LocalizationTbl.Item($item.Key) } } # Only do something if the key has a name if(!([System.String]::IsNullOrEmpty($ListItemName))) { $myData += Create-RTFSubItem $ListItemName $item.Value } } } } $myData += '\pard\par' return $myData } # Helper Funciton for Create-RTFList function Create-RTFSubItem([string]$Name, $Value) { [string]$output = '' foreach($item in $Value) { if($item -isnot [string]) { $item = $item.ToString() } # Format the list item depending on whether or not it has a value pair if([System.String]::IsNullOrEmpty($item)) { $output += "\fs18{\pntext\f3\'B7\tab}" + (Escape-RTF $Name) + '\fs20\par' + "`r`n" } else { $output += "\fs18{\pntext\f3\'B7\tab}\b " + (Escape-RTF $Name) + ':\b0 ' + (Escape-RTF $item) + '\fs20\par' + "`r`n" } } return $output } function File-ToHexLines { param ( [string]$path, [int]$ByteWidth = 40 ) if(!(Test-Path $path) -or $ByteWidth -lt 0 ){ return } [string]$output = '' [Byte[]]$data = Get-Content -ReadCount 0 -Path $path -Encoding Byte [int] $i = 0 if($ByteWidth -gt 0) { for($i; $i -lt ($data.Count - $ByteWidth); $i = $i + $ByteWidth) { [string]$line = '' for($j = 0 ; $j -lt $ByteWidth;$j++) { [string]$thisbyte = [convert]::ToString($data[$i+$j], 16).PadLeft(2,'0') $line += $thisbyte } $line += "`r`n" $output += $line } } for($i; $i -lt $data.Count ; $i++) { $output += [convert]::ToString($data[$i], 16).PadLeft(2,'0') } return $output } function Add-RTFImage([string]$Path, [string]$Type) { $myData = '{\*\shppict{\pict' switch ($Type.ToLower()) { 'emf' {$myData += '\emfblip'} 'png' {$myData += '\pngblip'} 'jpg' {$myData += '\jpegblip'} 'jpeg' {$myData += '\jpegblip'} 'mac' {$myData += '\macpic'} 'pict' {$myData += '\macpict'} default {$myData += '\jpegblip'} } #pic size $myData += "`r`n" + (File-ToHexLines $path -ByteWidth 64) + "}}`r`n" return $myData } function Select-Between([string]$InString, [string]$Start, [string]$End) { [String[]]$arrOutput = @() [int]$index = $InString.IndexOf($Start) + $Start.Length While ($index -ne $Start.Length -1) { $arrOutput += $InString.Substring($index, $InString.IndexOf($End, $index) - $index) $index = $InString.IndexOf($Start, $index) + $Start.Length } return $arrOutput }