When working with regular expressions in PowerShell to check if certain string matches a pattern we can use it in both directions: to match or not match it (with –match and –notmatch operators). That’s very handy and it saves us a lot of time, because we don’t have to come up with regular expression that would do it for us. But there are certain situations where operators are not used.
One of these situations is ValidatePattern attribute. If we would like to use it to accept any string that doesn’t match certain pattern, we would have to make pattern smarter. Luckily, there is better alternative: just use ValidateScript and –notmatch inside it. It gives us option to give a user more meaningful error message, so we would probably take this route anyway.
But that’s not the only situation when matching is the only choice. Working more and more with PowerCLI I’m forcing myself to use Get-View as much as I can. It’s usually very fast and makes some interesting filters possible. Parameter that is used for this filtering (named filter, so no surprises there) is using regular expression under the hood:
-Filter <Hashtable>
Specifies a hash of <name>-<value> pairs, where <name> represents the property value to test, and <value> represents a pattern the property must match. If more than one pair is present, all the patterns must match.
Now: I want to list all VMs with names that don’t match ‘test’. This is pretty easy task with –notmatch, but here we have to figure out a way to match something that does not contain word ‘test’. For that we need to use negative lookahead assertion.
Pattern that we need is relatively simple, we will define it as validation for parameter in a function so that we can easily test it:
function Test-Name { param ( [ValidatePattern('^(?!.*test)')] [string]$Name ) $Name }
When we try anything with “test” it will just fail:
We have a pattern, but I guess it’s worth spending few minutes to explain it. Let’s break it down into few pieces, starting from assertion itself.
Negative lookahead assertion: (?!pattern) – is a way to make sure, that nothing in front of us looks like a pattern provided. We want to be sure that “test” is not present, so “test” itself should be part of the pattern that we want to avoid. But using just “test” won’t give us what we need. Negative lookahead is observing anything that immediately follows, so using only test would give us either whole string (if “test” is not the first word) or almost whole string (if “test” is the first word):
Wildcard: .* – even if between us and “test” there are other characters, we want to continue looking, until we get to the end of string, or our trouble maker, whichever comes first. But that’s not enough. If we would let pattern run loose, it would just move until it would find a spot where whole word “test” is not visible any more. Any guess where that would be? If you guessed “after t before e in test” you guessed right:
Anchor: ^ – we need to be sure that pattern is absent. So we need to be sure, that observations will happen in a place where everything is visible. Anchor that is “glued” to beginning of string to match is our best bet. If we are sure that we look from this place, there is no way “test” will sneak in. This way we can use Get-View to filter, even if our intention is to filter out anything that matches given pattern.
We have our pattern, so going back to Get-View: our command could look like this:
Get-View -ViewType VirtualMachine -Filter @{ Name = '^(?!.*test)' }
We managed to revers our pattern and even though filter will match, we get what we wanted.