Haiku #74
^ 1 \d{5} \d + ?
Is that Klingon or something?
No, just VoiceRegex.
Today's haiku is the direct result of a challenge issued by the legendary Lync Server PowerShell writer Jean Ross. (Many people consider Jean to be the second-greatest Lync Server PowerShell writer in history.) Yesterday Jean and the author of today's haiku were discussing some of the more obscure cmdlets when New-CsVoiceRegex was mentioned. "New-CsVoiceRegex?" she said. "I'd like to see you write a haiku about that." Needless to say, that sounded like a challenge.
Note. So is the author of today's haiku the type of person who would ever back down from a challenge? You bet he is; he does it all the time. But when he got to work this morning he realized he still had a haiku to write, and so he decided that New-CsVoiceRegex would be as good as anything else.
So there, Jean Ross: take that!
Note from Jean. The actual conversation was more along the lines of “New-CsVoiceRegex is an interesting cmdlet, you should write a haiku about that one.” But if the author of today’s haiku, a.k.a. Greg Stemp, wants to take that as a challenge, that’s okay. We got the haiku didn’t we?
As you might have guessed, the Regex part of New-CsVoiceRegex stands for, well, regex, programming jargon for regular expressions. Regular expressions, which have been around since the 1950s, are used for pattern matching. For example, have you ever typed a command like this from the command prompt:
dir *.ps1
If you have (and of course you have), then you're already familiar with pattern matching. With the command dir *.ps1 you're telling the operating system that you aren't looking for a particular file; instead, you're looking for all the files that meet the specified pattern. The syntax *.ps1 means you're looking for all the files that have any file name (that's what the asterisk is for: it means "anything") and then ends with the file extension .ps1. For example, these files meet that pattern and will be returned by the command:
File1.ps1
My_script.ps1
The script that I wrote to return user account information.ps1
X.ps1
By comparison, these files don't meet the pattern and won't be returned by the command:
File1.ps
My_script.ps195
Ps1.txt
Readme.doc
Regular expressions are really just a fancier way to do pattern matching. For example, suppose we have a variable named $lineUris that contains all the line URIs for all our users.
Note. How could you get all your line URIs into a variable in the first place? Well, that doesn't really matter for now. But you could always try a command like this one:
$lineUris = Get-CsUser | Select-Object LineUri | Where-Object {$_.LineUri –ne ""}
Now that we have these numbers we'd like to know whether we've assigned any line URI where the last five digits fall in the range 30000 through 34999. In other words, has anyone been assigned a line URI like TEL:+120655530010 or TEL:+120655532329?
So how can we answer a question like that? Well, thanks to regular expressions and pattern matching, that's actually pretty easy. This command should do the trick:
$lineUris | Where-Object {$_.LineUri -match "[30000-34999]$"}
We won't go into a detailed explanation of regular expression syntax today (you'll thank us for that later) but "[30000-34999]$" basically says we're looking for any line URI whose last five digits fall into the range 30000 through 34999. We use [30000-34999] to specify the numeric range, and we use the dollar sign character ( $ ) to indicate that the digits in question must occur at the end of the string.
Note. Why? Because we don't want to match a phone number like TEL:+13455551298.
As you can see, there are least two things that are true about regular expressions: 1) they're very powerful; and, 2) they can get pretty complicated. For example, here's an actual regular expression used for some sort of phone number validation:
^(1\s*[-\/\.]?)?(\((\d{3})\)|(\d{3}))\s*[-\/\.]?\s*(\d{3})\s*[-\/\.]?\s*(\d{4})\s*(([xX]|[eE][xX][tT])\.?\s*(\d+))*$
And yes, that's what we were going to say. But they won't let us publish words like that in the Lync Server PowerShell blog.
Speaking of Lync Server, what do regular expressions have to do with you, the Lync Server administrator? Well, as we've already seen with the line URI example, regular expressions can be a handy tool for data mining. In addition to that, however, Lync Server also uses regular expressions to "normalize" phone numbers. (That is, to take a phone number typed in by a user – e.g., 5551298 – and convert it to a format – for example, +12065550712 – that Lync Server can work with.) As a Lync Server administrator, you're going to have to write voice normalization rules to translate the phone numbers typed in by your users.
Note. You're right: we should have asked you to sit down before we sprung that on you. Sorry.
This, at long last, is where New-CsVoiceRegex comes into play. The New-CsVoiceRegex cmdlet helps you to write regular expressions that can be used in a voice normalization rule. Admittedly, you can't write super-complicated regexes using New-CsVoiceRegex; that's not what it's designed for. But it can help you identify (and match) the more common phone numbers formats that your users are likely to type in.
How can it do that? Well, to begin with, voice normalization requires you to do two things: you need to figure out the kind of phone number you're working with, and you need to transform that phone number into a number that Lync Server can work with. Let's run through a quick example of how New-CsVoiceRegex can help you with this task.
Let's assume your users typically make internal calls by simply dialing someone's extension; for example, if Ken Myer has the phone number 1-206-555-1219 someone calling Ken Myer will probably just enter this number:
51219
How do you know if someone is trying to dial an internal number? Well, you never know that for sure. But it's a pretty good guess that if someone dials a 5-digit number, and that 5 digit number begins with a 5, then they're trying to dial an internal number.
So how can you create a regular expression that can identify internal phone numbers? Here's one way:
$regex = New-CsVoiceRegex –ExactLength 5 –StartsWith 5
All we've done here is call the New-CsVoiceRegex cmdlet and included two parameters: ExactLength, which specifies the exact number of digits we're looking for (5), and StartsWith, which indicates the value of the first digit we're looking for (also a 5). That regular expression is going to match a number like 51219; however, it will not match a number like 5551219 (more than 5 digits) nor will it match a number like 41219 (doesn't start with a 5).
Note. Then what do you do about a number like 5551219? You'll either need to create a more-complicated regular expression or you'll need to create a separate voice normalization rule for matching a 7-digit number that starts with 555:
$regex = New-CsVoiceRegex –ExactLength 7 –StartsWith 555
If we look at the value of the variable $regex we should see this:
Pattern Translation
------- -----------
^(5\d{4})$ $1
The pattern – (^(5\d{4})$ – is the regular expression we created. (Oh, OK: the regular expression that New-CsVoiceRegex created for us.) The translation – $1 – is the normalized phone number. The value $1 simply means that we're going to use the exact number that the user typed in.
Of course, we don't want to use the exact number that the user typed in; Lync Server doesn't know what do with a phone number like 51219. That means we need to specify how that number should be translated, something we can do in our voice normalization rule, or something we can do using New-CsVoiceRegex. (Well, as long as it's a very simple translation.) For example:
$regex = New-CsVoiceRegex –ExactLength 5 –StartsWith 5 –DigitsToPrepend +120655
As you can see, this time around we've included the DigitsToPrepend parameter and told New-CsVoiceRegex that we want to add this to the front of every phone number that matches the pattern:
+120655
Why? Because that will turn the phone number 51219 into this:
+12065551219
And that's a number that Lync Server can deal with. If we now look at the value of $regex we should see this:
Pattern Translation
------- -----------
^(5\d{4})$ +120655$1
Notice anything different about the Translation property? That's because we added the DigitsToPrepend parameter to our command.
You may have also noticed that our resulting regular expression is stored in the variable $regex. (At least we hope you noticed that.) If you want use New-CsVoiceRegex that's how you do things: you create a regular expression, you store that regular expression in a variable, and then you use that variable, along with the New-CsVoiceNormalizationRule cmdlet, to create a new voice normalization rule. For example:
$regex = New-CsVoiceRegex –ExactLength 5 –StartsWith 5 –DigitsToPrepend +120655
New-CsVoiceNormalizationRule -Parent SeattleUser -Name SeattleFourDigit -Description "Dialing with internal five-digit extension" -Pattern $regex.Pattern -Translation $regex.Translation
Of course, for better or worse, we don't have time to discuss the New-CsVoiceNormalizationRule cmdlet today; that's something we'll have to do sometime down the road.
Like, say, the next time Jean Ross challenges us to write a haiku.
Note. What's that? Why doesn't Jean Ross just write those haikus herself? You know, we never thought to ask. Why doesn't she just write those haikus herself? Hmmm ….
Note from Jean. I’m busy. Or, to use the most popular catch phrase here at Microsoft, “It’s like a fire drill around here.”