• Holiday Sets and the Workflows that Love Them

    by Christopher Wallick, BT 

    There’s so much to consider when thinking about the varied and sundry PowerShell cmdlets that are used to manage and configure Microsoft Lync Server 2010. In this riveting installment of “A Snippet from the Shell” we’ll look into the seedy world of the Response Group application and see how, with a few quick lines, we can create a custom “Holiday Set”. (Okay, it’s not that seedy – or even all that riveting for that matter – but I had to grab your attention somehow and I didn’t want to resort to using words like “futterwacken”, “vorpal”, or “Jabberwocky”. )

     

    At a very high level the Response Group application is a mechanism by which Lync Server 2010 routes incoming calls to a preselected group of “agents” by using a “hunt group” or an “Interactive Voice Response (IVR)”. That’s a lot of quotation marks! I use them here to underscore that there may be some Lync terminology that may be new to some of you and, quite frankly, may be used differently from how others of you are used to. The way these response group components fit together is a topic for another article entirely, but for today I thought it would be useful to take a closer look at the creation of a holiday set.

     

    As many of you may know a holiday set is nothing more than a predefined list of public holidays when the business will be closed. This is different from defining regular business hours. As you go through building a response group workflow you will see there are many different configurable settings around how to treat an incoming call based on the time of day, the day of the week, and as we’ll see here, the day of the year.

     

    By default there are no predefined holiday sets listed in the RGS Configuration tool. So how does one go about adding a holiday set? That’s a great question and I’m glad you asked. The first thing to note is you can’t do it in the Lync Server Control Panel nor within the RGS Configuration tool. It can only be done in PowerShell.

     

    So, here’s where we boldly go to the Lync Server Management Shell. Ready? Great! The first thing to remember when writing a PowerShell script is that PowerShell executes each line of code in order. If this is a bit confusing don’t worry, it’ll all make sense soon. The holiday set itself is a kind of logical container that contains information about individual holidays. All of the holidays together make the holiday list property which is then referenced by the holiday set. Still with me? Well, stick with it and all will become clear in a minute.

     

    For the sake of example let us consider the federal holidays observed by the U.S. for our first holiday set. In the U.S. the first federally observed holiday is New Year’s Day. The next is Martin Luther King Day, then President’s Day and so on. The first thing we’ll create in our script will be the holidays themselves. We’ll assign each holiday a variable that we’ll call later.

     

    $a = New-CsRgsHoliday –Name "New Year Days" -StartDate "01/01/2011" -EndDate "01/02/2011"

    $b = New-CsRgsHoliday –Name "Martin Luther King Day" -StartDate "01/17/2011" -EndDate "01/18/2011"

    $c = New-CsRgsHoliday –Name "Presidents Day" -StartDate "02/21/2011" -EndDate "02/22/2011"

     

    Here you can see we use the New-CsRgsHoliday cmdlet to create the holiday with the appropriate values for Name, StartDate, and EndDate.

     

    Once created, we’ll then need to create the holiday set by using the New-CsRgsHolidaySet cmdlet.

     

    New-CsRgsHolidaySet $ser -Name "US_HolidaySet" -HolidayList($a, $b, $c, $d)

     

    Here we see a variable, $ser, after the New-CsRgsHolidaySet cmdlet. So, what’s that all about? This references the -Parent switch that sets the identity or service to which this holiday set will be applied. In this instance I’ve associated this information with a variable ($ser) rather than type out the long string referencing the response group service identity.

     

    Once done, you’ll be able to see the new holiday set exposed in the RGSConfig tool. How cool is that?! I know; very cool, right? Wait a second though! What if, like me, you’ve made a mistake or forgot to include a particular holiday? What do you do then? If your answer was to light your hair on fire and run down the hall screaming you’d be wrong. The correct answer is that you’ll need to simply rebuild the holiday list property and then use a Set cmdlet to upload the changes. Given that the holiday set has already been created we’ll use the Get-CsRgsHolidaySet cmdlet and assign the result to a variable.

     

    $hs = Get-CsRgsHolidaySet $ser -Name "US_HolidaySet"

     

    Then we’ll use the Remove method to delete all holidays from the holiday list.

     

    $hs.HolidayList.Remove()

     

    Now remember this is happening all in local memory and hasn’t actually been applied yet. Next we’ll repopulate the holiday list with the complete list of holidays by using the following string:

     

    $hs.HolidayList = ($a, $b, $c, $d, $e)

     

    Now we’ll use Set-CsRgsHolidaySet to upload the changes.

     

    Set-CsRgsHolidaySet $hs

     

    Clear as mud? Awesome! I hope this helps and until next time happy shelling!

     

    See more Snippets from the Shell

     

  • Listing the Names of the Agent Groups Assigned to a Response Group Queue

    The best thing about being a technical writer at Microsoft is that you are constantly reminded how little you actually know about the technology you're charged with writing about.

     

    Note. No, we're not being facetious: that really is the best thing about being a technical writer at Microsoft.

     

    Hey, how do you think we feel about that?

     

    For example, the other day we received an email from someone wondering why he couldn't use the Get-CsRgsQueue cmdlet to view the names of the agent groups assigned to a Response Group queue. Needless to say, our initial reaction was that the person who sent the email had no idea what he was talking about. "Of course you can use the Get-CsRgsQueue cmdlet to view the names of the agent groups assigned to a Response Group queue," we thought to ourselves. "After all, one of the properties returned by Get-CsRgsQueue is the AgentGroupIDList property. Are we the only people in the world who know anything at all about Microsoft Lync Server 2010?!?"

     

    Needless to say, you can already guess where this headed. Just to prove to ourselves that we really were right, we created a new Response Group queue, created a pair of agent groups, then assigned those two agent groups to the new queue. We then ran this command in order return information about all our Response Group queues, including this newly-created queue:

     

    Get-CsRgsQueue

     

    And when we ran that command did we get back a value for the AgentGroupIDList property? You bet we did. See for yourself:

     

    Identity          : service:ApplicationServer:pool0.litwareinc.com/7c051be4-9dc7-435f-8ddc-b53ffcfac41                    1

    TimeoutAction     : Action=Terminate

    OverflowAction    : Action=TransferToVoicemailUri

                        Uri=sip:+14255551298@litwareinc.com

    Name              : help Desk

    Description       :

    TimeoutThreshold  :

    OverflowThreshold :

    OverflowCandidate : NewestCall

    AgentGroupIDList  : {service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd, service:ApplicationServer:pool0.litwareinc.com/368ed763-b747-4c71-bccc-68c0c7124624}

     

    Now, you might very well be looking at this output and wondering, "Why did they give their agent groups names like service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd?" Well, the truth is, we didn't: we actually named our agent groups Agent Group 1 and Agent Group 2. (OK, maybe not the best names in the world, but still a sight better than service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd. So then why the weird value for the AgentGroupIDList property?

     

    Well, as it turns out, the Get-CsRgsQueue cmdlet doesn't bring back the names of the agent groups assigned to a queue; instead, it returns the identities of those agent groups. With the Response Group application, an agent group Identity consists of the address of the Application Server where the group resides (service:ApplicationServer:pool0.litwareinc.com) plus a GUID that is automatically assigned to the group at the time it gets created (767a3a88-f856-4503-bb1d-cdfa6036bffd). Our email correspondent was right: Get-CsRgsQueue doesn't return the names of the assigned agent groups after all.

     

    Note. So if the email correspondent was right does that mean that we were wrong? Um, we don't really have time for any more questions today. Let's keep moving, OK?

     

    It doesn't matter who was right and who was wrong here; after all, regardless of who knew what they were talking about (and who didn't) we still had a problem: how do we get back the names of the agent groups that have been assigned to a Response Group queue? Identities like service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd are great for computers, but not all that much fun for people to try to work with.

     

    Well, one way we could get back the names would be to first call the Get-CsRgsAgentGroup cmdlet to get back information about all our agent groups. We could then scroll through the results, trying to match up the identities retrieved by Get-CsRgsQueue with the identities retrieved by Get-CsRgsAgentGroup. As soon as we found an agent group that has the Identity service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd we could then check the value of the Name property and determine the name of the group. And then we could repeat the process with the next Identity returned by Get-CsRgsQueue.

     

    And that's a great idea, except for one thing: it's actually a terrible idea. If you only have 4 agent groups you could maybe do this yourself, but what if you had to slog your away through 500 or 600 agent groups? Quick; are these two Identities a perfect match or not:

     

    service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd

    service:ApplicationServer:pool0.litwareinc.com/767a3a88-f356-4503-bb1d-cdfa6036bffd

     

    See what we mean? That approach could take forever.

     

    Note. By the way, the two Identities are not a perfect match.

     

    So is there a better way to solve this problem? Of course there is. Instead of you slogging through all the agent groups looking for a match, let Windows PowerShell do it for you:

     

    $agentIDs = Get-CsRgsQueue -Identity service:ApplicationServer:pool0.litwareinc.com -Name "Help Desk" | Select-Object -ExpandProperty AgentGroupIDList

     

    foreach ($myQueue in $agentIDs)

        {

            $service = $myQueue.ServiceID

            $groupIdentity = $service.FullName + "/" + $myQueue.InstanceID

            $group = Get-CsRgsAgentGroup -Identity $groupIdentity

            $group.Name

        }

     

    Let's see if we can explain how this script works. In the first line we use the Get-CsRgsQueue cmdlet to retrieve the Response Group queue that has the Identity service:ApplicationServer:pool0.litwareinc.com and the Name Help Desk. We grab the information for this queue and pipe it to the Select-Object cmdlet; from there we use Select-Object's ExpandProperty parameter to "expand" the values found in the AgentGroupIDList property. That simply means that we convert all the values in the property to individual objects. When we do that, we no longer have a big, gloppy value that looks like this:

     

    {service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd, service:ApplicationServer:pool0.litwareinc.com/368ed763-b747-4c71-bccc-68c0c7124624}

     

    Instead, we have two objects, one for each of the agent groups that was assigned to the queue. Those two objects get stored in a variable named $agentIDs.

     

    Note. Yes, we did hardcode in the queue Identity and Name, didn't we? That was actually intentional; it helped us keep the script as concise and easy-to-understand as possible. Of course, that also means that, unless you go in and make changes to it, the script can only retrieve information about the Help Desk queue.

     

    So what's the alternative? Well, we could have used a command line argument to represent the queue name:

     

    $agentIDs = Get-CsRgsQueue -Identity service:ApplicationServer:pool0.litwareinc.com -Name $args[0] | Select-Object -ExpandProperty AgentGroupIDList

     

    In the preceding example, we're telling PowerShell to take the first command line argument passed to the script (that's what $args[0] represents) and use that for the queue name. That means we have to start our script by including the appropriate queue name as a command-line argument:

     

    C:\Scripts\AgentGroupList.ps1 "Help Desk"

     

    If you have multiple instances of the Response Group application you can also use a command line argument to represent the Identity:

     

    $agentIDs = Get-CsRgsQueue -Identity $Args[0] -Name $args[1] | Select-Object -ExpandProperty AgentGroupIDList

     

    Of course, we then have to include both the Identity and the Name when starting the script:

     

    C:\Scripts\AgentGroupList.ps1 "service:ApplicationServer:pool0.litwareinc.com" "Help Desk"

     

    But we digress. (Somewhat out of character for us, we know, but ….) After we've stashed the agent group identities in the variable $agentIDs we next set up a foreach loop to loop through each of those IDs. The first thing we do inside the loop? Why, we execute these two lines of code, of course:

     

    $service = $myQueue.ServiceID

    $groupIdentity = $service.FullName + "/" + $myQueue.InstanceID

     

    As we noted, our agent IDs are stored in the variable $agentIDs. However, we don't really have the IDs per se; instead, we have a pair of objects that have property values like these:

     

    ServiceId

    InstanceID

    NonNormalized

    service:ApplicationServer:pool0.litwareinc.com

    767a3a88-f856-4503-bb1d-cdfa6036bffd

    767a3a88-f856-4503-bb1d-cdfa6036bffd

    service:ApplicationServer:pool0.litwareinc.com

    368ed763-b747-4c71-bccc-68c0c7124624

    368ed763-b747-4c71-bccc-68c0c7124624

     

    With that in mind, what we're doing in the first two lines of code is combining the FullName of the ServiceID property, a slash (/) and the InstanceID property to form a real agent Identity, one that looks like this:

     

    service:ApplicationServer:pool0.litwareinc.com/767a3a88-f856-4503-bb1d-cdfa6036bffd

     

    Now that we've constructed the Identity (stored in the variable $groupIdentity) we can use this line of code to retrieve the Response Group agent group that has that Identity:

     

    $group = Get-CsRgsAgentGroup -Identity $groupIdentity

     

    At this point we echo back the group Name, then loop around and repeat the process with the next agent group in our collection.

     

    The net result? We get the names of the agent groups associated with the Help Desk queue:

     

    Agent Group 1

    Agent Group 2

     

    Which is all we ever wanted to see in the first place.

     

    Now, that works pretty well for a single Response Group queue. But what if you wanted to get back a list of the agent groups that have been assigned to all your Response Group queues? Can PowerShell do something like that, too?

     

    Do you even need to ask? We won't bother explaining how this next script works, but we will tell you that it returns a collection of all your Response Group queues as well the names of all the agent groups assigned to those queues. Give it a try and see for yourself:

     

    $allQueues = Get-CsRgsQueue

     

    foreach ($queue in $allQueues)

        {

            $agentID = Get-CsRgsQueue -Identity $queue.Identity | Select-Object -ExpandProperty AgentGroupIDList

            $queue.Name

     

            foreach ($myQueue in $agentID)

            {

                $service = $myQueue.ServiceID

                $groupIdentity = $service.FullName + "/" + $myQueue.InstanceID

                $group = Get-CsRgsAgentGroup -Identity $groupIdentity

                Write-Host "    ", $group.Name

            }

            Write-Host

        }

     

    Here's one more for the road. What we've done up till now is take a Response Group queue and find out which agent groups have been assigned to that queue. But what if we wanted to take the opposite tack: suppose we have the name of an agent group and we'd like to see which queues (if any) that group has been assigned to. What then? Well, then we could run a script like this one:

     

    $group = Get-CsRgsAgentGroup -Name "Group No. 2"

    $allQueues = Get-CsRgsQueue

     

    foreach ($queue in $allQueues)

        {

            if ($queue.AgentGroupIDList -contains $group.Identity)

                {

                    $queue.Name

                }

        }

     

    Again, we won't bother to explain how this one works, at least not today; if you have questions, email us at cspshell@microsoft.com and we'll see what we can do to answer them. After all, we do know pretty much everything there is to know about Lync Server.

     

    Pretty much.

     

  • Granting Policies to Users

    by Christopher Wallick, BT 

    Well we’ve made it into the New Year and from my point of view 2011 is going to be a very busy year. So, in this edition of “Snippets from the Shell” I thought I’d take a moment to focus on some basic user administration that you’ll probably come across fairly frequently.

     

    When a user is enabled for Enterprise Voice certain attributes are set on the user object. These attributes (or properties if we’re looking at the Lync Server 2010 Central Management Store) determine such things as which voice policy will be applied to the user, the dial plan that will tell the user’s Lync client how to normalize numbers the user types in, and amongst other things, whether or not the user’s voicemail is being served by a hosted Exchange implementation. Once all these attributes are set the user goes about his or her business happily consuming Enterprise Voice Services.

     

    Now, let us suppose the user is transferred from, say, San Francisco to Chicago. Obviously, we’ll have to update or change the property values for those attributes that deal with Enterprise Voice. Whilst there are a number of ways we can change these values, let’s look at how we might try to change the user’s voice policy setting.

     

    You might think that you could simply get the CsUser properties and pass all that information to a variable (for the sake of this example let’s use $user), identify the sub-property value (i.e. $user.VoicePolicy), and set the value of the sub-property to the new voice policy. Unfortunately this won’t work because the cmdlet wasn’t built that way. In this case you’ll have to “grant” the user the ability to use the new voice policy, as shown in the following example:

     

    Grant-CsVoicePolicy –Identity JUser –PolicyName ChicagoVoicePolicy

     

    You can also pipe the results of Get-CsUser to the Grant-CsVoicePolicy cmdlet. In any event this will probably save you some time if you’ve got to reset the voice policy on a number of users through a script.

     

    See more Snippets from the Shell

     

  • Assign Active Directory Phone Numbers to Line URIs

    To see a full explanation of how these scripts work, see the article Active Directory Phone Numbers and Line URIs: Together at Last! http://blogs.technet.com/b/csps/archive/2011/02/01/howtoadphonetolineuri.aspx

     

    Convert a Phone Number to a Line URI and Assign to User

     

    This script converts a phone number in a format similar to the following to a Line URI and assigns the Line URI to the user:

     

    1-(206)-555-1219

    1-206-555-1219

    1.206.555.1219

    1(206)555-1219

     

    $enabledUsers = Get-CsAdUser -Filter {Enabled -ne $Null}

     

    foreach ($user in $enabledUsers)

        {

            $phoneNumber = $user.Phone

            $phoneNumber = $phoneNumber -replace "[^0-9]"

            $phonenumber = "TEL:+" + $phoneNumber

            Set-CsUser -Identity $user.Identity -LineUri $phoneNumber

        }

     

    Convert an Incomplete Phone Number to a Line URI

     

    The following script will convert a number a partial phone number to a line URI, modifying the area code based on prefix. In this script, if the extension starts with the number 5 then the area code is assumed to be 206. If the extension starts with the number 6 then the area code is 425.

     

    $enabledUsers = Get-CsAdUser -Filter {Enabled -ne $Null}

     

    foreach ($user in $enabledUsers)

        {

            $phoneNumber = $user.Phone

            $phoneNumber = $phoneNumber -replace "[^0-9]"

            $extension = $phoneNumber -match "^5"

            if ($extension)

                {

                    $phoneNumber = "TEL:+120655" + $phoneNumber

                }

            else

                {

                    $phoneNumber = "TEL:+142556" + $phoneNumber

                }

          

            Set-CsUser -Identity $user.Identity -LineUri $phoneNumber

        }

    Convert a Phone Number with an Extension to a Line URI

     

    This script will convert a phone number with an extension, in the format:

     

    1-206-555-1111 x1219

     

    to a valid line URI with the same extension:

     

    TEL:+12065551111;ext=1219

     

    The script will assign the line URI to the user with that phone number.

     

    $enabledUsers = Get-CsAdUser -Filter {Enabled -ne $Null}

     

    foreach ($user in $enabledUsers)

        {

            $phoneNumber = $user.Phone

            $phoneNumber = $phoneNumber -replace "[^0-9]"

            $extension = $phoneNumber -match "^5"

            if ($extension)

                {

                    $phoneNumber = "TEL:+120655" + $phoneNumber

                }

            else

                {

                    $phoneNumber = "TEL:+142556" + $phoneNumber

                }

          

            Set-CsUser -Identity $user.Identity -LineUri $phoneNumber

        }

     

  • How to Undo That Thing You Now Wish You Had Never Done in the First Place

    Although we can't guarantee this, we can pretty much guarantee this: once you've switched to Microsoft Lync Server 2010 you'll never have nightmares again. Or, at the very least, you'll never have nightmares about Microsoft Lync Server again. Ever.

     

    Note. A strong statement? Perhaps. On the other hand, Sigmund Freud is universally acknowledged as history's foremost authority on dream studies and dream interpretation. Before writing this article, we carefully read everything Freud ever wrote. Do you know how many references to Lync Server nightmares we found in the collected works of Sigmund Freud? That's right: zero.

     

    Of course, even as you're sitting back reading this article you can probably think of at least one or two potential Lync Server nightmare scenarios. For example, consider this. Your organization has 105 users who have been enabled for Lync Server. In turn, those users have been assigned one of 3 different client policies; the breakdown of client policy assignments looks something like this:

     

    Policy

    No. of Users Assigned This Policy

    ClientPolicy1

    21

    ClientPolicy2

    13

    ClientPolicy3

    71

     

    Note. What's that? You say you'd like to able to generate a table similar to this? No problem:

     

    Get-CsUser | Group-Object ClientPolicy | Select-Object Name Count | Format-Table -Autosize

     

    But we digress. We also need to note that there's a reason why a user has been assigned a particular policy. Actually, there could be a lot of reasons why a user has been assigned a particular policy. Maybe he or she belongs to a specified security group, maybe he or she has a specific job title, maybe her or she works in a specified department and in a specific city. For our purposes, it doesn't really matter why a user has been assigned a specific policy. What does matter is that the whole thing is complicated, and you can't simply assume that all the users in a given department have the same client policy. It just doesn't work that way.

     

    With us so far? OK, let's now assume that there's been a change in management, and your new manager has decided that it's silly to have all these different client policies: he wants one policy to be assigned to all users.

     

    Note. Yes, typically we would say "he or she wants …." For some reason, though, any time a crazy decision like this gets made we assume it must be a he and not a she.

     

    So is the nightmare scenario the fact that you have to assign the same client policy to all your Lync Server-enabled users? Nope; that's not a nightmare scenario, not by a long shot. In fact, all that takes is one little PowerShell command:

     

    Get-CsUser | Grant-CsClientPolicy –PolicyName "LitwareincClientPolicy"

     

    Instead, here's the nightmare scenario: no sooner do you make this change then your help desk is deluged with complaints. As it turns out, one policy doesn't work in your organization: different users need different capabilities, and now, by taking those capabilities away, you're preventing people from being able to do their jobs. At that point, your manager turns to you and says, "Well, there's only one thing we can do now: you'll just have to put things back the way they used to be. You need to reassign each person the client policy they had before we assigned them all the LitwareincClientPolicy policy."

     

    So is that going to be a problem? No; that's going to be a nightmare. For better or worse, Lync Server doesn't have any sort of undo feature when it comes to most administrative tasks; for example, there's no way to undo the bulk assignment of policies. Furthermore, Lync Server doesn't keep a history of any kind: you can' just thumb through a list of all the client policies that have ever been assigned to a user and then restore one of those policies. Granted, if you've backed up Active Directory (which is where user data is stored) you could theoretically restore the overwritten data; however, in that case all the other changes made to Active Directory since the time you assigned the LitwareincClientPolicy policy would be lost.

     

    Uh-oh.

     

    So doesn't this mean that you should start having nightmares about Microsoft Lync Server 2010? Believe it or not, the answer is no. Admittedly, Lync Server doesn't have a built-in undo function for administrative tasks. However, if you're willing to write a couple of simple Windows PowerShell scripts, you can create your own undo function, at least for many of the things you'll do in Lync Server.

     

    Like, say, bulk assignment of client policies. In the scenario we just described, the problem isn't that we did bulk assignment of client policies; instead, the problem is that, if anything went wrong with that bulk assignment there was no easy way to put things back the way they used to be. Why is there no easy way to put things back the way they used to be? You got it: because there's no easy way of knowing how things used to be. That's why we decided to assign client policies using the following script:

     

    $backupData = (Get-CsUser | Select-Object Identity,ClientPolicy)

    $backupData | Export-Csv –Path "C:\Backup\ClientPolicies.csv" –NoTypeInformation

     

    foreach ($user in $backupData)

        {Grant-CsClientPolicy –Identity $user.Identity –PolicyName "LitwareincClientPolicy"

        }

     

    Nice, huh? Except for one thing: what does this script actually do? Well, in the first line, we use the Get-CsUser cmdlet to retrieve information about all the users who have been enabled for Lync Server. We then pipe that data to the Select-Object cmdlet, which grabs the values for just the Identity and the ClientPolicy properties. (Why just those two property values? Because those are the only two property values we care about.) That filtered data – Identity and ClientPolicy – is then stored in a variable named $backupData.

     

    In the second line, we then take the variable $backupData and pipe it to the Export-Csv cmdlet, which creates a comma-separated values file at C:\Backup\ClientPolicies.csv. That file is going to look something like this:

     

    "Identity","ClientPolicy"

    "CN=adelaney,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy1"

    "CN=aglagdkikh,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy3"

    "CN=aolecka,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy2"

     

    Etc., etc. In other words, it's the Identity of each of our Lync Enabled-users along with the name of the client policy that's currently assigned to them.

     

    Note. Good question: what is the NoTypeInformation parameter for? Well, without it, Export-Csv would give us a text file that looked like this:

     

    #TYPE System.Management.Automation.PSCustomObject

    "Identity","ClientPolicy"

    "CN=adelaney,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy1"

    "CN=aglagdkikh,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy3"

    "CN=aolecka,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy2"

     

    We don't have any use for that first line of type-related data, so we tacked on the NoTypeInformation parameter to prevent Export-Csv from saving that line of data in the first place.

     

    When Export-Csv finishes running, we'll have a text file that contains the names of all our users and the name of the client policy (if any) that was assigned to each one: that is, a snapshot of our client policy picture before we did the bulk update. And speaking of the bulk update, that's what we do in the final block of code:

     

    foreach ($user in $backupData)

        {Grant-CsClientPolicy –Identity $user.Identity –PolicyName "LitwareincClientPolicy"

        }

     

    Nothing fancy there: we've just set up a foreach loop to loop through each user account stored in $backupData and assign the client policy LitwareincClientPolicy to each account. As soon as we're done with that our client policy breakdown will look like this:

     

    Policy

    No. of Users Assigned This Policy

    LitwareincClientPolicy

    105

     

    Where are our other per-user client policies, like ClientPolicy1, ClientPolicy2, and ClientPolicy3? That's easy: they don't show up in the report because they are no longer assigned to anyone. Per our marching orders, all of our users have been assigned LitwareincClientPolicy instead.

     

    OK, so suppose that was a mistake, suppose we now wish we hadn't assigned everyone the same client policy after all? That's OK; it's definitely nothing to lose sleep over. Don't forget that, before we did our bulk update, we took a snapshot of the way things used to be. Assuming we didn't throw away ClientPolicies.csv (hint: don't throw away ClientPolicies.csv) we can undo our bulk policy assignment simply by running this script:

     

    $backupData = Import-Csv –Path "C:\Backup\ClientPolicies.csv"

     

    foreach ($user in $backupData)

        {Grant-CsClientPolicy –Identity $user.Identity –PolicyName $user.ClientPolicy

        }

     

    So what exactly does this script do? Well, in the first line it uses the Import-Csv cmdlet to read in all that data we stored in ClientPolicies.csv. After the data has been retrieved (and stored in our old friend, the variable $backupData) we then set up a foreach loop to iterate through all that data. Inside the loop we do one thing and one thing only: we assign each user the client policy that he or she used to have assigned to them. That's what this line of code does:

     

    Grant-CsClientPolicy –Identity $user.Identity –PolicyName $user.ClientPolicy

     

    If you're wondering how this works, $user.Identity represents the Identity of the user account and $user.ClientPolicy represents the client policy that this user used to have assigned to them. For example, suppose the first line of user account data in ClientPolicies.csv looks like this:

     

    "CN=adelaney,OU=Redmond,DC=litwareinc,DC=com","ClientPolicy1"

     

    In that case, $user.Identity will be equal to CN=adelaney,OU=Redmond,DC=litwareinc,DC=com and $user.ClientPolicy will be equal to ClientPolicy1. Which also means that the user adelaney is going to be reassigned ClientPolicy1. That's because we'll actually be running this line of code:

     

    Grant-CsClientPolicy –Identity "CN=adelaney,OU=Redmond,DC=litwareinc,DC=com" –PolicyName "ClientPolicy1"

     

    As soon as the script finishes, everything will be as it once was:

     

    Policy

    No. of Users Assigned This Policy

    ClientPolicy1

    21

    ClientPolicy2

    13

    ClientPolicy3

    71

     

    Pleasant dreams, eh?

     

    Of course, maybe you don't need to reassign client policies for all of your users; maybe the only people who can't use LitwareincClientPolicy are those people who used to be assigned ClientPolicy1. That's fine; it's easy enough to modify the script so it only reassigns policies to a subset of users (that is, users who used to be assigned ClientPolicy1):

     

    $backupData = Import-Csv –Path "C:\Backup\ClientPolicies.csv"

     

    foreach ($user in $backupData)

        {if ($user.ClientPolicy –eq "ClientPolicy1")

            {Grant-CsClientPolicy –Identity $user.Identity –PolicyName $user.ClientPolicy}

          }

     

    In this modified script, the first thing we do inside the loop is check to see if our user used to be assigned the client policy ClientPolicy1:

     

    if ($user.ClientPolicy –eq "ClientPolicy1")

     

    If that turns out to be true, then we reassign them their old policy. If it's not true (for example, if they used to be assigned ClientPolicy2) then we don't do anything at all.

     

    Pretty cool, huh?

     

    So does this mean you now have a sure-fire way to undo anything you might do in Lync Server? No. Instead, what you have is a way to undo a very specific task: bulk assignment of client policies (something which you might never do anyway). On the other hand, you also have an idea of how you can use a simple script to provide a little peace-of-mind before you undertake a major operation; you could easily modify this script to back up other types of policy data, to back up user information data, to back up client version policy rules, etc., etc. In other words, not only will this approach prevent nightmares, but it might also inspire you to dream up new ways to backup and restore Lync Server information. After all, as George Carlin once said:

     

    "Some people see things that are and ask, 'Why?' Some people dream of things that never were and ask, 'Why not?' Some people have to go to work and don't have time for all that."

     

    Which reminds us: we need to get back to work right now. If you have questions about these scripts, or how you might be able to modify them to fit your own needs, just email us: cspshell@microsoft.com