Last active
November 9, 2015 19:24
-
-
Save bill-long/e1de3ea480837e1a2742 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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