Linting in PowerShell

Continuing the theme of C++ flavoured tools I miss, lint is another essential that doesn’t appear to exist in the PowerShell world. Until I discovered ScriptAnalyzer which, it seems, can do pretty much the same job for .ps1 files. Here I wrote a simple loop to check all files in the same directory.


# Install-Module PSScriptAnalyzer 

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

Get-ChildItem -Path $here -Filter *.ps1 -Recurse | ForEach-Object {

    Invoke-ScriptAnalyzer -Path $_.FullName -IncludeDefaultRules -OutVariable Failures

	If ($Failures.Count -gt 0) {
		Write-Output "$_.FullName - $($Failures.Count) rule failures"
	}
}


Configuration Files in PowerShell

Developing a mini build and configuration system with PSake recently and was getting annoyed with the number of hard-coded, machine- and environment-specific strings there were so I did some research on alternatives to storing configuration files external to the script. Once again, the PowerShell team have thought about this and provide a solution in the shape of a psd (PowerShell Data) file - .psd1. Data can be structured hierarchically either as key value pairs or hashes. The easiest way to create a file is to generate it from a scratch script using a here string:


@"
@{
    Agent = @{

        Version = '1.0.0'
        Debug = 'true'
    }

    LocalFolders = @{
    
        PowerShellScripts = 'C:\Scripts\PS'
    }
}

"@ | Out-File -FilePath 'Agent.psd1' -Force


Then read it back in in the worker script and reference sub-objects using normal dot notation.


$Config = Import-LocalizedData -FileName Agent.psd1

$Config.Agent.Version


Microbit Ghost Detector

Just in time for Halloween, here’s a ghost detector that uses the microbit temperature sensor to sense changes in ambient temperature - which we all know from watching supernatural tv shows - is a sure sign that there’s a ghost somewhere nearby :)


# ghost detector - looking for changes in temperature 
from microbit import *

sleep_time = 200
warning_time = 5000


class Pinger:

  def __init__(self):
    self.direction = 1
    self.x = 2
    self.y = 2

  def update(self):
    display.set_pixel(self.x, self.y, 0)
    if self.x >= 4:
        self.x = 4
        self.direction = -self.direction
    elif self.x <= 0:
        self.x = 0
        self.direction = -self.direction

    self.x += self.direction
    display.set_pixel(self.x, self.y, 9)


class GhostDetector:

  def __init__(self):
     self.reset()

  def ghost_detected(self):
    now_temperature = temperature()
    return self.starting_temperature != now_temperature

  def reset(self):
    self.starting_temperature = temperature()


pinger = Pinger()
detector = GhostDetector()

while True:

  pinger.update()
  sleep(sleep_time)

  if detector.ghost_detected():
     detector.reset()
     display.show(Image.GHOST)
     sleep(warning_time)
     display.clear()


Grepping in PowerShell

One tool I really miss sometimes is grep, the command line tool for searching for text matches inside an arbitrary set of files. In the same way you might use “dir” to find a file inside a directory that matches a name pattern, or another outward-facing characteristic like file attribute, you can use grep to find any files containing matching text.

In this most recent episode of pining for grep, I was only interested in finding out if a particular namespace was being used anywhere in my code and didn’t really care where. You could easily extend this code to report the file name when it finds a match.


[string]$SourceFolder = 'D:\MyCode\Source'

$SearchTerms = @( 'using System.' )

Get-ChildItem -Path $SourceFolder -Filter *.cs -Recurse | % {

    $Path = $_.FullName
    $FileContent = Get-Content -Path $Path

    $SearchTerms | % {

        $SearchPattern = $_
        $FileContent | Select-String -Pattern $SearchPattern
    }
}


Additionally, I used a list of search terms but that could be replaced with a simple string search variable.

Sample output from this script:

grep

PSake Cheatsheet

PSake is a PowerShell module, similar to msbuild, that allow you to parcel up chunks of code into discrete Tasks and create dependencies between them. PSake then determines the order they should run and handles that complexity. Here are the highlights of functionality I use all the time.

Tasks

The basic unit of execution in PSake is the Task:


Task Run-FirstTask -Description 'My First Task' {

	Write-Host 'Doing stuff'
	
}


You can give each task a description as well as a name to help with documenting what each task is supposed to do.

Running

We run the task by invoking PSake (after Importing the module if we need to):


Invoke-Psake .\MyFirstTask.psake.ps1 

Invoke-Psake -BuildFile .\MyFirstTask.psake.ps1 -NoLogo -Verbose


A nice feature of running is a report at the end showing how long the process took overall and how long each individual task took.

Discoverability

PSake scripts can become complicated to read through in one go so it supports self-documenting of all of your Tasks and their dependencies using the -docs switch:


Invoke-Psake .\MyFirstTask.psake.ps1 -docs
 

Dependencies

More than one Task can depend on a sub-task and PSake will work out the correct order of execution to honour each dependency statement.


Task MyFirstTask -Depends CleanStuff, CopyStuff {


}

Task CopyStuff -Depends CleanStuff {


}

Task CleanStuff {


}


Default

A default taks can be run if nothing is specified from the command line.


Task Default {


}


Parameters

PSake can accept parameters that it makes available as variables in your tasks to allow them to be a little more flexible:


Invoke-Psake -Parameters @{ 'Version' = '1.0' }

$Parameters {

	$Version = '1.0.0'
}

Invoke-Psake -Parameters $Parameters



Alias

A task can have more than one name.


Task ? -Description 'List tasks' -alias 'Help' { 
	WriteDocumentation 
}




Assertions and Required Variables

To add to the robustness of a script you can ask that PSake will error if one or more variables are not declared. You can also check that a condition is met inside a task by using the Assert statement.


Task Copy-Files -RequiredVariables BuildPath, Version, MachineName {

}

Task Verify {

  Assert (Test-Path 'c:\scripts\debug.txt') 'Debug.txt file has not been deployed'
  Assert (![string]::IsNullOrEmpty($Version)) '$Version was null or empty'
}





Preconditions

PSake will skip a Task if the precondition is not met.


Task Copy-Files -Precondition { $MakeACopyOfFiles -eq $true } {
	'Making a copy of the files'
}


Microbit Dinosaur Detector

It’s been a while since I posted a microbit sample so here’s one that would work well on the set of Jurassic Park, a dinosaur detector.

As you will know from the film, the way you know the T-Rex is approaching is by standing a glass of water on the dashboard of your stranded Jeep. As the dinosaur approaches, the vibrations from it’s stamping cause ripples in the water so you know when to panic.

The accelerometer in the microbit works well as a crude vibration detector. We note the current x and y orientation of the microbit at start up and watch for a change.


from microbit import *

sleep_time = 200
warning_time = 5000

class Pinger:
    
  def __init__(self):
    self.direction = 1
    self.x = 2
    self.y = 2
     
  def update(self):
    display.set_pixel(self.x, self.y, 0)
    if self.x >= 4:
        self.x = 4
        self.direction = -self.direction
    elif self.x <= 0:
        self.x = 0
        self.direction = -self.direction
        
    self.x += self.direction
    display.set_pixel(self.x, self.y, 9) 
    
    
class DinosaurDetector:
   
  def __init__(self):
    self.horizontal_start = 0
    self.vertical_start = 0
    self.reset()
     
  def dinosaur_detected(self):
    horizontal_now = accelerometer.get_x()
    vertical_now = accelerometer.get_y()
  
    return self.horizontal_start != horizontal_now or self.vertical_start != vertical_now

  def reset(self):
    self.horizontal_start = accelerometer.get_x()
    self.vertical_start = accelerometer.get_y()


display.scroll('Dino Detector', delay=100)

detector = DinosaurDetector()
pinger = Pinger()

glass1 = Image("00000:00000:00900:00000:00000")
glass2 = Image("00000:00900:09090:00900:00000")
glass3 = Image("09990:90009:90009:90009:09990")
glass4 = Image("09090:90009:00000:90009:09090")
ripple_animation = [glass1, glass2, glass3, glass4, glass3, glass2, glass1]

while True:
  
  pinger.update()
  sleep(sleep_time)
    
  if detector.dinosaur_detected():
      detector.reset()
      display.show(ripple_animation, delay=100)
      display.show(Image.GIRAFFE)
      sleep(warning_time)
      display.clear()

The “pinger” is a battlestar galactica style roving pixel to show that the microbit is alive and monitoring it’s environment for dinosaurs. The water ripple animation is there in place of the dashboard water and the giraffe icon is the closest stock image I could get to a sauropod.

This is the first time I’m trying a version of one of these examples using classes instead of exclusively inline code. I think the behaviour of the detector and the scanning “pinger” warranted a little bit extra verbosity to make the main loop a bit more compact.

NUnit Test Actions for Great Good

Lots of c# unit tests I see build up a set of abstract base classes to allow for things like setting up common environments and handling common startup and teardown tasks. This is never a good approach, in my opinion, and can lead to some difficult workarounds due to the high level of coupling this implies. NUnit has a better approach by allowing a text fixture or method to be decorated with a custom attribute that allows code to be run before and/or after all the tests in that test fixture or just the one affected test method.


using NUnit.Framework;

[TestFixture]
[CommonTestHousekeeping]
public class ExampleTest
{
	[Test]
	public void Example_Must_Do_Housekeeping_First()
	{
		... 
	}
}



using NUnit.Framework;

... 

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class CommonTestHousekeepingAttribute : Attribute, ITestAction
{
	public ActionTargets Targets
	{
		get { return ActionTargets.Default; }
	}

	public void BeforeTest(TestDetails testDetails)
	{
        // do stuff here 
		// ...
		
		if (NotAbleToDoTheThingIWasSupposedToDo)
		{
			// report an error back to the NUnit framework
			throw new ApplicationException("Could not do the thing");
		}
	}

	public void AfterTest(TestDetails testDetails)
	{
        // do stuff here 
		// ...
	}
}



Now rather than having all that coupling to a base class, if you decide your test no longer needs the housekeeping, you remove the attribute and nothing else changes.

Generating Notepaper with PdfSharp

PdfSharp is a lovely library that lets you generate/draw pdf documents programmatically. I used it to generate some notepaper and found it easy to work with once the basics of the objects are understood. We need to create a document, give it some properties, then we can save it.


using PdfSharp.Drawing;
using PdfSharp.Pdf;

...

PdfDocument document = new PdfDocument
{
	PageLayout = PdfPageLayout.SinglePage,
	PageMode = PdfPageMode.UseNone
};

document.Save(filename);


The next obvious thing is to add some pages. Each page can have it’s own size and orientation.


PdfPage newPage = document.AddPage();
newPage.Size = PdfSharp.PageSize.A4;
newPage.Orientation = PdfSharp.PageOrientation.Landscape;


Now that we have a page or two, we want to put some content into them. PdfSharp has a number of objects that are analogs of the GDI drawing objects in Windows, things like Graphics, Pens, Brushes etc. and a set of similar methods to draw lines, rectangles, ellipses.

In order to be able to draw some notepaper, I wanted to be able to have a white border around each page and maybe print on all the page or print an A5 signature as two halves of a landscape A4 page. This meant I needed something to represent one or more printable areas for each page.


public class PrintableArea
{
	public int Left { get; set; }
	public int Right { get; set; }
	public int Top { get; set; }
	public int Bottom { get; set; }

	public static IEnumerable<PrintableArea> FromPage(PdfPage page, int border, bool split)
	{
		var areas = new List<PrintableArea>();

		if (split)
		{
			PrintableArea pane1 = new PrintableArea
			{
				Left = border,
				Right = ((int)page.Width / 2) - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			PrintableArea pane2 = new PrintableArea
			{
				Left = pane1.Right + (2 * border),
				Right = (int)page.Width - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			areas.Add(pane1);
			areas.Add(pane2);
		}
		else
		{
			PrintableArea single = new PrintableArea
			{
				Left = border,
				Right = (int)page.Width - border,
				Top = border,
				Bottom = (int)page.Height - border
			};

			areas.Add(single);
		}

		return areas;
	}
}


Each kind of paper - graph, dotted, music manuscript, lined - implies an interface. Let’s call it IPageRenderer.


public interface IPageRenderer
{
	int PageBorder { get; set; }

	void Render(PdfPage page, IEnumerable<PrintableArea> areas);
}


So now we can treat a list of renderings as a group using a page generator.


public class PageGenerator
{
	public bool SplitPages { get; set; }

	public PdfSharp.PageSize PageSize { get; set; }

	public PdfSharp.PageOrientation PageOrientation { get; set; }

	public int PageBorder { get; set; }

	public void GeneratePages(PdfDocument document, int pages, IEnumerable<IPageRenderer> renderers)
	{
		foreach(var renderer in renderers)
		{
			renderer.PageBorder = this.PageBorder;

			for (int page = 0; page < pages; ++page)
			{
				PdfPage newPage = document.AddPage();
				newPage.Size = this.PageSize;
				newPage.Orientation = this.PageOrientation;

				var areas = PrintableArea.FromPage(newPage, this.PageBorder, this.SplitPages);
				renderer.Render(newPage, areas);
			}
		}
	}
}

...


var renderers = new List<IPageRenderer>();
renderers.Add(new ManuscriptPaperRenderer(58, 6));
renderers.Add(new DottedPaperRenderer(15, 1));
renderers.Add(new LinedPaperRenderer(18));
renderers.Add(new GraphPaperRenderer(16));

PageGenerator pageGenerator = new PageGenerator
{
	PageBorder = pageBorder,
	PageOrientation = pageOrientation,
	PageSize = PdfSharp.PageSize.A4,
	SplitPages = splitPages
};

int pagesEach = 1;
pageGenerator.GeneratePages(document, pagesEach, renderers);

			
			




Finally, the implementation of each of the different types of renderer.

Music Manuscript


public class ManuscriptPaperRenderer : IPageRenderer
{
	private readonly int staveSpacing;

	private readonly int lineSpacing;

	public ManuscriptPaperRenderer(int staveSpacing, int lineSpacing)
	{
		this.staveSpacing = staveSpacing;
		this.lineSpacing = lineSpacing;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.Black, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += this.staveSpacing)
				{
					DrawStave(gfx, pen, horizontalLine, area.Left, area.Right, this.lineSpacing);
				}
			}
		}
	}

	private static void DrawStave(XGraphics graphics, XPen pen, int topLine, int start, int end, int lineSpacing)
	{
		int linesPerStave = 5;
		for (int horizontalLine = 0; horizontalLine < linesPerStave; horizontalLine++)
		{
			graphics.DrawLine(pen, start, topLine + (horizontalLine * lineSpacing), end, topLine + (horizontalLine * lineSpacing));
		}

		// draw end caps to lines
		graphics.DrawLine(pen, start, topLine, start, topLine + ((linesPerStave - 1) * lineSpacing));
		graphics.DrawLine(pen, end, topLine, end, topLine + ((linesPerStave - 1) * lineSpacing));
	}
}


Dotted


public class DottedPaperRenderer : IPageRenderer
{
	private readonly int gridSize;

	private readonly int dotSize;

	public DottedPaperRenderer(int gridSize, int dotSize)
	{
		this.gridSize = gridSize;
		this.dotSize = dotSize;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int x = area.Left; x < area.Right; x += this.gridSize)
				{
					for (int y = area.Top; y < area.Bottom; y += this.gridSize)
					{
						gfx.DrawEllipse(pen, x, y, this.dotSize, this.dotSize);
					}
				}
			}
		}
	}
}


Lined


public class LinedPaperRenderer : IPageRenderer
{
	private readonly int lineSpacing;

	public LinedPaperRenderer(int lineSpacing)
	{
		this.lineSpacing = lineSpacing;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach (PrintableArea area in areas)
			{
				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += this.lineSpacing)
				{
					gfx.DrawLine(pen, area.Left, horizontalLine, area.Right, horizontalLine);
				}
			}
		}
	}
}


Graph


public class GraphPaperRenderer : IPageRenderer
{
	private readonly int gridSize;

	public GraphPaperRenderer(int gridSize)
	{
		this.gridSize = gridSize;
	}

	public int PageBorder { get; set; }

	public void Render(PdfPage page, IEnumerable<PrintableArea> areas)
	{
		using (XGraphics gfx = XGraphics.FromPdfPage(page))
		{
			XPen pen = new XPen(XColors.LightGray, 0.0);

			foreach(PrintableArea area in areas)
			{
				for (int verticalLine = area.Left; verticalLine < area.Right; verticalLine += gridSize)
				{
					gfx.DrawLine(pen, verticalLine, area.Top, verticalLine, area.Bottom);
				}

				for (int horizontalLine = area.Top; horizontalLine < area.Bottom; horizontalLine += gridSize)
				{
					gfx.DrawLine(pen, area.Left, horizontalLine, area.Right, horizontalLine);
				}
			}
		}
	}
}


Examples of each type of paper are available for download.