• PowerShell Overview: Format-Table vs. Select-Object

    Can you use the Format-Table cmdlet instead of the Select-Object cmdlet?

     

    Depending on what you're doing, yes, you can. In the lab we included an exercise where we had people retrieve user accounts and then display just the values of the DisplayName and Enabled attributes. To do that, we had them pipe the data to the Select-Object cmdlet, like so:

     

    Get-CsAdUser | Select-Object DisplayName, Enabled

     

    That results in output similar to this:

     

    DisplayName                           Enabled

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

    Ken Myer                              True

    Pilar Ackerman                        False

    David Jaffe                           True

    Aneta Olecka                          True

     

    Based on that. the question we were asked is this: instead of piping data to Select-Object, could we pipe it to Format-Table instead? You know, using a command like this one:

     

    Get-CsAdUser | Format-Table DisplayName, Enabled

     

    Well, let's see:

     

    DisplayName                           Enabled

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

    Ken Myer                              True

    Pilar Ackerman                        False

    David Jaffe                           True

    Aneta Olecka                          True

     

    Looks like we can, doesn't it?

     

    In fact, by using the Format-Table cmdlet we can not only get the same output as we can by using Select-Object, but we can actually get even better (i.e., more aesthetically-pleasing) output. By default, any time you display two columns of data in Windows PowerShell the software tends to show one column at the far left of the screen and the other column at the far right, leaving a big giant hole in the middle. If you use Select-Object to determine the properties to be displayed onscreen there isn't much you can do about that; Select-Object doesn't offer many formatting options. With Format-Table, however, you can add the AutoSize parameter, which tells PowerShell to make each column just wide enough to display the data in that column. Here's the command:

     

    Get-CsAdUser | Format-Table DisplayName, Enabled -AutoSize

     

    And here's the output:

     

    DisplayName       Enabled

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

    Ken Myer             True

    Pilar Ackerman      False

    David Jaffe          True

    Aneta Olecka         True

     

    A little nicer looking, and a lot easier to read.

     

    By the way, you can also select property values, and display data onscreen, by using the Format-List cmdlet:

     

    Get-CsAdUser | Format-List DisplayName, Enabled

     

    That gives you output similar to this:

     

    DisplayName : Ken Myer

    Enabled     : True

     

    DisplayName : Pilar Ackerman

    Enabled     : False

     

    DisplayName : David Jaffe

    Enabled     : True

     

    DisplayName : Aneta Olecka

    Enabled     : True

     

    The moral of the story is this: if all you need to do is display data onscreen then, yes, you can use either Format-Table or Format-List instead of Select-Object. (And, as we've seen, using either of these cmdlets might give you nicer-looking output than Select-Object does.)

     

    However (and this is an important however), you can't always use Format-Table (or Format-List) instead of Select-Object. For example, if you need to pipe data to another cmdlet Format-Table won't give you the expected results. Consider this command, which retrieves information about all your Lync Server-enabled user accounts, pulls out just the DisplayName, Enabled, and SipAddress properties, and then writes that information to a comma-separated values file:

     

    Get-CsUser | Select-Object DisplayName, Enabled, SipAddress | Export-Csv –Path C:\Logs\Users.csv

     

    That's going to give us a CSV file with lines that look like this:

     

    #TYPE Selected.Microsoft.Rtc.Management.ADConnect.Schema.OCSADUser

    "DisplayName","Enabled","SipAddress"

    "Ken Myer",$True,"sip:kenmyer@litwareinc.com"

    "Pilar Ackerman",$False,"sip:pilar@litwareinc.com"

     

    Now, let's try the same thing using Format-Table:

     

    Get-CsUser | Format-Table DisplayName, Enabled, SipAddress | Export-Csv –Path C:\Logs\Users.csv

     

    Here's what that CSV file looks like:

     

    #TYPE Microsoft.PowerShell.Commands.Internal.Format.FormatStartData

    "ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"

    "033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",

    "9e210fe47d09416682b841769c78b8a3",,,,,

    "27c87ef9bbda4f709f6b4002fa4af63c",,,,,

    "27c87ef9bbda4f709f6b4002fa4af63c",,,,,

     

    Yeesh; what's that all about? Well, the problem is that the Select-Object cmdlet passes user objects through the pipeline; we can determine that by looking at the TYPE header:

     

    #TYPE Selected.Microsoft.Rtc.Management.ADConnect.Schema.OCSADUser

     

    However, Format-Table doesn't send user objects through the pipeline. Instead, it passes a table object through the pipeline:

     

    #TYPE Microsoft.PowerShell.Commands.Internal.Format.FormatStartData

     

    That's really not what we want.

     

    So, yes, for displaying data onscreen, use Select-Object, Format-Table, or Format-List. For anything else, stick with Select-Object.

     

    Note. Anything else? In real life as well as in Lync Server PowerShell?

     

    Well, maybe not anything. But most things.

     

  • Set the Forward To Number in Lync Server

    How can I set the actual number that calls are forwarded to?

     

    Well, unfortunately, you can't. We wish you could, because we get asked this question all the time. But you can't.

     

    Sorry.

     

    If you're wondering what we're talking about (something that happens quite a bit, now that we think about it) Lync Server's voice policies let you determine whether or not your users are allowed to forward their work calls to an alternate phone (such as their home phone or cell phone). By default, users are allowed to forward calls. If you'd prefer that they not be allowed to do this then all you have to do is disable call forward in the appropriate voice policy. For example, this command disables call forwarding at the global scope:

     

    Set-CsVoicePolicy –Identity global –AllowCallForwarding $False

     

    That part's easy. However, what people really want to do is be able to specify the phone number that calls can be forwarded to. For example, suppose you want Ken Myer to have his calls forwarded to (425) 555-1219. Can you run some PowerShell command that will cause any calls to Ken's work number to automatically be forwarded to (425) 555-1219? As far as we know, no, you can't.


    Hey, you win a few, you lose a few, right?

  • List the Users and Client Endpoint Versions Connected to a Registrar Pool: Direct Connection

    Submitted by Scott Stubberfield and Nick Smith, Microsoft

     

    Nick Smith and Scott Stubberfield have updated a previous script of theirs which lists all the users connected to a Registrar pool along with the client version of the endpoint they used to log on to the system. According to Nick, updates made since the time the previous script was published include the following:

     

    • The script outputs the results to the screen instead of a .CSV file.  This allows administrators to pipe the results to cmdlets like Where-Object.
    • The script prompts for the pool FQDN and automatically queries all servers that are members of the pool.
    • There are two versions of the script. 
      • The Get-PoolUserRegistrations.ps1 script (this one) connects directly to the SQL instance on the remote server.  This requires that the Windows firewall be disabled or configured to allow SQL connections.
      • The Get-PoolUserRegistrations_Remoting.ps1 script uses Windows PowerShell remoting to run the SQL query locally on each machine.  With this script, the Windows firewall does not need to be disabled; however, you must run the Enable-PSRemoting cmdlet on each server in order to allow remote connections.

     

    The net result is that anyone running the script will get back data similar to this:

     

    ClientVersion        UserName                  Fqdn

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

    UCCAPI/4.0.7629.0... kenmyer@litwareinc.com    atl-cs-001.litwareinc.com

    UCCAPI/4.0.7629.0... packerman@litwareinc.com  atl-cs-001.litwareinc.com

    UCCAPI/4.0.7629.0... pdoyle@litwareinc.com     atl-cs-001.litwareinc.com

     

    To make use of this script, copy the code shown below, paste it into a text editor (such as Notepad) and then save the script as a .PS1 file (for example, C:\Scripts\Get-PoolUserRegistrations.ps1). From the Lync Server Management Shell you can then run the script by referencing the script file path followed by the fully qualified domain name of the Registrar pool you want to return information from. For example:

     

    C:\Scripts\Get-PoolUserRegistrations.ps1 –PoolFqdn atl-cs-001.litwareinc.com

     

    If you do not include a Registrar pool the script will prompt you to enter a pool FQDN.

     

    Here's the code:

     

    Param (
        $PoolFQDN = (Read-Host -Prompt "Please enter the pool fqdn")
    )

    #Get all the CS pool information
    $CSPool = Get-CSPool $PoolFQDN

    #Create empty variable that will contain the user registration records
    $overallrecords = $null

    #Loop through a front end computers in the pool
    Foreach ($Computer in $CSPool.Computers){

        #Get computer name from fqdn
        $ComputerName = $Computer.Split(".")[0]

        #Defined Connection String
        $connstring = "server=$ComputerName\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, `
            RE.EndpointId as EndpointId, `
            EP.ExpiresAt, `
            '$computer' as RegistrarFQDN `
            From `
           rtcdyn.dbo.RegistrarEndpoint RE `
           Inner Join `
            rtc.dbo.Resource R on R.ResourceId = RE.OwnerId `
           Inner Join `
            rtcdyn.dbo.Endpoint EP on EP.EndpointId = RE.EndpointId `
            Order By ClientVersion, UserName "

        #Make the connection to Server    
        $connection = New-Object System.Data.SqlClient.SqlConnection
        $connection.ConnectionString = $connstring
        $connection.Open()
        $command.Connection = $connection
        
        #Get the results
        $sqladapter = New-Object System.Data.SqlClient.SqlDataAdapter
        $sqladapter.SelectCommand = $command
        $results = New-Object System.Data.Dataset    
        $recordcount=$sqladapter.Fill($results)
        
        #Close the connection
        $connection.Close()
        
        #Append the results to the reuslts from the previous servers
        $overallrecords = $overallrecords + $Results.Tables[0]
    }

    $overallrecords

     

  • List the Users and Client Endpoint Versions Connected to a Registrar Pool: Remote Connection

    Nick Smith and Scott Stubberfield have updated a previous script of theirs which lists all the users connected to a Registrar pool along with the client version of the endpoint they used to log on to the system. According to Nick, updates made since the time the previous script was published include the following:

     

    • The script outputs the results to the screen instead of a .CSV file.  This allows administrators to pipe the results to cmdlets like Where-Object.
    • The script prompts for the pool FQDN and automatically queries all servers that are members of the pool.
    • There are two versions of the script. 
      • The Get-PoolUserRegistrations.ps1 script connects directly to the SQL instance on the remote server.  This requires that the Windows firewall be disabled or configured to allow SQL connections.
      • The Get-PoolUserRegistrations_Remoting.ps1 script (this one) uses Windows PowerShell remoting to run the SQL query locally on each machine.  With this script, the Windows firewall does not need to be disabled; however, you must run the Enable-PSRemoting cmdlet on each server in order to allow remote connections.

     

    The net result is that anyone running the script will get back data similar to this:

     

    ClientVersion        UserName                  Fqdn

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

    UCCAPI/4.0.7629.0... kenmyer@litwareinc.com    atl-cs-001.litwareinc.com

    UCCAPI/4.0.7629.0... packerman@litwareinc.com  atl-cs-001.litwareinc.com

    UCCAPI/4.0.7629.0... pdoyle@litwareinc.com     atl-cs-001.litwareinc.com

     

    To make use of this script, copy the code shown below, paste it into a text editor (such as Notepad) and then save the script as a .PS1 file (for example, C:\Scripts\Get-PoolUserRegistrations.ps1). From the Lync Server Management Shell you can then run the script by referencing the script file path followed by the fully qualified domain name of the Registrar pool you want to return information from. For example:

     

    C:\Scripts\Get-PoolUserRegistrations.ps1 –PoolFqdn atl-cs-001.litwareinc.com

     

    If you do not include a Registrar pool the script will prompt you to enter a pool FQDN.

     

    Here's the code:

    Param (
        $PoolFQDN = (Read-Host -Prompt "Please enter the pool fqdn")
    )

    #Get all the CS pool information
    $CSPool = Get-CSPool $PoolFQDN

    #Create empty variable that will contain the user registration records
    $overallrecords = $null

    #Create the scirptblock to execute on remote server
    $RemoteScriptBlock = {
        #Defined Connection String
        $connstring = "server=.\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, `
            RE.EndpointId as EndpointId, `
            EP.ExpiresAt, `
            '$computer' as RegistrarFQDN `
            From `
           rtcdyn.dbo.RegistrarEndpoint RE `
           Inner Join `
            rtc.dbo.Resource R on R.ResourceId = RE.OwnerId `
           Inner Join `
            rtcdyn.dbo.Endpoint EP on EP.EndpointId = RE.EndpointId `
            Order By ClientVersion, UserName "
        
        #Make the connection to Server    
        $connection = New-Object System.Data.SqlClient.SqlConnection
        $connection.ConnectionString = $connstring
        $connection.Open()
        $command.Connection = $connection
        
        #Get the results
        $sqladapter = New-Object System.Data.SqlClient.SqlDataAdapter
        $sqladapter.SelectCommand = $command
        $results = New-Object System.Data.Dataset    
        $recordcount=$sqladapter.Fill($results)
        
        #Close the connection
        $connection.Close()

        $Results.Tables[0]
    }

    #Loop through a front end computers in the pool
    Foreach ($Computer in $CSPool.Computers){

        #Get computer name from fqdn
        $ComputerName = $Computer.Split(".")[0]

        $PSSession = New-PSSession -Computername $ComputerName
       
        $overallrecords = $overallrecords + (Invoke-Command -Session $PSSession -ScriptBlock $RemoteScriptBlock)
            
        Remove-PSSession $PSSession
    }

    $overallrecords

     

  • Haiku #65

    Warning: It might not

    Rain today. Stay calm and test

    All your outbound calls.

     

    Just a few minutes ago the author of today's Lync Server PowerShell haiku checked the weather report. (Which, admittedly, is a silly thing to do when you live in the Seattle area. But he did it anyway.) When he got to the Web page with the Seattle area forecast he saw this in big red letters at the top of the page:

     

    Non-Precipitation Warning for Seattle Metropolitan Area

     

    Alas, it's true: in Seattle they warn you when it's not going to rain, just to make sure that you have plenty of time to prepare yourself for this once-in-a-lifetime event.

     

    Note. So what do Seattle area residents do when it's not raining? Hey, how would we know? We've only lived here for 22 years.

     

    Ha! Get it? You see, because it rains all the time here and – well, never mind.

     

    But all kidding aside, we Seattle residents do what anyone else does when it's not raining: we clean and oil our umbrellas; we ogle the raingear in the latest L. L. Bean catalog; and we use the Test-CsPstnOutboundCall cmdlet to ensure that we can still use Lync Server to make phone calls out to the PSTN network.

     

    Ah, good question: how do you clean an umbrella? Well, from personal experience we can tell you how not to clean an umbrella: don't put it in the washing machine; that's just an all-around bad experience. Instead, wash your umbrella in a bathtub full of warm water and a little bit of laundry detergent.

     

    Any other questions? Oh, that's a good one, too: how do you use the Test-CsPstnOutboundCall cmdlet to ensure that you can still use Lync Server to make phone calls out to the PSTN network? Let's see if we can figure that out.

     

    Before we do that, however, we should take a moment to clarify what the PSTN network is. PSTN is short for Public Switched Telephone Network, and is what we tend to think of as the "regular" telephone network as opposed to a Voice over IP network like Enterprise Voice. (And this includes cell phones as well as your good old-fashioned "landlines.") Connecting a VoIP network to the PSTN network can be a little tricky, what with PSTN gateways and SIP trunks and assorted other paraphernalia. Because of that, once you have everything hooked up it's a good idea to test the system every now and then. That's where Test-CsPstnOutboundCall comes in.

     

    The basic idea behind Test-CsPstnOutboundCall is pretty easy: the cmdlet simply makes a phone call to a phone number out on the PSTN network. Couldn't you just fire up Microsoft Lync and make a phone call from there? Sure, and that works great as long as the call gets through. But what happens if the call fails? With Microsoft Lync, all you know is that the call failed. By comparison, the Test-CsPstnOutboundCall cmdlet keeps a log of everything it does, and can even display each step it takes in trying to make that call. (To see that step-by-step display, just include the Verbose parameter in your call to the cmdlet.) For example, you might run the cmdlet and get back information similar to this:

     

    VERBOSE: 'Register' activity started.

    Sending Registration request:

      Target Fqdn:      = atl-cs-001.litwareinc.com

      User Sip Address  = kenmyer@litwareinc.com

      Registrar Port    = 5061

    Auth Type 'Trusted' is selected.

    An exception 'The endpoint is unable to register. See the ErrorCode for specific reason.' occurred during Workflow Microsoft.Rtc.SyntheticTransactions.Worklfows.STOcsPSTNWorkflow execution.

     

    As you can see, in this case the test never really got started: our test user (kenmyer@litwareinc.com) was unable to log on to the system. That could just be a problem with that test user account; for example, maybe you don't even have a user with that SIP address. Alternatively, you could be having problems with the Registrar for that pool. Either way, you have more to go on than just the fact that you dialed the phone and nothing happened.

     

    Note. OK, we admit it: that might not be the greatest example we could have come up with. However, we PowerShell blog writers don't currently have a test environment where we can make calls to the PSTN network; every call we try to make will end up failing. So we used an invalid user account just so we'd have a semi-realistic error message to show you.

     

    Basically it was either that somewhat-lame example or we were going to have to walk you through the steps involved in washing your umbrella.

     

    As for how you actually use Test-CsPstnOutboundCall, well, there are a couple different ways. If you have configured health monitoring test accounts for a pool (see the help topic for the New-CsHealthMonitoringConfiguration cmdlet) then you can run Test-CsPstnOutboundCall using a command as simple as this:

     

    Test-CsPstnOutboundCall -TargetFqdn atl-cs-001.litwareinc.com -TargetPstnPhoneNumber "+12065551298" -Verbose

     

    Basically all you have to do is include the TargetFqdn parameter (followed by the fully qualified domain name of the Registrar pool being tested) and the TargetPstnPhoneNumber parameter, followed by the phone number you want to call. In this example we've configured that phone number using the E.164 format. However, that's not required. We could have also formatted the phone number, like, say, this:

     

    -TargetPstnPhoneNumber "206-555-1298"

     

    When you do that, Test-CsPstnOutboundCall will use the dial plan of the test user to try and normalize that phone number. If that dial plan can't normalize the number (that is, convert it to a format Lync Server understands) then the call will fail, just as it would in real life.

     

    The moral of that story? To fully test the system as users will use it, set phone numbers using the same format your users will likely use; that way, you can see if the dial plan will be able to make sense of that number. On the other hand, if all you want to do is verify that the system is capable of making calls, then use the E.164 format. If you do that, then dial plan problems won't interfere with your test.

     

    If you haven't set up any health monitoring accounts then you can run Test-CsPstnOutboundCall using any valid Lync Server user account and a pair of commands similar to these:

     

    $cred1 = Get-Credential "litwareinc\kenmyer"

     

    Test-CsPstnOutboundCall -TargetFqdn atl-cs-001.litwareinc.com -TargetPstnPhoneNumber "+12065551298" -UserSipAddress "sip:kenmyer@litwareinc.com" -UserCredential $cred1 -Verbose

     

     

    As you can probably see, in the first line of code we use the Windows PowerShell Get-Credential cmdlet to create a credentials object for the user in question; that means we have to enter both the user name (in the format domain_name\user_name) and his or her password. That credentials information gets safely tucked away in a variable named $cred1, and is then used when we call Test-CsPstnOutboundCall. Note, too, that when we run that cmdlet we also need to specify the SIP address of our test user:

     

    -UserSipAddress "sip:kenmyer@litwareinc.com"

     

    So what happens after that? Well, after that Test-CsPstnOutboundCall will try to log the test user on to Lync Server. If the logon succeeds, the cmdlet will then attempt to place a phone call across the PSTN gateway. (As implied earlier, this phone call will be placed using the dial plan, voice policy, and other policies and settings assigned to the test account.) When the call is answered, the cmdlet sends dual-tone multi-frequency (DTMF) codes across the network in order to verify media connectivity.

     

    Keep in mind that Test-CsPstnOutboundCall makes an actual phone call: the target phone will ring and must be answered for the test to succeed. Which simply means that maybe you want to call, say, your cell phone as opposed to one of those 1-900 numbers you saw advertised on late-night TV.

     

    Just a suggestion.

     

    So do we have any other suggestions before we call it a day? Just one. If you ever need to predict the weather in Seattle, here's a good rule of thumb. If you can look out the window and can see Mt. Rainier, then that means it's going to rain. And if you look out the window and can't see Mt. Rainier, then that means it's already raining.

     

    Note. Hey, we don't write these ourselves. We just steal them from other Web sites. See you Monday.