Get-PublicFolderIDFixReport.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
<#PSScriptInfo
 
.VERSION 2.3.1
 
.GUID 6cdcff5d-bf70-4297-aab7-a558335ec932
 
.AUTHOR Aaron Guilmette
 
.COMPANYNAME Microsoft
 
.COPYRIGHT 2021
 
.TAGS Public Folder idfix
 
.LICENSEURI
 
.PROJECTURI https://www.undocumented-features.com/2021/04/21/newly-revamped-get-publicfolderidfixreport-tool/
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
.DESCRIPTION
Checks public folders for a number of conditions that could lead to problems during a public folder migration (either on-premises to on-premises or on-premises to cloud).
 
.PRIVATEDATA
 
#>


<#
.SYNOPSIS
Public Folder IDFix and Error Check report.
 
.NOTES
2021-04-30 - Resolved type in variable name.
           - Updated to check MEPFs for null alias.
2021-04-23 - Updated email address validation to stop using Net.Mail.MailAddress.
 
Conditions checked:
- Invalid primary SMTP address for mail-enabled public folders
- Invalid characters in mail-enabled public folder aliases
- Invalid characters in public folder names
- Duplicate aliases
- publicFolder Microsoft Exchange System Objects must reference a valid public folder
- Orphaned publicFolder Microsoft Exchange System Objects
#>


[CmdletBinding()]
param (
    [string]$ExchangeServer,
    [string]$Logfile = (Get-Date -Format yyyy-MM-dd) + "_PublicFolderIDFixLog.txt",
    [string]$MailPublicFolderOutputFile = (Get-Date -Format yyyy-MM-dd) + "_MailPublicFolderIDFixReport.csv",
    [string]$PublicFolderOutputFile = (Get-Date -Format yyyy-MM-dd) + "_PublicFolderIDFixReport.csv",
    [string]$UnlinkedMESOOutputFile = (Get-Date -Format yyyy-MM-dd) + "_UnlinkedMESOOutputReport.csv",
    [switch]$MailEnabledPublicFoldersOnly
)

## Miscellaneous Functions

# Verify AD Tools are installed for MESO object verification
function VerifyADTools($ParamName)
{
    Write-Log -LogFile $Logfile -LogLevel INFO -Message "Checking for Active Directory Module."
    # Check for Active Directory Module
    If (!(Get-Module -ListAvailable ActiveDirectory))
    {
        Write-Log -LogFile $Logfile -LogLevel INFO -ConsoleOutput -Message "$($ParamName) requires the Active Directory Module. Attempting to install."
        Try
        {
            $Result = Add-WindowsFeature RSAT-ADDS-Tools
            switch ($Result.Success)
            {
                True    {
                    Write-Log -LogFile $Logfile -LogLevel SUCCESS -ConsoleOutput -Message "Feature Active Directory Domain Services Tools (RSAT-ADDS-Tools) successful."
                    If ($Result.ExitCode -match "restart" -or $Result.RestartNeeded -match "Yes") { Write-Log -LogFile $Logfile -LogLevel WARN -ConsoleOutput -Message "A restart may be necessary to use the newly installed feature." }
                    Import-Module ActiveDirectory
                }
                False {
                    Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "Feature Active Directory Domain Services Tools (RSAT-ADDS-Tools unsuccessful."
                    Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Feature: $($Result.FeatureResult.DisplayName)"
                    Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Result: $($Result.Success)"
                    Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Exit code: $($Result.ExitCode)"
                }
            }
        }
        Catch
        {
            $ErrorMessage = $_
            Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "An error has occurred during feature installation. Please see $($Logfile) for details."
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Feature: $($Result.FeatureResult.DisplayName)"
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Result: $($Result.Success)"
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Exit code: $($Result.ExitCode)"
        }
        Finally
        {
            If ($DebugLogging)
            {
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Feature Display Name: $($Result.FeatureResult.DisplayName)"
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Feature Name: $($Result.FeatureResult.Name)"
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Result: $($Result.Success)"
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Restart Needed: $($Result.RestartNeeded)"
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Exit code: $($Result.ExitCode)"
                Write-Log -LogFile $Logfile -LogLevel DEBUG -Message "Skip reason: $($Result.FeatureResult.SkipReason)"
            }
        }
    }
    Else { Import-Module ActiveDirectory; Write-Log -LogFile $Logfile -LogLevel INFO -Message "Active Directory Module loaded." }
    If (!(Get-Module -ListAvailable ActiveDirectory))
    {
        Write-Log -LogFile $Logfile -LogLevel ERROR -ConsoleOutput -Message "Unable to install Active Directory module. $($ParamName) configuration will not be successful. Please re-run AADConnectPermissions.ps1 without DeviceWriteBack parameter to continue."
        Break
    }
} # End Function VerifyADTools

# Logging function
function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel)
{
    $Message = $Message + $Input
    If (!$LogLevel) { $LogLevel = "INFO" }
    switch ($LogLevel)
    {
        SUCCESS { $Color = "Green" }
        INFO { $Color = "White" }
        WARN { $Color = "Yellow" }
        ERROR { $Color = "Red" }
        DEBUG { $Color = "Gray" }
    }
    if ($Message -ne $null -and $Message.Length -gt 0)
    {
        $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss")
        if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty)
        {
            Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] $Message"
        }
        if ($ConsoleOutput -eq $true)
        {
            Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color
        }
    }
} # End Function Write-Log

function LocateExchange
{
    If (!$ExchangeServer)
    {
        $global:ProgressPreference = "SilentlyContinue"
        Write-Progress -Activity "No Exchange server specified. Attempting to locate Exchange Servers registered in Configuration container."
        Write-Log -LogFile $Logfile -LogLevel WARN -Message "No Exchange server specified. Attempting to locate Exchange Servers registered in configuration container."
        [array]$ExchangeServers = (Get-ADObject -Filter { objectCategory -eq "msExchExchangeServer" } -SearchBase (Get-ADRootDSE).configurationNamingContext).Name
        If ($ExchangeServers)
        {
            $SuccessfulTest = @()
            Write-Log -LogFile $Logfile -LogLevel INFO -Message "Found $($ExchangeServers.Count) Exchange servers registered in configuration partition. Selecting a server."
            ForEach ($obj in $ExchangeServers)
            {
                $Result = Try { Test-NetConnection $obj -ea stop -wa silentlycontinue -Port 443 }
                catch { $Result = "FAIL" }
                If ($Result.TcpTestSucceeded -eq $True)
                {
                    Write-Log -LogFile $Logfile -LogLevel SUCCESS -Message "Successfully connected to discovered Exchange Server: $($obj)."
                    $SuccessfulTest += $obj
                }
                if ($Result.TcpTestSucceeded -eq $False)
                {
                    Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Unable to connect to discovered Exchange Server: $($obj)."
                }
            }
            If ($SuccessfulTest)
            {
                $script:ExchangeServer = (Get-Random $SuccessfulTest)
                Write-Log -Logfile $Logfile -LogLevel SUCCESS -Message "Selected Exchange Server $($ExchangeServer)."
                Write-Progress -Activity "Selected Exchange Server $($ExchangeServer)." -Id 2 -Completed
            }
            Else
            {
                If (!$ExchangeServer)
                {
                    Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Cannot locate or connect to an Exchange server. ExchangeServer parameter must be specified if CreateMailboxes parameter is used. Error Code: EXERR01" -ConsoleOutput
                    Exit
                }
                
            }
        }
        $global:ProgressPreference = "Continue"
    }
    Else
    {
        Write-Log -LogFile $LogFile -LogLevel ERROR -Message "Cannot locate or connect to an Exchange Server. ExchangeServer parameter must be specified if CreateMailboxes parameter is used. Error Code: EXERR02" -ConsoleOutput
        Exit
    }
    # return $ExchangeServer
}

function ConnectToExchange
{
    If (!($ExchangeServer)) { LocateExchange }
    # Connect to Exchange Server
    try
    {
        $SessionInfo = Get-PSSession
        if ($SessionInfo.ConfigurationName -match "Microsoft.Exchange" -and $SessionInfo.ComputerName -match $ExchangeServer)
        {
            Write-Log -Message "You are already connected to an Exchange instance." -LogLevel INFO -LogFile $Logfile
        }
        else
        {
            Write-Log -Message "Connecting to $($ExchangeServer)..." -LogFile $Logfile -LogLevel INFO
            $Session = New-PSSession -ConfigurationName Microsoft.Exchange -Authentication Kerberos -ConnectionUri http://$($ExchangeServer)/powershell -WarningAction SilentlyContinue -InformationAction SilentlyContinue
            Try { Import-PSSession $Session -WarningAction SilentlyContinue -DisableNameChecking -InformationAction SilentlyContinue -ea Stop | Out-Null }
            Catch
            {
                Write-Log "Cannot connect to Exchange Server $($ExchangeServer). EXERR200" -LogFile $Logfile -LogLevel ERROR -ConsoleOutput; Break
            }
        }
    }
    catch { Write-Log -Message "Cannot connect to Exchange Server $($ExchangeServer)." -LogFile $Logfile -LogLevel ERROR -ConsoleOutput; Break }
} # End Function ConnectToExchange

# Email Address validation
function ValidateEmail($address)
{
    $address -match "^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"
}

## All public folder functions
function Get-PublicFolderData
{
    Try
    {
        Write-Progress -Activity "Gathering information on public folders." -Status "Please be patient, as this may take a while if you have a significant number of public folders."
        
        # Write-Host -NoNewLine -ForegroundColor Cyan "Gathering information on non-mail-enabled public folders. Please be patient, "
        # Write-Host -ForegroundColor Cyan "as this may take a while if you have a significant number of public folders."
        $Global:PublicFolders = Get-PublicFolder -Recurse -ResultSize Unlimited | select Name, Identity, EntryId
    }
    Catch
    {
        Write-Host -ForegroundColor Yellow "Failed to retrieve public folders."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Get-PublicFolderData: $($Error.Exception.Message)"
    }
}

## Mail-enabled folder functions
# Get Mail-Enabled Public Folder Data
function Get-MEPFData
{
    Try
    {
        Write-Progress -Activity "Gathering information on mail-enabled public folders." -Status "Please be patient, as this may take a while if you have a significant number of public folders."
        # Write-Host -NoNewLine -ForegroundColor Cyan "Gathering information on mail-enabled public folders. Please be patient, "
        # Write-Host -ForegroundColor Cyan "as this may take a while if you have a significant number of public folders."
        [array]$Global:MailPublicFolders = Get-MailPublicFolder -ResultSize Unlimited -EA SilentlyContinue -WA SilentlyContinue | Select Alias, DisplayName, DistinguishedName, EmailAddresses, EntryId, ExternalEmailAddress, Guid, Identity, LegacyExchangeDN, PrimarySmtpAddress, PublicFolderType, RecipientDisplayType, RecipientType, RecipientTypeDetails, WindowsEmailAddress
        
        Write-Progress -Activity "Gathering information on mail-enabled recipients." -Status "Please be patient, as this may take a while if you have a significant number of objects."
        [array]$Global:Recipients = Get-Recipient -resultsize Unlimited -ea silentlycontinue -wa SilentlyContinue | select Alias,Identity
    }
    Catch
    {
        Write-Host -ForegroundColor Yellow "Failed to retrieve mail-enabled public folders."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Get-MEPFData: $($Error.Exception.Message)"
    }
}

function Get-MESOData
{
    Try
    {
        Write-Progress -Activity "Gathering information on Microsoft Exchange System Objects container." -CurrentOperation "Please be patient."
        # Write-Host -ForegroundColor Cyan "Gathering information on Microsoft Exchange System Objects container."
        $global:MESOObjects = Get-ADObject -Filter { objectClass -eq "PublicFolder" } -Properties mailNickName,displayName, msExchPublicFolderEntryId, mail, proxyAddresses
    }
    Catch
    {
        Write-Host -Fore Yellow "Error querying Microsoft Exchange System Objects container."; Break
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Get-MESOData: $($Error.Exception.Message)"
    }
}

# Connect to Directory
VerifyADTools
LocateExchange
ConnectToExchange

# Gather data
Get-PublicFolderData
Get-MEPFData
Get-MESOData


# Forward checks against mail public folders
$MEPFObjectData = @()
$i = 1
foreach ($folder in $MailPublicFolders)
{
    Write-Progress -Activity "Processing Mail-enabled public folders" -Status "Processing $folder.Identity" -PercentComplete ($i++ / $MailPublicFolders.Count * 100)
    ### Check Alias
    <# Check to see if Alias has any bad or invalid characters in it, suggest new ones alias
    Rules for MailNickname per IDFix Guidelines
    - Must not be null
    - Must be unique
    - Invalid characters: {whitespace} \ ! # $ % & * + / = ? ^ ` { } | ~ < > ( ) ' ; : , [ ] " @
    - may not begin or end with a period
    - less than 64 characters long
    #>

    If ($MEPFObject) { Remove-Variable MEPFObject }
    $MEPFObject = [pscustomobject]@{
        MEPF = $Folder.DisplayName
        Path = $Folder.Identity
        InvalidChars = $null
        SuggestedAlias = $null
        AliasIsDuplicated = $null
        WindowsEmailAddressPresent = $null
        WindowsEmailAddressIsValid = $null
        WindowsEmailAddressInvalidData = $null
        PrimarySmtpAddressPresent = $null
        PrimarySmtpAddressIsValid = $null
        PrimarySmtpAddressInvalidData = $null
        PrimarySmtpAddressInEmailAddressesPresent = $null
        PrimarySmtpAddressInEmailAddressesValid = $null
        PrimarySmtpAddressInEmailAddressesInvalidData = $null
        MissingEntryIdInMESO = $null
        EntryId = $null
    }
    
    # Check to see if Alias is null
    If ($Folder.Alias -eq $null)
    {
        Write-Verbose "Folder $($Folder.DisplayName) alias ($($Folder.Alias)) is null."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Folder $($Folder.DisplayName) alias ($($folder.Alias)) is null."
        $InvalidChars = '[^a-zA-Z0-9\s]'
        $SuggestedAlias = $Folder.DisplayName -replace $InvalidChars, ""
        $SuggestedAlias = $SuggestedAlias.Substring(15)
        If ($SuggestedAlias -iin $Recipients.Alias)
        {
            Write-Verbose "$SuggestedAlias is in $Recipients.Alias"
            do
            {
                $SuggestedAlias = ($SuggestedAlias + "_" + ([guid]::NewGuid())).Replace("-", "")
                If ($SuggestedAlias.Length -gt 63) { $SuggestedAlias = $SuggestedAlias.Substring(0, 63) }
            }
            while ($SuggestedAlias -iin $Recipients.Alias)
        }
        $MEPFObject.InvalidChars = "True"
        $MEPFObject.SuggestedAlias = "$($SuggestedAlias)"
        
        If ($SuggestedAlias) { Remove-Variable SuggestedAlias }
    }
    
    # Check to see if Alias has bad characters
    If ($Folder.Alias -match "^(\.)|[\\\!\#\$\%\&\*\+\/\=\?\^\`\{\}\|\~\<\>\(\)\'\;\:\,\[\]\""\@ ]|(\.)$|(\.\.)")
    {
        Write-Verbose "Folder $($Folder.DisplayName) alias ($($Folder.Alias)) has invalid characters."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Folder $($Folder.DisplayName) alias ($($folder.Alias)) has invalid characters."
        $InvalidChars = '[^a-zA-Z0-9]'
        $SuggestedAlias = $Folder.Alias -replace $InvalidChars, ""
        If ($SuggestedAlias -iin $Recipients.Alias)
        {
            Write-Verbose "$SuggestedAlias is in $Recipients.Alias"
            do
            {
                $SuggestedAlias = ($SuggestedAlias + "_" + ([guid]::NewGuid())).Replace("-", "")
                If ($SuggestedAlias.Length -gt 63) { $SuggestedAlias = $SuggestedAlias.Substring(0, 63) }
            }
            while ($SuggestedAlias -iin $Recipients.Alias)
        }
        $MEPFObject.InvalidChars = "True"
        $MEPFObject.SuggestedAlias = "$($SuggestedAlias)"
        
        If ($SuggestedAlias) { Remove-Variable SuggestedAlias }
    }
    
    # Check to see if Alias is duplicated
    else
    {
        Write-Verbose "Checking to see if alias ($($Folder.Alias)) for folder ($($folder.DisplayName)) is present more than once."
        $count = (($Recipients.Alias -match $Folder.Alias).Count)
        If ($count -gt 1)
        {
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Alias ($($Alias)) of folder ($($folder.DisplayName)) is in Recipients more than once." -ConsoleOutput
            do
            {
                $SuggestedAlias = ($Folder.Alias + "_" + ([guid]::NewGuid())).Replace("-", "")
                If ($SuggestedAlias.Length -gt 60) { $SuggestedAlias = $SuggestedAlias.Substring(0, 63) }
            }
            while ($SuggestedAlias -iin $Recipients.Alias)
            $MEPFObject.AliasIsDuplicated = "True"
            $MEPFObject.SuggestedAlias = "$($SuggestedAlias)"
        }
        If ($SuggestedAlias) { Remove-Variable SuggestedAlias }
    }
    ####
    
    <# Check SMTP addresses for validity. There are 2 core addresses:
       - LDAP mail attribute, shown in AD, reported as WindowsEmailAddress in Get-MailPublicFolder
       - LDAP proxyAddresses:SMTP value, reported as PrimarySmtpAddress in Get-MailPublicFolder
    #>

    
    # Construct Vars
    If ($WindowsEmailAddress) { Remove-Variable WindowsEmailAddress }
    If ($PrimarySmtpAddress) { Remove-Variable PrimarySmtpAddress }
    If ($PrimarySmtpAddressInEmailAddresses) { Remove-Variable PrimarySmtpAddressInEmailAddresses }
    
    try { $WindowsEmailAddress = $folder.WindowsEmailAddress } catch { }
    try { $PrimarySmtpAddress = $folder.PrimarySmtpAddress.ToString() } catch { }
    try { $PrimarySmtpAddressInEmailAddresses = ($folder.EmailAddresses | ? { $_ -clike "SMTP:*" }).SubString(5) } catch { }
    
    # Evaluate WindowsEmailAddress
    if ($WindowsEmailAddress -eq $null -or $WindowsEmailAddress -eq "")
    {
        $MEPFObject.WindowsEmailAddressPresent = "False"
        Write-Verbose "MEPF $($folder.Identity) does not have a valid WindowsEmailAddress value."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) does not have a valid WindowsEmailAddress value."
    }
    else
    {
        $MEPFObject.WindowsEmailAddressPresent = "True"
        if (ValidateEmail -address $WindowsEmailAddress)
        {
            # Do nothing, since the address is valid.
        }
        else
        {
            $MEPFObject.WindowsEmailAddressIsValid = "False"
            $MEPFObject.WindowsEmailAddressInvalidData = $WindowsEmailAddress.ToString()
            Write-Verbose "MEPF $($folder.Identity) has invalid WindowsEmailAddress value $($WindowsEmailAddress)."
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) has invalid WindowsEmailAddress value $($WindowsEmailAddress)."
        }
    }
    
    # Evaluate PrimarySmtpAddress
    if ($PrimarySmtpAddress -eq $null -or $PrimarySmtpAddress -eq "")
    {
        $MEPFObject.PrimarySmtpAddressPresent = "False"
        Write-Verbose "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value."
    }
    else
    {
        $MEPFObject.PrimarySmtpAddressPresent = "True"
        If (ValidateEmail -address $PrimarySmtpAddress)
        {
            # Do nothing, since the address is valid.
        }
        Else
        {
            $MEPFObject.PrimarySmtpAddressIsValid = "False"
            $MEPFObject.PrimarySmtpAddressInvalidData = $PrimarySmtpAddress
            Write-Verbose "MEPF $($folder.Identity) has invalid PrimarySmtpAddress value $($PrimarySmtpAddress)."
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) has invalid PrimarySmtpAddress value $($PrimarySmtpAddress)."
        }
    }
    
    # Evaluate PrimarySmtpAddressInEmailAddresses
    if ($PrimarySmtpAddressInEmailAddresses -eq $null -or $PrimarySmtpAddressInEmailAddresses -eq "" )
    {
        $MEPFObject.PrimarySmtpAddressInEmailAddressesPresent = "False"
        Write-Verbose "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value in the EmailAddress array."
        Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value in the EmailAddress array."
    }
    else
    {
        $MEPFObject.PrimarySmtpAddressInEmailAddressesPresent = "True"
        If (ValidateEmail -address $PrimarySmtpAddressInEmailAddresses)
        {
            # Do nothing, since the address is valid.
        }
        Else
        {
            $MEPFObject.PrimarySmtpAddressInEmailAddressesValid = "False"
            $MEPFObject.PrimarySmtpAddressInEmailAddressesInvalidData = $PrimarySmtpAddressInEmailAddresses
            Write-Verbose "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value in the EmailAddress array."
            Write-Log -LogFile $Logfile -LogLevel ERROR -Message "MEPF $($folder.Identity) does not have a valid PrimarySmtpAddress value in the EmailAddress array."
        }
    }
    ####
    
    # Check to see if MEPF has corresponding object in Microsoft Exchange System Objects container
    If ($Folder.EntryId -iin $MESOObjects.msExchPublicFolderEntryId)
    {
        # Do nothing. Entry ID from PublicFolder is present in MESO.
    }
    Else
    {
        <# Add MEPF that is missing a corresponding object in the MESO container. To resolve:
           1. Capture the MEPF's SMTP and X500 proxy addresses
           2. Mail-disable the folder.
           3. Mail-enable the folder.
           4. Re-add proxy addresses saved from step 1.
        #>

        $MEPFObject.MissingEntryIdInMESO = "True"
        $MEPFObject.EntryId = $Folder.EntryId
    }
    $MEPFObjectData += $MEPFObject
}

# Checks all public folders for invalid Name values (either "\" or "/" in Name property)
$PublicFolderData = @()
foreach ($Folder in $PublicFolders)
{
    If ($Folder.Name -match "(\\|\/)")
    {
        Write-Verbose "Name value for $($Folder.Name) has invalid '\' or '/' characters."
        $InvalidChars = '[\/\\]'
        $SuggestedName = $Folder.Name -replace $InvalidChars, ""
        $InvalidPFData = [pscustomobject] @{
            Name = $Folder.Name;
            SuggestedName = $SuggestedName
            Path = $Folder.Identity;
        }
        $PublicFolderData += $InvalidPFData
        Remove-Variable InvalidPFData, SuggestedName
    }
}

# Checks Microsoft Exchange System Object container for public folder objects whose
# msExchPublicFolderEntryId value does not match the a folder in Get-MailPublicFolder.
# This likely indicated orphaned objects in the MESO container.
[array]$UnlinkedMESOReport = @()
If ($MESOObjects -and $MailPublicFolders)
{
    Write-Verbose "Looking for orphaned objects in the MESO container."
    foreach ($MEPF in $MESOObjects)
    {
        if ($MEPF.msExchPublicFolderEntryId -notin $MailPublicFolders.EntryId)
        {
            If ($MEPF.msExchPublicFolderEntryId -eq $null) { $EntryId = "NULL" }
            Else { $EntryId = $MEPF.msExchPublicFolderEntryId }
            $UnlinkedMESO = [pscustomobject]@{
                "Name" = $MEPF.Name;
                "DN"   = $MEPF.DistinguishedName;
                "mail" = $MEPF.Mail;
                "msExchPublicFolderEntryId" = $EntryId
            }
            $UnlinkedMESOReport += $UnlinkedMESO
            Write-Log -LogFile $Logfile -Message "Found potentially unlinked object ($($MEPF.Name)) in Microsoft Exchange System Objects container." -LogLevel INFO
            Remove-Variable UnlinkedMESO,EntryId
        }
    }
}

# Save Reports
Write-Host -NoNewline "The file: ";
Write-Host -NoNewLine -ForegroundColor Green "$($MailPublicFolderOutputFile) ";
Write-Host "contains the IDFix data for mail-enabled public folders, including invalid alias and SMTP address information and mail-enabled public folders that have missing entries in the Microsoft Exchange System Objects container."
$MEPFObjectData | Export-Csv $MailPublicFolderOutputFile -NoType -Force
Write-Host ""
Write-Host -NoNewline "The file: ";
Write-Host -NoNewline -ForegroundColor Green "$($PublicFolderOutputFile) ";
Write-Host "contains IDFix data for all public folders with invalid 'name' attribute data."
$PublicFolderData | Export-Csv $PublicFolderOutputFile -NoType -Force

Write-Host ""
Write-Host -NoNewline "The file: ";
Write-Host -NoNewline -ForegroundColor Green "$($UnlinkedMESOOutputFile) ";
Write-Host "contains potentially orphaned publicFolder objects in the Microsoft Exchange System Objects container. These are objects with no corresponding public folder in the store."
$UnlinkedMESOReport | Export-Csv $UnlinkedMESOOutputFile -NoType -Force