Archive for August, 2011

One Liners: Finding Out Which Lync Pool Servers a User is Associated With, and the Preferred Connection Order

August 31st, 2011 2 comments

Lync 2013 logo 128x128Sometimes, you need to do some Lync logging to investigate a problem with a user. If you have multiple servers in a pool, you sometimes have to enable logging on each until you figure out which one the client is actually connecting to. We can find out which servers the user is associated with and the preferred order that the client will connect using the following in the Lync Management Shell:


Such as:


The output shows us the primary and backup pool FQDNs, and the order in which it will connect to servers in each pool.

PrimaryPoolFqdn                     : lyncpool01.contoso.local
BackupPoolFqdn                      : lyncpool02.contoso.local
UserServicesPoolFqdn                : lyncpool01.contoso.local
PrimaryPoolMachinesInPreferredOrder : {1:2-2, 1:2-1}
BackupPoolMachinesInPreferredOrder  : {1:3-2, 1:3-1}

But what that doesn’t tell us, is the actual names of the servers in the pool, and which one is 1:2-2, and 1:2-1, etc. So we expand a little further and use:

Get-CsUserPoolInfo -Identity "user" | Select-Object -ExpandProperty PrimaryPoolMachinesInPreferredOrder

For example,

Get-CsUserPoolInfo -Identity "prichard" | Select-Object -ExpandProperty PrimaryPoolMachinesInPreferredOrder

This will show the registrar pools and their respective servers in the preferred order the user will connect:

MachineId         : 1:2-2
Cluster           : 1:2
Fqdn              : lyncpoolserver03.contoso.local
PrimaryMacAddress : 000000
Topology          : Microsoft.Rtc.Management.Deploy.Internal.DefaultTopology
MachineId         : 1:2-1
Cluster           : 1:2
Fqdn              : lyncpoolserver02.contoso.local
PrimaryMacAddress : 000000
Topology          : Microsoft.Rtc.Management.Deploy.Internal.DefaultTopology

We see that this user will connect to lyncpoolserver03 first, since it’s listed first. If that server is not available, then the user would be redirected to lyncpoolserver02. Note that this only shows the information for the primary pool. If you have a backup pool, the information for those servers is not shown here (but is shown if you use BackupPoolMachinesInPrefferedOrder as the ExpandedPropery). However, if you do have a backup registrar pool, and want to use it as a backup pool for users homed on the first, you should have Director servers, as mentioned in Another Reason to Include a Director in Your Lync Server 2010 Deployment.

We can then wrap this in a function:

function Get-CsUserConnectionInfo {
 param (
  [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory, HelpMessage = "No username specified")]
  [string] $user
 Get-CsUserPoolInfo –Identity $user | Select-Object –ExpandProperty PrimaryPoolMachinesInPreferredOrder
} # end function Get-CsUserConnectionInfo

For easy access. Toss it into your PowerShell profile and access it using


Also, the Get-CsConnections.ps1 script will show you the current connections on a per-user basis if needed.

Script: New-ADPasswordReminder.ps1 – Email Users When Their Password Will Soon Expire

August 27th, 2011 348 comments

Note: Development on this script has moved to Github. Please see the repo at


In today’s increasingly mobile workforce, many users don’t login to Active Directory on a domain joined computer. Some use only Outlook Web Access/App, some use non-domain joined machines external to the company network, some just use mobile devices such as smartphones and tablets. And others may use Macs.

Users who login via domain joined machines on the company network get the reminder several days ahead. The default is 14 days, but can be configured in the Default Domain Group Policy under Interactive logon: Prompt user to change password before expiration.

OWA users see a notification when they login as well. In OWA 2007 running on IIS6, this can be adjusted via PasswordExpirePrenotifyDays. In fact, with OWA 2007 and 2010, you can even change your password after it expires, using the Password Reset Feature in Exchange 2007 and 2010. However, there are times when that’s just not a remedy. The password reset feature requires the Exchange server to be running on Windows 2008 or later, as it relies on IIS 7. Many Exchange 2007 shops are not on that platform yet.

Anyone who’s ever worked on a Help Desk knows that a LOT of users call to say they can’t login, only to determine it’s because their password expired. Many, if not most, are those types of users mentioned above. Others don’t notice, or simply ignore the notice when logging in. So let’s really make sure we notify them of the pending expiration. There are some third-party tools, including some that run on SharePoint, that enable a user to reset their password. But this is after the fact. Sure, we could use some third-party application to send a reminder, but… well… why? PowerShell to the rescue!

In the pre-Windows 2008 domain functional level days, we could just peek at the Default Domain GPO, and grab the Maximum Password Age parameter, since it was a global setting. Then we could go through Active Directory, find users who are not set to “never expire”, use some math, and come up with a list of users whose password expired soon.

But with the changes implemented with Windows Server 2008, we can now have Fine Grained Password policies, which allows us to have more than just one password policy in our organization. So, Executives get one password, IT people with elevated rights get another, etc. Cool in theory, but frustrating in our endeavor to notify users when they’ll expire.

I blatantly admit that I used part of a script by M. Ali, who wrote a blog post Find out when your Password Expires. The script looks checks Get-AdDomain, and looks at the DomainMode parameter in the results. From here, we know whether we can just peek at the Default Domain policy, or if we need to look deeper. Regardless of which way, we look through the users using Get-AdUser, and grab the PasswordExpired, PasswordNeverExpires, and PasswordLastSet fields. Obviously, if the account is expired, no need to keep reminding the user. And if the password never expires, then we also don’t need to notify the user. With PasswordLastSet, our math comes into play to determine when the password will expire. Not terribly short and sweet, but effective.

Once we know when the password will expire, we can then set a window for when we should notify the users. It makes sense to match what’s in the GPO so that notifications are consistent regardless of platform. This script is set to 14 days by default.

Next, we need to craft some information that we want to convey to the user. In this case, we’ll use some HTML formatting so that we can properly convey the importance of the info, as well as include some additional formatting. I’ve mocked up something based on some third-party tools, and on the comments and recommendations of IT Professionals and users. It’s simple enough to change, but be warned that many clients, including Outlook, don’t strictly adhere to HTML standards. So it can take quite a bit of trial and error to find out what does actually appear the way you want it to.

Installation and Setup

First, you need a receive connector that will accept mail from PowerShell. I cover that in Creating a receive connector to use for sending email from PowerShell. Next, the script must run on a machine with PowerShell 2.0 installed. This is a prerequisite for Exchange 2010 (and installed by default on Windows 2008 R2), but not for Exchange 2007. If you’re reluctant to upgrade PowerShell on your 2007 box, it can be run from any other box that has PowerShell 2.0 and the Exchange Management tools installed. Note: Exchange Management tools should always be updated and patched to the same level that your Exchange servers are.

Second, you’ll need the ActiveDirectory module available on the machine that will run the script. The ActiveDirectory module is installed when you add the Remote-Server Administration Tools feature in Windows Server 2008 R2. If the module is not detected, the script will attempt to install it automatically the first time it runs.

Next, grab the latest zip file from the DOWNLOAD section below. It includes the script and contains a couple of images that are used in the warning for users who’s password expires in < 24 hours (seen in the Outlook screenshot above). The images need to be accessible to all users who will receive the reminder emails. This is likely to be a public web site.

Crack open the script in your favorite editor and update the lines in the param() block to match your environment. This includes $Company, $OwaUrl, $PSEmailServer, $EmailFrom, $HelpDeskPhone, $HelpDeskURL and $DaysToWarn. If you want to target a specific OU, set $OU. Also, set $ImagePath to a path holding the included image files (or those you add/edit). This path should be available to all users who may receive the reminder message. This is probably a public server.

	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify a company name.")]
	[string]$Company = "Contoso Ltd",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify an OWA URL")]
	[string]$OwaUrl = "",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify the IP address of your email server")]
	[string]$PSEmailServer = "",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify a name and email address for the email 'from' field")]
	[string]$EmailFrom = "Help Desk ",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[string]$HelpDeskPhone = "(586) 555-1010",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[string]$HelpDeskURL = "",
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[string] $TranscriptFilename = $MyInvocation.MyCommand.Name + " " + (hostname)+ " {0:yyyy-MM-dd hh-mmtt}.log" -f (Get-Date),
	[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, Mandatory=$false, HelpMessage="This must be zero")]
	[int]$global:UsersNotified = 0,
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false, HelpMessage="Please specify how many days before expiration that users should begin to be notified.")]
	[int]$DaysToWarn = 14,
	[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$false)]
	[string] $ImagePath = "",
	[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, Mandatory=$false)]
	[string] $ScriptName = $MyInvocation.MyCommand.Name,
	[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, Mandatory=$false)]
	[string] $ScriptPathAndName = $MyInvocation.MyCommand.Definition,
	[parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false, Mandatory=$false, HelpMessage="Please specify an Organizational Unit")]
	[string] $ou

Open an Exchange Management Shell session and run the script in demo mode to see a list of users that are expiring soon.The script won’t email the users in demo mode. It merely shows you who it WOULD, and how long till their password expires.

.\New-PasswordReminder.ps1 -demo

As we see in the example screenshot, Claudia’s password expires in 5 days, and the password policy that applies to her requires the password to be changed every 42 days. If we run the script normally, Claudia will receive the email reminder since it’s within the 14 day window defined in the script.

To run the script normally (non-demo mode), manually, just omit the -demo. There is no output to the screen when run normally, as the script is designed to be run as a scheduled task.

Once you’re satisfied that the script is running correctly, we can set it to run as a scheduled task. I have a blog post Running PowerShell scripts via Scheduled Tasks that details everything. In my production environment, it runs at 6am each day.

One of the hardest parts was getting a decently formatted email that looked good. This could take some trial and error, and the original script didn’t really have a way built in to preview what the end user would see. As a result, some hapless users would be flooded with your “test” messages. I fixed that by creating a preview mode. Manually run the script with the preview switch, and a user to send the email to. For example

.\New-PasswordReminder.ps1 -Preview -PreviewUser bgates

This will send an email to the user, bgates. The email is formatted for a password that expires in one day, so the user gets the additional banner near the top as well.

Next up was creating a scheduled task. Not really terribly difficult to do manually, but I could see where it might take some trial and error. So, I added the install switch, which will create a scheduled task for the script, configuring it to run at 6am each day. Of course, that time can be manually adjusted by opening the scheduled task once it’s created. The install mode will ask for credentials to run the scheduled task under. Install it as so:

.\New-PasswordReminder.ps1 -Install

Note: The scheduled task is configured to point to where the script is when you run the install switch. So don’t move it later!

To send an email that does not contain the images or their related formatting, specify $NoImages when running the script. This will send essentially an HTML formatted text email.

Next up, I added some simple logging to the application event log. The script will write a single entry when it starts, and a single entry when it finishes, noting how many users were processed (sent an email). I would love to hear how this script works in large environments. If you’re willing, please let me know (via comments below) how long it’s taking to run in your environment, and the number of users in AD.

Please send me your suggestions!


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.


New-PasswordReminder.ps1 [-Demo] [-Install] [[-PreviewUser] ] [-NoImages] [-WhatIf] [-Confirm] []

Demo Runs the script in demo mode. Demo mode displays users who are expiring soon, but does not send them the reminder email.

Install Creates a scheduled task to run the script automatically every day at 6:00am

Defines the user to send the preview email to.

Specifies that a HTML text only message should be sent instead of one that contains the fancy formatting.


Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

In addition to the info listed above:

If you leave the following parameters blank, the related text will be removed from the email sent to users: $HelpDeskURL. This will get expanded in the future.

You can change the format of the date displayed in the email by changing the value of $DateFormat. The default is “d”, which yields a date such as 09/07/2012 (MM/dd/yyyy). If you’d like the European style, use “MM/dd/yyyy” instead.

Frequently Asked Questions

Question: Does this work with Exchange Server 2013

Answer: Yes


v2.9 – 09-13-2013

v2.8 – 05-03-2013



v2.4 – image files used in emails


See the changelog for this script which details all versions and their features.

Update Rollup 5 (UR5) for Exchange Server 2010 SP1 Released

August 23rd, 2011 No comments

Microsoft has released the following update rollup for Exchange Server 2010:

  • Update Rollup 5 for Exchange Server 2010 SP1 (2582113)

If you’re running Exchange Server 2010 SP1, you need to apply Update Rollup 5 for Exchange 2010 to address the issues listed below.

Remember, you only need to download the latest update for the version of Exchange that you’re running.

Here is a list of the fixes included in update rollup 5:

  1. 2275156 The inline contents disposition is removed when you send a “Content-Disposition: inline” email message by using EWS in an Exchange Server 2010 environment
  2. 2499044 You cannot save attachments in an email message by using OWA if the subject line contains special characters in an Exchange Server 2010 environment
  3. 2509306 Journal reports are expired or lost when the Microsoft Exchange Transport service is restarted in an Exchange Server 2010 environment
  4. 2514766 A RBAC role assignee can unexpectedly run the Add-ADPermission command on an Exchange Server 2010 server that is outside the role assignment scope
  5. 2529715 Slow network or replication issues after you change the number of virus scanning API threads in Microsoft Exchange Server 2010
  6. 2536704 Mailbox users who are migrated by using ILM 2007 cannot use the Options menu in OWA in an Exchange Server 2010 environment
  7. 2537094 French translation errors occur when you edit a response to a meeting request by using OWA in an Exchange Server 2010 SP1 environment
  8. 2554604 A RBAC role assignee can unexpectedly manage certificates that are outside the role assignment scope in an Exchange Server 2010 environment
  9. 2555800 You cannot use the GetItem operation in EWS to retrieve properties of an email message in an Exchange Server 2010 environment
  10. 2555850 You cannot delete a mailbox folder that starts with a special character in its name by using Outlook in an Exchange Server 2010 environment
  11. 2556096 The columns in the .csv logging file are not lined up correctly when you perform a discovery search on a mailbox in an Exchange Server 2010 environment
  12. 2556107 The columns in the .csv logging file are not lined up correctly when you perform a discovery search on a mailbox in an Exchange Server 2010 environment
  13. 2556133 A device that uses Exchange ActiveSync cannot access mailboxes in an Exchange Server 2010 environment
  14. 2556156 Extra.exe crashes when it performs RPC activity checks against an Exchange Server 2010 server
  15. 2556352 “ChangeKey is required for this operation” error message in Outlook for Mac 2011 in an Exchange Server 2010 environment
  16. 2556407 Certain client-only message rules do not take effect on email messages that are saved as drafts in an Exchange Server 2010 environment
  17. 2559926 “There are no items to show in this view.” error message when you try to view a folder by using Outlook in an Exchange Server 2010 environment
  18. 2572958 The “Test-OutlookConnectivity -Protocol HTTP” command fails with an HTTP 401 error in an Exchange Server 2010 environment

Download the rollup here. This update will be available via Windows Update in late September. The next rollup, Update Rollup 6 for Exchange Server 2010 SP1 is planned for October 2011.

Installation Notes:

If you haven’t installed Exchange Server yet, you can use the info at Quicker Exchange installs complete with service packs and rollups to save you some time.

Microsoft Update can’t detect rollups for Exchange 2010 servers that are members of a Database Availability Group (DAG). See the post Installing Exchange 2010 Rollups on DAG Servers for info, and a script, for installing update rollups.

Update Rollups should be applied to Internet facing Client Access Servers before being installed on non-Internet facing Client Access Servers.

If you’re installing the update rollup on Exchange servers that don’t have Internet access, see “Installing Exchange 2007 & 2010 rollups on servers that don’t have Internet access” for some additional steps.

Also, the installer and Add/Remove Programs text is only in English – even when being installed on non-English systems.

Note to Forefront users:

If you don’t disable Forefront before installing a rollup or service pack, and enable afterwards, you run the risk of Exchange related services not starting. You can disable Forefront by going to a command prompt and navigating to the Forefront directory and running FSCUtility /disable. To enable Forefront after installation of a UR or SP, run FSCUtility /enable.

One Liners: See Failed Inbound Messages for the Past Few Days

August 22nd, 2011 No comments

Exchange 2013 logo 128x128Dealing with spam is like herding cats. It moves in every direction, and just when you think you might have it corralled, something comes along in a completely different direction.

Exchange has some fabulous features for reducing the amount of spam that lands in end-user mailboxes, and those features are well documented. Sometimes, you just want to see what’s being stopped. That’s where today’s one liner comes in. This little tidbit will troll through the tracking logs of the server you run it on, and display the failed messages from the last 7 days – most of which are stopped by the Content Filtering Agent. Of course, you can change the number of days to look back, as larger environments will no doubt have a tremendous number of failed messages. Here we see the sender’s email address, recipients, message subject, and the time stamp when the message was attempted.

Get-MessageTrackingLog -ResultSize unlimited -Start ((Get-Date).AddDays(-7)) | Where-Object {$_.EventId -eq "fail"} | Select-Object Sender,Recipients,MessageSubject,TimeStamp

We can specify a specific server to search on:

Get-MessageTrackingLog -ResultSize unlimited -Server  -Start ((Get-Date).AddDays(-7)) | Where-Object {$_.EventId -eq "fail"} | Select-Object Sender,Recipients,MessageSubject,TimeStamp

Or, search all servers:

Get-TransportServer | Get-MessageTrackingLog -ResultSize unlimited -Start ((Get-Date).AddDays(-7)) | Where-Object {$_.EventId -eq "fail"} | Select-Object Sender,Recipients,MessageSubject,TimeStamp

And, we can also dump the data to a .csv file for manipulation:

Get-MessageTrackingLog -ResultSize unlimited -Start ((Get-Date).AddDays(-7)) | Where-Object {$_.EventId -eq "fail"} | Select-Object Sender,Recipients,MessageSubject,TimeStamp | Export-Csv c:\failedmessages.csv


One Liners: Restarting Stopped Services

August 18th, 2011 2 comments

PowerShell-logo-128x84During a recent power “issue”, I had to restart an entire rack full of Hyper-V servers. While an Exchange VM was booting, a networking issue caused the VM to not be able to connect to anything else, including domain controllers. As a result, many services couldn’t start. Rather than bouncing the server, or manually starting the services, this little one liner came in handy. Unfortunately, Get-Service doesn’t expose the startmode. That would make it too easy. So, we use Get-WMIObject:

Get-WMIObject win32_service | Where-Object {$ -match "exchange" -and $_.startmode -eq "Auto" -and $_.state -ne "running"} | Start-Service

Of course, we can remove the name check and look for all services on the server that should be (but aren’t) started, and start them:

Get-WMIObject win32_service | Where-Object {$_.startmode -eq "Auto" -and $_.state -ne "running"} | Start-Service

Ståle Hansen has reminded me that in Lync, there is also another solution:

Get-CsWindowsService -ExcludeActivityLevel | Where-Object {$_.Status -like "Stopped"} | Start-CsWindowsService

Disabling Loopbackcheck Programatically with PowerShell

August 17th, 2011 No comments

In some versions of Exchange, some cmdlets such as Test-OutlookWebServices can generate a 401 error when run from the Exchange server itself. Microsoft released a KB article (896861) that details disabling via the reqistry the loopback check. Other applications such as Lync, SharePoint, or  Symantec’s Enterprise Vault sometimes require this change as well to resolve issues. While it’s pretty easy to just create the registry entry in regedit (see the aforementioned KB article), if you’re putting together server-build scripts, or just like to use PowerShell to do the work, this method might be easier.

This little tidbit is broken down into two parts. The first part looks to see if the registry key exists, and if so, if it’s not set to the correct value (1). If it meets this criteria, we set the dword’s value to 1. The second part of the script determines if the dword exists at all, and if not, creates it and sets it to 1. If we make it all the way through, we know the dword exists and has the right value.

if ((Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa -name DisableLoopbackCheck -ErrorAction SilentlyContinue) -and (((Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa -name DisableLoopbackCheck -ErrorAction SilentlyContinue).DisableLoopbackCheck)-ne 1)){
    Set-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa -name DisableLoopbackCheck -value 1
}elseif (!(Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\Lsa -name DisableLoopbackCheck -ErrorAction SilentlyContinue)){
    New-ItemProperty HKLM:\System\CurrentControlSet\Control\Lsa -Name "DisableLoopbackCheck" -Value "1" -PropertyType dword

one liners: Finding users with forwarding addresses set

August 16th, 2011 4 comments

Exchange 2013 logo 128x128Sometimes while implementing new corporate policies, such as those that control forwarding messages outside of an environment, an admin needs to figure out who is configured that way. This can be a daunting task to go down through every account, visually inspecting each. PowerShell comes to the rescue in this one liner:

Get-Mailbox -ResultSize Unlimited | Where-Object {$_.ForwardingAddress -ne $null} | Select-Object Name, @{Expression={$_.ForwardingAddress};Label="Forwarded to"}, @{Expression={$_.DeliverToMailboxAndForward};Label="Mailbox & Forward"}

As we see in our test, one user, Robert Sweet, is configured for forwarding. His account forwards to a contact called “Robert Sweet [External]”, and based on the Mailbox & Forward being False, we know that it only forwards to the external address, and does not also deliver to the Exchange mailbox.

If we needed to, we could use

Get-Contact "Robert Sweet [External]" | Format-List

to get info about the contact, including the destination SMTP address. If we need to disable forwarding for all of the enabled users, we can use

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress -ne $null} | Set-Mailbox -ForwardingAddress $null

one liners: Finding Users Who Have Send-As or Full Access Permissions to Mailboxes

August 15th, 2011 7 comments

Exchange 2013 logo 128x128This comes up pretty often, especially around migrations and upgrades, or after some embarrassing incident. A manager wants to have a report of users who have send-as rights to other mailboxes. Fortunately, we can use PowerShell to do the heavy lifting:

Get-Mailbox -ResultSize Unlimited | Get-ADPermission | Where-Object {($_.ExtendedRights -like "*send-as*") -and -not ($_.User -like "nt authority\self")} | Format-Table Identity, User -auto

This gives us a nice list of those users. As we see, user msweet has send-as permissions to Timothy Gaines’ mailbox:

To find users who have Full Access to the mailbox of others, we can use:

Get-Mailbox -ResultSize Unlimited | Get-MailboxPermission | Where-Object {($_.AccessRights -match "FullAccess") -and -not ($_.User -like "NT AUTHORITY\SELF")} | Format-Table Identity, User

And we see that the same msweet has full control to the mailbox of user Oz Fox

In each example, we can replace the Get-Mailbox -ResultSize unlimited with a narrower scope, such as Get-Mailbox to look at specific accounts.

Note that in bigger environments, it can take quite a bit of time for this to run.

Script: Get-CsConnections.ps1 – See User Connections, Client Versions, Load Balancing in Lync & Skype for Business Server

August 11th, 2011 200 comments

Lync 2013 logo 128x128Tracy A. Cerise and Mahmoud Badran came up with a script to show Lync connections, and the users connected. This was quite informative as it could be used to show load balance distribution, client versions being used, and more.

I took the script and updated it a little, including:

  1. Removed the help function and the header block and inserted comment based help. So a user can run get-help Get-CsConnections.ps1 and get the help, just like any other script and cmdlet.
  2. Added a parameter to display the user list. My needs didn’t require the user list – just the statistics at the beginning. So I added the feature to show the user list by running Get-CsConnections.ps1 -IncludeUsers.
  3. Added a couple of functions, including one that cleans up some variables when exiting.
  4. Adjusted some of the formatting. I noticed things didn’t always line up when the server FQDNs were really long, like those in child domains.
  5. Did a prereq check to verify the Lync module is loaded. If not, it gets loaded. That way, the script will still run fine if it’s run from an ordinary PowerShell console.
  6. Accounted for the pool parameter being just a NetBIOS name by adding the $env:userdnsdomain to the NetBIOS name to create the FQDN. This appears to work fine if the Lync servers and user running the script are both in the same domain. If not, then an FQDN would be required.
  7. Renamed the script to Get-CsConnections.ps1 and some of the functions to the normal verb-noun format.
  8. Added a feature to show just a specific client version number, and the users connected with that client version. This can help you determine who is connecting with what versions, which is helpful when looking into licensing, upgrades, etc.
  9. Added support for Lync Server 2013, which uses a different query than Lync Server 2010.
  10. Tons more info in updates and releases following that. See the changelog for more info.


Get-CsConnections.ps1 [[-Pool] ] [[-SIPAddress] ] [[-FilePath] ] [-IncludeUsers] [-IncludeHighUsers] [-IncludeSystem] [[-UserHighConnectionFlag] ] [[-ClientVersion] ] [-ShowFullClient] [ShowTotal] [[-Server] ] [-WhatIf] [-Confirm] []

Run the script specifying the front end pool or server to target:

Get-CsConnections.ps1 -Pool [pool FQDN]


Get-CsConnections.ps1 -Server [server FQDN]

The script automatically determines the version (2010 or 2013) of the pool, and uses the correct query.

If I can find an auto-detect method for server versions, I’ll include that in a later build.

Will show you unique client versions, their user agent, and the number of connections for each:

Current connected users listed by client name/version

Current connected users listed by client name/version

Distribution of connections across frontend servers (load balancing):

Connections by server (load balancing)

Connections by server (load balancing)

The number of unique users and clients connected:

Total unique users and clients

Total unique users and clients

And, adding the -IncludeUsers switch, such as:

Get-CsConnections.ps1 -Pool [pool FQDN] -IncludeUsers

will also show the users who are connected, and the number of connections they each have:

Connections per user

Connections per user

Using -IncludeHighUsers instead of -IncludeUsers will only list those users who meet the UserHighConnectionFlag (shown in white) or exceed the UserHighConnectionFlag (shown in red).

Get-CsConnections.ps1 -SipAddress [sip address] -Pool [pool FQDN]

Will show you the information for a single user:

Connection info for a specific user

Connection info for a specific user

Get-CsConnections.ps1 -Pool [pool FQDN] -ClientVersion [version number]

Will show the connection data for just that version number, including listing the users connected with that client version. This is helpful if the first method lists some version numbers you’d like to track down. Here, I used a client version of 13.1. MC/13.1.x is the OCS client on the Mac.

Connections by client version

Connections by client version

Using the -ShowFullClient option will show extended info for client name/version. However, the previous ‘Client Version’ column is not shown due to formatting restrictions. Here we can see more info, especially about mobile devices, Lync Phone Edition, and Mac clients.

Show Full Client extended info

Show Full Client extended info

Using -ShowTotal will also add additional info to the bottom section, including total number of users who are Lync enabled, total who are voice enabled, and percentage of total Lync enabled users who are connected.

ShowTotal option with totals and percentage

ShowTotal option with totals and percentage

You can export the info to a .csv file for viewing/manipulation in Excel using:

Get-CsConnections.ps1 -Pool [pool FQDN] -FilePath [path to csv file]


Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

NOTE: In order to gain remote access to each Front End server’s RTCLOCAL database where connection information is found, you need to open two local firewall ports; one static UDP port (1434), and one dynamic TCP port. We can use netsh to open the two required ports. First, open an elevated command prompt, and paste the following line. You should get “Ok.” in return:

netsh advfirewall firewall add rule name="SQL Browser (UDP 1434)" dir=in action=allow protocol=UDP localport=1434 profile=domain description="Created for Get-CsConnections.ps1. For more information, see"

Next, find the dynamically assigned port used by the Named Instance (RTCLOCAL):

  • On the Front End server, open SQL Server Configuration Manager.
  • Expand SQL Server Network Configuration.
  • Click on Protocols for RTCLOCAL.
  • On the right side, right click on TCP/IP, and choose Properties.
  • Click on the IP Addresses tab.
  • Scroll to the last section, called IPAll.
  • Note the TCP Dynamic Ports value


Replace [dynamic port] in the code below with the dynamic port number, and run the entire following command:

netsh advfirewall firewall add rule name="SQL RTCLOCAL Dynamic Port (tcp-in)" dir=in action=allow protocol=TCP localport=[dynamic port] profile=domain description="Created for Get-CsConnections.ps1. For more information, see"

If you look at the inbound rules for the firewall, you’ll now see the two new rules:


Repeat the process for both ports on each Front End server.

Note: The dynamically assigned port is unique to each Front End server, not the pool. So you’ll need find the value on each server. Once the two ports are open on each Front End server in the pool, the script should work fine.

Thanks to James Cussen for explaining what config is needed for SQL access through the firewall.


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.


v3.5 – 08-14-2019 –

v3.4 – 03-27-2018 –

v3.3 – 01-0-2017 –

v3.2 – 11-17-2016 –

v3.1 – 04-07-2016 –

v3.0 – 09-09-2015 –

v2.9 – 10-28-2014 –

v2.8 – 06-10-2014 –

v2.7 – 05-24-2014 –

v2.6 – 02-08-2014 –

v2.5 – 11-26-2013 –

v2.4 – 09-13-2013 –

v2.3 – 08-01-2013 –

v2.2 – 05-10-2013 –

v2.1 – 12-13-2012 –

v2.0 – 10-16-2012 –

v1.9 – 09-21-2012 –

v1.8 – 09-14-2012 –

v1.7 –

v1.6 –

v1.4 –

v1.3 –

v1.0 –


See the changelog for a complete list of features added in each release

One Liners: Finding AD Disabled Accounts Who are Still Lync/Skype for Business Enabled

August 10th, 2011 18 comments

Lync 2013 logo 128x128Fellow MVP Jeff Guillet wrote an article about the fact that disabling a user’s Active Directory account doesn’t mean they can’t log into Lync/Skype for Business. This is due to the way Lync uses certificates and authentication based on them. I highly recommend you read the article.

I recently was writing some documentation for a customer and wanted to include this important information, including methods for resolving the problem after the fact.

If you’ve not been disabling users in Lync while disabling them in AD, here’s a one liner to find those users:

Get-CsAdUser -ResultSize Unlimited | Where-Object {$_.UserAccountControl -match "AccountDisabled" -and $_.Enabled -eq $true} | Format-Table Name,Enabled,SipAddress -auto

You can shorten it somewhat by not checking if $_.Enabled is $true, but just that it exists. You can get a count of the users using:

Get-CsAdUser -ResultSize Unlimited | Where-Object {$_.UserAccountControl -match "AccountDisabled" -and $_.Enabled} | Measure-Object

and, if you want, can disable them in one line using

Get-CsAdUser -ResultSize Unlimited | Where-Object {$_.UserAccountControl -match "AccountDisabled" -and $_.Enabled} | Disable-CsUser

Update 09-14-2012: Be careful using that last option if you’ve configured test accounts for synthetic testing using the New-CsHealthMonitoringConfiguration cmdlet as I mention in Lync Synthetic Tests: What They are and When They Don’t Work – Part I.

Update 04-12-2014: Replaced aliases with full cmdlet per best practices.

Update 09-19-2014: Added -ResultSize Unlimited