Saturday, 30 August 2014

PowerShell Script to Index Functions

Getting ever more ambitious with PowerShell scripts - some over 1000 lines, and growing all the time - I thought it would be useful to have an index/contents page of functions at the start of the script (my PowerShell editor of choice is NotePad++.)

The below script can be run as:

.\Index-Functions.ps1 filename.ps1

And it puts the index at the start of the PowerShell script/updates the index. It uses a couple of useful additional functions that are included below.

The Script - Index-Functions.ps1

Apologies for the formatting, I really should use something else for posting PS Scripts!

# START OF SCRIPT #

## Function Pad-Right ##
## ================== ##

Function Pad-Right{
       Param([String]$in, [Int]$padR = 30, [String]$pad = " ")
       $in.PadRight($padR,$pad).SubString(0,$padR)
}

## Function Prompt-Menu ##
## ==================== ##

Function Prompt-Menu{

       # DESCRIPTION: Prompts for valid answers to a menu.
       # PARAMETER $args[0]: The question
       # PARAMETER $args[1]/$args[1..X]: An array with answers in / the answers

       $question = $args[0]
      
       # Check if $args[1] is an array (i.e. has more than 1 element)
       If(($args[1].count) -ne 1){
              $answers = $args[1] # Possible answers is array $args[1]
       } else {
              $answers  = $args[1..(($args.count)-1)] # Possible answers are args 1 to ...
       }

       # Checks if the answer ($readIn) is valid
       do {
              $readIn = Read-Host "$question"
              $readIn = $readIn.ToUpper()
              foreach($answer in $answers){
                     if($readIn -eq $answer.ToUpper()){return $readIn}
              }
       } while($true) # An infinite loop!
}

## Function Index-Functions ##
## ======================== ##

Function Index-Functions {

       # DESCRIPTION: Injects/Updates an Index of Functions at the start of the script.
       # PARAMETER filename: Name of the PS script file to be indexed.

       Param([String]$filename)
       If(!$filename){return}             # If no filename paramater - return
       If(!(Test-Path $filename)){return} # If test-path fails - return    
       $contents = Get-Content $filename  # Get contents of the file
       If(!$contents){return}             # If no contents (empty file) - return
      
       # $indexDisplay starts with a basic header
       $indexDisplay = "<#","Line  Function","~~~~  ~~~~~~~~"
      
       # Check for an existing index ($hasIndex = true/false) #
       # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
             
       If($contents[1] -eq "Line  Function"){  
              $hasIndex    = $true           # We have an index
              $endOfIndex  = $false          # Not found the end of the existing index
              $contentRows = $contents.Count # Count the number of rows in $contents
              $i=3 # Check from 4th line since header is 3 lines (arrays start at 0)
              do {
                     # We're looking for the #> at the end of the index
                     If($contents[$i].contains("#>")){              
                           $endOfIndex = $true # Found the end of the index
                           # Remove the index from the $contents
# ($i+2 because have a blank line after the index)
                           $contents = $contents[($i+2)..$contentRows]
                     }
                     $i++ # Accumlate $i for the next row
              } while (!$endOfIndex) # Stops when found end of index
       }else{$hasIndex = $false} # Othewise there is no index
      
       # Search $contents for functions and create the index #
       # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
                    
       $i = 1       # Start on line 1 of the script
       $index = @() # Initialize the index array
       foreach ($content in $contents){ 
              $capitalize = $content.ToUpper() # Capitalized version of the row         
              # For a Function we expect FUNCTION at the start of the line
              If($capitalize.StartsWith("FUNCTION")){               
                     $split = $content.Split("{")  # Split row at "{" to get [0] "Function Name"
                     $split = $split[0].Split(" ") # Split at " " to get [1] "Name"
                     $index += $i                  # Add line number to index array
                     $index += $split[1]           # Add Function name to array
              }
              $i ++ # Accumlate $i for the next row
       }
      
       # Construct the index #
       # ~~~~~~~~~~~~~~~~~~~ #
      
       $i = 0
       $linesInIndex = ($index.count / 2) # $linesInIndex has 'line number', 'function name'...
       do {
              # Take line number from $index, add number of lines in the index to it,
# +5 for index header and footer
              $lineNumber = ($index[2*$i] + $linesInIndex + 5).ToString()
              # Add to IndexDisplay line number with a ')', and
# pad it right with " " for 6 columns; then add the function name
              $indexDisplay += (Pad-Right ($lineNumber + ")") 6) + ($index[2*$i+1])
              $i++ # Accumulate $i for the next row
       } while ($i -lt $linesInIndex)
       $indexDisplay += "#>","" # Adds a footer to the index
      
       # Displays the index, prompt (Y/N), inject new/updated index into script file #
       # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
             
       ""
       $indexDisplay # Display the Index
      
       # Change screen output depending on whether the file had an index before
       If ($hasIndex){$bleep = "replace the index at"}else{$bleep = "be added to"}
       "The above Index will $bleep the start of $filename."
       "The last saved version will be saved with the .backup extension.";""
      
       # Uses Prompt-Menu function with Y/N as the only possible answers
       $answer = Prompt-Menu "Do you want to continue (Y/N)?" "Y" "N"

       If($answer -eq "Y"){
              $backupFile = $filename + ".backup"
              If(Test-Path $backupFile){Remove-Item $backupFile}
              Rename-Item $filename $backupFile
              [Void](New-Item $filename -type file -force)
              $indexDisplay += $contents # Contents is added to index (at start of file)
              $indexDisplay | Out-File $filename
       }
       ""
}

Index-Functions $args[0]

# END OF SCRIPT #

No comments:

Post a Comment