Skip to content

Instantly share code, notes, and snippets.

@bill-long
Last active November 9, 2015 19:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bill-long/e1de3ea480837e1a2742 to your computer and use it in GitHub Desktop.
Save bill-long/e1de3ea480837e1a2742 to your computer and use it in GitHub Desktop.
# Fix-MailEnabledFromUnlinkedOutput.ps1
#
# The purpose of this script is to read the output of
# Find-UnlinkedPFProxies.ps1 and try to link those proxies
# back to their folders.
#
# This script must be run from Exchange Management Shell.
# File is required, DC is optional
# Example syntax:
# .\Fix-MailEnabled C:\FindUnlinkedOutput.txt
# .\Fix-MailEnabled C:\FindUnlinkedOutput.txt DC01
# .\Fix-MailEnabled -File C:\FindUnlinkedOutput.txt -DC DC01
param([string]$File, [string]$DC, [string]$emailAddress, [string]$smtpServer)
# Log file stuff
$ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
$Global:LogFileName = "FixMailEnabled"
$Global:LogFile = $ScriptPath + "\" + $LogFileName + ".log"
$Global:ErrorLogFile = $ScriptPath + "\FixMailEnabled-Errors.log"
$sendEmailOnError = $false
if ($emailAddress -ne $null -and $emailAddress.Length -gt 0 -and $smtpServer -ne $null -and $smtpServer.Length -gt 0)
{
$sendEmailOnError = $true
}
function Test-Transcribing
{
$externalHost = $host.gettype().getproperty("ExternalHost", [reflection.bindingflags]"NonPublic,Instance").getvalue($host, @())
try
{
$externalHost.gettype().getproperty("IsTranscribing", [reflection.bindingflags]"NonPublic,Instance").getvalue($externalHost, @())
}
catch
{
write-warning "This host does not support transcription."
}
}
function writelog([string]$value = "")
{
$Global:LogDate = Get-Date -uformat "%Y %m-%d %H:%M:%S"
("$LogDate $value")
}
function writeerror([string]$value = "")
{
$Global:LogDate = Get-Date -uformat "%Y %m-%d %H:%M:%S"
Add-Content -Path $Global:ErrorLogFile -Value ("$LogDate $value")
if ($sendEmailOnError)
{
writelog("Sending email notification...")
Send-MailMessage -From "Fix-MailEnabled@Fix-MailEnabled" -To $emailAddress -Subject "Fix-MailEnabled script error" `
-Body $value -SmtpServer $smtpServer
}
}
$isTranscribing = Test-Transcribing
if (!($isTranscribing))
{
$transcript = Start-Transcript $Global:LogFile -Append
writelog ($transcript)
}
else
{
writelog ("Transcript already started. Logging to the current file will continue.")
}
writelog ("Fix-MailEnabled starting.")
# Directory objects will be exported prior to deletion. This could
# potentionally create a lot of export files. By default these are
# put in the same folder as the script. If you want to put them
# elsewhere, change this path and make sure the folder exists.
$ExportPath = $ScriptPath
if ($DC -eq "")
{
# Choose a DC
$rootDSE = [ADSI]("LDAP://RootDSE")
$DC = $rootDSE.Properties.dnsHostName
writelog ("DC parameter was not supplied. Using DC: " + $DC)
}
$reader = new-object System.IO.StreamReader($File)
# Loop through the lines in the file
while ($null -ne ($buffer = $reader.ReadLine()))
{
if ($buffer.Length -eq 0 -or (!($buffer.StartsWith("CN="))))
{
continue
}
$dnOfOrphanedProxy = $buffer
writelog("Processing directory object: " + $dnOfOrphanedProxy)
$proxyObject = [ADSI]("LDAP://" + $DC + "/" + $dnOfOrphanedProxy)
if ($proxyObject.Path -eq $null)
{
# It's possible the object is in a different domain than the one
# held by the specified DC, so let's try again without a specific DC.
$proxyObject = [ADSI]("LDAP://" + $dnOfOrphanedProxy)
if ($proxyObject.Path -eq $null)
{
writelog (" Skipping folder because the objectGUID was not found: " + $folderPath)
continue
}
}
# Alright, we need to mail-enable the folder. Ideally it would link up to
# the existing directory object, but often, that doesn't seem to happen, and
# we get a duplicate. So, what we're going to do here is delete the existing
# directory object, mail-enable the folder, and then set the proxy addresses
# from the old directory object onto the new directory object.
# !!!!!! Note that we are making a huge assumption here !!!!!!!
# We don't know the path to the public folder, so we are assuming that we can
# find it based on the name of the directory object. This may not be correct
# in all cases. If folders have been renamed, we could potentially put the
# wrong proxyAddresses on the wrong folder.
$displayName = $proxyObject.Properties["displayName"][0].ToString()
$folder = Get-PublicFolder -Recurse -ResultSize unlimited | WHERE { $_.Name -eq $displayName }
# If we have a Count, there's more than one
if ($folder.Count -ne $null -and $folder.Count -gt 1)
{
writelog(" Skipping folder due to ambiguous name: " + $displayName)
continue
}
$folderPath = $folder.Identity.ToString()
if ($folder -ne $null)
{
if ($folder.MailEnabled)
{
writelog (" Skipping folder because it is already mail-enabled: " + $folderPath)
continue
}
}
else
{
writelog (" Skipping folder because it was not found: " + $folderPath)
continue
}
# If we got to this point, we found the PublicFolder object and it is not
# already mail-enabled.
writelog ("Found problem folder: " + $folderPath)
# Export the directory object before we delete it, just in case
$guidString = (New-Object Guid($proxyObject.Properties["objectGUID"])).ToString()
$fileName = $ExportPath + "\" + $guidString + ".ldif"
$ldifoutput = ldifde -d $proxyObject.Properties.distinguishedName -f ($fileName)
writelog (" " + $ldifoutput)
writelog (" Exported directory object to file: " + $fileName)
# Save any explicit permissions
$explicitPerms = Get-MailPublicFolder $proxyObject.Properties.distinguishedName[0] | Get-ADPermission | `
WHERE { $_.IsInherited -eq $false -and (!($_.User.ToString().StartsWith("NT AUTHORITY"))) }
# Save group memberships
# We need to do this from a GC to make sure we get them all
$memberOf = ([ADSI]("GC://" + $proxyObject.Properties.distinguishedName[0])).Properties.memberOf
# Save proxyAddresses
$proxyAddressArray = $proxyObject.Properties["proxyAddresses"]
$newProxyAddresses = @()
foreach ($proxy in $proxyAddressArray)
{
$newProxyAddresses += $proxy
}
# Save msExchHideFromAddressLists
[bool]$hideFromAddressLists = $proxyObject.Properties["msExchHideFromAddressLists"][0]
# Save legacyExchangeDN to make it an X500 on the new object
$legacyExchangeDN = $proxyObject.Properties["legacyExchangeDN"][0].ToString()
$newProxyAddresses += "X500:" + $legacyExchangeDN
# Delete the current directory object
# For some reason Parent comes back as a string in Powershell, so
# we have to go bind to the parent.
$parent = [ADSI]($proxyObject.Parent.Replace("LDAP://", ("LDAP://" + $DC + "/")))
if ($parent.Path -eq $null)
{
$parent = [ADSI]($proxyObject.Parent)
}
if ($parent.Path -eq $null)
{
$proxyObject.Parent
writelog (" Skipping folder because bind to parent container failed: " + $folderPath)
continue
}
$parent.Children.Remove($proxyObject)
writelog (" Deleted old directory object.")
# Mail-enable the folder
Enable-MailPublicFolder $folderPath
writelog (" Mail-enabled the folder.")
# Disable the email address policy and set the addresses.
# Because we just deleted the directory object a few seconds ago, it's
# possible that that change has not replicated everywhere yet. If the
# Exchange server still sees the object, setting the email addresses will
# fail. The purpose of the following loop is to retry until it succeeds,
# pausing in between. If this is constantly failing on the first try, it
# may be helpful to increase the initial pause.
$initialSleep = 30 # This is the initial pause. Increase it if needed.
writelog (" Sleeping for " + $initialSleep.ToString() + " seconds.")
Start-Sleep $initialSleep
$retryCount = 0
$maxRetry = 3 # The maximum number of times we'll retry
$succeeded = $false
while (!($succeeded))
{
writelog (" Setting proxy addresses...")
# Retrieve the new proxy object
$newMailPublicFolder = Get-MailPublicFolder $folderPath
# Now set the properties
$Error.Clear()
Set-MailPublicFolder $newMailPublicFolder.Identity -EmailAddressPolicyEnabled $false -EmailAddresses $newProxyAddresses `
-HiddenFromAddressListsEnabled $hideFromAddressLists
if ($Error[0] -eq $null)
{
$succeeded = $true
}
else
{
writelog (" Error encountered in Set-MailPublicFolder: " + $Error[0].ToString())
if ($retryCount -lt $maxRetry)
{
$retryCount++
writelog (" Pausing before retry. This will be retry number " `
+ $retryCount.ToString() + ". Max retry attempts is " + $maxRetry.ToString() + ".")
Start-Sleep 60 # This is how long we'll pause before trying again
}
else
{
writelog (" Max retries reached. You must manually set the properties.")
writelog (" See the error log for more details.")
writeerror ("Failed to set proxyAddresses on folder.`r`nFolder: " + $folderPath + `
"`r`nProxy Addresses:`r`n" + $proxyAddresses + `
"`r`nGroup membership:`r`n" + $memberOf + `
"`r`nExplicit Permissions:`r`n" + ($explicitPerms | Select-Object User,AccessRights | out-string) + "`r`n")
break
}
}
}
if ($succeeded -and $explicitPerms -ne $null)
{
$succeeded = $true
writelog (" Setting explicit permissions on new directory object...")
$newMailPublicFolder = Get-MailPublicFolder $folderPath
foreach ($permission in $explicitPerms)
{
$Error.Clear()
$temp = Add-ADPermission $newMailPublicFolder.Identity -User $permission.User -AccessRights $permission.AccessRights
if ($Error[0] -ne $null)
{
$succeeded = $false
writelog (" Error setting explicit permissions. You must manually set the permissions:")
writelog ($explicitPerms)
writeerror ("Failed to set explicit permissions on folder.`r`nFolder: " + $folderPath + `
"`r`nExplicit Permissions:`r`n" + ($explicitPerms | Select-Object User,AccessRights | out-string) + "`r`n")
break
}
}
}
if ($succeeded -and $memberOf -ne $null)
{
writelog (" Setting group memberships...")
$newMailPublicFolder = Get-MailPublicFolder $folderPath
$proxy = [ADSI]("LDAP://<GUID=" + $newMailPublicFolder.Guid + ">")
$proxyDn = $proxy.Properties.distinguishedName[0]
$succeeded = $true
foreach ($group in $memberOf)
{
$Error.Clear()
$groupObject = [ADSI]("LDAP://" + $group)
$temp = $groupObject.Properties.member.Add($proxyDn)
$groupObject.CommitChanges()
if ($Error[0] -ne $null)
{
writelog (" Error setting group memberships. You must add the folder to these groups:")
writelog ($memberOf)
writeerror ("Failed to set group memberships on folder.`r`nFolder: " + $folderPath + `
"`r`nGroup memberships:`r`n" + $memberOf + "`r`n")
$succeeded = $false
break
}
}
}
writelog (" Set the properties on the new directory object.")
writelog (" Done with this folder.")
}
$reader.Close()
writelog "Done!"
if (!($isTranscribing))
{
Stop-Transcript
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment