• Quick Start: Managing Microsoft Lync Server 2010 Using Remote PowerShell

    Most of the articles found on this site assume that you are working on a computer running a Microsoft Lync Server 2010 service or server role, and that the Lync Server Management Shell has been installed on that computer. We did that for two reasons: 1) That’s the easiest way to do things; and, 2) It lets us put the focus on the task at hand (e.g., creating a new voice policy) rather than the preliminary commands required to manage Lync Server from a computer where the Management Shell has not been installed.

     

    But while that might make our lives easier, it doesn’t necessarily make your lives easier. After all, you might want to do at least some of your management tasks remotely; for example, you might want to configure your Address Book servers or enable a few user accounts while sitting at your desk rather than at a Lync Server front end machine. Fortunately, that’s very possible to do; unfortunately, it’s not immediately obvious how you would go about doing that.

     

    But that’s OK; that’s why we decided to put together this Quick Start guide to remote management of Microsoft Lync Server 2010.

     

    Step 1: Make Sure You’re Running Windows PowerShell 2.0

     

    What happens if you’re not running Windows PowerShell 2.0? Nothing – literally. You must have Windows PowerShell 2.0 or you will never, ever be able to use PowerShell to manage Lync Server. If you aren’t sure what version of PowerShell you’re running then start up Windows PowerShell and type this at the PowerShell prompt:

     

    Get-Host

     

    In turn you should get back something similar to this:

     

    Name             : ConsoleHost

    Version          : 2.0

    InstanceId       : b4c50031-fcae-4d6e-ab0b-200b6beecf9f

    UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface

    CurrentCulture   : en-US

    CurrentUICulture : en-US

    PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy

    IsRunspacePushed : False

    Runspace         : System.Management.Automation.Runspaces.LocalRunspace

     

    If the Version property isn’t equal to 2.0 then you’re not running PowerShell 2.0.

     

    But hey, don’t cry; after all, you can always download and install the latest and greatest version of Windows PowerShell from here: http://support.microsoft.com/kb/968929.

     

    As soon as you’re confident that you’re running PowerShell 2.0 you can move on to Step 2.

     

    And no, no rush; take your time. We’ll wait.

     

    Step 2: Create a Windows PowerShell Credentials Object

     

    OK, admittedly, this step might be optional: it depends on the user account you used when you logged on to Windows in the first place. For this Quick Start guide, however, we’re going to assume that the account you used to log on to Windows isn’t the same account you use to manage Lync Server. (That’s a security best practice, and we know that everyone always follows our security best practices.) Regardless, we need to create a credentials object (a very secure credentials object, we might add) that contains our user name and password. To do that, first type the following at the command prompt, substituting your domain name for litwareinc and your logon name for kenmyer (make sure you’re using the domain name and logon name for your Lync Server administrator account):

     

    $credential = Get-Credential "litwareinc\kenmyer"

     

    That should bring up a credentials dialog box that looks a little something like this:

     

     

    Type your password in the Password box and then press ENTER. When you do so, the dialog box will disappear and you should see something like this:

     

    UserName                                               Password

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

    litwareinc\kenmyer                  System.Security.SecureString

     

    If you do, then you’ve successfully created the credentials object. But save your self-congratulations for later; it’s time for Step 3. If you don’t see something like this type $credential at the PowerShell prompt and then press ENTER. If you still don’t see something like this then you’ll need to go back and try Step 2 from the beginning.

     

    Step 3: Creating a Remote PowerShell Session

     

    Now the fun part begins: it’s time to make a connection to one of your front end Lync Server machines (in this case, atl-cs-001.litwareinc.com). How do you do that? Why, by running the following PowerShell command, of course:

     

    $session = New-PSSession -ConnectionUri "https://atl-cs-001.litwareinc.com/OcsPowershell" -Credential $credential

     

    When you run this command, it might not look like anything happened. To verify that something did happen (and that you were able to make a connection to atl-cs-001) type $session at the Windows PowerShell prompt and then press ENTER. If all went well you should see something similar to this:

     

    Id  Name      ComputerName  State   ConfigurationName    Availability

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

     1  Session 1 atl-cs-00...  Opened  Microsoft.PowerShell    Available

     

    OK, so now are we ready to start managing Lync Server remotely? Well, almost. First, however, we have to import our new PowerShell session; that’s because we still don’t have access to all the Communications Server cmdlets. To verify that, type the following at the Windows PowerShell prompt and then press ENTER:

     

    Get-CsVoicePolicy

     

    You should get back an error message stating that:

     

    The term ‘Get-CsVoicePolicy’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

     

    So should you check the spelling and try again? No. Instead, move on to Step 4.

     

    Step 4: Importing the Remote PowerShell Session

     

    When you import a PowerShell session you will finally have access to all the Lync Server cmdlets. (Well, maybe not all of them; we’ll explain that in a moment.) To import your new session, just type the following at the Windows PowerShell prompt and then press ENTER:

     

    Import-PsSession $session

     

    There will likely be a momentary pause while PowerShell retrieves all the relevant cmdlets, functions, scripts, and other Communications Server-paraphernalia, and then, at long last, you should see something like this onscreen:

     

    ModuleType   Name                       ExportedCommands

    Script       tmp_77b73956-53ca-4e7a...  {New-CsNetworkInterSitePol...}

     

    And now if you type Get-CsVoicePolicy you should get back, well, you should get back information about all your voice policies.

     

    Well, assuming you have permission to run Get-CsVoicePolicy in the first place. It’s important to keep in mind that Lync Server enforces RBAC (Role-Based Access Control) on all remote management sessions. What does that mean? Well, in Lync Server, the administrative tasks you are allowed to carry out are based on the RBAC roles that have been assigned to you; in turn, each RBAC role is assigned a number of Lync Server cmdlets. For example, suppose you’ve been assigned a hypothetical RBAC role that gives you access to only four cmdlets:

     

    ·         Disable-CsUser

    ·         Enable-CsUser

    ·         Get-CsUser

    ·         Set-CsUser

     

    What do you suppose is going to happen if you try to run the command Get-CsVoicePolicy? You got it: you’re going to get back this same error message:

     

    The term 'Get-CsVoicePolicy' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

     

    Why that error message? Here’s why: When you import your PowerShell session RBAC ensures that the only Lync Server cmdlets that get imported are the ones that have been assigned to your RBAC role. If you want to see all the Lync Server cmdlets you have access to during your remote session, type this command at the PowerShell prompt and then press ENTER:

     

    Get-Command *-Cs* -CommandType Function

     

    Note. Yes, that command does return functions rather than cmdlets. But that’s because PowerShell imports cmdlets as functions rather than as cmdlets.

     

    If your RBAC role limits you to four cmdlets then Get-Command is only going to return the following items:

     

    Disable-CsUser

    Enable-CsUser

    Get-CsUser

    Set-CsUser

     

    As you can see, the Get-CsVoicePolicy cmdlet was never imported. That’s why PowerShell says it has no idea what you’re talking about when you try to run Get-CsVoicePolicy.

     

    Note: To learn more about RBAC in Lync Server, read A Brief Introduction to Role-Based Access Control – Part 1.

     

    Incidentally, even if you’re the Grand Pooh-Bah of Lync Server you won’t have access to all the Lync Server cmdlets when working remotely. That’s because a handful of cmdlets -- such as Test-CsComputer – can only be run locally; they will not work in a remote session. Fortunately, Lync Server is smart enough not to hand over those cmdlets when you import the PowerShell session. You might not have all the cmdlets available to you in a remote session, but you can rest assured that all the cmdlets that are available to you will work as expected.

     

    Note. Of course, that doesn’t mean that you won’t occasionally run into things that don’t work as expected. For example type this at the Windows PowerShell prompt and then press TAB:

     

    Get-CsV

     

    PowerShell should respond by invoking tab expansion and changing your command to this:

     

    Get-CsVoiceConfiguration

     

    OK, now type this at the PowerShell prompt and then press TAB:

     

    Get-Help Get-CsV

     

    You might expect that PowerShell would invoke tab expansion and change your command to this:

     

    Get-Help Get-CsVoiceConfiguration

     

    For better or worse, however, it won’t expand Get-CsV, at least not in this case. Why not? Well, when you invoke TAB expansion when using Get-Help PowerShell looks for a cmdlet that starts with Get-CsV. However, we don’t have any cmdlets that start with Get-CsV; remember, all our Lync Server cmdlets were imported as functions. As a result, tab expansion won’t work with Get-Help. If you want more information about Get-CsVoiceConfiguration you’ll just have to type in the entire command:

     

    Get-Help Get-CsVoiceConfiguration

     

    Step 5: Ending Your Remote Windows PowerShell Session

     

    What? All that work and now you want to quit? OK, fine; all good things must come to an end, right? If you want to end your remote management session just type the following at the Windows PowerShell prompt and then press ENTER:

     

    Remove-PsSession $session

     

    That’s all you have to do.

     

    Step 6: What Comes Next?

     

    As you might expect, there are plenty of nuances we haven’t covered yet; we’ll have to get to those at a later date. In the meantime, though, this should get you started. Which, needless to say, is about all you can ask from a Quick Start guide.

  • Listing All the Values in a Multi-Valued Property

    So you’re interested in taking a peek at the global file transfer filter configuration settings used in your organization; to be a little more specific, you’d like to see all the file types (as determined by file extension) that users are not allowed to transfer to one another using Microsoft Lync. As it turns out, there’s a cmdlet – Get-CsFileTransferFilterConfiguration – designed to return that very information; all you have to do is type the following at the Windows PowerShell prompt and then press ENTER:

     

    Get-CsFileTransferFilterConfiguration –Identity global

     

    When you do that, you’ll see something like this:

     

    Identity   : Global
    Extensions : {.ade, .adp, .app, .asp...}
    Enabled    : True
    Action     : Block

     

    Well, the command worked … we think. To be honest, though, we sort of expected to get back more than just four file extensions; after all, by default there are scores of file extensions blocked by Microsoft Lync Server 2010. And what’s the deal with the Extensions property; what’s with the curly braces and the three dots at the end?

     

    Well, here’s the deal with the curly braces and the three dots at the end. First of all, the curly braces are PowerShell’s way of telling us that we’re dealing with a multi-valued property: a property that can contain more than one value. That should be fairly obvious just from looking at the Extensions property; after all, we can see that this property contains at least four values: .ade; .adp; .app; .asp. And if four values aren’t multiple values, well, we don’t know what is.

     

    Ah, good catch: why did we say that the property contains “at least” four values? That’s where the three dots come into play. Any time you’re viewing PowerShell output and you see three dots like that you can bet dollars to doughnuts that there are actually more values stashed in that particular property; they just wouldn’t all fit onscreen.

     

    Note. The phrase “dollars to doughnuts” means that you are 100% positive that something is true. Allegedly, the term came about from bettors who were so confident they had backed the right horse that they were willing to bet something valuable (dollars) against something totally worthless (doughnuts). To be honest, though, we don’t buy that story: who in their right mind would call a doughnut worthless?

    OK, maybe those lemon-filled ones. But other than that ….

     

    As it turns out, the default global file transfer filter configuration settings include more than 70 file extensions:

     

    .ade
    .adp
    .app
    .asp
    .bas
    .bat
    .cer
    .chm

     

    Etc., etc. And that’s great. But how exactly do we see that complete list of file extensions?

     

    Here’s one way:

     

    Get-CsFileTransferFilterConfiguration –Identity global | Select-Object –ExpandProperty Extensions

     

    What we’ve done here is retrieve all the file transfer filter configuration settings information and then piped that information to the Select-Object cmdlet. From there we used the –ExpandProperty parameter to “expand” the values found in the Extensions property. When you expand a property you simply show all the values stored in that property. In other words, the preceding command is going to give us output that looks like this:

     

    .ade
    .adp
    .app
    .asp
    .bas
    .bat
    .cer
    .chm

     

    Which is exactly what we had in mind in the first place.

     

    Now, with file transfer filter configuration settings, our multi-valued property (Extensions) stores a bunch of values (in this case, a bunch of string values). In other cases, however, a multi-valued property might store a whole bunch of objects. For example, suppose you run the cmdlet Get-CsTopology. One of the properties that gets returned is the Machines property; this property happens to store a bunch of computer objects:

     

    Machines    : {Redmond:1-1, Redmond:2-1...}

     

    So is this something you should worry about? Nope (well, not unless you like worrying). Instead, you should just sit back and let PowerShell worry about it for you. As it turns out, any time you have a multi-valued property that stores a bunch of objects PowerShell will automatically show all the property values of all those objects. For example, run this command from the Windows PowerShell prompt:

     

    Get-CsTopology | Select-Object –ExpandProperty Machines

     

    You should get back something that looks like this:

     

    MachineId         : Redmond:1-1
    Cluster           : Redmond:1
    Fqdn              : atl-cs-001.litwareinc.com
    PrimaryMacAddress : 71:43:48:99:35:ca
    Topology          : Microsoft.Rtc.Management.Deploy.Internal.DefaultTopology
    MachineId         : Redmond:2-1
    Cluster           : Redmond:1
    Fqdn              : atl-cs-001.litwareinc.com
    PrimaryMacAddress :
    45:29:38:41:15:ea
    Topology          : Microsoft.Rtc.Management.Deploy.Internal.DefaultTopology

     

    Not bad, eh?

  • Move or Enable Multiple User Accounts

    Submitted by Scott Stubberfield and Nick Smith, Microsoft

     

    Windows PowerShell makes it easy for you to enable a new user for Microsoft Lync Server 2010, and makes it just as easy for you to move a single user account from one Registrar pool to another. But what if you need to perform these user management tasks on a whole bunch of users, some of whom need to be enabled for Lync Server and some of whom need to have their accounts moved to a different Registrar pool? What do you do then?

     

    Scott Stubberfield and Nick Smith have come up with one solution: you simply put all this information in a text file and then use a PowerShell script to read that text file and then take the appropriate action. The script they put together reads a comma-separated values file (a file containing user information), loops through the collection of users listed on that file and then does one of two things:

     

    ·         If the MoveOrEnable field for a user is marked as Move, the script uses the Move-CsUser cmdlet to move the user account from its current Registrar pool to a new Registrar pool (the Target field).

    ·         If the MoveOrEnable field for a user is marked as Enable, the script uses the Enable-CsUser cmdlet to enable the user account for Lync Server. The script assigns the user to the Registrar pool indicated by the Target field, and gives the user the SIP address listed in the SipUri field.

     

    For example, suppose the first user listed in the .CSV file has these property values:

     

    SipUri

    sip:seeuser11@p10.ca

    MoveOrEnable

    Enable

    Target

    cspool.p10.ca

    UPN

    seeuser11@p10.local

     

    Because MoveOrEnable is set to Enable, the script will enable the user for Lync Server, and will do so using the following command:

     

    Enable-CsUser –Identity "seeuser11@p10.local" –RegistrarPool "cspool.p10.ca" –SipAddress "sip:seeuser11@p10.ca"

     

    Nice, huh? And the best part is this: the script will take care of everything whether you have one user listed in the .CSV file or 100,000 users listed in the .CSV file.

     

    Here’s what the code looks like:

     

    param( [string] $importfile = $(Read-Host -prompt `
        "Please enter a file name"))

    $importedusers = Import-CSV $importfile

     

    $transcriptname = "MoveorEnableUsers" + `
        (Get-Date -format s).Replace(":","-") +".txt"

    Start-Transcript $transcriptname

     

    foreach ($importeduser in $importedusers)

        {

            if ($importeduser.MoveorEnable -eq "Move")

                {

                    Move-CsUser $importeduser.SipUri -target `
                       $importeduser.Target -verbose

                }

            else

        {

            Enable-CsUser $importeduser.UPN -SipAddress `
                $importeduser.SipUri -RegistrarPool `
                $importeduser.Target -Verbose

        }

     

     

        }

    Stop-Transcript

     

    To make use of this script, copy the code to Notepad (or any other text editor), and then save the file using a .PS1 file extension (for example, C:\Scripts\MoveOrEnableUsers.ps1). In addition, you must create a .CSV file similar to this, and save that file to your hard drive as well:

     

    SipUri,MoveorEnable,Target,UPN

    sip:seeuser11@p10.ca,Enable,"cspool.p10.ca",seeuser11@p10.local

    sip:seeuser12@p10.ca,Move,"cspool.p10.ca",seeuser12@p10.local

    sip:seeuser13@p10.ca,Enable,"cspool.p10.ca",seeuser13@p10.local

    sip:seeuser14@p10.ca,Enable,"cspool.p10.ca",seeuser14@p10.local

    sip:seeuser15@p10.ca,Enable,"cspool.p10.ca",seeuser15@p10.local

    sip:seeuser16@p10.ca,Enable,"cspool.p10.ca",seeuser16@p10.local

    sip:seeuser17@p10.ca,Enable,"cspool.p10.ca",seeuser17@p10.local

    sip:seeuser18@p10.ca,Move,"cspool.p10.ca",seeuser18@p10.local

    sip:seeuser19@p10.ca,Enable,"cspool.p10.ca",seeuser19@p10.local

     

    To run the script from within the Lync Server Management Shell, just type the full path to the .PS1 file and then press ENTER:

     

    C:\Scripts\MoveOrEnableUsers.ps1

     

    When the script starts, it will prompt you to enter the location of the .CSV file. Type in the file path (e.g., C:\Scripts\Users.csv) and press ENTER; in turn, the script will read the data from the .CSV and, as requested, either move or enable each user listed in the file.

     

    As an added bonus, the script also maintains a detailed log of everything it does. That log will be stored in the current working directory, and will have a file name based on the current date and time. For example:

     

    MoveorEnableUsers2010-06-10T09-33-03.txt

  • List Connections to Registrar Pools

    Submitted by Scott Stubberfield and Nick Smith, Microsoft

     

    So just who is connected to your Registrar pool, and how many people are connected to Pool A vs. Pool B? Don’t ask us; we have no idea whatsoever. Instead, you should ask Scott Stubberfield and Nick Smith, who’ve written a script that can contact your front-end pools and return this distribution information. Their script connects to the backend Microsoft Lync Server 2010 databases and retrieves information about the endpoints currently connected to two different Registrar pools. The retrieved information, sorted by client version and by user name, is then saved to a comma-separated values file named PoolDistribution.csv.

     

    Here’s the code:

     

    #Defined Connection String

    $connstring = "server=p10-CSFE01\rtclocal;database=rtcdyn;`
        trusted_connection=true;"

    $connstring2 = "server=p10-CSFE02\rtclocal;database=rtcdyn; `
        trusted_connection=true;"

     

    #Define SQL Command

     

    $command = New-Object System.Data.SqlClient.SqlCommand

     

    $command.CommandText = "Select (cast (RE.ClientApp as `
        varchar (100))) as ClientVersion, `

        R.UserAtHost as UserName, `

        Reg.Fqdn `

        From `

        rtcdyn.dbo.RegistrarEndpoint RE `

        Inner Join `

        rtc.dbo.Resource R on R.ResourceId = RE.OwnerId `

        Inner Join `

        rtcdyn.dbo.Registrar Reg on Reg.RegistrarId =   `
        RE.PrimaryRegistrarClusterId `

        Order By ClientVersion, UserName "

     

    #Make the connection to Server 1

     

    $connection = New-Object System.Data.SqlClient.SqlConnection

    $connection.ConnectionString = $connstring

    $connection.Open()

     

    $command.Connection = $connection

     

     

    $sqladapter = New-Object System.Data.SqlClient.SqlDataAdapter

    $sqladapter.SelectCommand = $command

     

    $results = New-Object System.Data.Dataset

     

    $recordcount=$sqladapter.Fill($results)

     

    $connection.Close()

     

    $overallrecords = $overallrecords + $Results.Tables[0]

     

    #Make the connection to Server 2

     

    $connection.ConnectionString = $connstring2

    $connection.Open()

     

    $command.Connection = $connection

     

     

    $results = New-Object System.Data.Dataset

    $recordcount=$sqladapter.Fill($results)

     

    $connection.Close()

     

    $overallrecords = $overallrecords + $Results.Tables[0]

     

    #End Section 2

     

    $overallrecords | Export-Csv "PoolDistribution.csv"

    Write-Host -ForegroundColor Green "Query complete"

     

    To use this script, copy the code shown above, paste it into a text editor (like Notepad) and then save the file with a .PS1 extension (for example, C:\Scripts\PoolDistribution.ps1). After that, you can run the script from within the Lync Server Management Shell simply by typing the full path to the .PS1 file and then pressing ENTER:

     

    C:\Scripts\PoolDistribution.ps1

     

    As written, the script is designed to connect to two different Registrar pools: p10-CSFE01 and p10-CSFE02. If you only want to connect to one Registrar pool, remove one of the pool names listed at the beginning of the script, and remove (or comment out) the following block of code:

     

    #Make the connection to Server 2

     

    $connection.ConnectionString = $connstring2

    $connection.Open()

     

    $command.Connection = $connection

     

     

    $results = New-Object System.Data.Dataset

    $recordcount=$sqladapter.Fill($results)

     

    $connection.Close()

     

    $overallrecords = $overallrecords + $Results.Tables[0]

     

    #End Section 2

     

    To connect to additional Registrar pools you must do two things. First, add the new pools to the set of pools listed at the beginning of the script. For example, if you need to add the pool p10-CSFE03 the first part of your script should look similar to this:

     

    #Defined Connection String

    $connstring = "server=p10-CSFE01\rtclocal;database=rtcdyn;`
        trusted_connection=true;"

    $connstring2 = "server=p10-CSFE02\rtclocal;database=rtcdyn; `
        trusted_connection=true;"

    $connstring3 = "server=p10-CSFE03\rtclocal;database=rtcdyn; `
        trusted_connection=true;"

     

    Note the variable name: $connstring3.

     

    Next, copy the following block of code, changing the comments so they refer to Server/Section 3, and using the variable $connstring3 instead of $connstring2. In other words:

     

    #Make the connection to Server 3

     

    $connection.ConnectionString = $connstring3

    $connection.Open()

     

    $command.Connection = $connection

     

     

    $results = New-Object System.Data.Dataset

    $recordcount=$sqladapter.Fill($results)

     

    $connection.Close()

     

    $overallrecords = $overallrecords + $Results.Tables[0]

     

    #End Section 3

     

    To add additional pools just repeat those steps as needed.

  • List All the Microsoft Lync Server 2010 Cmdlets and the RBAC Roles Those Cmdlets Have Been Assigned To

    Submitted by Cezar Ungureanasu, Microsoft

     

    Cezar Ungureanasu is a Program Manager (make that the Program Manager) for the Microsoft Lync Server 2010 implementation of Windows PowerShell. Not too long ago, Cezar asked us if we could write a script that could take all the Lync Server cmdlets and determine which Role-Based Access Control (RBAC) roles those cmdlets were assigned to. Cezar posed the question using Microsoft Lync; seconds later he sent another instant message that said, “Never mind; I’ve got it figured out.” As a result, we didn’t have to do a thing.

     

    Needless to say, we wish there were more Cezars around here.

     

    At any rate, Cezar created a script that retrieves all the Lync Server cmdlets, grabs all the RBAC roles, and then methodically checks to see which roles each of those cmdlets have been assigned to. After doing all these checks the script then creates a tab-separated values file that looks something like this when opened in Excel:

     

      

    That looked like a pretty useful little script to us, so we asked Cezar if he’d be willing to share his code with the rest of the world. What did he say when we asked him about that? This:

     

    $roles = Get-CsAdminRole  | where-object { $_.IsStandardRole -eq $true} | Sort Identity

    $d = "Cmdlet"

    foreach($role in $roles)

        {

            $d = $d + "`t" + $role.Identity

        }

     

    Out-File -FilePath "C:\cmdlettorole.tsv" -InputObject $d

     

    $x = Get-Command -module Lync -commandType cmdlet | Sort Name

     

    foreach($i in $x)

        {

            $a = $i.Name

            $c = $a +"`t"

            foreach($role in $roles)

                {

                    if ($role.cmdlets -match $i.Name)

                        {

                            $c = $c + "yes" + "`t"

                        }

                    else

                        {

                            $c = $c + "no" + "`t"

                        }

                 }

            Out-File -FilePath C:\cmdlettorole.tsv -InputObject $c -Append

        }

     

    Nice, huh? Best of all, the script is amazingly easy to run. Just copy the code and paste it into your favorite text editor. Save the file with a .ps1 file extension; for example C:\Scripts\CmdletsAndRoles.ps1. And then just run that script from within the Lync Server Management Shell:

     

    C:\Scripts\CmdletsAndRoles.ps1

     

    Like we said: amazingly easy.