So I went to a PowerShell training class we brought here on campus a while back. Going into a class like that already holding some general knowledge of scripting and programming, as well as specific knowledge of PowerShell (rudimentary, but still), means that by lunch-time on the first day I was bored, bored, bored. However, since I was basically getting three or four full days to play with PowerShell, I decided to play. These three little scripts are what came out of that play-time… :-)

All three of the following scripts are really very basic; nothing ground-breaking here. Hopefully as I progress in my PowerShell abilities I can provide better examples of scripts I have created to make my job easier.

The main thing to know about the scripts is that they just insert a function into the environment. To use them you must first execute the script, then call the function. In this way you can have the scripts execute in your Microsoft.PowerShell_profile.ps1 file, and then the functions will be available in the environment without having to execute the actual script. (EDIT: You’ll note that even I don’t use these scripts anymore; I never updated them and the GNU versions work just fine. I’m leaving this post here only for historical purposes.)

ps_head.ps1

The first script is called ps_head.ps1 and in all reality is just a tiny little function. It implements the most basic function of the Unix head command: it returns the first few lines of a file. The syntax is:

ps_head [-lines] <file>

Here’s the script:

if (Test-Path function:ps_head) { Remove-Item function:ps_head }
# head file -lines
function global:ps_head($path,$lines="-10") {
    if ($lines.GetType().Name -eq "String" ) {
        $lines = $lines.trim("-")
    }
    if ($path -ne $Null) {
        if (Test-Path $path) {
        Get-Content -path $path -totalCount $lines
        }
    }
}
Write-Host "Added ps_head to global functions." -Fore White

ps_tail.ps1

Next in line is ps_tail.ps1 which, as you can probably guess, does the same thing as the Unix tail command. Syntax is the same as above:

ps_tail [-lines] <file>
if (Test-Path function:ps_tail) { Remove-Item function:ps_tail }

# ps_tail file -lines
function global:ps_tail {
    param ($path, $lines="-10", [switch] $f)

    if ($lines.GetType().Name -eq "String" ) {
        $lines = [string]$lines.trim("-")
    }
    if ($path -ne $Null) {
        if (Test-Path $path) {
            $content = Get-Content -path $path
            for($i=$content.length - $lines; $i -le $content.length; $i++) {
                $content[$i]
            }
        }
    }
}

Write-Host "Added ps_tail to global functions." -Fore White

ps_grep.ps1

The third and final script for this post is ps_grep.ps1. It implements a couple of grep’s command-line options; namely, -c and -i. The syntax is:

ps_grep [-c] [-i] pattern <file>

(Each command-line switch needs to be provided separately; currently you’re not able to run them all together, as in “grep -cRi pattern *.*”. I might work on that. [EDIT: clearly, I didn’t, and won’t.])

Something to note is that ps_grep will loop through all of an object’s properties in search of the supplied regex pattern and not just the Name property, etc. Also, if you use the function in a pipeline and pattern is a simple string (i.e., not a regular expression), the match will always be case-insensitive. When I have a bit of time I’ll try to figure that one out.

if (Test-Path function:ps_grep) { Remove-Item function:ps_grep }

function global:ps_grep([switch] $c, [switch] $i, $pattern, $file="", $inputObject=$Null) {
    BEGIN {
        # This section executes before the pipeline.
        # $count will contain the number of matches found.
        $count = 0

        if ($inputObject) {
            $inputObject
        }
    } # end 'begin'

    PROCESS {
        # This section executes for each object in the pipeline.

        # Did we get a filename?  If so we're operating on a file, not stdout.
        if ($file -ne "") {

            # Is the filename a valid file?
            if (Test-Path $file) {

                # Grep the file for $pattern, if we were given $pattern.
                if($pattern) {
                    if (!$i) {
                        # User has specified as case-sensitive match.
                        $case = "-CaseSensitive"
                    }
                    $command = "Select-String $case -pattern $pattern -InputObject (Get-Item $file)"
                    foreach ($match in Invoke-Expression($command)) {
                        if (!$c) {
                            # only output lines if -c was not specified.
                            Write-Host $match
                        }
                        # Increment our count.
                        $count++
                    }
                } # end 'if ($pattern)'
            } # end 'if (test-path)'
            else {
                # The filename passed does not exist.  Die.
                Write-Host "File does not exist."
            } # end 'else'
        } # end 'if ($file...)'

        # Didn't get a filename, so we're operating on stdout.
        elseif($pattern) {
            # Save the pipelined object into an internal var to avoid confusion below.
            $obj = $_

            # Loop through $obj's properties and look for $pattern
            foreach ($prop in ($obj | Get-Member -MemberType Properties) ) {
                if($obj.($prop.name.ToString()) -match $pattern) {
                    if (!$c) {
                        # Found a match, output the result to stdout.
                        Write-Output $obj | ($MyInvocation.InvocationName) -inputObject $_
                    }

                    # Increment our count.
                    $count++

                    # No reason to keep searching this property.
                    break
                } # end 'if ($obj...)'
            } # end 'foreach'
        } # end 'elseif'
    } # end 'process'

    END {
        # If '-c' was specified on the command line, output the total count.
        if ($c) {
            Write-Host $count
        } # end 'if ($c)'
    } # end 'end{}'
} # end function

Write-Host "Added ps_grep to global functions." -Fore White

If you have any ideas, let me know how I can improve these little functions.