Bagikan melalui


Counting Retrieved Data

Why Can't I Get a Count of My Dial Plans?

Not too long ago we got an email from someone who had encountered a strange problem when working with Lync Server PowerShell:

"I was just trying to get a count of things," he wrote. "Sometimes I could do that and sometimes I couldn't. For example, this command returns all my conferencing policies:

(Get-CsConferencingPolicy).Count

"No problem there. But then I tried doing the same thing for dial plans, using this command:

(Get-CsDialPlan).Count

"When I ran that command, I got nothing. I could count some things, like users, but I couldn't count other things, like Address Book settings. Have I discovered a bug in Lync Server?"

As it turns out, the answer is no: this isn't a bug. (As if there could be any bugs in Lync Server!) We have to admit that it took us a little while to figure out what the problem was, but once we did we thought it might be worth passing this information along to all of you. After all, you never know when you might run into the same problem.

Besides, we promised we'd write something for today, and we hadn't actually gotten around to doing that yet. Mahjong doesn't play itself, you know?

To explain the problem, and offer up a solution, let's start out by running the command that actually worked:

(Get-CsConferencingPolicy).Count

If you're new to Windows PowerShell, we need to explain that what we're doing here is first running the Get-CsConferencingPolicy cmdlet in order to return a collection consisting of all the conferencing policies available in our organization. That command is enclosed in parentheses in order to make sure PowerShell completes the command – that is, returns all the conferencing policies – before doing anything else. After all the policies have been returned, we then use the Count property to figure out how many policies are in the collection. Onscreen, the whole thing looks something like this:

PS C:\> (Get-CsConferencingPolicy).Count

13

PS: C:\>

We got back 13 because we have 13 conferencing policies in our test domain. Makes sense, right?

Now let's try a command that didn't work:

(Get-CsDialPlan).Count

When we tried returning a count of all the dial plans, we got back, well, nothing:

PS C:\> (Get-CsDialPlan).Count

PS C:\>

That would be fine, except for one thing: we know for sure that we have at least one dial plan; you always have at least one dial plan. (You can't delete the global dial plan.) Hmmm, maybe we have discovered a bug after all ….

No, wait, scratch that: it turns out that it’s not a bug. Here's the problem: the problem is that we have only one dial plan. (Why is that a problem? We'll get to that in a second.) To verify our suspicions, we removed all our conferencing policies except for the global policy; that left us with just one conferencing policy. When we tried to get a count of our conferencing policies, we – well, see for yourself what happened:

PS C:\> (Get-CsConferencingPolicy).Count

PS C:\>

Good heavens! Did we break something, or does PowerShell just have problems counting to 1?

As it turns out, the answer is: neither. (If you got that wrong, don't feel bad; we would have bet on the authors of this article having broken something, too.) No, the problem is this: the Count property is a property that is valid on collections and arrays. If you have a collection (or an array) then the Count property tells you the number of items in that collection. It doesn't matter whether we have a collection of conferencing policies or dial plans or whatever; the Count property is there to tell us how many items are in the collection.

So why is that a problem? Be patient; we're getting to that. As you may or may not know, Windows PowerShell objects typically have a GetType() parameter that provides information about the datatype of the object you're working with. For example, back when we had 13 conferencing policies we ran this command:

(Get-CsConferencingPolicy).GetType()

In turn, PowerShell reported back the following:

IsPublic IsSerial Name BaseType

-------- -------- ---- --------

True True Object[] System.Array

As you can see we're dealing with an array of objects (System.Array). And, as we've already determined, arrays support the Count property.

Trivia Note. Arrays also support the Length property, which returns the exact same information as the Count property. Arrays also support the LongLength property, which returns the exact same information as the Length and Count properties. And – well, I guess we decided three properties that all return the exact same information was enough.

Actually, that's true only up to a point: although Length and Count are identical the LongLength property, as the name implies, supports really big arrays, arrays with more than 2 billion items. We're going to assume that won't be a problem for most of you. A quick, off-the-top-our-heads calculation suggests that, if you created a new dial plan every minute, it would take around 4,000 years to create 2 billion dial plans. But hey, if anyone actually does create 2 billion dial plans let us know and we'll talk more about the LongLength property.

Now, where were we? Ah, yes: if we have multiple conferencing policies and we run Get-CsConferencingPolicy we get back an array of conferencing policies.

Note. Wow: that's the loudest "Well, duh" we've ever heard.

So what happens after we delete 12 of our conferencing policies, leaving us with just one conferencing policy? Let's try the same command and see for ourselves:

(Get-CsConferencingPolicy).GetType()

Well, whatta you know: look what we got back when we ran that command:

IsPublic IsSerial Name BaseType

-------- -------- ---- --------

True False MeetingPolicy Microsoft.Rtc.Management.Writab…

This time we didn't back an array; instead, we got back a singleton, a single instance of the Microsoft.Rtc.Management.WritableConfig.Policy.Meeting.MeetingPolicy object. That's why the Count property doesn't do us any good: the MeetingPolicy object doesn't actually have a Count property. By contrast, if we have more than one object we get back an array (an array of MeetingPolicy objects), and then we can use the Count property. If we only have one object we get back a singleton, and we can't use the Count property. Case closed!

Oh, wait: case not closed, not yet anyway. We now understand why we're getting unexpected results when we use the Count property: that's because we're getting back different objects that support different property sets. We still have a problem, though. Remember this command:

PS C:\> (Get-CsDialPlan).Count

PS C:\>

That leaves one question still open: do we have 1 dial plan or do we have 0 dial plans? There's really no way of knowing, at least not by using the Count property.

So how can we tell, for sure, how many somethings we have? The best way is to use the Measure-Object cmdlet. For example:

Get-CsDialPlan | Measure-Object

That's going to return data that looks like this:

Count : 1

Average :

Sum :

Maximum :

Minimum :

Property :

Or, if you don’t want to get back all those blank values (for things like Average, Maximum, and Minimum, which don't make any sense in this case anyway), use this command:

(Get-CsDialPlan | Measure-Object).Count

And what if you have a deep, personal attachment to the Count property? Well, to be perfectly honest, in that case you would seem to have problems that we can't really help you with. But if you're dead set on using Count instead of Measure-Object, well, you can always do this:

[array] $x = Get-CsDialPlan

$x.Count

If you retrieve an object (like a dial plan) and store it in a variable that has been cast as an array (in other words, we're forcing the variable $x to be an array), that variable will, well, always be configured as an array, even if that array contains just 1 item. That means that, in this example, $x is an array that contains a single object. And, because it is an array, that means $x supports the Count property.

But wait: there's a catch here, a catch that crops up if you have zero instances of something. For example, suppose you don't have any dial-in conferencing access numbers. Onscreen you're going to see something like this:

PS C:\> [array] $x = Get-CsDialInConferencingAccessNumber

PS C:\> $x.Count

PS C:\>

By contrast, using Measure-Object returns an actual value of 0:

PS C:\> Get- CsDialInConferencingAccessNumber | Measure-Object | Select-Object Count | Format-List

Count : 0

PS C:\>

With that in mind, we recommend that you use Measure-Object any time you need to get a count of something. But hey, to each his own, right?

Comments

  • Anonymous
    January 01, 2003
    Thanks Shay! One of the cool things about PowerShell is all the different ways you have to get things done. This is a great example.

  • Anonymous
    January 01, 2003
    The comment has been removed

  • Anonymous
    January 01, 2003
    Hey Chris, Actually, that's exactly how Get-CsDialPlan works too. In your example you have only one instance of Notepad running. If you had more than one, when you typed $x.count you'd get a count of the number of instances of Notepad that are running. $x.gettype().fullname in that case would return System.Object[]. Get-CsDialPlan is the same. You can pass it a parameter to retrieve only one instance, like this: PS> Get-CsDialPlan -Instance DialPlan1 In this case you get back one dial plan, and you'll have to use the notation in the article or that Shay suggested to get a count of 1 returned object. The main difference between the two is that with Get-CsDialPlan, it's possible to have only one dial plan, so calling it without any parameters will retrieve that one dial plan. With Get-Process, you'll never have only one process on your machine, so calling it with no parameters will always return an array.

  • Anonymous
    December 01, 2010
    Another way to ensure we can work with the Count property on an unknown result count is by forcing the result to an Array, using the Array sub-expression notation '@(...)' (we can also cast the result explicitly with [array]). @(Get-CsDialInConferencingAccessNumber).Count That way, even if we get just one object back (scalar) we will still have a count property to work with. -Shay http://PowerShay.com

  • Anonymous
    December 01, 2010
    I can't speak for the Get-CsDialPlan cmdlet as don't have access to Lync Server, but when I run "Get-Process notepad" (with one instance of notepad open) and save it to a variable, the variable is of type System.Diagnostics.Process not an array. PS> $x = get-process "notepad" PS> $x.count PS> $x.gettype().fullname System.Diagnostics.Process I'm not sure why Get-CsDialPlan would behave differently, but the Array notation Shay suggested is a more universal fix.

  • Anonymous
    December 03, 2010
    The point I'm trying to make is that I believe this statement is incorrect: "If you retrieve an object (like a dial plan) and store it in a variable, that variable will always be configured as an array, even if that array contains just 1 item." However I since I don't have access to Lync with Powershell I can only use Get-Process to test on my machine.  Unless Get-CsDialplan behaves differently than any other cmdlet I've seen, then this code will output nothing: $x = Get-CsDialPlan $x.Count becuase $x will still be set to the single instance of the dial plan, not an array as the article states.

  • Anonymous
    July 19, 2011
    Well, duh, this is one of the dumpiest things I've ever heard. Have you ever heard of Array with one element?!? It's still called 'array' in all programming languages. this PS stuff is so inconsistent with himself, it will not be adopted.