Inventorying what extensionAttributes are used in your Active Directory Environment

Recently we my team was asked what extensionAttributes were in use in my company’s Active Directory environment so I wrote this quick PowerShell script to search for any filled in extensionAttribute and compile a list of that objects

  • Name
  • ObjectClass
  • The name of the filled in extenstionAttribute
  • That attributes value

Then compile a summary list of all filled in attributes that provides

  • The name of the filled in extensionAttribute
  • How many objects it was found filled in on
  • The total unique filled in values found
  • The most frequent filled in value

as well as a list of extensionAttibutes not filled in

PowerShell Script

#Load the Active Directory Module
#This could be done with ADSI accelrator as well, but this is much easier to work with
Import-Module ActiveDirectory -ErrorAction Stop

#Table of known attrbitues
$AttributeTable = @{
    extensionAttribute = 1..15
    msExchExtensionAttribute = 16..45
    msExchExtensionCustomAttribute = 1..5

#Array to hold unused attributes
$EmptyList = @()

#Array list to hold all objects with at least one attibute filled in
$ExtensionEntries = [System.Collections.ArrayList]@()

foreach ($Attribute in $AttributeTable.GetEnumerator()) {
    [String]$AttributeBase = $Attribute.Name
    $Attribute.value |
        ForEach-Object {
            $CurrenntAttribute = "$AttributeBase$_"
            [array]$current = Get-ADObject -Filter "$CurrenntAttribute -like '*'" -Properties $CurrenntAttribute |
                Select-object Name, ObjectClass, @{N='AttributeName';E={$CurrenntAttribute}}, @{N='AttributeValue';E={$_.$CurrenntAttribute}}
            if ($current.count -ne 0) {
            Else {

Write-output "--Currently used Attributes--"
$ExtensionEntries |
    Group-Object AttributeName |
    Sort-Object Count -Descending |
    Select-Object Name, Count, @{N = 'TotalUniqueValues'; E = { ($_.Group.AttributeValue | Sort-Object -Unique).count } }, @{N = 'MostFrequentValue'; E = { ($_.Group.AttributeValue | Group-Object | Sort-Object Count -Descending | Select-Object -First 1).Name}}

Write-output "`n--Unused Attributes--"
$EmptyList | Sort-object

Example Output

--Currently used Attributes--

Name                            Count TotalUniqueValues MostFrequentValue
----                            ----- ----------------- -----------------
msExchExtensionAttribute16      18849              6007 {...
extensionAttribute1              1194               226 Dell Inc.
extensionAttribute3              1015               713 4 Seats
extensionAttribute2               934                71 Precision Tower 3420
extensionAttribute4               875               607 No Asset Tag
extensionAttribute10              721               310 CONTOSO
extensionAttribute5               687               389 20221119
extensionAttribute14              224               151 06/17/2010
extensionAttribute15              120                 8 PreDefault
msExchExtensionAttribute17         83                 1 False
extensionAttribute9                60                23 USA
extensionAttribute12               51                 1 MimeCastUSA
extensionAttribute13               17                 4 USR
msExchExtensionCustomAttribute1     3               989 moore
msExchExtensionAttribute45          1                 1 {"Account Name":"ScriptRunner","Account Type":"task","Description":"Run various automations","Application Name":"n/a","Windows Service Name":"n/a","Servers Windows Service will ru...

--Unused Attributes--
Posted in Active Directory, Exchange, PowerShell | Leave a comment

Removing certain email addresses by domain or prefix from all mail objects in Exchange on-premises

Over the summer my company moved our email ingress and egress from on premises Cisco IronPort Gateways to Exchange Online Protection. While this moved us to a modern and cloud-based mail hygiene solution, we did lose some of custom mail routing schemes we implemented when we had full control of our mail flow. One such custom feature was the ability to selectively block SMTP addresses on any given mail object in our on-premises Exchange environment from external mail without having to block all external mail to the object.

This was accomplished by adding a second proxy address for each address we wanted to block from receiving external email at the Cisco Iron port level with ‘HIDE’. Such that when our Cisco IronPort’s did an LDAP lookup for an internal proxy address, if the same address was returned twice, (an SMTP and HIDE entry) then drop the message since that address should be internal only. In this example of the proxyaddresses on a given mail object: would be blocked at the Cisco IronPort level since an LDAP search would return the following two addresses

The remaining address would be allowed through

Once we moved to Exchange Online Protection, and the HIDE proxy addresses no longer functioned, we had to clean-up all the entries we had in our environment. To do so we created an Exchange Management Shell PowerShell script to do the following

  1. Run the script from the Exchange management shell
    1. The script assume you are getting non-deserialized objects. If you are not using the Exchange Management shell, then you will need to update it to take into account that you are getting deserialized objects
  2. Ensure you are viewing the entire forest
    1. This was needed for us since we have Exchange on premises in a resource domain and thus our public folders are in this domain while everything else is in a separate domain
  3. Grab all recipients
  4. Check if there are any HIDE proxyaddresses on the mail object. If not, then skip if so then
  5. Determine the Recipient detail type so we know which Set-* command to use to remove the addresses
    1. We could have just used Set-ADObject and instead to update the proxyaddresses property, but I wanted to keep this strictly within the commands available to Exchange Administrator
    1. In our environment we had a lot of contacts that required updating before we could edit the email addresses. So while we tried to force an update whenever we came across one, it still required manual intervention at the console to approve the change. We only had a few dozen in this state so after coming across the first one we filtered them out in the big run and then addressed them and manually accepted the update. In this case Set-ADObject would have allowed us to automate it, but wouldn’t fix the underlying need to update them
  6. Save the results to an array for later referencing in case we need to roll back

Outside the need to update our mail contacts here are some issues we ran into while running the script

  1. We encountered a number of distribution groups that gave the following error when we tried to remove the addresses : ‘Members can’t add themselves to security groups. Please set the group to Closed or Owner Approval for requests to join.’
    • In some research this was due to the fact that the distribution group was flipped to a Security using ADUC (Active directory users and computers) or vice verse, as opposed to done in Exchange
    • This put the distribution group on an inconsistent state as far as Exchange was concerned and we needed to run the following parameters when running the set-distributiongroup with the following parameters: -MemberJoinRestriction closed -MemberDepartRestriction closed
  2. We had exactly 1 object with a plus sign (‘+’) in it’s name, which the various set-* cmdlets do not like when using a distinguished name with a plus sign. In this case switching the script to leverage the Alias instead for each set-* cmdlet fixed that issue


#This will ensure mail objects in other forests are included
Set-ADServerSettings -ViewEntireForest:$TRUE
$AllObjects = Get-Recipient -ResultSize Unlimited
[System.Collections.ArrayList]$Changelist = @()

ForEach ($MailObject in $AllObjects) {
    $TableEntry = [PSCustomObject]@{
        Name        = $MailObject.Name
        DistinguishedName = $MailObject.DistinguishedName
        PreviousEmailAddresses = $MailObject.EmailAddresses
        RecipientTypeDetails = $MailObject.RecipientTypeDetails
        RemovedAddress      = ""
        Result = "UNPROCESSED"
    }#$TableEntry = [PSCustomObject]@
    #If you were looking for specific doamin you could use a regex like such
    #[String[]]$ProxyAddressesToRemove = ($MailObject.EmailAddresses | Where-object SMTPAddress -match '^.*@DOMAIN\.com$').ProxyAddressString
    [String[]]$ProxyAddressesToRemove = ($MailObject.EmailAddresses | Where-object prefix -eq 'hide').ProxyAddressString
    If ($ProxyAddressesToRemove.count -ne 0) {
        $TableEntry.RemovedAddress = $ProxyAddressesToRemove
            switch -regex ($MailObject.RecipientTypeDetails) {
                '^(MailUniversalSecurityGroup|MailUniversalDistributionGroup|RoomList)$' { $SetCommand = "Set-DistributionGroup"; break }
                '^(UserMailbox|TeamMailbox|SharedMailbox|RoomMailbox|EquipmentMailbox|DiscoveryMailbox)$' { $SetCommand = "Set-Mailbox"; break }
                'MailUser' { $SetCommand = "Set-MailUser"; break }
                '^(RemoteUserMailbox|RemoteRoomMailbox|RemoteEquipmentMailbox|RemoteSharedMailbox)$' { $SetCommand = "Set-RemoteMailbox"; break }
                'MailContact' { $SetCommand = "Set-MailContact"; break }
                'PublicFolder' { $SetCommand = "Set-MailPublicFolder"; break }
                'DynamicDistributionGroup' { $SetCommand = "Set-DynamicDistributionGroup"; break }
                Default {Throw "Unhandled RecipientTypeDetails: $($MailObject.RecipientTypeDetails)"}
            }#switch -regex ($MailObject.RecipientTypeDetails) {
            #We noticed that a good majority of the mail contacts in our enviroment were of 'ExchangeVersion' '0.0 (6.5.6500.0)'
            #Any attempt to update them resulted in a prompt to update, so in all instances we force an upgrade to our current version, which is '0.20 ('
            if ($SetCommand -ne 'Set-MailContact"' ) {
                .$SetCommand $MailObject.DistinguishedName -EmailAddresses @{Remove=$ProxyAddressesToRemove} -erroraction stop
            }#if ($SetCommand -ne 'Set-MailContact"' ) {
            Else {
                .$SetCommand $MailObject.DistinguishedName -EmailAddresses @{Remove=$ProxyAddressesToRemove} -ForceUpgrade:$true -Confirm:$false -erroraction stop
            $TableEntry.Result = 'HIDES REMOVED'
        Catch {
            $TableEntry.Result = $_
        $Changelist.add($TableEntry) | Out-Null
    }#If ($ProxyAddressesToRemove.count -ne 0) {
        $TableEntry.Result = 'NO ACTION NEEDED'
        $Changelist.add($TableEntry) | Out-Null
}#ForEach ($MailObject in $AllObjects) {
Posted in Uncategorized | Tagged , | Leave a comment

Removing 1000’s of folders in an on oprem Exchange Mailbox via EWS

Recently our helpdesk received a report of a user leveraging Outlook 2013 (x86) in a VM on a Mac to access a local mailbox (i.e. Not O365) crashing with an out of memory error. Before they reached out to my team (which are the stewards of Exchange 2016 and O365) they tired the following

The issue only went away once we removed all attached shared mailboxes (2 total). When adding them back, we noticed one of the shared mailboxes generated a lot of entries in the ‘Sync Issues’ folder. When we investigated the shared mailbox in question, it had 4207 user created folders. And while the user had access to this mailbox for years, it wasn’t until the gained access to another shared mailbox that this issue starting to occur. So we surmised that this mailbox in particular was the cause and was ripe for clean-up due to the amount of folders it contained. Using the get-mailboxfolderstatistics cmdlet we generated a CSV of all the user created folders along with some metrics around them for the owners of the shared mailbox to review for deletion

get-mailboxfolderstatistics PROBLEM_MAILBOX | Where-Object FolderType -eq "User Created" | Select Name, FolderPath, ItemsInFolder, ItemsInFolderAndSubfolders,@{N='FolderSize(MB)';E={$_.FolderSize.tomb()}}, @{N="FolderAndSubfolderSize(MB)";e={$_.FolderAndSubfolderSize.toMB()}}, NewestItemReceivedDate, OldestItemReceivedDate | Export-CsV -Path C:\Temp\FolderReview.csv

The owners then edited that list down to 3551 folders to delete. At that point we leverage EWS (Exchange Web Services) in PowerShell to delete the folders in question . This was a quick and dirty process as I didn’t have much time to create something more elegant. One of wish list items I had was figuring out how to properly weed out only top level folder paths to delete, but after spending 30 minutes trying to figure that out I decided it wasn’t worth the effort for this one off task. If I did delete a top level folder and the list had multiple child folders then our deletion attempt would just error out. We also used an account setup for application impersonation for this effort. Bare bones script below:

Make an Exchange EMS Connection
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri YOUR_EXCHANGE_PS_URL -Authentication Kerberos -Name Exchange -ErrorAction Stop
Import-PSSession $ExchSession -allowclobber -ErrorAction Stop -DisableNameChecking -WarningAction SilentlyContinue *> $NULL

#Variables for EWS connection
$ExchangeVersion = "Exchange2013"
[String]$EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

#Impersonation Account
$ImpersonationAccountNAme = "YOUR IMPERSONATION ACCOUNT"
$ImpersanationAccountPassword = "YOUR IMPERSONATION ACCOUNT"

#Mailbox clean up info
$TaggedFolders = Import-Csv -Path 'YOUR_CSV.CSV'
$AllFolders = get-mailboxfolderstatistics $Mailbox |
    Where-Object FolderType -eq "User Created"
$FoldersToRemove = $AllFolders |
    Where-Object FolderPath -in $TaggedFolders.FolderPath

# Load the needed dlls
Add-Type -Path $EWSManagedApiPath
Add-Type -AssemblyName System.Web

# Set EWS Service
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)  

# Setup impersonation
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName, $ImpersonationPassword) -ErrorAction Stop
$Service.ImpersonatedUserId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList "SmtpAddress", $Mailbox

$service.AutodiscoverUrl($Mailbox, { $true })  

#Function to convert the folderID from the Exchange Management shell to one we can us in EWS
function ConvertId {    
    param (
        $OwaId = "$( throw 'OWAId is a mandatory Parameter' )"
    process {
        $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId      
        $aiItem.Mailbox = $Mailbox     
        $aiItem.UniqueId = $OwaId   
        $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::OwaId 
        $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId) 
        return $convertedId.UniqueId

# Run through the folders ot delete
$FoldersToRemove | 
    ForEach-Object {
        Write-Output "Deleting Folder $($_.FolderPath)"
        try {
            $urlEncodedId = [System.Web.HttpUtility]::UrlEncode($_.FolderId.ToString())
            $folderid = New-Object Microsoft.Exchange.WebServices.Data.FolderId((Convertid $urlEncodedId))  
            $ewsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $folderid)        

            "Folder Deleted"
        catch {
            Write-Warning "Problems deleting $($_.FolderPath)"

Posted in Uncategorized | Tagged , | Leave a comment

Inventorying Exchange custom attribute (extensionAttribute#\CustomAttribute#) usage within your environment

My team recently fielded a request for an unused Exchange custom attribute to use for various automation tasks around AD user groups. Since multiple teams at my company have access to various types of AD objects, we decided to do a quick inventory of the usage of those various attributes. Some quick background on those attributes

In total there are 50 total attributes that Exchange uses to extend the AD schema:

  • extensionAttribute1 to extensionAttribute15 (String)
  • msExchExtensionAttribute16 to msExchExtensionAttribute45 (String)
  • msExchExtensionCustomAttribute1 to msExchExtensionCustomAttribute5 (String: Multi-Valued)

MS recommends sticking to 1-15 and the 5 extension ones. 16-45 might be used for future Exchange features, but I personally haven’t seen this.

in Exchange Online, the data from extensionAttribute# are stored as CustomAttribute#. The multi valued ones also get synced up as ExtensionCustomAttribute#

Normally, in an Exchange Hybrid environment, Azure AD connect will sync the attributes that appear in Exchange Online. You can also extend the rest of the extension attributes as well as any locally created AD attributes.

The following PowerShell code will generate a list of which objects have those 50 attributes filled

$AttributeTable = @{
    extensionAttribute = 1..15
    msExchExtensionAttribute = 16..45
    msExchExtensionCustomAttribute = 1..5

$ExtensionEntries = [System.Collections.ArrayList]@()

foreach ($Attribute in $AttributeTable.GetEnumerator()) {
    [String]$AttributeBase = $Attribute.Name
    $Attribute.value | 
        ForEach-Object {
            $CurrenntAttribute = "$AttributeBase$_"
            [array]$current = Get-ADObject -Filter "$CurrenntAttribute -like '*'" -Properties $CurrenntAttribute | Select-object Name, ObjectClass, $CurrenntAttribute, @{N='FilledAttribute';E={$CurrenntAttribute}}
            if ($current.count -ne 0) {

$ExtensionEntries | group-object FilledAttribute | Sort-object Name | Select-object Name, Count

The direct output will be like so:

Name                            Count
----                            -----
extensionAttribute1              1115
extensionAttribute2               842
extensionAttribute3               925
extensionAttribute4               799
extensionAttribute5               603
extensionAttribute9                63
extensionAttribute10              580
extensionAttribute12                1
extensionAttribute13               17
extensionAttribute14              235
extensionAttribute15              110
msExchExtensionAttribute16      17047
msExchExtensionAttribute17         72
msExchExtensionAttribute45          1
msExchExtensionCustomAttribute1     3

But you can also sort by ObjectClass, or any other properties you would like to add as part of the Get-ADObject call

Posted in Uncategorized | Tagged , , | Leave a comment

Hybrid free busy not working for just one user

A quick break down of the environment where this issue occurred

  • Office 365 in Hybrid with Exchange 2016
  • Local 2019 AD Domain with Azure AD Connect syncing to Office 365 every 30 minutes, with no AD write back
  • 350+ could mailboxes with 2200+ still on prem

I recently had an issue at work with my Exchange online mailbox not being able to pull free busy information from any of my company’s onprem mailboxes. My mailbox was migrated a little over 2 years ago and I’m not exactly sure when it stopped, but I only noticed as my company started to enact it’s return to work plan early in the summer of 2021 and I couldn’t see the free busy information of any of our conference rooms. All of which we haven’t migrated to Exchange Online yet. At first, I checked in with my teammates and discovered the issue was just with my account. From there I tried using the Microsoft Remote Connectivity Analyzer to troubleshoot, but got a generic error when attempting the various Exchange tests. I then checked for any odd attributes set on my account, but that turned up nothing obvious. I then tried doing a New-MoveRequest PowerShell cmdlet, which sometimes helps with fixing odd issues in Exchange Online. The hope in doing so is that the post move validations might correct whatever was causing the free buys look up issue. So, after exhausting all the options I could think of I opened a ticket with Microsoft Support. After going through the normal troubleshooting steps, they suggested that I clear out the following AD attributes and wait for a sync from our on premises AD to Azure AD

  • msExchRecipientDisplayType
  • msExchRecipientTypeDetails
  • msExchMailboxGuid

Before clearing these attributes, I made a note of the values and then waited for 2 Azure AD connect sync session. Unfortunately the issue persisted and my companies various other integrated SAAS platforms were throwing errors since those attributed weren’t populated for my account. At first, I tired running the Update-Recipient PowerShell cmdlet in both Exchange Online and Exchange on premises to see if it would re-populate those attributes. Sadly it did not so I added back the values I saved for each. About 24 hours later I checked if the issue was still present, and it was fixed! MS support wasn’t exactly sure why the issue was resolved, but the next time I run into an odd Exchange issue I’m going to try clearing those attributes first.

Posted in Uncategorized | Tagged , , | Leave a comment

Adventures in PowerShell dot sourcing

As my company has started our journey to Office 365, we have had to go back and update a lot of our daily checks, scripts, and automations we put in place for user accounts. As a specific example we have a disable user script that performs various actions on a user’s mailbox depending on the wishes of the manager of said user. Depending on where the mailbox is located (on premises or in the cloud), we need to use either the set-mailbox or set-remotemailbox commands. In order support both scenarios in our existing scripts we leveraged dot sourcing to store the needed commands per mailbox type to the same variables used throughout the script. So that when we need to perform those mailbox actions, we don’t need to add a separate if or case statement for each action for the location of the mailbox. We instead leverage the variable holding the command via dot sourcing. The snippet below shows that we use the msExchRemoteRecipientType attribute of the user to determine the location of the user mailbox, and then save the appropriate commands to the matching variables.

        #Checking which Set-*mailbox command to use depending on the user is on prem or in the cloud
        if ($FoundAccount.msExchRemoteRecipientType -ne $NULL) {
            Write-Verbose "$UserAccount : Has a remote mailbox, using *-remotemailbox for all Exchange tasks"
            $SetMBXCommand = "Set-RemoteMailbox"
            $GetMBXCommand = "Get-RemoteMailbox"
            $DisableMBXCommand = "Disable-RemoteMailbox"
        }#if ($FoundAccount.msExchRemoteRecipientType -ne $NULL) {
        Else {
            Write-Verbose "$UserAccount : Has a local mailbox, using *-mailbox for all Exchange tasks"
            $SetMBXCommand = "Set-Mailbox"
            $GetMBXCommand = "Get-Mailbox"
            $DisableMBXCommand = "Disable-Mailbox"

When performing an action on the user mailbox we do source the variable for the command, which will expand the variable and treat it as the command store in the variable.

.$DisableMBXCommand $UserAccount -ErrorAction Stop -Confirm:$FALSE
Posted in Exchange, Office 365, PowerShell | Leave a comment

Issues installing Windows Admin Center on Windows Server 2016

At my Job we have been playing around with Windows Admin Center, and while it’s pretty neat it has been a pain to install. We ran into multiple issues trying to get it to properly install in Gateway Mode on our Windows Servers 2016 management host that weren’t called out in the troubleshooting steps. Here are the steps we had to go through in order to get it to actually install

  1. You can’t install it over Remote Desktop
  2. You must have the following services enabled and running
    1. Windows Update
    2. Windows firewall

The last two were interesting since they aren’t really called out during the install. They are sorta called out in in the install log, but it’s not obvious that it’s directly related to the services not being enabled and running. Hopefully this helps out anyone else who has issues installing it on Windows server 2016.

Posted in Windows Admin Center | Leave a comment

Hunting down personal Microsoft accounts using an corporate Email address in O365

Recently my company started endeavored on setting up an O365 (Office 365) tenant in full Hybrid mode with our on-premise Exchange infrastructure. One of the issues we ran into as we started migrating pilot users was that some of our various Exchange integrations didn’t handle the authentication when the user in question had an existing personal Microsoft account associated with their corporate email address.  As an example, users in this state would se something like this when they logged into the O365 portal

My company leverages Blackberry UEM as our mobile email solution, and for any user migrated to Office 365 that was in this state could not user mobile email access via BlackBerry work due to the fact that it would receive this prompt and not be able to process it. We contacted MS and ask if we could be provided a list of all personal Microsoft accounts that were using a domain owned by my company, but for obvious legal reasons they could not. So the only option left to use was to test every email address in our environment against the office 365 login page ( After some testing we were able to properly inspect the login process and create a PowerShell script that could present an email address to this page and return not only if it was associated with a personal account but the federated gateway it was associated with. Here is a link to the Github repository for the PowerShell function in question. We tested it against about 8000 email addresses with no issues, but your mileage may vary.

Posted in Office 365 | Leave a comment

Properly scoping get-inboxrule for a custom RBAC role group

After creating a custom RBAC role in Exchange for our help desk so that they could handle simpler end user requests like mailbox size, conference room permissions, etc. after deploying it we got reports that the get-inboxrule command was not working as expected and was throwing a “You may need elevated permissions. isn’t within your current write scopes. Can’t perform save operation.” Error whenever a member of the help desk ran that command against another user. At first we weren’t exactly sure what the issue was because roles like “View-Only recipients” (which was one of the roles that was used to create the custom role group) seemed to be in the right scope for other commands. After some searching we came across a blog post by Pawet Jarosz explaining the problem. So we needed to recreate the role group with a new management role for get-inboxrule that was in the proper scope. Once we did that our help desk was now able to properly view other user’s inbox rules. Below is the custom role group we created

Add all the Get and test commands from “View-Only Configuration”
New-ManagementRole –Name “Helpdesk View-Only Configuration” –Parent “View-Only Configuration”
Get-ManagementRoleEntry “Helpdesk View-Only Configuration*” |
Where Name -notmatch ‘(Get)|(Test)|(Write-AdminAuditLog)|(Start-AuditAssistant)(Get-InboxRule)’ |
Remove-ManagementRoleEntry -Confirm:$False
Add all the Get and test commands from “View-Only recipients”
New-ManagementRole –Name “Helpdesk View-Only recipients” –Parent “View-Only recipients”
Get-ManagementRoleEntry ” Helpdesk View-Only recipients*” |
Where Name -notmatch ‘(Get)|(Test)|(Write-AdminAuditLog)|(Start-AuditAssistant)(Get-InboxRule)’ |
Remove-ManagementRoleEntry -Confirm:$False
Add all the Get and test commands from “Monitoring”
New-ManagementRole –Name “Helpdesk Monitoring” –Parent “Monitoring”
Get-ManagementRoleEntry ” Helpdesk Monitoring*” |
Where Name -notmatch ‘(Get)|(Test)|(Write-AdminAuditLog)|(Start-AuditAssistant)(Get-InboxRule)’ |
Remove-ManagementRoleEntry -Confirm:$False
Get-Inbox rules has scope issues so we need to create a separate role for that
New-ManagementRole –Name “Helpdesk View Inbox Rules” –Parent ‘Mail Recipients’
Get-ManagementRoleEntry ” Helpdesk View Inbox Rules*” |
Where Name -notmatch ‘Get-InboxRule’ |
Remove-ManagementRoleEntry -Confirm:$False
$GroupSplat = @{
Name = ‘Helpdesk Exchange Tasks’
Roles = @(” Helpdesk View-Only Configuration”, ” Helpdesk View-Only recipients”, ” Helpdesk Monitoring”, ” Helpdesk View Inbox Rules”)
Description = ” specific collection of cmdlets needed for view only Exchange management”
members = @(“Global Helpdesk”)
New-RoleGroup @GroupSplat

Posted in Exchange | Leave a comment

Checking Exchange IIS logs for users who downloaded files via OWA

At my work we use a 3rd party application that rides ontop of OWA in Exchange in order to block attachment downloads across certain IP ranges (namely all external access). We recently had an issue with it silently failing and we needed to figure out who might have downloaded attachments via OWA while the application was down. In looking over the IIS logs we determined that we could accurately figure this out by looking for

  1. Any cs-uri-stem like ‘/owa*Get*Attachment*’. In particualr these were the ones we want to capture
    1. /owa/service.svc/s/GetFileAttachment
    2. /owa/service.svc/s/GetAllAttachmentsAsZip
    3. We did not want /owa/service.svc/s/GetAttachmentThumbnail
  2. Any cs-uri-query like ‘*&ClientRequestId=*&encoding=*’
  3. In our parsing of the logs the same cs-uri-stem was used for uploading and downloading files, but downloads had that near the of the query while uploads had ‘*&ClientId=*’

With this info we used the following log parser query to look for any successful file downloads from the IP ranges we considered external as well as any non internal IP range. We also included some other items to weed out any actions a health mailbox performed

SELECT Distinct
TO_LOCALTIME(TO_TIMESTAMP(date, time)) AS [DateTime],
s-ip as [Server-IP],
REVERSEDNS(s-ip) as [ExchangeServer],
cs-uri-stem as [OWAUrl],
cs-username as [UserName],
c-ip as [Client-IP],
REVERSEDNS(c-ip) as [RemoteHostName],
cs(User-Agent) as [Browser]
Where cs-uri-stem Like '/owa%Get%Attachment%'
and cs-uri-stem <> '/owa/service.svc/s/GetAttachmentThumbnail'
and cs-uri-query like '%&ClientRequestId=%&encoding=%'
and sc-status < 400 and cs-username NOT LIKE 'HealthMailbox%' and cs-method = 'GET' and (c-ip like '192.172.%' or c-ip like '189.18.5.%') and (c-ip not like '10.%' or c-ip not like '172.20.2%') and cs(User-Agent) <> 'AMProbe/Local/ClientAccess'
Posted in Exchange, LogParser | Leave a comment