Skip to content

Commit 25b2e41

Browse files
authored
Merge pull request #2550 from microsoft/Jun26SU
Jun26SU Release
2 parents 2d8807f + d6de9c9 commit 25b2e41

42 files changed

Lines changed: 3612 additions & 50 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.build/cspell-words.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Eventlog
4141
evtx
4242
Exchweb
4343
exfiltration
44+
exopf
45+
exov
4446
fabrikam
4547
FIPFS
4648
fips
@@ -81,6 +83,7 @@ ldifde
8183
Liant
8284
Linq
8385
LOCALAPPDATA
86+
lockdown
8487
logman
8588
lsass
8689
Lync
@@ -89,6 +92,9 @@ Mbps
8992
mbxs
9093
Mddhhmm
9194
Mddhhmmss
95+
mepf
96+
mepfdns
97+
mepfs
9298
meso
9399
mesos
94100
mfcmapi
@@ -151,6 +157,7 @@ Sids
151157
Snapin
152158
SPDLT
153159
STMP
160+
subfolders
154161
syncall
155162
tcpip
156163
TDSDSA
@@ -159,6 +166,7 @@ UCMA
159166
unconfigured
160167
unrequired
161168
USERDNSDOMAIN
169+
validatemepf
162170
Vianet
163171
Visio
164172
vmxnet

.github/CODEOWNERS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
# EXO PF Team
1717
/PublicFolders/Migration/ @vishmittal @microsoft/css-exchange-admins
18-
/PublicFolders/Hybrid/ @vishmittal @microsoft/css-exchange-admins
18+
/PublicFolders/MailPublicFolderSync/ @vishmittal @microsoft/css-exchange-admins
1919
/PublicFolders/Update-PublicFolderPermissions.ps1 @vishmittal @microsoft/css-exchange-admins
2020
/docs/PublicFolders/Migration/ @vishmittal @microsoft/css-exchange-admins
21-
/docs/PublicFolders/Hybrid/ @vishmittal @microsoft/css-exchange-admins
21+
/docs/PublicFolders/MailPublicFolderSync/ @vishmittal @microsoft/css-exchange-admins
2222
/docs/PublicFolders/Update-PublicFolderPermissions.md @vishmittal @microsoft/css-exchange-admins

Diagnostics/HealthChecker/Analyzer/Security/Invoke-AnalyzerSecurityCveCheck.ps1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ function Invoke-AnalyzerSecurityCveCheck {
144144
"Oct25SU" = (NewCveEntry @("CVE-2025-59249") @($ex2016, $ex2019, $exSE))
145145
"Dec25SU" = (NewCveEntry @("CVE-2025-64666", "CVE-2025-64667") @($ex2016, $ex2019, $exSE))
146146
"Feb26SU" = (NewCveEntry @("CVE-2026-21527") @($ex2016, $ex2019, $exSE))
147+
"Jun26SU" = (NewCveEntry @("CVE-2026-42897", "CVE-2026-45500", "CVE-2026-45501", "CVE-2026-45502", "CVE-2026-45503", "CVE-2026-45504", "CVE-2026-47631") @($ex2016, $ex2019, $exSE))
147148
}
148149

149150
# Need to organize the list so oldest CVEs come out first.

Diagnostics/HealthChecker/Tests/HealthChecker.E16.Main.Tests.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ Describe "Testing Health Checker by Mock Data Imports - Exchange 2016" {
146146
$cveTests.Contains("CVE-2020-1147") | Should -Be $true
147147
$cveTests.Contains("CVE-2023-36039") | Should -Be $true
148148
$cveTests.Contains("ADV24199947") | Should -Be $true
149-
$cveTests.Count | Should -Be 63
149+
$cveTests.Count | Should -Be 70
150150
$downloadDomains = GetObject "CVE-2021-1730"
151151
$downloadDomains.DownloadDomainsEnabled | Should -Be "false"
152152

153-
$Script:ActiveGrouping.Count | Should -Be 71
153+
$Script:ActiveGrouping.Count | Should -Be 78
154154
}
155155
}
156156

Diagnostics/HealthChecker/Tests/HealthChecker.E19.Main.Tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ Describe "Testing Health Checker by Mock Data Imports" {
225225
$cveTests.Contains("CVE-2023-36434") | Should -Be $true
226226
$cveTests.Contains("CVE-2023-36039") | Should -Be $true
227227
$cveTests.Contains("ADV24199947") | Should -Be $true
228-
$cveTests.Count | Should -Be 63
228+
$cveTests.Count | Should -Be 70
229229
$downloadDomains = GetObject "CVE-2021-1730"
230230
$downloadDomains.DownloadDomainsEnabled | Should -Be "False"
231231
TestObjectMatch "Extended Protection Vulnerable" "True" -WriteType "Red"

PublicFolders/Export-PublicFolderStatistics.ps1

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
Number of folder statistics results to buffer before writing to the output
2222
CSV. Must be 1 or greater. Defaults to 5.
2323
24+
.PARAMETER ScriptUpdateOnly
25+
Use this switch to only update the script to the latest released version without
26+
performing any other actions.
27+
28+
.PARAMETER SkipVersionCheck
29+
Use this switch to skip the automatic version check and script update.
30+
2431
.EXAMPLE
2532
.\Export-PublicFolderStatistics.ps1 -InputFile PublicFoldersToQuery.csv
2633
@@ -37,23 +44,33 @@
3744
- Progress is reported with `Write-Progress` and errors are logged to host.
3845
#>
3946

40-
[CmdletBinding()]
47+
[CmdletBinding(DefaultParameterSetName = "Default")]
4148
param (
4249

43-
[Parameter(Mandatory = $true)]
50+
[Parameter(Mandatory = $true, ParameterSetName = "Default")]
4451
[string]
4552
$InputFile,
4653

47-
[Parameter(Mandatory = $false)]
54+
[Parameter(Mandatory = $false, ParameterSetName = "Default")]
4855
[string]
4956
$OutputFile = "PublicFolderStatistics.csv",
5057

51-
[Parameter(Mandatory = $false)]
58+
[Parameter(Mandatory = $false, ParameterSetName = "Default")]
5259
[ValidateRange(1, [int]::MaxValue)]
5360
[int]
54-
$BatchSize = 5
61+
$BatchSize = 5,
62+
63+
[Parameter(Mandatory = $true, ParameterSetName = "ScriptUpdateOnly")]
64+
[switch]
65+
$ScriptUpdateOnly,
66+
67+
[Parameter(Mandatory = $false)]
68+
[switch]
69+
$SkipVersionCheck
5570
)
5671

72+
. $PSScriptRoot\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1
73+
5774
# Import the list of public folders from the specified CSV input file. The
5875
# CSV is expected to contain a column named 'Identity' with each folder path.
5976
$publicFolders = Import-Csv -Path $InputFile
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
# .SYNOPSIS
5+
# Import-MailPublicFolders.ps1
6+
# Create dummy mail public folder objects in target forest
7+
#
8+
# The script needs to be run from Onprem.
9+
#
10+
# One of the forest involved should always be cloud.
11+
#
12+
# If the input do not contain the switch parameter, then the target forest will be assumed as Onprem.
13+
#
14+
# Default URI to connect to cloud is https://outlook.office365.com/powerShell-liveID. This can be changed by passing the appropriate URI to ConnectionUri parameter
15+
#
16+
# Example input to the script:
17+
#
18+
# Import-MailPublicFolders.ps1 -ToCloud
19+
20+
[CmdletBinding(DefaultParameterSetName = "Default")]
21+
param (
22+
[Parameter(Mandatory=$false)]
23+
[PSCredential] $Credential,
24+
25+
[Parameter(Mandatory=$false)]
26+
[Switch] $ToCloud,
27+
28+
[Parameter(Mandatory=$false)]
29+
[string] $ConnectionUri = "https://outlook.office365.com/powerShell-liveID",
30+
31+
[Parameter(Mandatory=$true, ParameterSetName="ScriptUpdateOnly")]
32+
[switch] $ScriptUpdateOnly,
33+
34+
[Parameter(Mandatory=$false)]
35+
[switch] $SkipVersionCheck
36+
)
37+
38+
. $PSScriptRoot\..\..\Shared\ScriptUpdateFunctions\GenericScriptUpdate.ps1
39+
40+
# Create a tenant PSSession against Exchange Online with modern auth.
41+
function CreateTenantSession() {
42+
param (
43+
[string] $ConnUri,
44+
[PSCredential] $Credential
45+
)
46+
47+
Import-Module -Name ExchangeOnlineManagement -ErrorAction SilentlyContinue
48+
if (Get-Module -Name ExchangeOnlineManagement) {
49+
$connectParams = @{
50+
ConnectionUri = $ConnUri
51+
Prefix = "Remote"
52+
ErrorAction = "SilentlyContinue"
53+
}
54+
55+
if ($null -ne $Credential) {
56+
$connectParams.Credential = $Credential
57+
}
58+
Connect-ExchangeOnline @connectParams
59+
} else {
60+
Write-Warning "This script uses modern authentication to connect to Exchange Online and requires EXO V2 module to be installed. Please follow the instructions at https://docs.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2?view=exchange-ps#install-the-exo-v2-module to install EXO V2 module."
61+
exit
62+
}
63+
}
64+
65+
## Get organization guid
66+
function GetOrganizationGuid() {
67+
param ($targetForest)
68+
69+
$organizationGuid = ""
70+
if ($targetForest) {
71+
$orgConfig = Get-OrganizationConfig -ErrorAction:SilentlyContinue
72+
} else {
73+
$orgConfig = Get-RemoteOrganizationConfig -ErrorAction:SilentlyContinue
74+
}
75+
76+
# Return the results
77+
if ($null -ne $orgConfig) {
78+
$organizationGuid = $($orgConfig.Guid.ToString())
79+
}
80+
81+
return $organizationGuid
82+
}
83+
84+
## Retrieve mail public folders
85+
function GetMailPublicFolders() {
86+
param ($fromSource, $targetForest)
87+
88+
$mailPublicFolders = @()
89+
if (($fromSource -and $targetForest) -or (!$fromSource -and !$targetForest)) {
90+
$mailPublicFolders = Get-MailPublicFolder -ResultSize:Unlimited -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
91+
} else {
92+
$mailPublicFolders = Get-RemoteMailPublicFolder -ResultSize:Unlimited -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
93+
}
94+
95+
return $mailPublicFolders
96+
}
97+
98+
## MailPublicFolders whose external email address do not point to an
99+
## existing object need to be removed
100+
function RemoveOrphanMailPublicFolders() {
101+
param ($srcFolderHashtable, $tgtMailPublicFolders, $targetForest, $orgGuid)
102+
103+
foreach ($mailPublicFolder in $tgtMailPublicFolders) {
104+
if ($null -ne $mailPublicFolder.ExternalEmailAddress) {
105+
if ($srcFolderHashtable.ContainsKey($mailPublicFolder.ExternalEmailAddress.ToString())) {
106+
$srcFolderHashtable.Remove($mailPublicFolder.ExternalEmailAddress.ToString())
107+
continue
108+
} elseif ($srcFolderHashtable.ContainsKey($mailPublicFolder.ExternalEmailAddress.ToString().ToUpper().Replace("SMTP:", ""))) {
109+
$srcFolderHashtable.Remove($mailPublicFolder.ExternalEmailAddress.ToString().ToUpper().Replace("SMTP:", ""))
110+
continue
111+
}
112+
}
113+
114+
if ($null -ne $mailPublicFolder.LegacyExchangeDN -and ($mailPublicFolder.LegacyExchangeDN.Contains($orgGuid))) {
115+
if ($targetForest) {
116+
Disable-RemoteMailPublicFolder -Identity $mailPublicFolder.Alias -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue -Confirm:$false
117+
} else {
118+
Disable-MailPublicFolder -Identity $mailPublicFolder.Alias -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue -Confirm:$false
119+
}
120+
}
121+
}
122+
123+
return $srcFolderHashtable
124+
}
125+
126+
## Retrieve accepted domains
127+
function GetAcceptedDomains() {
128+
param ($targetForest)
129+
130+
$acceptedDomains = @()
131+
if ($targetForest) {
132+
$acceptedDomains = Get-RemoteAcceptedDomain -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
133+
} else {
134+
$acceptedDomains = Get-AcceptedDomain -ErrorAction:SilentlyContinue -WarningAction:SilentlyContinue
135+
}
136+
137+
return $acceptedDomains
138+
}
139+
140+
## Import mail public folders.
141+
function ImportMailPublicFolders() {
142+
param ($targetForest, $acceptedDomains, $srcFolderHashtable, $orgGuid)
143+
144+
if ($targetForest) {
145+
$cmdletToExecute = "New-RemoteSyncMailPublicFolder"
146+
} else {
147+
$cmdletToExecute = "New-SyncMailPublicFolder"
148+
}
149+
150+
$acceptedDomainCount = $acceptedDomains.Count
151+
$inputParameters = @{}
152+
foreach ($mailPublicFolder in $($srcFolderHashtable.Values)) {
153+
# Collect the properties of mail enabled public folder
154+
$alias = $mailPublicFolder.Alias.Trim()
155+
$name = $mailPublicFolder.Name.Trim()
156+
$entryId = $mailPublicFolder.EntryId.ToString()
157+
$windowsEmailAddress = $mailPublicFolder.WindowsEmailAddress.ToString()
158+
$externalEmailAddress = $mailPublicFolder.PrimarySmtpAddress.ToString()
159+
160+
if ($alias.Length -lt 1 -or
161+
$entryId.Length -lt 1 -or
162+
$externalEmailAddress.Length -lt 1) {
163+
continue
164+
}
165+
166+
$entryId = $orgGuid + $entryId
167+
168+
$emailAddressesArray = @($mailPublicFolder.EmailAddresses)
169+
170+
if ($windowsEmailAddress -ne "") {
171+
$localPart = @($windowsEmailAddress.Split('@'))[0]
172+
for ($index = 0; $index -lt $acceptedDomainCount; $index++) {
173+
$emailAddressesArray += $localPart + "@" + $acceptedDomains[$index].DomainName.ToString()
174+
}
175+
}
176+
177+
for ($index = 0; $index -lt $emailAddressesArray.Count; $index++) {
178+
$emailAddressesArray[$index] = $emailAddressesArray[$index].ToString().Replace("SMTP:", "")
179+
$emailAddressesArray[$index] = $emailAddressesArray[$index].ToString().Replace("smtp:", "")
180+
}
181+
182+
# Remove duplicate email addresses if any
183+
$emailAddressesArray = $emailAddressesArray | Sort-Object -Unique
184+
185+
$inputParameters.Clear()
186+
$inputParameters.Add("Name", $name)
187+
$inputParameters.Add("Alias", $alias)
188+
189+
if ($($mailPublicFolder.HiddenFromAddressListsEnabled) -eq "True") {
190+
$inputParameters.Add("HiddenFromAddressListsEnabled", $true)
191+
}
192+
193+
$inputParameters.Add("EmailAddresses", $emailAddressesArray)
194+
$inputParameters.Add("EntryId", $entryId)
195+
196+
if ($windowsEmailAddress -ne "") {
197+
$inputParameters.Add("WindowsEmailAddress", $windowsEmailAddress)
198+
}
199+
200+
if ($externalEmailAddress -ne "") {
201+
$inputParameters.Add("ExternalEmailAddress", $externalEmailAddress)
202+
}
203+
204+
$inputParameters.Add("ErrorAction", "Continue")
205+
$inputParameters.Add("WarningAction", "Continue")
206+
207+
# Execute the command
208+
&$cmdletToExecute @inputParameters
209+
}
210+
}
211+
212+
################################ BEGINNING OF SCRIPT ################################
213+
214+
# Create a PSSession for this organization
215+
CreateTenantSession -ConnUri $ConnectionUri -Credential $Credential
216+
217+
# Determine the guid of the organization from where to export objects
218+
$organizationGuid = GetOrganizationGuid -targetForest $ToCloud
219+
220+
# Get mail enabled public folders from source forest
221+
$sourceMailPublicFolders = @(GetMailPublicFolders -fromSource $true -targetForest $ToCloud)
222+
223+
$sourceFoldersHashTable = @{}
224+
foreach ($mailPublicFolder in $sourceMailPublicFolders) {
225+
$sourceFoldersHashTable.Add($mailPublicFolder.PrimarySmtpAddress.ToString(), $mailPublicFolder)
226+
}
227+
228+
# Get mail enabled public folders from target forest
229+
$targetMailPublicFolders = @(GetMailPublicFolders -fromSource $false -targetForest $ToCloud)
230+
231+
if ($targetMailPublicFolders.Count -gt 0) {
232+
$sourceFoldersHashTable = RemoveOrphanMailPublicFolders -srcFolderHashtable $sourceFoldersHashTable -tgtMailPublicFolders $targetMailPublicFolders -targetForest $ToCloud -orgGuid $organizationGuid
233+
}
234+
235+
Write-Host "Successfully removed any existing orphan mail public folders already created by the script"
236+
237+
if ($sourceMailPublicFolders.Count -lt 1) {
238+
if ($ToCloud) {
239+
Write-Host "Couldn't find any mail enabled public folder objects in Onprem environment"
240+
} else {
241+
Write-Host "Couldn't find any mail enabled public folder objects in Cloud environment"
242+
}
243+
244+
Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
245+
exit
246+
}
247+
248+
# Retrieve the accepted domains for this organization
249+
$acceptedDomains = @(GetAcceptedDomains -targetForest $ToCloud)
250+
251+
# Import the mail enabled public folders to other forest
252+
ImportMailPublicFolders -targetForest $ToCloud -acceptedDomains $acceptedDomains -srcFolderHashtable $sourceFoldersHashTable -orgGuid $organizationGuid
253+
254+
Write-Host "Completed importing of mail enabled public folders"
255+
256+
# Terminate the PSSession
257+
Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
258+
################################ END OF SCRIPT ################################

0 commit comments

Comments
 (0)