Tuesday, July 28, 2009

Windows PowerShell for developers part 2

Welcome back to Sleep Can Wait. In today's post I'll be continuing on the subject of my previous post related to the use of Windows PowerShell for developers. The continuation of this topic will show you how to extend our previously defined functions and functionality for reuse. In a future post we'll explore the use of MSBuild via PS.

To start, here is our PS profile ($profile) as we left it last time:


# Variables to hold the physical location of our current project
$projectDir = "C:\Projects\MyProject"

# Updates the current project from the SVN repository
function Update-Project
{
Write-Host "Updating project $projectDir" -ForegroundColor Yellow
svn up $projectDir
}

# Commits the current project from the SVN repository
funciton Commit-Project([string] $message)
{
Write-Host "Committing $projectDir to SVN" -ForegroundColor Yellow
svn ci $projectDir -m $message
}


So let's get down to business. There are a few things right off the bat that strike me as obvious candidates for refactoring. The first is our use of "Write-Host" with a foreground color. Let's extract a function from our use.

As is good practice let's first define our comment followed by the function:

# Write the provided text to the host with ForegroundColor Yellow
function Write-Message([string] $message)
{
Write-Host $message -ForegroundColor
}

That was quick and painless. However, it's probably a good idea to make sure the local variable $message is not null or empty and handle the error accordingly:


function Write-Message([string] $message)
{
if($message.length -eq 0)
{
Write-Error "variable [message] cannot be null" -Category InvalidArgument
$error.Clear()
break
}
Write-Host $message -ForegroundColor Yellow
}


Now that is done we can refactor our existing functions to utilize our new Write-Message function.

# Updates the current project from the SVN repository
function Update-Project
{
Write-Message "Updating project $projectDir"
svn up $projectDir
}

# Commits the current project from the SVN repository
function Commit-Project([string] $message)
{
Write-Message "Committing $projectDir to SVN"
svn ci $projectDir -m $message
}


Now we can move on to the function refactoring with some substance!
Let's first take a look at our existing Update-Project function

function Update-Project
{
Write-Message "Updating project $projectDir"
svn up $projectDir
}

As you can see this function is very simple and essentially hard-coded to always update the same project. Our first order of business is to define a new project called "MyFirstProject" in a variable $myFirstProjectDir

$myFirstProjectDir = "C:\Projects\MyFirstProject"

Now we can extend Update-Project to accept a parameter $path (along with some basic error handling)

function Update-Project([string] $path)
{
if($path.length -eq 0)
{
Write-Error "variable [path] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}

svn up $path
}

We now have a more generic function which accepts a path to the directory which must be updated. We can now write a strongly typed function to utilize this generic function.

# Updates MyFirstProject
function Update-MyFirstProject
{
Update-Project $myFirstProject
}

For each new project we have we must only implement a simple one-line function to utilize existing functionality... beautiful!

Now let's refactor our Commit-Project function in the same manner

function Commit-Project([string] $path, [string] $message)
{
Write-Message "Committing $projectDir to SVN"
if($path.length -eq 0)
{
Write-Error "variable [path] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}
if($message.length -eq 0)
{
Write-Error "variable [message] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}
svn ci $path -m $message
}


We can now add a simple one-line function to commit MyFirstProject

# Commits MyFirstProject
function Commit-MyFirstProject([string] $message)
{
Commit-Project $myFirstProjectDir $message
}


One final modification that I'd like to make is to make our global variables constants to prevent accidental modifications. Additionally, we can now remove $projectDir as it is no longer used.

Replace "$myFirstProjectDir" with:

if($myFirstProjectDir-eq $null)
{
Set-Variable -Name myFirstProjectDir -Value "C:\Projects\MyFirstProject" -Option Constant
}

One thing to note is when defining the name of your variable "-Name myFirstProjectDir" you do not include the dollar sign ($). Additionally, we check to see if the variable is null first to prevent errors. Specifically if you do not have this safeguard PS will throw an exception stating that a variable already exists by that name.

Your complete PS profile should now look like this:

# MyFirstProject directory
if($myFirstProjectDir-eq $null)
{
Set-Variable -Name myFirstProjectDir -Value "C:\Projects\MyFirstProject" -Option Constant
}



# Updates MyFirstProject
function Update-MyFirstProject
{
Update-Project $myFirstProject
}

# Commits MyFirstProject
function Commit-MyFirstProject([string] $message)
{
Commit-Project $myFirstProjectDir $message
}

# Updates the current project from the SVN repository
function Update-Project([string] $path)
{
if($path.length -eq 0)
{
Write-Error "variable [path] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}
svn up $path
}

# Commits the current project from the SVN repository
function Commit-Project([string] $path, [string] $message)
{
Write-Message "Committing $projectDir to SVN"
if($path.length -eq 0)
{
Write-Error "variable [path] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}
if($message.length -eq 0)
{
Write-Error "variable [message] cannot be empty" -Category InvalidArgument
$error.Clear()
break
}
svn ci $path -m $message
}

#Write the provided text to the host with ForegroundColor Yellow
function Write-Message([string] $message)
{
if($message.length -eq 0)
{
Write-Error "variable [message] cannot be null" -Category InvalidArgument
$error.Clear()
break
}
Write-Host $message -ForegroundColor Yellow
}

Rock and Roll! I hope you enjoy this knowledge and use it as a stepping stone to more useful development!