Querying msExchDelegateListLink in Exchange Online with PowerShell

12 Aug 2020 Update:

A few readers contacted me to report this script no longer works. I got around to investigating it today and see the cause, revealed in a Fiddler trace:

X-AnchorMailbox(larger)

It would seem Microsoft now needs an anchor mailbox, likely to determine what tenant this request is for. I was able to modify my script to accomidate. Sadly, Microsoft is decomissioning the TechNet Gallery, so I may not update that site, just to have them delete it anyway. Please consider including the following in the script yourself:

 

#Other attributes available here: https://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.autodiscover.usersettingname(v=exchg.80).aspx

$Headers = @{
‘X-AnchorMailbox’ = $Credential.UserName
}

$WebResponse = Invoke-WebRequest https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc -Credential $Credential -Method Post -Body $AutoDiscoverRequest -ContentType ‘text/xml; charset=utf-8’ -Headers $Headers
[System.Xml.XmlDocument]$XMLResponse = $WebResponse.Content

$Credential= Get-Credential
Get-AlternateMailboxes -SMTPAddress bob.smith2@contoso.com -Credential $Credential

 

Original Post:

 

There are a number of articles that describe the relationship between the FullAccess permission of an Exchange mailbox and the msExchDelegateListLink attribute. Here are two good ones:

In short, this attribute lists all the other mailboxes your mailbox has FullAccess to, unless AutoMapping was set to $false when assigning the permission. It can be a handy attribute to query when trying to learn what mailboxes might appear in an end-user’s Outlook profile.

This attribute is synced to Office 365 via Azure AD Connect, however, for whatever reason, it is not synced back on-premises for new or migrated mailboxes. It is also not exposed in Get-User, Get-Mailbox, Get-MailboxStatistics, Microsoft Graph or Azure AD Graph.

The information is however included in the user’s AutoDiscover XML response. This is how Outlook knows what mailboxes to mount. If you want to look at this data manually, use the ctrl+right-click tool from the Outlook icon on the system tray. This article describes how to do that, if somehow you’re reading this but don’t already know about this tool:

You can also look at the AutoDiscover XML file via the venerable TestConnectivity.Microsoft.com web site. Look at the bottom of of the file, and you’ll see “AlternativeMailbox” entries.

<AlternativeMailbox>

        <Type>Delegate</Type>

        <DisplayName>crowley test 1</DisplayName>

        <SmtpAddress>crowleytest1@mikecrowley.us</SmtpAddress>

        <OwnerSmtpAddress>crowleytest1@mikecrowley.us</OwnerSmtpAddress>

      </AlternativeMailbox>

      <AlternativeMailbox>

        <Type>Delegate</Type>

        <DisplayName>crowley test 2</DisplayName>

        <SmtpAddress>crowleytest2@mikecrowley.us</SmtpAddress>

        <OwnerSmtpAddress>crowleytest2@mikecrowley.us</OwnerSmtpAddress>

</AlternativeMailbox>

While not exactly the msExchDelegateListLink attribute, its the same difference.

This is neat, but to be useful at scale, we need to query this in PowerShell. Fortunately, there are two methods to fetch the AutoDiscover XML.

You can query these endpoints directly or through the the Exchange Web Services (EWS) API. If you don’t have a preference, Microsoft’s documentation recommends SOAP, which is the approach I’ll discuss here.

Using Invoke-WebRequest and SOAP, we can request specific attributes, such as AlternateMailboxes. Other useful attributes are listed in this article:

While I’m not a developer (developers, please keep your laughter to yourself!), I did manage to cobble together the following SOAP request, which will be the string we “post” to the AutoDiscover service. You’ll notice I’ve marked the user we’re querying and any attributes I might want in bold (modify this list to suit your needs):

<soap:Envelope xmlns:a=”http://schemas.microsoft.com/exchange/2010/Autodiscover&#8221;
xmlns:wsa=”http://www.w3.org/2005/08/addressing&#8221;
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221;
xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”&gt;
<soap:Header>
<a:RequestedServerVersion>Exchange2013</a:RequestedServerVersion>
<wsa:Action>http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/GetUserSettings</wsa:Action&gt;
<wsa:To>https://autodiscover.exchange.microsoft.com/autodiscover/autodiscover.svc</wsa:To&gt;
</soap:Header>
<soap:Body>
<a:GetUserSettingsRequestMessage xmlns:a=”http://schemas.microsoft.com/exchange/2010/Autodiscover”&gt;
<a:Request>
<a:Users>
<a:User>
<a:Mailbox>bob@contoso.com</a:Mailbox>
</a:User>
</a:Users>
<a:RequestedSettings>
<a:Setting>UserDisplayName</a:Setting>
<a:Setting>UserDN</a:Setting>
<a:Setting>UserDeploymentId</a:Setting>
<a:Setting>MailboxDN</a:Setting>
<a:Setting>AlternateMailboxes</a:Setting>
</a:RequestedSettings>
</a:Request>
</a:GetUserSettingsRequestMessage>
</soap:Body>
</soap:Envelope>

(For this post, I only care about AlternateMailboxes.)

AutoDiscover requires authentication, so we’ll also need to use the Get-Credential cmdlet. Interestingly, any mailbox can query the AutoDiscover response for any other user in the Office 365 tenant. This means, through PowerShell, I can look up the msExchDelegateListLink / AlternativeMailbox values for other users (even without administrative privileges).

I’ve written a function to return the results in a PowerShell array like this:

Get-AlternateMailboxes-Example

 

I should also point out:

  • It has the Exchange Online URL hard-coded within, though you could adapt this for other URLs if you’d like.
  • Both SMTPAddress and Credential parameters require valid Exchange Online mailboxes (though, as previously mentioned they do not need to be the same mailbox).

Usage Example:

Get-AlternateMailboxes -SMTPAddress bob@contoso.com -Credential (Get-Credential)

Finally, here is the script itself:

 

Azure AD Connect PowerShell Cmdlets

documentation

Click the image!

Microsoft TechNet used to be one of the best documentation libraries in the industry. Sadly, it still is; so what’s that tell you about the industry today?

Office 365 and Azure are truly great cloud services, but the frequency of updates and new releases are a challenge for Microsoft’s own sales team to keep up with, let alone us in the field, trying to work with the stuff. As made abundantly clear by their actions (e.g. killing tech conferences, technical writer layoffs, shuttering TechNet subscriptions, and abandoning the MCM program), Microsoft doesn’t really see “the problem”.

When Microsoft shipped DirSync and then later Azure AD Sync, documentation of the associated PowerShell modules became increasingly sparse, though some cmdlets did have a help synopsis, as I discussed last year. Azure AD Connect, the current version of Office 365 and Azure Active Directory synchronization technology, has 69 cmdlets in the “ADSync” module.

Wanna take a guess at how many of these have an associated help topic? Don’t forget, this product was launched earlier this summer and is now on it’s second public release.

Zero

(Pause for effect)

So, I have listed all 69 cmdlets here, with a brief note about what I’ve found so far. Right now, most are empty, but I will fill them in as I discover their purpose and/or have more time. If you’ve got a question about one I don’t have detailed, leave a comment and I’ll try to prioritize some research for you. I haven’t checked with the Azure AD team on this, so please take my findings with a grain of salt, and hope for real support documentation to arrive soon!

NOTE: This refers to the “ADSync” module that ships with Azure AD Connect 1.0.8667.0.

Cmdlet

Add-ADSyncAADServiceAccount

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncAttributeFlowMapping

My
Comments

Maps a source to target
attribute.

Export one of the rules
from the editor to see this and other samples.

Sample
Usage

Add-ADSyncAttributeFlowMapping  `

-SynchronizationRule $syncRule[0] `

-Source @(‘mailNickname’,‘sAMAccountName’)
`

-Destination ‘cloudFiltered’
`

-FlowType ‘Expression’
`

-ValueMergeType ‘Update’ `

-Expression ‘IIF(IsPresent([isCriticalSystemObject])
|| IsPresent([sAMAccountName]) = False || [sAMAccountName] =
“SUPPORT_388945a0” || Left([mailNickname], 14) =
“SystemMailbox{” || Left([sAMAccountName], 4) = “AAD_” ||
(Left([mailNickname], 4) = “CAS_” && (InStr([mailNickname],
“}”) > 0)) || (Left([sAMAccountName], 4) = “CAS_”
&& (InStr([sAMAccountName], “}”) > 0)) ||
Left([sAMAccountName], 5) = “MSOL_” ||
CBool(IIF(IsPresent([msExchRecipientTypeDetails]),BitAnd([msExchRecipientTypeDetails],&H21C07000)
> 0,NULL)) ||
CBool(InStr(DNComponent(CRef([dn]),1),”\\0ACNF:”)>0), True,
NULL)’
`

-OutVariable syncRule

Cmdlet

Add-ADSyncConnector

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncConnectorAnchorConstructionSettings

My
Comments

Sample
Usage

 

 

Cmdlet

Add-ADSyncConnectorAttributeInclusion

My
Comments

Sample
Usage

 

 

Cmdlet

Add-ADSyncConnectorHierarchyProvisioningMapping

My
Comments

Sample
Usage

 

 

Cmdlet

Add-ADSyncConnectorObjectInclusion

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncGlobalSettingsParameter

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncJoinConditionGroup

My
Comments

Used in the construction of
sync rules.

Export one of the rules
from the editor to see this and other samples.

Sample
Usage

Add-ADSyncJoinConditionGroup  `

-SynchronizationRule $syncRule[0] `

-JoinConditions @($condition0[0]) `

-OutVariable syncRule

Cmdlet

Add-ADSyncRule

My
Comments

Export one of the rules
from the editor to see this and other

samples.

Sample
Usage

Add-ADSyncRule  `

-SynchronizationRule $syncRule[0]

Cmdlet

Add-ADSyncRunProfile

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncRunStep

My
Comments

Sample
Usage

 

Cmdlet

Add-ADSyncScopeConditionGroup

My
Comments

Used in the construction of
sync rules.

Export one of the rules
from the editor to see this and other samples.

Sample
Usage

Add-ADSyncScopeConditionGroup  `

-SynchronizationRule $syncRule[0] `

-ScopeConditions @($condition0[0],$condition1[0],$condition2[0]) `

-OutVariable syncRule

Cmdlet

Disable-ADSyncConnectorPartition

My
Comments

Sample
Usage

 

Cmdlet

Disable-ADSyncConnectorPartitionHierarchy

My
Comments

Sample
Usage

 

Cmdlet

Disable-ADSyncExportDeletionThreshold

My
Comments

 Disables the accidental deletion safety feature.

More info here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-aadconnectsync-feature-prevent-accidental-deletes/

Sample
Usage

 Disable-ADSyncExportDeletionThreshold

Cmdlet

Enable-ADSyncConnectorPartition

My
Comments

Sample
Usage

 

Cmdlet

Enable-ADSyncConnectorPartitionHierarchy

My
Comments

Sample
Usage

 

Cmdlet

Enable-ADSyncExportDeletionThreshold

My
Comments

 Enables the accidental deletion safety feature. To verify, run Get-MsolDirSyncConfiguration.More info here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-aadconnectsync-feature-prevent-accidental-deletes/

Sample
Usage

Enable-ADSyncExportDeletionThreshold

Cmdlet

Get-ADSyncAADPasswordResetConfiguration

My
Comments

I believe this is used to
report on password write-back.

Sample
Usage

Get-ADSyncAADPasswordResetConfiguration -Connector ‘demo1923.onmicrosoft.com – AAD’

 

Cmdlet

Get-ADSyncAADPasswordSyncConfiguration

My
Comments

Indicates whether or not
password hash sync is enabled (SYNC)

Sample
Usage

Get-ADSyncAADPasswordSyncConfiguration -SourceConnector ‘laptop.lab’

Cmdlet

Get-ADSyncConnector

My
Comments

Gets the management agents
(connectors) used by the sync service.

Sample
Usage

Get-ADSyncConnector

Cmdlet

Get-ADSyncConnectorHierarchyProvisioningDNComponent

My
Comments

Couldn’t get it to work

Sample
Usage

x =
Get-ADSyncConnector -Name
‘laptop.lab’

Get-ADSyncConnectorHierarchyProvisioningDNComponent -ShowHidden -Connector $x

Cmdlet

Get-ADSyncConnectorHierarchyProvisioningMapping

My
Comments

Couldn’t get it to work

Sample
Usage

$x =
Get-ADSyncConnector -Name
‘laptop.lab’

Get-ADSyncConnectorHierarchyProvisioningMapping -Connector $x

Cmdlet

Get-ADSyncConnectorHierarchyProvisioningObjectClass

My
Comments

Didn’t test: I presume it
lists the objects to be synced (e.g. people, contacts, etc)

Sample
Usage

 

 

Cmdlet

Get-ADSyncConnectorParameter

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncConnectorPartition

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncConnectorPartitionHierarchy

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncConnectorTypes

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncGlobalSettings

My
Comments

Displays Global
Configuration Settings.

Sample
Usage

  (Get-ADSyncGlobalSettings).Parameters
| Where name -eq Microsoft.SynchronizationOption.AnchorAttribute

Cmdlet

Get-ADSyncGlobalSettingsParameter

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncRule

My
Comments

 Lists the sync rules

Sample
Usage

 

Cmdlet

Get-ADSyncRunProfile

My
Comments

Sample
Usage

 

Cmdlet

Get-ADSyncSchema

My
Comments

Sample
Usage

 

 

Cmdlet

Get-ADSyncServerConfiguration

My
Comments

Sample
Usage

 

Cmdlet

New-ADSyncConnector

My
Comments

Sample
Usage

 

Cmdlet

New-ADSyncJoinCondition

My
Comments

Sample
Usage

 

Cmdlet

New-ADSyncRule

My
Comments

Export one of the rules
from the editor to see this and other samples.

Sample
Usage

New-ADSyncRule  `

-Name ‘In from
AD – User Join’
`

-Identifier ‘c2db05cb-39bd-4e17-a19a-26718c692e48’
`

-Description
`

-Direction ‘Inbound’
`

-Precedence 100
`

-PrecedenceAfter ‘00000000-0000-0000-0000-000000000000’ `

-PrecedenceBefore ‘00000000-0000-0000-0000-000000000000’ `

-SourceObjectType ‘user’ `

-TargetObjectType ‘person’ `

-Connector ‘43617e64-d544-4426-9354-e7d7508915b1’
`

-LinkType ‘Provision’
`

-SoftDeleteExpiryInterval 0 `

-ImmutableTag ‘Microsoft.InfromADUserJoin.003’ `

-OutVariable syncRule

Cmdlet

New-ADSyncRunProfile

My
Comments

Sample
Usage

 

Cmdlet

New-ADSyncScopeCondition

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncAADPasswordResetConfiguration

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncAADPasswordSyncConfiguration

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncAADServiceAccount

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncAttributeFlowMapping

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncConnector

My
Comments

 Removes one of your Management Agents (Connectors)

Sample
Usage

 

Cmdlet

Remove-ADSyncConnectorAnchorConstructionSettings

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncConnectorAttributeInclusion

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncConnectorHierarchyProvisioningMapping

My
Comments

Sample
Usage

 

 

Cmdlet

Remove-ADSyncConnectorObjectInclusion

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncGlobalSettingsParameter

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncJoinConditionGroup

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncRule

My
Comments

 Removes a sync rule.

Sample
Usage

 

Cmdlet

Remove-ADSyncRunProfile

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncRunStep

My
Comments

Sample
Usage

 

Cmdlet

Remove-ADSyncScopeConditionGroup

My
Comments

Sample
Usage

 

Cmdlet

Search-ADSyncDirectoryObjects

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncAADCompanyFeature

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncAADPasswordResetConfiguration

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncAADPasswordSyncConfiguration

My
Comments

 See details here:  http://blogs.technet.com/b/undocumentedfeatures/archive/2015/11/18/reset-aadsync-or-aadconnect-password-hash-sync-configuration.aspx

Sample
Usage

Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector -TargetConnector $aadConnector -Enable $false

Cmdlet

Set-ADSyncAADPasswordSyncState

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncConnectorParameter

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncGlobalSettings

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncSchema

My
Comments

Sample
Usage

 

Cmdlet

Set-ADSyncServerConfiguration

My
Comments

Sample
Usage

 

Cmdlet

Set-MIISADMAConfiguration

My
Comments

Sample
Usage

 

Cmdlet

Test-AdSyncUserHasPermissions

My
Comments

Sample
Usage

 

Cmdlet

Update-ADSyncConnectorPartition

My
Comments

Sample
Usage

 

Cmdlet

Update-ADSyncConnectorSchema

My
Comments

Sample
Usage

 

Cmdlet

Update-ADSyncDRSCertificates

My
Comments

Sample
Usage

 

I’m Speaking @ IT/Dev Connections – UPDATED!

ImSpeakingAtDevConnections

I am 34,000 feet in the air at the moment, headed to the IT / Dev Connections conference in Las Vegas, Nevada! Judging by the list of sessions and speakers, I expect this to be a great event. I am also very interested to see how many of you all are in attendance, especially since Microsoft has killed so many of their conferences (MEC, MMS, etc).

I have once again been given an opportunity to present at this seminar, so I invite you to attend both of my sessions:

 Tuesday @ 1:45 in Pinyon 2 – Exchange Online Protection In-Depth

 Wednesday @ 1:15 in Pinyon 2 – Mastering PowerShell for Exchange Online

This blog post will also serve as the means to download my PowerPoint presentations as well as the PowerShell samples, so check back after my sessions are over for those.

UPDATE:

Thanks for everyone who attended my sessions! Here are the resources as promised:

A New and an Updated PowerShell Script

NOTE: Updated November 2016 to include -ServersToQuery and -StartTime and parameters.

e.g.

.\RDPConnectionParser.ps1 -ServersToQuery Server1, Server2 -StartTime "November 1"

————————–

Hey everyone, yes I’m still alive!

Connection Report for Remote Desktop 

I wrote a script that connects to one or multiple servers and captures Remote Desktop logons, disconnects, reconnects and logoffs along with the connecting IP:

Download RDPConnectionParser.ps1 here

Recipient Address Report (Formally ProxyAddressCount)

I also updated the “Exchange Proxy Address (alias) Report” script.  It now includes a few environment metrics, as well as the regular CSV-style output:

Download the updated script here

What are the Azure DirSync Cmdlets [Updated]?

ARTICLE UPDATED August 2014 to address the PowerShellConfig module.

NOTE: If you are using Azure AD Connect, see this new article.

As you may have seen, DirSync’s PowerShell functionality can now be called from the “Import-Module” cmdlet instead of running a custom DirSyncConfigShell.psc1 file. If we look at this new module, we can see 92 DirSync-related cmdlets:

DirSync PowerShell Module

Notice the screenshot is actually listing the commands of the “Microsoft.Online.Coexistence.PS.Config module” and “PowerShellConfig” (very descriptive!), not “DirSync”. That is because the DirSync module is a wrapper of sorts, calling “%programfiles% \Windows Azure Active Directory Sync\dirsync\DirSync.psd1” on your behalf. The DirSync module itself contains no cmdlets.

So, what do these cmdlets do anyway? Not all of them are well documented online, so you should start with the help file. Unfortunatley, even the help file omits a synopsis for the 67 “PowerShellConfig” cmdlets.  For the 25 within Microsoft.Online.Coexistence.PS.Config module, run the below command to generate an output similar to the following table:

ipmo DirSync
gcm -m Microsoft.Online.Coexistence.PS.Config | get-help | select name, synopsis | epcsv $env:userprofile\desktop\DirSyncCmdlets.csv -notype


Name

Synopsis

Disable-DirSyncLog

This commandlet is used to disable logging for the Azure Active Directory Sync tool.

Disable-MSOnlineObjectManagement Disable-MSOnlineObjectManagement -Credential <pscredential> [-ObjectTypes <string[]>] [-WhatIf] [-Confirm] [<CommonParameters>]
Disable-MSOnlinePasswordSync Disable-MSOnlinePasswordSync -Credential <pscredential> [-WhatIf] [-Confirm] [<CommonParameters>]
Disable-MSOnlineRichCoexistence Disable-MSOnlineRichCoexistence -Credential <pscredential> [-WhatIf] [-Confirm] [<CommonParameters>]
Disable-OnlinePasswordWriteBack

This commandlet is used to disable writing back user password resets from cloud to onpremise Active Directory.

Disable-PasswordSyncLog

This commandlet is used to disable logging for the Password Sync feature of the Azure Active Directory Sync tool.

Enable-DirSyncLog

This commandlet is used to configure the logging level for the Azure Active Directory Sync tool.

Enable-MSOnlineObjectManagement Enable-MSOnlineObjectManagement -ObjectTypes <string[]> -TargetCredentials <pscredential> -Credential <pscredential> [-WhatIf] [-Confirm] [<CommonParameters>]
Enable-MSOnlinePasswordSync Enable-MSOnlinePasswordSync -Credential <pscredential> [-WhatIf] [-Confirm] [<CommonParameters>]
Enable-MSOnlineRichCoexistence Enable-MSOnlineRichCoexistence -Credential <pscredential> [-WhatIf] [-Confirm] [<CommonParameters>]
Enable-OnlinePasswordWriteBack

This commandlet is used to enable writing back user password resets from cloud to onpremise Active Directory.

Enable-PasswordSyncLog

This commandlet is used to configure the logging level for the Password Sync feature of the Azure Active Directory Sync tool.

Get-CoexistenceConfiguration

Gets a configuration information from the Microsoft Online Coexistence Web Server

Get-DirSyncConfiguration Get-DirSyncConfiguration -TargetCredentials <pscredential> [<CommonParameters>]
Get-DirSyncLogStatus

This commandlet is used to retrieve the current logging level for the Azure Active Directory Sync tool.

Get-OnlinePasswordWriteBackStatus

This commandlet is used to obtain the current status of writing back user password resets from cloud to onpremise Active Directory.

Get-PasswordSyncLogStatus

This commandlet is used to retrieve the current logging level for the Password Sync feature of the Azure Active Directory Sync tool.

Get-PreventAccidentalDeletes

This commandlet is used to retrieve the current status of the object deletion threshold for DirSync.

Set-CoexistenceConfiguration

Configures Microsoft Online Directory Synchronization Tool.

Set-CompanyDirSyncFeatures Set-CompanyDirSyncFeatures -TargetCredentials <pscredential> -FeaturesFlag <int> [<CommonParameters>]
Set-DirSyncConfiguration Set-DirSyncConfiguration -TargetCredentials <pscredential> -DirSyncConfiguration <CloudDirSyncConfiguration> [<CommonParameters>]
Set-FullPasswordSync

Resets the password sync state information forcing a full sync the next time the service is restarted.

Set-PreventAccidentalDeletes

This commandlet is used to enable or disable the object deletion threshold for DirSync.

Start-OnlineCoexistenceSync

Starts synchronization with Microsoft Online

Update-MSOLDirSyncNetworkProxySetting

Updates the directory sync service to use the current user’s http proxy settings.

The de-“magicification” of DirSync is definitely a good thing for all Azure customers.  Having said this, I’d still keep the Codeplex FIM modules around, since they do offer a lot more control of and visibility into the underlying FIM Sync Service.

Here are the cmdlets without help documentation:

 Add-AttributeFlowMapping
 Add-ConfigurationParameter
 Add-ConnectorAnchorConstructionSettings
 Add-ConnectorAttributeInclusion
 Add-ConnectorFilter
 Add-ConnectorHierarchyProvisioningMapping
 Add-ConnectorObjectInclusion
 Add-RelationshipConditionGrouping
 Add-RunStep
 Add-SynchronizationConditionGrouping
 Disable-ConnectorPartition
 Disable-ConnectorPartitionHierarchy
 Enable-ConnectorPartition
 Enable-ConnectorPartitionHierarchy
 Export-ServerConfiguration
 Get-AADConnectorPasswordResetConfiguration
 Get-ConfigurationParameter
 Get-Connector
 Get-ConnectorHierarchyProvisioningDNComponent
 Get-ConnectorHierarchyProvisioningMapping
 Get-ConnectorHierarchyProvisioningObjectClass
 Get-ConnectorPartition
 Get-ConnectorPartitionHierarchy
 Get-ConnectorTypes
 Get-GlobalSettings
 Get-PasswordHashSyncConfiguration
 Get-RunProfile
 Get-Schema
 Get-SynchronizationRule
 Import-MIISServerConfig
 Import-ServerConfiguration
 Initialize-Connector
 Initialize-RunProfile
 Initialize-SynchronizationRule
 New-Connector
 New-RunProfile
 New-SynchronizationRule
 Remove-AADConnectorPasswordResetConfiguration
 Remove-AttributeFlowMapping
 Remove-ConfigurationParameter
 Remove-Connector
 Remove-ConnectorAnchorConstructionSettings
 Remove-ConnectorAttributeInclusion
 Remove-ConnectorFilter
 Remove-ConnectorHierarchyProvisioningMapping
 Remove-ConnectorObjectInclusion
 Remove-PasswordHashSyncConfiguration
 Remove-RelationshipConditionGrouping
 Remove-RunProfile
 Remove-RunStep
 Remove-SynchronizationConditionGrouping
 Remove-SynchronizationRule
 Set-AADConnectorPasswordResetConfiguration
 Set-ConfigurationParameter
 Set-Connector
 Set-GlobalSettings
 Set-MIISADMAConfiguration
 Set-MIISECMA2Configuration
 Set-MIISExtMAConfiguration
 Set-MIISFIMMAConfiguration
 Set-PasswordHashSyncConfiguration
 Set-ProvisioningRulesExtension
 Set-RunProfile
 Set-Schema
 Set-SynchronizationRule
 Update-ConnectorPartition
 Update-ConnectorSchema

As time allows, I will return with more detail on each of the above DirSync cmdlets; so long for now!

DirSync 1.0.6593.0012

Late Monday, Microsoft released another update to the DirSync software, this time with a build number of 6593.0012. You can download it in from the usual link.

DirSync 1.0.6593.0012

As with previous DirSync updates, there has been no official announcement of the release, however the “use at your own risk” Wiki does mention one of the new features:

Version 6593.0012
Date Released 2/3/2014
Notable Changes

New features:

  • Additional Attributes are synchronized on User and Contact objects

Attributes documented here

The new attributes referenced in the link are userCertificate and userSMIMECertificate. Interestingly pwdLastSet was also added, however there is no mention of that one in the article. These additions serve an unknown purpose for now, however one might speculate that they are in support of new capabilities soon to be available in the service?!

Before you upgrade, you may wish to get a “before and after” review of the attribute inclusion list. The best way to review this is in the “Configure Attribute Flow” area of each management agent. At the end of this post, I have also shared an experimental PowerShell method of getting this information.

It is noteworthy that the author of this update, a Microsoft Program Manager for DirSync, is linking to yet another community wiki page instead of the seemingly defunct Knowledge Base article KB-2256198. Sadly, it would appear that the crumbling integrity of the TechNet/Support documentation may be latest casualty in a growing list of IT Pro-related cuts Microsoft has made along their quest to the cloud…

<#
Description:
This script counts and dumps the attribute inclusion lists from each MA.
It does not evaluate attribute flow or applicable object types.

February 3 2014
Mike Crowley
http://mikecrowley.us
#>

#Import Modules
Import-Module SQLps -WarningAction SilentlyContinue

#Get SQL Info
$SQLServer = (gp 'HKLM:SYSTEM\CurrentControlSet\services\FIMSynchronizationService\Parameters').Server
if ($SQLServer.Length -eq '0') {$SQLServer = $env:computername}
$SQLInstance = (gp 'HKLM:SYSTEM\CurrentControlSet\services\FIMSynchronizationService\Parameters').SQLInstance
$MSOLInstance = ($SQLServer + "\" + $SQLInstance)

#Get Management Agent Attribute Info
[xml]$OnPremAttributes = (Invoke-Sqlcmd -MaxCharLength 10000 -ServerInstance $MSOLInstance -Query "SELECT attribute_inclusion_xml FROM [FIMSynchronizationService].[dbo].[mms_management_agent] WHERE [ma_name] = 'Active Directory Connector'").attribute_inclusion_xml
[xml]$CloudAttributes = (Invoke-Sqlcmd -MaxCharLength 10000 -ServerInstance $MSOLInstance -Query "SELECT attribute_inclusion_xml FROM [FIMSynchronizationService].[dbo].[mms_management_agent] WHERE [ma_name] = 'Windows Azure Active Directory Connector'").attribute_inclusion_xml
$ADAttributes = $OnPremAttributes.'attribute-inclusion'.attribute
$AzureAttributes = $CloudAttributes.'attribute-inclusion'.attribute

#Output to Screen
Write-Host $ADAttributes.count "Attributes synced from AD to the Metaverse" -F Cyan
Write-Host $AzureAttributes.count "Attributes synced from the Metaverse to Azure" -F Cyan
Write-Host "See" $env:TEMP\DirSyncAttributeList.txt "for detail" -F Cyan

#Output to File
"******AD Attributes******" | Out-File $env:TEMP\DirSyncAttributeList.txt
$ADAttributes | Out-File $env:TEMP\DirSyncAttributeList.txt -Append
" "| Out-File $env:TEMP\DirSyncAttributeList.txt -Append
"******Azure Attributes******" | Out-File $env:TEMP\DirSyncAttributeList.txt -Append
$AzureAttributes | Out-File $env:TEMP\DirSyncAttributeList.txt -Append

##END

Dirsync: Determine if Password Sync is Enabled

For those not interested in the complete DirSync Report I published last week, now you can run just the Password Hash Sync portion, in a script I published here: Dirsync: Determine if Password Sync is Enabled.

For deployments with remote SQL installations: As with the previous report, note that we make use of the SQL PowerShell Module, which must be present on the computer.

Sample Output(s)

DirSync “Busted Users” Report

If you administer DirSync for your organization, you likely have seen emails like this, indicating some of your users didn’t sync.

DirSync Error Email

It can be a frustrating email, since the “error description” is for some reason blank and the “On-premises object ID” column is not something that’s easy to correlate to a user account within your Active Directory. There are also application event log entries (FIMSynchronizationService #6111 and Directory Synchronization #0), but again these aren’t exactly rich with detail.

Many of you know that DirSync is actually a customized installation FIM 2010 R2’s Synchronization Service. Within the miisclient.exe console, you can look at your most recent “Export” job and examine the errors one at a time.

Miisclient.exe Console


(By the way, this is actually the place to go if you wanted to configure filtering for directory synchronization.)

Using this console certainly works, but it’s not an efficient way to resolve errors. Microsoft seems to acknowledge this, but falls short of a fix with that email, in my opinion. Instead of wearing out your mouse, I propose you use the PowerShell script I have written below. Within, I leverage the free FimSyncPowerShellModule which you’ll need to download and copy to:

…\System32\WindowsPowerShell\v1.0\Modules\FimSyncPowerShellModule\FimSyncPowerShellModule.psm1

Once you’ve copied the module, you’re ready to run the report, which can be downloaded here.

Here is a sample output, followed by the code itself.

Sample Output

<#
Description:
This script generates a list of users who are failing to export to Azure AD.

This script makes use of the FimSyncPowerShellModule
https://fimpowershellmodule.codeplex.com/
(Download and copy to C:\Windows\System32\WindowsPowerShell\v1.0\Modules\FimSyncPowerShellModule\FimSyncPowerShellModule.psm1)

October 18 2013
Mike Crowley
http://mikecrowley.us
#>

#Import the FimSyncPowerShellModule Module
ipmo FimSyncPowerShellModule

#Get the last export run
$LastExportRun = (Get-MIIS_RunHistory -MaName 'Windows Azure Active Directory Connector' -RunProfile 'Export')[0]

#Get error objects from last export run (user errors only)
$UserErrorObjects = $LastExportRun | Get-RunHistoryDetailErrors | ? {$_.dn -ne $null}

$ErrorFile = @()

#Build the custom Output Object
$UserErrorObjects | % {
 $TmpCSObject = Get-MIIS_CSObject -ManagementAgent 'Windows Azure Active Directory Connector' -DN $_.DN
 [xml]$UserXML = $TmpCSObject.UnappliedExportHologram
 $MyObject = New-Object PSObject -Property @{
 EmailAddress = (Select-Xml -Xml $UserXML -XPath "/entry/attr" | select -expand node | ? {$_.name -eq 'mail'}).value
 UPN = (Select-Xml -Xml $UserXML -XPath "/entry/attr" | select -expand node | ? {$_.name -eq 'userPrincipalName'}).value
 ErrorType = $_.ErrorType
 DN = $_.DN
 }
 $ErrorFile += $MyObject
 }

$FileName = "$env:TMP\ErrorList-{0:yyyyMMdd-HHmm}" -f (Get-Date) + ".CSV"
$ErrorFile | select UPN, EmailAddress, ErrorType, DN | epcsv $FileName -NoType

#Output to the screen
$ErrorFile | select UPN, EmailAddress, ErrorType, DN

Write-Host
Write-Host $ErrorFile.count "users with errors. See here for a list:" -F Yellow
Write-Host $FileName -F Yellow
Write-Host

DirSync Report

Azure Active Directory Sync (DirSync) seems so simple on the surface doesn’t it?  “Next, Next, Finish”, right?  Ha!  If you’ve ever had to revisit your DirSync server to troubleshoot or make a configuration change, you know there can be more than meets the eye.  A lot of useful information happens to be scattered across various registry keys, SQL tables and XML files.  If you’re not familiar with the FIM Management Console, and these other locations it might be hard to see what’s going on.

Here’s a free script that aims to help by creating a dashboard highlighting useful DirSync configurations.  See the image below for a sample output.  Before you run it you should be aware of the limitations listed in the “known issues” area of the script.

Oct 2014 Update: Fellow MVP, Michael Van Horenbeeck has written an update to this script for use with the new Azure AD Sync Tool.  Please be sure to check it out here: http://vanhybrid.com/2014/10/26/azure-ad-sync-tool-html-report/

DirSync Report


You can Review the script below or download it and try it for yourself!

&lt;#
Description:
This script gathers DirSync information from various locations and reports to the screen.

November 5 2013
Mike Crowley
http://mikecrowley.us

Known Issues:
1) All commands, including SQL queries run as the local user.  This may cause issues on locked-down SQL deployments.
2) For remote SQL installations, the SQL PowerShell module must be installed on the dirsync server.
    (http://technet.microsoft.com/en-us/library/hh231683.aspx)
3) The Azure Service account field is actually just the last account to use the Sign In Assistant.
    There are multiple entries at that registry location.  We're just taking the last one.
4) Assumes Dirsync version 6385.0012 or later.

#&gt;

#Console Prep
cls
Write-Host &quot;Please wait...&quot; -F Yellow
ipmo SQLps

#Check for SQL Module
if ((gmo sqlps) -eq $null) {
    write-host &quot;The SQL PowerShell Module Is Not loaded.  Please install and try again&quot; -F Red
    write-host &quot;http://technet.microsoft.com/en-us/library/hh231683.aspx&quot; -F Red
    Write-Host &quot;Quitting...&quot; -F Red; sleep 5; Break
    }

#Get Dirsync Registry Info
$DirsyncVersion = (gp 'hklm:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Online Directory Sync').DisplayVersion
$DirsyncPath = (gp 'hklm:SOFTWARE\Microsoft\MSOLCoExistence').InstallPath
$FullSyncNeededBit = (gp 'hklm:SOFTWARE\Microsoft\MSOLCoExistence').FullSyncNeeded
$FullSyncNeeded = &quot;No&quot;
If ((gp 'hklm:SOFTWARE\Microsoft\MSOLCoExistence').FullSyncNeeded -eq '1') {$FullSyncNeeded = &quot;Yes&quot;}

#Get SQL Info
$SQLServer = (gp 'HKLM:SYSTEM\CurrentControlSet\services\FIMSynchronizationService\Parameters').Server
if ($SQLServer.Length -eq '0') {$SQLServer = $env:computername}
$SQLInstance = (gp 'HKLM:SYSTEM\CurrentControlSet\services\FIMSynchronizationService\Parameters').SQLInstance
$MSOLInstance = ($SQLServer + &quot;\&quot; + $SQLInstance)
$SQLVersion = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY ('productlevel'), SERVERPROPERTY ('edition')&quot;

#Get Password Sync Status
[xml]$ADMAxml = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT [ma_id] ,[ma_name] ,[private_configuration_xml] FROM [FIMSynchronizationService].[dbo].[mms_management_agent]&quot; | ? {$_.ma_name -eq 'Active Directory Connector'} | select -Expand private_configuration_xml
$PasswordSyncBit = (Select-Xml -XML $ADMAxml -XPath &quot;/adma-configuration/password-hash-sync-config/enabled&quot; | select -expand node).'#text'
$PasswordSyncStatus = &quot;Disabled&quot;
If ($PasswordSyncBit -eq '1') {$PasswordSyncStatus = &quot;Enabled&quot;}

#Get Account Info
$ServiceAccountGuess = (((gci 'hkcu:Software\Microsoft\MSOIdentityCRL\UserExtendedProperties' | select PSChildName)[-1]).PSChildName -split ':')[-1]
$ADServiceAccountUser = $ADMAxml.'adma-configuration'.'forest-login-user'
$ADServiceAccountDomain = $ADMAxml.'adma-configuration'.'forest-login-domain'
$ADServiceAccount = $ADServiceAccountDomain + &quot;\&quot; + $ADServiceAccountUser

#Get DirSync Database Info
$SQLDirSyncInfo = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT DB_NAME(database_id) AS DatabaseName, Name AS Logical_Name, Physical_Name, (size*8)/1024 SizeMB FROM sys.master_files WHERE DB_NAME(database_id) = 'FIMSynchronizationService'&quot;
$DirSyncDB = $SQLDirSyncInfo | ? {$_.Logical_Name -eq 'FIMSynchronizationService'}
$DirSyncLog = $SQLDirSyncInfo | ? {$_.Logical_Name -eq 'FIMSynchronizationService_log'}

#Get connector space info (optional)
$ADMA = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT [ma_id] ,[ma_name] FROM [FIMSynchronizationService].[dbo].[mms_management_agent] WHERE ma_name = 'Active Directory Connector'&quot;
$AzureMA = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT [ma_id] ,[ma_name] FROM [FIMSynchronizationService].[dbo].[mms_management_agent] WHERE ma_name = 'Windows Azure Active Directory Connector'&quot;
$UsersFromBothMAs = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT [ma_id] ,[rdn] FROM [FIMSynchronizationService].[dbo].[mms_connectorspace] WHERE object_type = 'user'&quot;
$AzureUsers = $UsersFromBothMAs | ? {$_.ma_id -eq $AzureMA.ma_id}
$ADUsers = $UsersFromBothMAs | ? {$_.ma_id -eq $ADMA.ma_id}

#Get DirSync Run History
$SyncHistory = Invoke-Sqlcmd -ServerInstance $MSOLInstance -Query &quot;SELECT [step_result] ,[end_date] ,[stage_no_change] ,[stage_add] ,[stage_update] ,[stage_rename] ,[stage_delete] ,[stage_deleteadd] ,[stage_failure] FROM [FIMSynchronizationService].[dbo].[mms_step_history]&quot; | sort end_date -Descending

#GetDirSync interval (3 hours is default)
$SyncTimeInterval = (Select-Xml -Path ($DirsyncPath + &quot;Microsoft.Online.DirSync.Scheduler.exe.config&quot;) -XPath &quot;configuration/appSettings/add&quot; | select -expand Node).value

#Generate Output
cls

Write-Host &quot;Report Info&quot; -F DarkGray
Write-Host &quot;Date: &quot; -F Cyan -NoNewline ; Write-Host (Get-Date) -F DarkCyan
Write-Host &quot;Server: &quot; -F Cyan -NoNewline ; Write-Host  $env:computername -F DarkCyan
Write-Host

Write-Host &quot;Account Info&quot; -F DarkGray
Write-Host &quot;Active Directory Service Account: &quot; -F Cyan -NoNewline ; Write-Host $ADServiceAccount -F DarkCyan
Write-Host &quot;Azure Service Account Guess: &quot; -F Cyan -NoNewline ; Write-Host $ServiceAccountGuess -F DarkCyan
Write-Host

Write-Host &quot;DirSync Info&quot; -F DarkGray
Write-Host &quot;Version: &quot; -F Cyan -NoNewline ; Write-Host $DirsyncVersion -F DarkCyan
Write-Host &quot;Path: &quot; -F Cyan -NoNewline ; Write-Host $DirsyncPath -F DarkCyan
Write-Host &quot;Password Sync Status: &quot; -F Cyan -NoNewline ; Write-Host $PasswordSyncStatus -F DarkCyan
Write-Host &quot;Sync Interval (H:M:S): &quot; -F Cyan -NoNewline ; Write-Host $SyncTimeInterval -F DarkCyan
Write-Host &quot;Full Sync Needed? &quot; -F Cyan -NoNewline ; Write-Host $FullSyncNeeded -F DarkCyan
Write-Host

Write-Host &quot;User Info&quot; -F DarkGray
Write-Host &quot;Users in AD connector space: &quot; -F Cyan -NoNewline ; Write-Host $ADUsers.count -F DarkCyan
Write-Host &quot;Users in Azure connector space: &quot; -F Cyan -NoNewline ; Write-Host $AzureUsers.count -F DarkCyan
Write-Host &quot;Total Users: &quot; -F Cyan -NoNewline ; Write-Host $UsersFromBothMAs.count -F DarkCyan
Write-Host

Write-Host &quot;SQL Info &quot; -F DarkGray
Write-Host &quot;Version: &quot; -F Cyan -NoNewline ; Write-host $SQLVersion.Column1 $SQLVersion.Column2 $SQLVersion.Column3 -F DarkCyan
Write-Host &quot;Instance: &quot; -F Cyan -NoNewline ; Write-Host  $MSOLInstance -F DarkCyan
Write-Host &quot;Database Location: &quot; -F Cyan -NoNewline ; Write-Host $DirSyncDB.Physical_Name -F DarkCyan
Write-Host &quot;Database Size: &quot; -F Cyan -NoNewline ; Write-Host $DirSyncDB.SizeMB &quot;MB&quot; -F DarkCyan
Write-Host &quot;Database Log Size: &quot; -F Cyan -NoNewline ; Write-Host $DirSyncLog.SizeMB &quot;MB&quot; -F DarkCyan
Write-Host

Write-Host &quot;Most Recent Sync Activity&quot; -F DarkGray
Write-Host &quot;(For more detail, launch:&quot; $DirsyncPath`SYNCBUS\Synchronization Service\UIShell\miisclient.exe&quot;)&quot; -F DarkGray
Write-Host &quot;  &quot; ($SyncHistory[0].end_date).ToLocalTime() -F DarkCyan -NoNewline ; Write-Host &quot; --&quot; $SyncHistory[0].step_result -F Gray
Write-Host &quot;  &quot; ($SyncHistory[1].end_date).ToLocalTime() -F DarkCyan -NoNewline ; Write-Host &quot; --&quot; $SyncHistory[1].step_result -F Gray
Write-Host &quot;  &quot; ($SyncHistory[2].end_date).ToLocalTime() -F DarkCyan -NoNewline ; Write-Host &quot; --&quot; $SyncHistory[2].step_result -F Gray
Write-Host

Converting SMTP Proxy Addresses to Lowercase

Update: Be aware, this script has not been tested with SIP, X400 or other address types. I am working on an update to validate these scenarios, but in the meantime, proceed at your own risk with these address types.

I recently encountered a question in an online forum where someone asked for a script to convert all of their user’s email addresses to lower case values.  While this doesn’t affect the message delivery, it can have an impact on aesthetics when the address is displayed in an external recipient’s email client.  An Exchange Email Address Policy can do this to some degree, but I wanted to see how it could be done with PowerShell.

The challenge with a script like this is twofold:

  1. Email addresses (proxy addresses) are a multi-valued attribute, which can be tricky to work with.
  2. PowerShell is generally not case-sensitive, and therefore when we try to rename Mr. Gallalee’s email address in the screenshot below, we can see that it does not work:

WARNING: The command completed successfully but no settings of 'demolab.local/Users/Rob Gallalee' have been modified.

After a little bit of inspiration from a script written by Michael B Smith, I came up with the below:


$MailboxList = Get-Mailbox  -ResultSize unlimited

$MailboxList | % {

$LoweredList = @()
$RenamedList = @()

foreach ($Address in $_.EmailAddresses){
if ($Address.prefixstring -eq "SMTP"){
$RenamedList += $Address.smtpaddress + "TempRename"
$LoweredList += $Address.smtpaddress.ToLower()
}
}
Set-mailbox $_ -emailaddresses $RenamedList -EmailAddressPolicyEnabled $false
Set-mailbox $_ -emailaddresses $LoweredList

#Without this line the "Reply To" Address could be lost on recipients with more than one proxy address:
Set-mailbox $_ -PrimarySmtpAddress $_.PrimarySmtpAddress
}

This script works as follows:

  1. Puts all mailboxes into the $MailboxList variable.  If you don’t want all mailboxes,  edit the Get-Mailbox cmdlet as you see fit.
  2. Filters out X400 and other non-SMTP addresses.
  3. Creates an array called $RenamedList which stores each proxy address with “TempRename” appended to it (e.g. Rgallalee@demolab.localTempRename).
  4. Creates another array ($LoweredList) and use the “ToLower” method on each proxy address.
  5. Sets the proxy address for the user to the value of $RenamedList and then to $LoweredList.
    1. This is how we get around the case case insensitivity – name it to something else and then name it back.
  6. Step 4 and 5 don’t preserve the “Primary” / “Reply-To” address, so we set it back manually with the last line.

Note: This script turns off the email address policy for each user.

As always, feedback is welcome.

EDIT Dec 2018:
This is a similar approach, but for mailboxes migrated to Office 365. In this case, only the Primary SMTP addresses are targeted.

It may also be faster than the above, due to the fact we’re only operating against mailboxes that have uppercase (vs all of them).

Set-ADServerSettings -ViewEntireForest:$true

$TargetObjects = Get-RemoteMailbox -ResultSize Unlimited | Where {$_.PrimarySmtpAddress.ToLower() -cne $_.PrimarySmtpAddress}

Write-Host $TargetObjects.count "Remote mailboxes have one or more uppercase characters." -ForegroundColor Cyan

#Backup First
Function Get-FileFriendlyDate {Get-Date -format ddMMMyyyy_HHmm.s}
$DesktopPath = ([Environment]::GetFolderPath("Desktop") + '\')
$LogPath = ($DesktopPath + (Get-FileFriendlyDate) + "-UppercaseBackup.xml")

$TargetObjects | select DistinguishedName, PrimarySMTPAddress, EmailAddresses | Export-Clixml $LogPath
Write-Host "A backup XML has been placed here:" $LogPath -ForegroundColor Cyan
Write-Host

$Counter = $TargetObjects.Count

foreach ($RemoteMailbox in $TargetObjects) {

    Write-Host "Setting: " -ForegroundColor DarkCyan -NoNewline
    Write-Host $RemoteMailbox.PrimarySmtpAddress -ForegroundColor Cyan
    Write-Host "Remaining: " -ForegroundColor DarkCyan -NoNewline
    Write-Host $Counter -ForegroundColor Cyan

    Set-RemoteMailbox $RemoteMailbox.Identity -PrimarySmtpAddress ("TMP-Rename-" + $RemoteMailbox.PrimarySmtpAddress) -EmailAddressPolicyEnabled $false
    Set-RemoteMailbox $RemoteMailbox.Identity -EmailAddresses @{remove = $RemoteMailbox.PrimarySmtpAddress}

    Set-RemoteMailbox $RemoteMailbox.Identity -PrimarySmtpAddress $RemoteMailbox.PrimarySmtpAddress.ToLower()
    Set-RemoteMailbox $RemoteMailbox.Identity -EmailAddresses @{remove = ("TMP-Rename-" + $RemoteMailbox.PrimarySmtpAddress)}

    $Counter --
}

Write-Host
Write-Host "Done." -ForegroundColor DarkCyan

#End

 

Combining PowerShell Cmdlet Results

In my last post I used used New-Object to create an desirable output when the “Get-Mailbox” cmdlet didn’t meet my needs.  If your eyes glazed over trying to read the script, let me make it a bit simpler by focusing on a straight forward example.

Say you need to create a list of user’s mailbox size with their email address.  This sounds like a simple request, but what you’d soon find is that mailbox sizes are returned with the Get-MailboxStatistics cmdlet and the email address is not.  For that, you need to use another cmdlet, such as Get-Mailbox.

With the New-Object cmdlet, we are able to make a custom output that contains data from essentially wherever we want.

See this example:

$MyObject = New-Object PSObject -Property @{
EmailAddress = $null
MailboxSize = $null
}

In this example, I have created a new object with 2 fields, and saved it as the $MyObject variable.

For now, we’ve set the data to null, as shown below:

$MyObject

The next step is to populate each of those fields.  We can write to them one at a time with lines like this:

$MyObject.EmailAddress = (Get-Mailbox mcrowley).PrimarySmtpAddress
$MyObject.MailboxSize = (Get-MailboxStatistics mcrowley).TotalItemSize

Note: The variable we want to populate is on the left, with what we want to put in it on the right.

To confirm our results, we can simply type the variable name at the prompt:

$MyObject with data

Pretty cool, huh?

Ok, so now about that list.  My example only shows the data for mcrowley, and you probably need more than just 1 item in your report, right?

For this, you need to use the foreach loop.  You can read more about foreach here, but the actual code for our list is as follows:

(I am actually going to skip the $null attribute step here)

$UserList = Get-mailbox -Resultsize unlimited
$MasterList = @()
foreach ($User in $UserList) {
$MyObject = New-Object PSObject -Property @{
EmailAddress = (Get-Mailbox $User).PrimarySmtpAddress
MailboxSize = (Get-MailboxStatistics $User).TotalItemSize
}
$MasterList += $MyObject
}
$MasterList

$MasterList with data

Finally, if you wanted to make this run faster, we really don’t need to run “get-mailbox” twice.  For better results, replace the line:

EmailAddress = (Get-Mailbox $User).PrimarySmtpAddress

With this one:

EmailAddress = $User.PrimarySmtpAddress

Exchange Proxy Address (alias) Report

EDIT (April 4 2015):

This script has been updated significantly, please make sure you’re using version 5.

————–

Exchange Server stores user’s alternate email addresses as a multi-valued attribute within Active Directory.  For example, if my colleague Jorge has jdiaz@demolab.local as well as diazj@demolab.local, his proxyAddresses attribute would look like this:

ADUC - ProxyAddresses

Notice, the capital SMTP vs. the lowercase smtp.  There can be only one uppercase SMTP, and this represents the primary, or “reply to” address.

While, it’s very easy to view someone’s proxy addresses (often called aliases, but don’t confuse it with the “alias” attribute) within the Exchange Management Console, it can be tough to work with in the Exchange Management Shell (PowerShell) due to the data being stored as a  “Multi-Valued” attribute.  The usual “Get-Mailbox” output not only shows all addresses as a single item, but in the case “mcrowley” below, we can see the shell truncates:

get-mailbox mcrowley | select emailaddresses

While there are ways (example1, example2) to manipulate this output on the screen, I recently needed to create a complete list of all users possessing one or more secondary email address, and document what those addresses were.

On the surface, this sounds simple.  We want a list of users who have more than 1 proxy address.  At first, I thought of something like this:

Get-Mailbox -Filter {emailaddresses -gt 1} | Select EmailAddresses

Get-Mailbox -Filter {emailaddresses -gt 1} | Select EmailAddresses

But we can see this doesn’t actually capture the correct users.  In the above example, LiveUser1 only has a single proxy address, but it was returned anyway.  This is because the result is actually converted to a number, and the “-gt” or “greater than” operation is done on this number; not what we want.

I have written a script to help!

Features:

  1. This script creates a CSV output of everyone’s SMTP proxy addresses.
  2. Reports to the screen the total number of users found.
  3. Reports to the screen the user(s) with the most proxy addresses.
  4. You can configure the threshold of users reported. For example, if you only wanted users with 2 or more proxy addresses included, you should change the line: “$Threshold = 0” to “$Threshold = 2”

Misc:

  1. Does not currently work with Exchange Online (planned enhancement).
  2. This uses “get-recipient” with no filters by default.  You may want to  replace this with something more restrictive, like “get-mailbox”, or use the -filter parameter.
  3. Requires PS 2.0 (for Exchange 2007, see here)

Here is a sample output, shown in excel:

Sample output to screen:

The guts of this script might help with this exact scenario, or really, anywhere you want to break out and evaluate multi-valued attributes.  Feel free to use it and adjust as you see fit!

Download the script here, or copy from the text below:

&lt;#

Features:
    1) This script Creates a TXT and CSV file with the following information:
        a) TXT file: Recipient Address Statistics
        b) CSV file: Output of everyone's SMTP proxy addresses.

Instructions:
    1) Run this from &quot;regular&quot; PowerShell.  Exchange Management Shell may cause problems, especially in Exchange 2010, due to PSv2.
    2) Usage: RecipientReportv5.ps1 server5.domain.local

Requirements:
    1) Exchange 2010 or 2013
    2) PowerShell 4.0

 
April 4 2015
Mike Crowley
 
http://BaselineTechnologies.com
 
#&gt;

param(
    [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false,HelpMessage='Type the name of a Client Access Server')][string]$ExchangeFQDN
    )

if ($host.version.major -le 2) {
    Write-Host &quot;&quot;
    Write-Host &quot;This script requires PowerShell 3.0 or later.&quot; -ForegroundColor Red
    Write-Host &quot;Note: Exchange 2010's EMC always runs as version 2.  Perhaps try launching PowerShell normally.&quot; -ForegroundColor Red
    Write-Host &quot;&quot;
    Write-Host &quot;Exiting...&quot; -ForegroundColor Red
    Sleep 3
    Exit
    }


if ((Test-Connection $ExchangeFQDN -Count 1 -Quiet) -ne $true) {
    Write-Host &quot;&quot;
    Write-Host (&quot;Cannot connect to: &quot; + $ExchangeFQDN) -ForegroundColor Red
    Write-Host &quot;&quot;
    Write-Host &quot;Exiting...&quot; -ForegroundColor Red
    Sleep 3
    Exit
    }

cls

 
#Misc variables
#$ExchangeFQDN = &quot;exchserv1.domain1.local&quot;
$ReportTimeStamp = (Get-Date -Format s) -replace &quot;:&quot;, &quot;.&quot;
$TxtFile = &quot;$env:USERPROFILE\Desktop\&quot; + $ReportTimeStamp + &quot;_RecipientAddressReport_Part_1of2.txt&quot;
$CsvFile = &quot;$env:USERPROFILE\Desktop\&quot; + $ReportTimeStamp + &quot;_RecipientAddressReport_Part_2of2.csv&quot;

#Connect to Exchange
Write-Host (&quot;Connecting to &quot; + $ExchangeFQDN + &quot;...&quot;) -ForegroundColor Cyan
Get-PSSession | Where-Object {$_.ConfigurationName -eq 'Microsoft.Exchange'} | Remove-PSSession
$Session = @{
    ConfigurationName = 'Microsoft.Exchange'
    ConnectionUri = 'http://' + $ExchangeFQDN + '/PowerShell/?SerializationLevel=Full' 
    Authentication = 'Kerberos'
    }
Import-PSSession (New-PSSession @Session) 

#Get Data
Write-Host &quot;Getting data from Exchange...&quot; -ForegroundColor Cyan
$AcceptedDomains = Get-AcceptedDomain
$InScopeRecipients = @(
    'DynamicDistributionGroup'
    'UserMailbox'
    'MailUniversalDistributionGroup'
    'MailUniversalSecurityGroup'
    'MailNonUniversalGroup'
    'PublicFolder'
    )
$AllRecipients = Get-Recipient -recipienttype $InScopeRecipients -ResultSize unlimited | select name, emailaddresses, RecipientType
$UniqueRecipientDomains = ($AllRecipients.emailaddresses | Where {$_ -like 'smtp*'}) -split '@' | where {$_ -NotLike 'smtp:*'} | select -Unique

Write-Host &quot;Preparing Output 1 of 2...&quot; -ForegroundColor Cyan
#Output address stats
$TextBlock = @(
    &quot;Total Number of Recipients: &quot; + $AllRecipients.Count
    &quot;Number of Dynamic Distribution Groups: &quot; +         ($AllRecipients | Where {$_.RecipientType -eq 'DynamicDistributionGroup'}).Count
    &quot;Number of User Mailboxes: &quot; + 	                    ($AllRecipients | Where {$_.RecipientType -eq 'UserMailbox'}).Count
    &quot;Number of Mail-Universal Distribution Groups: &quot; + 	($AllRecipients | Where {$_.RecipientType -eq 'MailUniversalDistributionGroup'}).Count
    &quot;Number of Mail-UniversalSecurity Groups: &quot; + 	    ($AllRecipients | Where {$_.RecipientType -eq 'MailUniversalSecurityGroup'}).Count
    &quot;Number of Mail-NonUniversal Groups: &quot; + 	        ($AllRecipients | Where {$_.RecipientType -eq 'MailNonUniversalGroup'}).Count
    &quot;Number of Public Folders: &quot; + 	                    ($AllRecipients | Where {$_.RecipientType -eq 'PublicFolder'}).Count
    &quot;&quot;
    &quot;Number of Accepted Domains: &quot; + $AcceptedDomains.count 
    &quot;&quot;
    &quot;Number of domains found on recipients: &quot; + $UniqueRecipientDomains.count 
    &quot;&quot;
    $DomainComparrison = Compare-Object $AcceptedDomains.DomainName $UniqueRecipientDomains
    &quot;These domains have been assigned to recipients, but are not Accepted Domains in the Exchange Organization:&quot;
    ($DomainComparrison | Where {$_.SideIndicator -eq '=&gt;'}).InputObject 
    &quot;&quot;
    &quot;These Accepted Domains are not assigned to any recipients:&quot; 
    ($DomainComparrison | Where {$_.SideIndicator -eq '&lt;='}).InputObject
    &quot;&quot;
    &quot;See this CSV for a complete listing of all addresses: &quot; + $CsvFile
    )

Write-Host &quot;Preparing Output 2 of 2...&quot; -ForegroundColor Cyan

$RecipientsAndSMTPProxies = @()
$CounterWatermark = 1
 
$AllRecipients | ForEach-Object {
    
    #Create a new placeholder object
    $RecipientOutputObject = New-Object PSObject -Property @{
        Name = $_.Name
        RecipientType = $_.RecipientType
        SMTPAddress0 =  ($_.emailaddresses | Where {$_ -clike 'SMTP:*'} ) -replace &quot;SMTP:&quot;
        }    
    
    #If applicable, get a list of other addresses for the recipient
    if (($_.emailaddresses).count -gt '1') {       
        $OtherAddresses = @()
        $OtherAddresses = ($_.emailaddresses | Where {$_ -clike 'smtp:*'} ) -replace &quot;smtp:&quot;
        
        $Counter = $OtherAddresses.count
        if ($Counter -gt $CounterWatermark) {$CounterWatermark = $Counter}
        $OtherAddresses | ForEach-Object {
            $RecipientOutputObject | Add-Member -MemberType NoteProperty -Name (“SmtpAddress” + $Counter) -Value ($_ -replace &quot;smtp:&quot;)
            $Counter--
            }
        }
        $RecipientsAndSMTPProxies += $RecipientOutputObject
    }
  
 
$AttributeList = @(
    'Name'
    'RecipientType'
    )
$AttributeList += 0..$CounterWatermark | ForEach-Object {&quot;SMTPAddress&quot; + $_}


Write-Host &quot;Saving report files to your desktop:&quot; -ForegroundColor Green
Write-Host &quot;&quot;
Write-Host $TxtFile -ForegroundColor Green
Write-Host $CsvFile -ForegroundColor Green

$TextBlock | Out-File $TxtFile
$RecipientsAndSMTPProxies | Select $AttributeList | sort RecipientType, Name | Export-CSV $CsvFile -NoTypeInformation

Write-Host &quot;&quot;
Write-Host &quot;&quot;
Write-Host &quot;Report Complete!&quot; -ForegroundColor Green

PowerShell Tip – Running a Service Pack Report – Faster

Imagine you wanted to run a quick report of all your server’s service pack level in your domain.  After all, SP1 just came out!  You could get this information quickly by using the Active Directory Module for Windows PowerShell.  If you don’t have at least one Windows 2008 R2 (RTM or SP1) Domain Controller, you could also do something similar with the free Quest PowerShell tools, but that’s for another day…

We can find this information using a few different methods.  Here I’ll show two:

Method 1

Get-ADComputer -Properties OperatingSystem, OperatingSystemServicePack -Filter * | Where-Object {$_.OperatingSystem -like '*server*'} |  Format-Table name, oper* -autosize

You can see with Method 1, we’re telling PowerShell to get all the computer accounts from Active Directory.  Then we pass those objects over to the “Where-object” cmdlet and ask it to select only those who have an OperatingSystem attribute containing “server”.  We then format the results in a table.  Give it a try.

Not too shabby; but let’s make it better!

Method2

Get-ADComputer -Properties OperatingSystem, OperatingSystemServicePack -Filter {OperatingSystem -like '*server*'} | Format-Table name, oper* -autosize

In Method 2, we’re making smarter use of the –Filter switch.  So instead of getting ALL the computer accounts, we do our filtering up-front.  This can lead to significant amount of time saved!

How much time, you ask?  Well, we can find out with the “Measure-Command” cmdlet.  Just put any command string in {} and it will tell you how long it took to run!

Here are the results from a small environment with fast servers. 677 milliseconds isn’t bad, but when you compare it to 73, you can begin to appreciate the potential.

clip_image001

One last thought:  You may wish to add this extra code to make your output prettier.  It will organize your results first by operating system and then by name:

Get-ADComputer -Properties OperatingSystem, OperatingSystemServicePack -Filter {OperatingSystem -like '*server*'} | Sort-Object operatingsystem, name | Format-Table name, oper* -autosize

Script for Missing UPNs

For various reasons I’ve found myself needing to fix customer sites where the User Principal Name (UPN) was not present for AD user accounts.

image

Most frequently this is because the environment was once NT4, which did not require this attribute.  Whatever the reason, I’ve fixed it using PowerShell.

If you don’t have 2008 R2 domain controllers you can use the free Quest PowerShell add-ins downloaded here.

If you DO have 2008 R2 domain controllers you can use the native Active Directory Module for Windows PowerShell.

Below is a script you can use for either scenario.  This will take all users with missing UPNs from the “My Users” OU in the “contoso.local” domain and set their UPN to username@contoso.local

Quest:

Get-QADUser –SearchRoot “contoso.local/My Users” -UserPrincipalName $null -SizeLimit 0 | % {$CompleteUPN = $_.samaccountname +"@contoso.local"; Set-QADUser -Id $_.DN -UserPrincipalName $CompleteUPN}

2008 R2 Native:

Get-ADUser  -Filter {-not (UserPrincipalName -like '*')} -SearchBase 'OU=My Users,DC=contoso,DC=local' | % {$CompleteUPN = $_.SamAccountName + "@contoso.local" ; Set-ADUser -Identity $_.DistinguishedName -UserPrincipalName $CompleteUPN}