A while back I wrote an article named Exchange Meeting Room Statistics about a script to gather statistics regarding Exchange meeting room usage for MSExchange.org. For this script to work, we have to give ourselves FullAccess to the meeting rooms’ mailbox, add them into our Outlook profile, and then use an Outlook COM Object to connect to Outlook and gather this information. Far from ideal, especially when trying to analyse dozens of rooms!
UPDATE (15/12/2017): I have updated the script to also work with Exchange Online (Office 365). If you want to analyse meeting rooms in EXO, simply add the -ExchangeOnline switch when running the script.
UPDATE (March/2020): I have finally written this newer version, specifically targeted at Exchange Online only, this time using Graph API! Please check it here.
This new EWS script, available in GitHub, will gather statistics such as the number of meetings during the specified times, the total and average meeting duration (in minutes), the total and average number of attendees, how many meetings started in the morning and afternoon, how many recurring meetings, and the 5 five organizers and attendees. It will export all the stats to a CSV file and also print in on the screen:
PS C:\Scripts\> .\Get-MeetingRoomStats.ps1 -RoomListSMTP "room.1@domain.com, room.2@domain.com" -From "03/01/2017" -To "04/01/2017"
From : 01/Mar/17 0:00:00
To : 01/Apr/17 0:00:00
RoomEmail : room.1@domain.com
RoomName : IT - 16 Floor - Room 16.23
Meetings : 104
Duration : 4920
AvgDuration : 47
TotAttendees : 442
AvgAttendees : 4
RecAttendees : 383
OptAttendees : 59
AMtotal : 46
AMperc : 44
PMtotal : 58
PMperc : 56
RecTotal : 38
RecPerc : 37
TopOrg : user.1@domain.com (12), user.2@domain.com (9), user.3@domain.com (9), user.4@domain.com (7), user.5@domain.com (5), user.6@domain.com
(4), user.7@domain.com (4), user.8@domain.com (4), user.9@domain.com (4), user.10@domain.com (3),
TopAtt : user.2@domain.com (25), user.4@domain.com (23), user.1@domain.com (19), user.3@domain.com (16), user.11@domain.com (16),
user.12@domain.com (15), user.9@domain.com (12), user.13@domain.com (11), user.14@domain.com (9), user.15@domain.com (9),
From : 01/Mar/17 0:00:00
To : 01/Apr/17 0:00:00
RoomEmail : room.2@domain.com
RoomName : IT - 16 Floor - Room 16.24
Meetings : 121
Duration : 6178
AvgDuration : 51
TotAttendees : 570
AvgAttendees : 5
RecAttendees : 537
OptAttendees : 33
AMtotal : 45
AMperc : 37
PMtotal : 76
PMperc : 63
RecTotal : 42
RecPerc : 35
TopOrg : user.16@domain.com (9), user.17@domain.com (8), user.10@domain.com (8), user.18@domain.com (7), user.19@domain.com (6),
user.20@domain.com (5), user.21@domain.com (5), user.22@domain.com (4), user.6@domain.com (4), user.23@domain.com (4),
TopAtt : user.24@domain.com (22), user.4@domain.com (20), user.17@domain.com (17), user.25@domain.com (16), user.16@domain.com (15),
user.11@domain.com (12), user.26@domain.com (12), user.21@domain.com (12), user.27@domain.com (11), user.28@domain.com (11),
You can download the script from here.
Thank you for this! It's great. I was having some trouble trying to get it to work with a large number of rooms. We have a request for a report like this for all of our rooms at one campus and that is over 300 conference rooms. I am sure I am over complicating this. I appreciate any help you can provide.
ReplyDeleteHi,
DeleteThank you! :)
Not a problem, that is not too hard. Instead of:
# Initialize some variables that will be used later in the script
[Array] $roomsCol = @()
# Connect to Exchange Server
$service = Connect-Exchange -Mailbox ($RoomListSMTP.Split(",")[0])
ForEach ($room in $RoomListSMTP.Split(",") -replace (" ", "")) {
You can do something like:
# Initialize some variables that will be used later in the script
[Array] $roomsCol = @()
$rooms = (Get-Mailbox -RecipientTypeDetails RoomMailbox -ResultSize Unlimited).primarySmtpAddress
# Connect to Exchange Server
$service = Connect-Exchange -Mailbox ($rooms[0])
ForEach ($room in $rooms) {
But you’ll have to run it from the Exchange server or from your workstation using the EMS.
You can also use a Do/While statement to get stats for each room per day for example. You just need to increment the $To and $From and the end of that statement for example.
Hope this helps!
Regards,
Nuno
Hi,
ReplyDeletethe script is awsome but with our exchange 2010 it returns 0 for meetings, duration and everything else. Only the rooms e-mail adress and room number is correctly returened. where can i look for bugfixing?
thanks
martin
Hi,
DeleteThank you! :)
Are you getting any errors when using the -Verbose parameter? If the script is not able to connect to Exchange or to a meeting room's calendar, it should throw an error. Do you have the required permissions to the meeting rooms' Calendar?
Try printing the result of $service after it is initiated to ensure you are indeed connected to Exchange.
Regards,
Nuno
Hi Nuno,
ReplyDeletegreat post.
Is it possible to run the above script to collect the room mailbox utilaztion where my mailboxes are there in 0365.
Please do confirm or what chaanges do we need to done as we have 2013 exchange hybrid model .
Thank you,
Druva
Hi Rangitha,
DeleteSorry for the delay in replying. Thank you for the feedback! :)
Please download the latest version of the script as I have just updated it to work with meeting rooms in Exchange Online!
Regards,
Nuno
Hi Nuno,
ReplyDeletegreat artical.
Could let us know how do we run the same above script with 0365 as we have 2013 exchange with hybrid model and all our room mailboxes on 0365.
Please help me.
Thank you,
Druva
Hi Druva,
DeleteSorry for the delay in replying. Thank you for the feedback! :)
Please download the latest version of the script as I have just updated it to work with meeting rooms in Exchange Online!
Regards,
Nuno
If you want to run it in Exchange 2010 and Win Server 2008 R2, you need do below adjustment.
ReplyDeleteInstll powershell 4.0
Add below code to Turst all certificate
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=@'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'@
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
# create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
Install EWS Web Api 1.2 instead of 2.0 or 2.2
Warm Regards
NNIT-Messaging team
what needs to be different for this script to connect to Exchange Online?
ReplyDeleteHi,
DeleteSorry for the delay in replying. Please download the latest version of the script as I have just updated it to work with meeting rooms in Exchange Online!
Regards,
Nuno
getting the following error:
ReplyDeleteUnable to connect to roomname@domain.com. Please check Permissions: Exception calling "Bind" with "2" argument(s): "Exchange Server doesn't support the requested version." Skipping roomname@domain.com.
Notes:
1) Using an account that is a member of Organization Management and has Full Access to mailbox.
2) Tried on multiple mailboxes -- same error.
3) output of $service variable is nothing
4) using Exchange 2010 SP3 Rollup 15 with EWS API 2.2.
Any ideas?
Hi Steve,
DeleteIn the Connect-Exchange function, have you updated the Exchange version in the "$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)" line?
Regards,
Nuno
I've got a CSV of a couple of thousand rooms, what would be the simplest way to import the list? It's in SMTP format.
ReplyDeleteHi Derek,
DeleteI would use Import-CSV and then a ForEach to call the script for each room in your CSV file. You would also need to update the last line when the data gets exported to a CSV file to either append it all to the same file, or to create a new file for each meeting room.
Hope this helps!
Regards,
Nuno
Thanks a lot, it's super
DeleteI had problem with AM/PM resolution when other as EN culture set, so I changed it to:
(Get-Date $meeting.Start -UFormat %H) -lt "12")
When you need to report all rooms, you can read them directly from AD with this command:
(Get-ADuser -Filter {msExchRecipientTypeDetails -eq 16} -Properties EmailAddress).EmailAddress
(They have to be created as RoomType mailboxes)
Beedo
Nice! Thanks for the tip Beedo! :)
DeleteHello Nuno,
ReplyDeleteI'm duplicating this message from TechNet Gallery:
I've tried to run your script on my work space but it gives me an error:
"Unable to connect ***@***.com. Please check permissions: Exception calling
"Bind" with "2" argument(s): "Exchange Server doesn't support the requested version.". Skipping ***@***.com."
We use Exchange 2010 SP3, so I've already changed your code for it. We also don't use Exchange Online.
What do you think could be the source of this error?
Thanks,
Avi711
Man, you are life saver! Thanks!
ReplyDeleteMan, you are lifesaver! Thanks!
ReplyDeleteHehe, thank you! :) Don't forget to check the new version that uses Graph API: https://letsexchange.blogspot.com/2020/03/exchange-online-meeting-room-statistics.html
DeleteI have error below if I try run it with O365
ReplyDeleteVERBOSE: Unable to connect to level@kross.pl. Please check permissions: Exception calling "Bind" with "2" argument(s): "The SMTP address has no mailbox associated with it.". Skipping level@kross.pl.
Can you help me?
As the error suggests, it seems that a room with that address does not exist. Have you double-checked it?
DeleteI have hybrid setup and Mailbox are on Exchange 2013 Version 15.0
ReplyDeleteShould I add like this
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange15.00)
No, just add "Exchange2013"
DeleteHi ,
ReplyDeleteOn running the script i get error : Unable to connect to xyz@domain.com. Please check permissions: Exception calling "Bind" with "2"
argument(s): "The request failed. The remote server returned an error: (401) Unauthorized.". Skipping xyz@domain.com.
I have hybrid environment, all the room mailbox is synced with AD, while i have Full Access on Mailbox and Reviewer Access on Calendar.
Hi,
DeleteIs the mailbox you are trying to connect to on-prem or cloud? I would say this is most likely permissions!
am getting error VERBOSE: Unable to connect to room@xxxx.org Please check permissions: Exception calling "Bind" with "2" argument(s): "The SMTP address has no mailbox associated with it.". Skipping room@xxxx.org. am running 2010 sp3 Hybrid, account used has full permission as it accessible on outlook.
ReplyDeleteAre you sure you typed the correct SMTP address for the meeting room? Try: "Get-Mailbox room@xxxx.org" and see if it resolves to the meeting room you want.
DeleteThis comment has been removed by the author.
ReplyDeleteHi Nuno,
ReplyDeleteRunning this in windows powershell from my PC, I get the error "unable to connect to the (mailbox SMTP) that I have full access to and then it says Exchange server doesn't support the requested version" You can't run Get-mailbox in windows PS
Hi. Yes, you need to run the script from the Exchange Management Shell.
DeleteIm running into an errow where "Exception calling "AutodiscoverUrl" with "1" argument(s): "Autodiscover blocked a potentially insecure redirection to
ReplyDeletehttps://autodiscover-s.outlook.com/autodiscover/autodiscover.xml."
Is there a workaround for this?
I don't think your AutoDiscover is configured correctly... Where is the internal record pointing to? Also, is the mailbox you want to get stats from on-prem or cloud?
DeleteHello, I am getting the following error:
ReplyDeleteVERBOSE: Unable to connect to LIB-PTM-Conference-Room@mydomain.com. Please check permissions: Cannot convert argument "1", with value: "LIB-PTM-Conference-Room@mydomain.com", for "FolderId" to type"Microsoft.Exchange.WebServices.Data.Mailbox": "Cannot convert the "LIB-PTM-Conference-Room@mydomain.com" value of type "Microsoft.Exchange.Data.SmtpAddress" to type Microsoft.Exchange.WebServices.Data.Mailbox".". Skipping
I have verified I have granted full access mailbox permission on the room mailboxes, and also "view only organization management" role. I am also running this from a 2012R2 server with the Exchange tools and Exchange Managed services API 2.2 installed, from the Exchange shell as administrator.
Hi,
DeleteDid you change the script at all? How are you calling the script?
Please make sure you are using the latest version from here: https://gallery.technet.microsoft.com/Exchange-Meeting-Room-2aab769a
For me it's not producing any results. Nothing happens after command execution. I get get-cred window, where I enter my cloud permissions and that's it. CSV file is completely blank too.
ReplyDeleteHi. Are you using the latest version from TechNet?
Deletehi, i have an error when i try to connect to O365. I use MFA, it's probably the problem ? I don't have any error on environment with no MFA. thanks
ReplyDeleteHi. Yes, it will be. But if you authenticate using the PowerShell module for MFA, you can then run the script. Is that not working?
DeleteHi, Thank you for the script! I can't connect to most of the user calendars with this error, some work, but most do not. They do exist and have calendars. I'm a global admin. Any pointers?
ReplyDelete-----------
VERBOSE: Binding to the user@domain.org Calendar folder.
VERBOSE: Unable to connect to user@domain.org. Please check permissions: Exception calling "Bind" with "2" argument(s): "The specifie
d folder could not be found in the store.". Skipping user@domain.org.
-------------
Hi, You're welcome! :) If some work and others don't, then it is either permissions or the wrong SMTP address. Could you please check these two?
DeleteHi, I have 5 rooms that i want the stats from. But the problem is I want it day by day
ReplyDeleteSo day 1
Room 1
Room 2
Room 3
Room 4
Room 5
Day 2 etc
But if I run the script I have to login again if I do day two. Is it possible to skip that once connected to office 365 powershell?
Hi Mitchel,
DeleteYes! Instead of using "$cred = Get-Credential" you could export your credentials to a secure XML file and then import them every time you run the script, without having to manually type your username and/or password :)
For example: https://www.jaapbrasser.com/quickly-and-securely-storing-your-credentials-powershell/
Regards,
Nuno
Getting error message "The response received from the service didn't contain valid XML" Please advise.
ReplyDeleteIs that for on-prem or cloud? For cloud, I have released a new version that uses Graph API instead: https://gallery.technet.microsoft.com/scriptcenter/Exchange-Online-Meeting-4894b38f
DeleteSince Technet has been removed. please send me the new location of the script
ReplyDeleteHi Brijesh,
DeletePlease check the latest Graph API version in this post: https://letsexchange.blogspot.com/2020/03/exchange-online-meeting-room-statistics.html
Best regards,
Nuno
Hi Nuno, Thank you for your reply. If possible can you provide me the onprem script for the same.
ReplyDeleteHi Brijesh,
DeleteSure, you can find it here: https://github.com/NunoFilipeMota/PublicScripts/blob/main/GetMeetingRoomStats_EWS.ps1
Regards,
Nuno
Thank you Nuno but it seems some error with -all parameter. I get below error message
ReplyDeleteC:\Secure\Get-MeetingRoomStats.ps1 : A parameter cannot be found that matches parameter name 'All'.
Hi Brijesh. This script does not have that parameter. You have to specify either a file with the addresses of the rooms you want to analyse with the -RoomListFile, or use the -RoomListSMTP to specify the rooms when calling the script.
DeleteHello, perfect dcript , rly, is it possible to do statistics per hour to find out which time is the busiest? exchange 2016
ReplyDeleteThanks! :)
DeleteYes, it is possible, but it won't be straightforward... You'll probably have to add a loop inside "ForEach ($meeting in $fiItems.Items) {" to check for meetings at each our, and then move the "$romObj = New-Object PSObject -Property @{" to inside this ForEach.
Regards,
Nuno
Any way we could get this original script cant seem to find the code anywhere.
ReplyDeleteHi Erwin. The very first version of the script is available in the article mentioned right at the beginning of the post. For the EWS and Graph API versions, there are links in the post.
DeleteRegards, Nuno
Hi, very interesting script. New to all this. Script does not appear to find EWS Managed API which I see installed in the default location at C:\Program Files\PackageManagement. How do I include this location in where the script looks for the .dll?
ReplyDeleteHi Andreas. That's strange as the script should detect it through the Registry... If you want to manually specify it's location, you can just update the $EWSdll variable, or replace the whole function with something like:
Delete$WebServicesdll = "C:\Program Files\Microsoft\Exchange\Web Services\x.x\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($WebServicesdll)
hello, in comment, you had specified " 0.3 - 201911 - Added "-All" switch to automatically process all meeting rooms in the environment" but this parameter is not recognised.
ReplyDeleteHi. Are you trying to use the EWS or Graph API version? If Graph API, are you using this one: https://github.com/NunoFilipeMota/PublicScripts/blob/main/GetMeetingRoomStats_GraphAPI.ps1 ?
DeleteHello, Does this script support exchange 2016 CU22 ?I am trying to run this script against E2k16 servers, i am not getting error while executing's but the excel file is empty. I have got around 200+ meeting rooms. Any help will be appreciated
ReplyDeleteHi Nuno , i am trying to use get room statistics using EWS . i am getting below error - i checked that there is no firewall/Azure conditional access policy error. i changed my domain to domain.com to hide my domain. Please do suggest. Thank you always for script, it worked for two years for me.
ReplyDelete==============================================================
.\Get-MeetingRoomStats.ps1 -RoomListSMTP IRV_RH_BIG2_FL2_Conf_Rm_A@domain.com
Exception calling "AutodiscoverUrl" with "1" argument(s): "Autodiscover blocked a potentially insecure redirection to
https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml. To allow Autodiscover to follow the redirection, use the AutodiscoverUrl(string,
AutodiscoverRedirectionUrlValidationCallback) overload."
At C:\Manoj\Room scripts\Get-MeetingRoomStats.ps1:105 char:3
+ $service.AutodiscoverUrl($Mailbox)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : AutodiscoverLocalException
Hi. If you want to run the script against Exchange Online, please use this version of the script instead that uses Graph API: https://letsexchange.blogspot.com/2020/03/exchange-online-meeting-room-statistics.html
DeleteHi i downloaded the latest version from Github and i am able to connect to O365 when i run the script with -All it finds 10 meetings rooms but fails with "Unable to query Graph API: 'Response status code does not indicate success: 404 (Not Found).'"
ReplyDeleteHi. If you want to run the script against Exchange Online, please use this version of the script instead that uses Graph API: https://letsexchange.blogspot.com/2020/03/exchange-online-meeting-room-statistics.html
DeleteHi. Seems to be a lot of confusion around the usage of the '-All' parameter to capture all rooms. I'm having the same problem while using the EWS version with Exchange 2016. The '-All' parameter is listed as working according to the notes of the script but it isn't recognized when running the script, i'm getting the same error.
ReplyDeleteim getting a the ampersand & character is not allowed. the & operator is reserved for future use; wrap an ampersand in double quatation marks to pass it as part of a string error.
ReplyDelete