In this article I introduce a simple PowerShell (PS) script for generating a User Story cards (or task cards) for physical Scrum Board. You can generate a set of png pictures ready to print and cut directly from Excel by issuing a single command.
I put this chapter at the beginning of the article because I assume that lot of you want to just use the script and vanish from this site. I mean if you are a non-technical person (a project manager or a business analyst) in the role of Scrum Master interested in the result of this script. That is ok.
Step 1: Create a product backlog in Excel format. Put the data on the first sheet in the following format: ID, Estimation, Priority, Story text
. See more details on the Figure 1.
Figure 1 – Product backlog in Excel.
The example in Excel is available in download package.
Step 2: Take a text from Listing 3 and save it as a story_to_print.ps1
. The name does not really matter.
Step 3: Open a Poweshell Console (Figure 2). Go to directory where the script story_to_print.ps1
is located (using cd
command for example) and run the command from Listing 1. As a second parameter specify the path to your product backlog of course.
.\story_to_print.ps1 .\data.xlsx
Listing 1 – Generate stories to print.
If you run powershell script for the first time, you might be interested in the following command. However, if you have ever run a PS script in your computer, it won’t be necessary.
Set-ExecutionPolicy RemoteSigned
Listing 2 – Setting execution policy to RemoteSigned.
Figure 2 – How to open a poweshell console.
And that is it. Simple, isn’t it? It does not matter in which form you keep your product backlog. Important is that you can do the export to Excel and then generate story cards.
Congratulations! If you continue reading you are probably a software engineer looking for detailed description of the script in this article. I am not someone who is a long-term worker in PowerShell. I started recently. Therefore the script could lack a bit of spirit of experienced PowerShell guru. Despite of this fact I decided to share it. It does what is demanded. It is not very long but it is very powerful. Same as PowerShell itself. I think that a single line in PowerShell could be very powerful but many lines could be completely unmanageable. Therefore you should think in kind of different way in writing PS script comparing to regular .NET application. I did not. My script is written with couple of functions which are very dependent on each other (expect one function I borrowed from [1]). But it is not a fault. It is partially an intention. PS is not meant to be for non-technical people, you know, all the stuff with the console… writing instead of clicking… no graphical user interface. According to me the result of the script should be a function which is going to be used by a target user to do something. However, if I write the script in this way, I have to add a new step in the chapter “How to use” and this is not practical for non-technical people. Simpler and quicker to use -> better. And let’s be honest, this script is for non-technical people. Why not to use Visual C# then? -> Too complicated for such a simple task.
The script contains around 150 lines including comments and empty lines for good readability. What it exactly does? It converts Excel file into a CSV file (taken from [1]), it loads the CSV file, parse it into a predefined structure and pass the data to two functions which take care of drawing. The whole script including comments could be found in the Listing 3.
# ---------------------------------------------------------------------------------------
# import types and base settings
# ---------------------------------------------------------------------------------------
Add-Type -AssemblyName System.Drawing
$l = get-location
[System.IO.Directory]::SetCurrentDirectory($l)
# ---------------------------------------------------------------------------------------
# Create new types
# ---------------------------------------------------------------------------------------
Add-Type -TypeDefinition @"
public struct UserStory {
public string Id;
public string Estimation;
public string Priority;
public string StoryText;
}
"@
# ---------------------------------------------------------------------------------------
# global const
# ---------------------------------------------------------------------------------------
# Rectangle size X axis
[int]$global:rectSx = 320
# Rectangle size Y axis
[int]$global:rectSy = 242
# Border
[int]$global:border = 80
# ---------------------------------------------------------------------------------------
# functions definition
# ---------------------------------------------------------------------------------------
function drawStory([int]$rX, [int]$rY, [System.Drawing.Graphics]$graphics, [UserStory]$story) {
# draw rectangle
$rectangle = new-object System.Drawing.Rectangle $rX,$rY,$rectSx,$rectSy
$color = [System.Drawing.Color]::FromArgb(0,0,0)
$pen = new-object System.Drawing.Pen $color, 3
$graphics.DrawRectangle($pen, $rectangle)
# draw ID
$font = new-object System.Drawing.Font Consolas,18
$brushFg = [System.Drawing.Brushes]::Black
$graphics.DrawString($story.Id,$font,$brushFg,$rX+10,$rY+12)
# draw Estimation
$font = new-object System.Drawing.Font Consolas,12
$graphics.DrawString("Estimation: "+$story.Estimation,$font,$brushFg,$rX+162,$rY+7)
# draw Priority
$graphics.DrawString("Priority: "+$story.Priority,$font,$brushFg,$rX+180,$rY+25)
# draw line
$header = 60
$graphics.DrawLine($pen, $rX, $rY+$header, $rX+$rectSx, $rY+$header)
#draw Story text
$font = new-object System.Drawing.Font Consolas,12
$rectangle = new-object System.Drawing.RectangleF ($rX+10),($rY+$header+20),($rectSx-10),($rectSy-20)
$graphics.DrawString($story.StoryText,$font,$brushFg,$rectangle)
}
# Drawing page containing 8 user stories
function drawPage($stories, $outName) {
# create image
$bmp = new-object System.Drawing.Bitmap 800,1131
$graphics = [System.Drawing.Graphics]::FromImage($bmp)
# draw background
$brushBg = [System.Drawing.Brushes]::White
$graphics.FillRectangle($brushBg,0,0,$bmp.Width,$bmp.Height)
# draw rest
# first column
$storyIndex = 0;
for ($i = $border; $i -le ($border+($rectSy*3)); $i+=$rectSy) {
drawStory $border $i $graphics $stories[$storyIndex++]
}
# second column
$rX = $border+$rectSx
for ($i = $border; $i -le ($border+($rectSy*3)); $i+=$rectSy) {
drawStory $rX $i $graphics $stories[$storyIndex++]
}
# dispose, save, close, open and other fellas
$graphics.Dispose()
$bmp.Save($outName)
invoke-item $outName
}
# function taken from http://jamesone111.wordpress.com/2011/02/07/how-to-read-excel-files-in-powershell/
function ConvertFrom-XLx {
param ([parameter( Mandatory=$true,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string]$path ,
[switch]$PassThru
)
begin { $objExcel = New-Object -ComObject Excel.Application }
Process { if ((test-path $path) -and ( $path -match ".xl\w*$")) {
$path = (resolve-path -Path $path).path
$savePath = $path -replace ".xl\w*$",".csv"
$objworkbook=$objExcel.Workbooks.Open( $path)
$objworkbook.SaveAs($savePath,6) # 6 is the code for .CSV
$objworkbook.Close($false)
if ($PassThru) {Import-Csv -Path $savePath }
}
else {Write-Host "$path : not found"}
}
end { $objExcel.Quit() }
}
# reading US from XLS
function loadStories($fileName) {
$stories = @()
ConvertFrom-Xlx $fileName
$fileName = $fileName -replace ".xl\w*$",".csv"
get-content $fileName | foreach-object {
$tmp = $_.ToString().Split(';')
$a = new-object UserStory
$a.Id = $tmp[0]
$a.Estimation = $tmp[1]
$a.Priority = $tmp[2]
$a.StoryText = $tmp[3]
$stories += $a
}
remove-item $fileName
return $stories
}
# ---------------------------------------------------------------------------------------
# Script processing
# ---------------------------------------------------------------------------------------
# Checking input parameters
if ($args.Count -ne 1) {
""
"--------------------------------------------------------------------------"
"Wrong input parameters."
"Use: .\story_to_print excel.xls"
""
return
}
$storiesL = loadStories $args[0]
for ($i=0;$i-lt($storiesL.Count);$i+=8) {
drawPage $storiesL[$i..($i+8)] ("a_" + (($i/8)+1).ToString() + ".png")
}
Listing 3 – The entire script for generating cards.
Have a nice development using SCRUM!
- [1] - http://jamesone111.wordpress.com/2011/02/07/how-to-read-excel-files-in-powershell/
- [2] - http://en.wikipedia.org/wiki/User_story (-> example of userstories taken)