Use PowerShell to Determine what Outlook Client Versions are accessing your Exchange Servers

Earlier this month I saw a blog article on The EXPTA {blog} about Reporting Outlook Client Versions Using Log Parser Studio and I thought I would show you a simple alternative using PowerShell that accomplishes the same task while giving you some additional flexibility.

This simple PowerShell function can be used to parse the Exchange Server RPC logs.

 1#Requires -Version 3.0
 2function Get-MrRCAProtocolLog {
 3
 4<#
 5.SYNOPSIS
 6    Identifies and reports which Outlook client versions are being used to access Exchange.
 7
 8.DESCRIPTION
 9    Get-MrRCAProtocolLog is an advanced PowerShell function that parses Exchange Server RPC
10    logs to determine what Outlook client versions are being used to access the Exchange Server.
11
12.PARAMETER LogFile
13    The path to the Exchange RPC log files.
14
15.EXAMPLE
16     Get-MrRCAProtocolLog -LogFile 'C:\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\RCA_20140831-1.LOG'
17
18.EXAMPLE
19     Get-ChildItem -Path '\\servername\c$\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\*.log' |
20     Get-MrRCAProtocolLog |
21     Out-GridView -Title 'Outlook Client Versions'
22
23.INPUTS
24    String
25
26.OUTPUTS
27    PSCustomObject
28
29.NOTES
30    Author:  Mike F Robbins
31    Website: http://mikefrobbins.com
32    Twitter: @mikefrobbins
33#>
34
35    [CmdletBinding()]
36    param (
37        [Parameter(Mandatory,
38                   ValueFromPipeline)]
39        [ValidateScript({
40            Test-Path -Path $_ -PathType Leaf -Include '*.log'
41        })]
42        [string[]]$LogFile
43    )
44
45    PROCESS {
46        foreach ($file in $LogFile) {
47            $Headers = (Get-Content -Path $file -TotalCount 5 | Where-Object {$_ -like '#Fields*'}) -replace '#Fields: ' -split ','
48
49            Import-Csv -Header $Headers -Path $file |
50            Where-Object operation -eq 'Connect' |
51            Select-Object -Unique -Property @{label='User';expression={$_.'client-name' -replace '^.*cn='}},
52                                            @{label='DN';expression={$_.'client-name'}},
53                                            client-software,
54                                            @{label='Version';expression={$_.'client-software-version'}},
55                                            client-mode,
56                                            client-ip,
57                                            protocol
58        }
59    }
60}

Piping this command to Out-GridView provides the same output in a similar looking interface as what was seen in the blog article mentioned earlier that was linked to.

1Get-ChildItem -Path '\\mail01\c$\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\*.log' |
2Get-MrRCAProtocolLog |
3Out-GridView -Title 'Outlook Client Versions'

rcaprotocol-log1.png

While you could accomplish this task as shown in the previous example, you would be pulling all of the log files across the network only to process and sort them down on your computer which may not make your network administrators too happy. A better approach might be to add the function to a module on the Exchange server.

In the following example, we've done just that. The function has been added to a module that exists on the actual Exchange server in a location specified in the $env:PSModulePath and PowerShell version 3 introduced module auto-loading so no need to explicitly import the module. We'll take advantage of PowerShell remoting so the filtering and processing is performed on the Exchange Server itself. Notice that we're only grabbing the log files that have been modified in the last 90 days in this example. Using this method, only the results are transmitted across the network to our workstation. We've also specifed alternate credentials for the command to be run on the Exchange server since being logged into your workstation or even running PowerShell with domain elevated credentials is trouble waiting for a place to happen.

1Invoke-Command -ComputerName mail01 {
2Get-ChildItem -Path 'C:\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\*.log' |
3Where-Object LastWriteTime -gt (Get-Date).AddDays(-90) |
4Get-MrRCAProtocolLog
5} -Credential (Get-Credential) |
6Out-GridView -Title 'Outlook Client Versions'

rcaprotocol-log5.png

Here's the type of output that is generated and it's easy enough to sort inside of Out-GridView instead of manually adding that to the PowerShell function itself which would adversely affect the performance since all of the results would have to finish processing before any of them would be displayed.

rcaprotocol-log2b.png

While that's nice and all, why stop there when we have the Power of PowerShell (with a capital "P" no less) at our fingertips?

Not sure about you, but 15.0.4615.1000 and 12.0.6672.5000 doesn't mean much to me other than being a manual process to search TechNet to determine what versions of Outlook those are for each and every version, each time we run this. If you're going to do something more than once, do it in PowerShell and be done with it <period>. What do I mean? Look up the build number to version translation once and write another reusable PowerShell function to automatically perform the translation for us from now on.

A simple helper function that translates the Outlook build number to the actual version has been created and minor modifications have been made to the original function as shown below. I've separated the function to translate the build number to the version to also make it into a reusable function in case I ever want to use it with another process.

 1#Requires -Version 3.0
 2function Get-MrRCAProtocolLog {
 3
 4<#
 5.SYNOPSIS
 6    Identifies and reports which Outlook client versions are being used to access Exchange.
 7
 8.DESCRIPTION
 9    Get-MrRCAProtocolLog is an advanced PowerShell function that parses Exchange Server RPC
10    logs to determine what Outlook client versions are being used to access the Exchange Server.
11
12.PARAMETER LogFile
13    The path to the Exchange RPC log files.
14
15.EXAMPLE
16     Get-MrRCAProtocolLog -LogFile 'C:\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\RCA_20140831-1.LOG'
17
18.EXAMPLE
19     Get-ChildItem -Path '\\servername\c$\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\*.log' |
20     Get-MrRCAProtocolLog |
21     Out-GridView -Title 'Outlook Client Versions'
22
23.INPUTS
24    String
25
26.OUTPUTS
27    PSCustomObject
28
29.NOTES
30    Author:  Mike F Robbins
31    Website: http://mikefrobbins.com
32    Twitter: @mikefrobbins
33#>
34
35    [CmdletBinding()]
36    param (
37        [Parameter(Mandatory,
38                   ValueFromPipeline)]
39        [ValidateScript({
40            Test-Path -Path $_ -PathType Leaf -Include '*.log'
41        })]
42        [string[]]$LogFile
43    )
44
45    PROCESS {
46        foreach ($file in $LogFile) {
47            $Headers = (Get-Content -Path $file -TotalCount 5 | Where-Object {$_ -like '#Fields*'}) -replace '#Fields: ' -split ','
48
49            Import-Csv -Header $Headers -Path $file |
50            Where-Object {$_.operation -eq 'Connect' -and $_.'client-software' -eq 'outlook.exe'} |
51            Select-Object -Unique -Property @{label='User';expression={$_.'client-name' -replace '^.*cn='}},
52                                            @{label='DN';expression={$_.'client-name'}},
53                                            client-software,
54                                            @{label='Version';expression={Get-MrOutlookVersion -OutlookBuild $_.'client-software-version'}},
55                                            client-mode,
56                                            client-ip,
57                                            protocol
58        }
59    }
60}
61
62function Get-MrOutlookVersion {
63    param (
64        [string]$OutlookBuild
65    )
66    switch ($OutlookBuild) {
67        {$_ -ge '15.0.4569.1506'} {'Outlook 2013 SP1'; break}
68        {$_ -ge '15.0.4420.1017'} {'Outlook 2013 RTM'; break}
69        {$_ -ge '14.0.7015.1000'} {'Outlook 2010 SP2'; break}
70        {$_ -ge '14.0.6029.1000'} {'Outlook 2010 SP1'; break}
71        {$_ -ge '14.0.4763.1000'} {'Outlook 2010 RTM'; break}
72        {$_ -ge '12.0.6607.1000'} {'Outlook 2007 SP3'; break}
73        {$_ -ge '12.0.6423.1000'} {'Outlook 2007 SP2'; break}
74        {$_ -ge '12.0.6212.1000'} {'Outlook 2007 SP1'; break}
75        {$_ -ge '12.0.4518.1014'} {'Outlook 2007 RTM'; break}
76        Default {'Unknown'}
77    }
78}

Now we have a human readable version of Outlook displayed in the results.

rcaprotocol-log3.png

Based on those results, I have users using unpatched versions of Outlook 2007 and 2013. Sounds like it's time to enter a help desk ticket for the desktop group to get that corrected.

The nice thing about writing a reusable function like this is it's multipurpose so I can use it not only with Out-GridView, but since its output produces objects, the results can be sent to a text, CSV, or HTML file, an email message, or worked with further such as to uniquely return a list of all Outlook client versions that have accessed this Exchange Server.

1Get-ChildItem -Path '\\mail01\c$\Program Files\Microsoft\Exchange Server\V15\Logging\RPC Client Access\*.log' |
2Get-MrRCAProtocolLog |
3Select-Object -Property Version -Unique |
4Sort-Object -Property Version -Descending

rcaprotocol-log4.png

How long did it take to create these functions? Not long as I had previously done something similar with IIS log files.

µ