<# .SYNOPSIS Create an HTML fragment file that when displayed, displays the source code with the various parts highlighted in different colors .DESCRIPTION This programs takes as input a Powershell file and pretty prints it via codes to show different parts in different colors. This even prints the indents correctly and even allows for wrapping. Version 1.1 .PARAMETER InFile Powershell file to be listed. Required .PARAMETER OutFile HTML fragment output file. If not supplied, will default to InFile with '.html appended and will be in the same directory as the input file. .PARAMETER OptFile Not used currently .PARAMETER TabEx Tab expansion. The default is set to 2 .PARAMETER LineNums Whether we want line numbers or not .NOTES Name: Pretty-Print.PS1 Author: Bryan Price DateCreated: Nov2011 .LINK http://bytehead.wikidot.com/pretty-print-ps1 .EXAMPLE .\pretty-print.ps1 c:\User\Generic\PS\Print-Report.ps1 -TabEx 8 # requires 2.0 #> param ($infile=$(Throw 'Paramater infile is required!'), $outfile, $optfile, [int] $tabex=2, [switch] $linenums) # Declare our globals here. $Global:oline = '' $Global:curcol = 1 $Global:curlin = 1 <# Thoughts 1. Configuration file fun! 2. Need to think about other options, full web page, or just the code. #> Function mung-htmlspace { # This will preserve any indentation, on the left, or internal # Repetitive spaces will be replaced by alternating non-break and regular spaces param($iline) if ($iline -eq $null) { return '' } $iline = $iline.TrimEnd(' ') # Get rid of trailing spaces if ( $iline.indexof(' ') -eq -1 ) { return $iline # No spaces, ignore } if ( $iline[0] -eq ' ' ) { # If the first char is a space, make non-breakable $tline = ' ' + $iline[1] # Keep 2nd char as is no matter what, can't be space } else { $tline = $iline[0] + $iline[1] # Keep the first 2 chars as is. } $iline = $iline.remove(0,2) # Replace two spaces with non-break and regular $iline = $iline -replace ' ', '  ' # Alternate &nb and reg $iline = $iline -replace ' ', '  ' # Fix remaining double spaces $tline = $tline + $iline # Tack on the remain line return $tline } Function start-new-line { begin { $numdigits = [int]([math]::log10($program.count) + 1) $lformat = "{0:D$numdigits}:" } process { if ($linenums.isPresent) { $pre = $lformat -f $Global:curlin # Make the line number (in the default color) if needed } else { $pre = "" # Don't worry about line number } $Global:oline = mung-htmlspace($Global:oline) # take care of HTML space issue $Global:oline = $Global:oline + '
' # add in break add-content ($pre + $Global:oline) -path $outfile # write out finished product $Global:oline = '' # Reset output line $Global:curcol = 1 # Set column to beginning ++$Global:curlin # Increment line tracking } } Function Space-out-line { param($tok) $i = [int] $tok.StartColumn # Cheat while ($tok.StartLine -gt $Global:curlin) { # Make sure we start on right line start-new-line } if ($i -gt $Global:curcol) { # Create spaces to where we need $Global:oline += (' ' * ($i - $Global:curcol)) $Global:curcol = $i # Set our new column placeholder } } Function add-nomove { # Add content without param($format) # modifying the current column count $Global:oline += $format } Function add-token { param($token) [void] [System.Reflection.Assembly]::Loadwithpartialname("System.Web") $atcol = $token.StartColumn # Temp column $atlen = $token.Length # Temp length $atlin = $token.StartLine # Temp line if ($token.Type -eq 'LineContinuation') { $atlen = 1 # MS includes the `R`N as well. Stupid } if ( ! ($token.StartLine -eq $token.EndLine) ) { # Multiple lines, multiline comment while( $atlin -lt $token.EndLine ) { # For each extra line $content = $program[$atlin-1].Substring($atcol - 1) $content = [System.Web.HttpUtility]::HtmlEncode($content) # Encode the string for HTML $Global:oline = $Global:oline + $content start-new-line $atcol = 1 # Reset column ++$atlin # Bump to next line $atlen = $token.EndColumn - 1 # Length is now the last column } } # Process like a regular line $content = $program[$atlin-1].Substring($atcol - 1, $atlen) $content = [System.Web.HttpUtility]::HtmlEncode($content) # Encode the string for HTML $Global:oline = $Global:oline + $content $Global:curcol += $atlen } Function detabify-array { for ($i = 0; $i -lt $program.count; ++$i ) { $line = $program[$i] # get line if ( ($x = ($line.indexof("`t")) ) -ne -1 ) { for ( ; $x -ne -1 ; $x=($line.indexof("`t")) ) { $line = $line.remove($x,1) for ( $j = (($x+1) % $tabex) + 1; $j -ne 0; --$j ) { $line = $line.insert($x,' ') # pad out line to enough spaces. } } $program[$i] = $line } } } # Actual program start $parser = [System.Management.Automation.PsParser] # No leading $, we're comparing to Content $autovars = @('$','?','^','_','Args','ConsoleFileName','Error','Event', 'EventSubscriber','ExecutionContext','False','ForEach','Home','Host','Input', 'LastExitCode','Matches','MyInvocation','NestedPromptLevel','NULL','PID', 'Profile','PSBoundParameters','PsCmdlet','PsCulture','PSDebugContext','PsHome', 'PSScriptRoot','PsUICulture','PsVersionTable','Pwd','Sender','ShellID', 'SourceArgs','SourceEventArgs','This','True') $infile = (Resolve-path $infile).Path if ($outfile -eq $null) { $outfile = $infile + '.html' } else { $outfile = (Resolve-path $outfile).Path } '' | Set-Content $outfile $program = Get-Content $infile detabify-array # Some programs sort on StartLine and StartColumn. Don't see the need. $pprogram = $parser::Tokenize($program, [ref] $null) # To keep from massively inflating the output, anything that we want to keep black, we may not mark at all # Keeping this generic so that I can modify to use CSS instead $pret = ` @{'Attribute' = ''; # Black 'Command' = ''; # Sienna 'CommandArgument' = ''; # Gray 'CommandParameter' = ''; # Blue Violet 'Comment' = ''; # Forest Green 'GroupEnd' = ''; # Black 'GroupStart' = ''; # Black 'Keyword' = ''; # Brown 'LineContinuation' = ''; # Black 'LoopLabel' = ''; # Turquoise 'Member' = ''; # Light Blue 'Number' = ''; # Fire Brick 'Operator' = ''; # Red 'Position' = ''; # Yellow 'StatementSeparator' = ''; # Black 'String' = ''; # Dark Violet 'Type' = ''; # Dark Orange 'Variable' = ''; # Blue 'AutoVar' = '' # Bright Green } $espan = '' $post = @{'Attribute' = $espan;'Command' = $espan; 'CommandArgument' = $espan; 'CommandParameter' = $espan; 'Comment' = $espan; 'GroupEnd' = $espan; 'GroupStart' = $espan; 'Keyword' = $espan; 'LineContinuation' = $espan; 'LoopLabel' = $espan; 'Member' = $espan; 'Number' = $espan; 'Operator' = $espan; 'Position' = $espan; 'StatementSeparator' = $espan; 'String' = $espan; 'Type' = $espan; 'Variable' = $espan; 'AutoVar' = $espan } foreach ( $token in $pprogram ) { if ($token.Type -ne 'NewLine') { Space-out-line $token $isauto = $false if($token.Type -eq 'Variable') { :test for($i = 0; $i -lt $autovars.Count; ++$i ) { if ( ($token.content.ToLower()) -eq ($autovars[$i].ToLower()) ) { $isauto = $true break } } } if ($isauto) { add-nomove($pret['AutoVar']) # Add in AutoVars formatting add-token($token) # Add token from $program array add-nomove($post['AutoVar']) # Add in finsihing formatting } else { add-nomove($pret[$token.Type.toString()]) # Add in formatting before token add-token($token) # Add token from $program array add-nomove($post[$token.Type.toString()]) # Add in finishing formatting } } else { start-new-line } } if ($Global:oline.length -gt 1) { start-new-line # Ensure we're at the end of the road } '' | Add-Content $outfile