So, my previous post on PowerShell has prompted some responses, internally and externally. Sufficient that I did actually re-word some parts of it, and sufficient that I feel a need to be positive and offer something to take away the burn.

So let’s have a go at explaining the pipeline, shall we?

To do this, I’m going to give an example of doing something without the pipeline. I hope that by the end of this post, the value of showing the other way first will be clear. But I’ll say up front, if you have written code like I’m about to show, don’t fret. It still works. There’s just a better way.

The example I’ve chosen is this:

You’re deploying an IIS Web Application using PowerShell, and as part of your deployment process, you want to delete the previous web site(s) from IIS.

So, let’s dig in. I’m going to be quite thorough, and it’s fine to follow along in the PowerShell prompt. You will, of course, need IIS installed if you do, but don’t worry, at the end there’s an example or two that should work for everyone.

We’ve been doing a lot of interviewing at work of late. You see, we’re looking for good Windows DevOps candidates, and the local market is… well… let’s just say that next-gen Windows guys are a little thin on the ground around here.

This is a problem. Because while next-gen windows candidates are thin on the ground, resumés claiming next-gen Windows skills are emphatically not thin on the ground. In fact, I have actually – literally – lost count of the number of resumés I’ve seen where PowerShell is touted as a key skill, only for the candidate to fail some very simple PowerShell questions in the initial screening. So let’s just run through a few basic principles so that we don’t all waste each other’s time.

If you use pingdom for your monitoring, and you have a requirement to lock down your endpoints to a specific set of clients, you may have a painful job on your hands.

Some engineers I’ve spoken to have implemented a kind of proxy to forward pingdom requests through to their locked-down endpoints. Others rely on User-Agent detection to allow Pingdom probes through while denying other traffic.

In my case, I’ve implemented a powershell script that runs at intervals, checking Pingdom’s published Probe IP List and syncing it to my target Security Group. here’s how it’s done.

The very first thing you’ll need to do, if you haven’t already, is contact AWS Support and get your rules-per-group limit increased. By default, you get 50 (at the time of writing), and that’s not enough for this.

Then the code.

First up, you need a list of the IPs you want to whitelist other than pingdom. Not much use only opening your endpoint to the monitoring service, is it?

1

2

3

4

5

$whitelist=@(

"123.123.123.123/32",

"124.124.124.124/32",

"52.52.52.0/24"

)

And so on. You may want to store this differently, but for me it’s just straight in the script. For now.

When you have those, you need to grab Pingdom’s probe IPs from their API

1

$probeIPs=Invoke-RestMethod https://my.pingdom.com/probes/feed

Excellent. Now, the pingdom addresses aren’t in CIDR format, so you need to convert them to CIDR and add them to the $whitelist array you set up earlier. For that, you need a function that does pipeline input.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

FunctionJoin-IPs# pipeline function to append to an incoming array of strings

{

param

(

[Parameter(ValueFromPipeline=$true)]

[string]

$In,

[string]

$JoinTo="/32"

)

process

{

return(""+$_+""+$jointo+"")

}

}

And then you just stick that in your pipeline and get back an array of al the IP ranges that are meant to be in your security group.

1

$ranges=$whitelist+=($probeIps|select-expand ip|Join-Ips-JoinTo"/32")

And there you have a list of all the CIDR ranges that are meant to be in your security group’s ingress rule.

My rule literally only opens one port – 443 – so if you have multiple ports, you may want to do this differently. It also does nothing to try and compress down multiple adjacent addresses into a single CIDR, so if you need that, you’re going to need to do a little extra work.

Now, we compare the sec group’s existing rules, and the array we just obtained, like so

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

$targetGroup=Get-EC2SecurityGroup-Region$region|`

?{$_.GroupName-eq"s-fictional-svc-WebElbGroup-H97BD3LE36JI"}

$currentranges=$targetgroup.IpPermissions|`

?{$_.FromPort-eq443}|select-expand IpRanges

$groupID=$targetgroup.GroupId

$diff=Compare-Object$currentranges$ranges

$diff|%{

# If the side indicator is =>, we add it

# if the side indicator is <=, we remove it

if($_.SideIndicator-eq"=>")

{

Write-Host"Granting Ingress perms to"$_.InputObject

Grant-EC2SecurityGroupIngress-GroupId$groupID`

-IpPermission@{

FromPort=443;

IpProtocol="tcp";

IPRanges=$_.InputObject;

ToPort=443

}

}

if($_.SideIndicator-eq"<=")

{

Write-Host"Revoking Ingress perms from"$_.InputObject

Revoke-EC2SecurityGroupEgress-GroupId$groupId`

-IpPermission@{

FromPort=443;

IpProtocol="tcp";

IPRanges=$_.InputObject;

ToPort=443

}

}

}

As you can see, we use Compare-Object to determine what needs to be added and what needs to be removed, and push just that rule up – or rip it out of – to the Security Group.

This technique can be used to whitelist any service that publishes its IPs in an API – in fact, if you’re whitelisting a client, you could get your client to publish their IP list to you and literally just put a script like this in place. Why do this crap manually? Let a script do it for you.

The installation, actually, was a breeze, with a reboot or two to get Hyper-V working on my laptop. But come time to run hello-world, there were problems.

OK, that’s bad. It suggests networking is broken. So, after a cursory inspection of my Hyper-V settings (everything looked OK), it was off to the internet. I found this article, which talks through setting up the beta version. Pretty good, but no dice.

Of late, I’ve been working on a little side project to test a rather complex Akamai Property. We wanted to be confident, after making changes, that the important bits were still working as we expected them to, and for some reason there was no easy, automated solution to test this.

Obviously I decided I’d write my testing project in Pester, and so it was that I began writing a whole bunch of tests to see what URLs returned what status code, which ones redirected, which ones were cache hits and cache misses and what headers were coming back.

First up, I wrote a generic function called “Invoke-AkamaiRequest”. This function would know whether we were testing against Staging or production, and would catch and correct PowerShell’s error behaviour – which I found undesirable – and allow us to send optional Akamai pragma headers (I’ll share this function in a later post).

With that up and running, I could start writing my tests. Here’s a set of simple examples

Now, that last one, testing a 301, is interesting. Not only do you need to test that a 301 or 302 status code is coming back, you also need to test where the redirect is sending you. So I started to write tests like this

1

2

3

4

5

It"Should redirect /blog/ to /advice/"{

$blog=Invoke-AkamaiRequest-path/blog/

($blog|select-expand statuscode|Should Be301)-and

($blog.headers.location|Should Be http://$tld/advice/)

}

And this worked fine. But it was a bit clunky. If only Pester had a RedirectTo assertion I could just throw in there, like so

1

2

3

It"Should redirect /blog/ to /advice/ "{

Invoke-AkamaiRequest-path/blog/|Should RedirectTo http://$tld/advice/

}

If. Only.

Oh, but it can!

Yes, you can write custom assertions for Pester. They’re pretty easy to do, too. What you need is a trio of functions describing the logic of the test, and what to return if it fails in some way. They are named PesterAssertion, PesterAssertionFailureMessage and NotPesterAssertionFailureMessage, where Assertion is the assertion name, in my case “RedirectTo”

For my particular case, the logic was to take in an HTTP response object, and check that the status was 301 (or 302), and match the Location: header to a specified value. Pretty simple really. Here’s the basic code:

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

functionPesterRedirectTo($value,$expected)

{

return[bool](($value.statuscode-eq301-or$value.statuscode-eq302)-and

$value.headers.location-eq$expected)

}

functionPesterRedirectToFailureMessage($value,$expected)

{

return"Expected to redirect to {$expected}"

}

functionNotPesterRedirectToFailureMessage($value,$expected)

{

return"Expected not to redirect to {$expected}"

}

I put these into my supporting module (not into the Pester module) and ran my tests. Happy happy days, it worked perfectly. Throwing different URLs at it resulted in exactly the behaviour I wanted.

All that remained was to make the failure messages a little smarter and make the Not assertion more useful, but I figured before I did that I should write this little post with the nice clean code before the extra logic goes in and makes everything unreadable.

You can probably think of several ways you could streamline your tests with assertions right now. I’ve also written RedirectPermanently and ReturnStatus assertions, and I’m looking into HaveHeaders and BeCompressed. I may even release these as an add-on module at some point soon.

You can probably think of things that should go right back into the Pester codebase, too. And there are a number of other ways you can customise and extend pester to fit your own use cases.

To summarise: Pester is not just a flexible and powerful BDD framework for PowerShell. It’s also easily extensible, adding even more power to your PowerShell toolbox.

Here at Planet Domain we have a lot of day to day challenges in performance and cost management.

We built a fantastic CD pipeline that allows our developers to rapidly spin up new infrastructure, enabling them to quickly build, test and deploy new features with minimal assistance from the Ops team.

Unfortunately, it also allows our developers to rapidly spin up new infrastructure than then lays idle for the greater part of the day, sucking money out of budget that could otherwise be used on beer, pizza and Xbox accessories.

Now, Cloudwatch is great, but it does tend to involve a lot of clicky-clicky mouse-movey, clicky-clicky stuff in the AWS console. And once you’ve got a lot of points to monitor, the graphs become unreadable. And the filtering options are sometimes finnicky, and it’s not easy to automate things like CloudWatch Dashboards using CloudFormation.

So to get a report on average CPU utilization across our AWS Autoscaling Groups, I turned to PowerShell. Continue reading →

This past week saw AWS’s Sydney Summit at the historic Hordern Pavilion. While many saw the “coming soon” announcements as a slight disappointment (Lambda coming soon. API gateway coming soon. Everything else: coming soon), I – and am not the only one – saw it as a bit of a wakeup call.

It’s been much talked of in Australian Cloud circles – so much so that I freely admit to feeling a degree of Lambda Fatigue at meetups and conferences over the last year or so.

But the actual release of Lambda in ap-southeast-2 is imminent. And with it will come a fundamental shift in the way many of us in Australian Cloud circles need to think about Cloud infrastructure, because the potential advanatages in cost, development time and scalability are just too big to ignore. If you’re going all-in on cloud and your plans don’t include Lambda somewhere, then you’re probably doing something badly wrong. Continue reading →

I will be sharing the stage with Fabien Ruffin, who is also speaking at the Sydney AWS Summit on the topic of New Relic. I will be an wandering around that conference and asking people awkward questions while probably sporting a purple hair-dye job and generally making a nuisance of myself while people try to extract sensible answers about Windows workloads in the AWS Cloud.

I will also be around the Sydney DevOps and Cloud meetup scene a bit in the next few months, discussing Windows, Cloud and Automation stuff in-between moments of objectionable surliness and plugging my NDC session.