Skip to content

Instantly share code, notes, and snippets.

@win2000b
Created February 20, 2026 11:19
Show Gist options
  • Select an option

  • Save win2000b/593bb8427ae7273a949a9c93faf68830 to your computer and use it in GitHub Desktop.

Select an option

Save win2000b/593bb8427ae7273a949a9c93faf68830 to your computer and use it in GitHub Desktop.
Script to Import SMTP Aliases and X500 into Exchange Online
<# ------------------------------------------------------------
Import_Primary_SMTP_X500_AddMissingOnly_WithStatusCsv.ps1
Reads: Identity,Type,Value
Writes: Identity,Type,Value,Status,Message,Timestamp
ADD MISSING ONLY:
- SMTP (Type=SMTP) -> adds secondary proxy (forces smtp:)
- X500 (Type=X500) -> adds x500:
- PrimarySMTP (Type=PrimarySMTP) -> optionally sets primary (controlled by $ApplyPrimarySMTP)
Hard-coded paths.
Supports -WhatIf/-Confirm via ShouldProcess.
------------------------------------------------------------- #>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param()
# ----------------------------
# Hard-coded settings
# ----------------------------
$InputCsv = "C:\Data\TestImport.csv"
$StatusCsv = "C:\Data\Import_Log_{0}.csv" -f (Get-Date -Format "yyyyMMdd_HHmmss")
# If you want to ALSO apply PrimarySMTP rows (this changes primary, but does NOT remove aliases),
# set this to $true. If you only want to restore SMTP aliases + X500, set to $false.
$ApplyPrimarySMTP = $true
# If your CSV is semicolon-delimited, set this to ';' otherwise ','
$Delimiter = ','
# ----------------------------
# Helpers
# ----------------------------
function New-StatusRow {
param(
[string]$Identity,
[string]$Type,
[string]$Value,
[string]$Status,
[string]$Message
)
[pscustomobject]@{
Identity = $Identity
Type = $Type
Value = $Value
Status = $Status
Message = $Message
Timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
}
}
function Normalize-PrimaryEmail {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return $null }
$v = $Value.Trim()
if ($v -match '^(?i)smtp:') { $v = $v.Substring(5) }
return $v.Trim()
}
function Normalize-SmtpProxy {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return $null }
$v = $Value.Trim()
# Add prefix if missing
if ($v -notmatch '^(?i)smtp:') { $v = "smtp:$v" }
# Force lowercase prefix to prevent accidental "primary"
return ("smtp:" + $v.Substring(5)).Trim()
}
function Normalize-X500Proxy {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return $null }
$v = $Value.Trim()
if ($v -notmatch '^(?i)x500:') { $v = "x500:$v" }
return ("x500:" + $v.Substring(5)).Trim()
}
# ----------------------------
# Start
# ----------------------------
Write-Host "Importing from: $InputCsv" -ForegroundColor Cyan
Write-Host "Writing status CSV: $StatusCsv" -ForegroundColor Cyan
Write-Host "Apply PrimarySMTP rows: $ApplyPrimarySMTP" -ForegroundColor Cyan
if (-not (Test-Path $InputCsv)) { throw "Input CSV not found: $InputCsv" }
# Import input
$rows = Import-Csv -Path $InputCsv -Delimiter $Delimiter
if (-not $rows -or $rows.Count -eq 0) { throw "No rows found in CSV: $InputCsv" }
# Validate required columns exist
$cols = $rows[0].PSObject.Properties.Name
foreach ($req in @("Identity","Type","Value")) {
if ($cols -notcontains $req) {
throw "CSV must contain columns: Identity, Type, Value. Missing: $req. Found: $($cols -join ', ')"
}
}
# Status rows accumulator
$statusOut = New-Object System.Collections.Generic.List[object]
# Cache per mailbox to avoid repeated Get-Mailbox calls
# Key: Identity -> HashSet of proxy strings (case-insensitive)
$proxyCache = @{}
foreach ($r in $rows) {
$identity = ([string]$r.Identity).Trim()
$type = ([string]$r.Type).Trim()
$value = [string]$r.Value # may be null
# Basic validation per row (and still write to status CSV)
if ([string]::IsNullOrWhiteSpace($identity)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedBlank" -Message "Blank Identity"))
continue
}
if ([string]::IsNullOrWhiteSpace($type)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedBlank" -Message "Blank Type"))
continue
}
if ($type -match '^(?i)error|none$') {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "Skipped" -Message "Non-actionable Type"))
continue
}
try {
# Prime cache if needed
if (-not $proxyCache.ContainsKey($identity)) {
$mbx = Get-Mailbox -Identity $identity -ErrorAction Stop
$set = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($p in ($mbx.EmailAddresses | ForEach-Object { $_.ToString() })) {
[void]$set.Add($p)
}
$proxyCache[$identity] = $set
}
switch -Regex ($type) {
'^(?i)PrimarySMTP$' {
if (-not $ApplyPrimarySMTP) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "Skipped" -Message "ApplyPrimarySMTP disabled"))
break
}
$primaryEmail = Normalize-PrimaryEmail -Value $value
if ([string]::IsNullOrWhiteSpace($primaryEmail)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedBlank" -Message "Blank PrimarySMTP value"))
break
}
if ($PSCmdlet.ShouldProcess($identity, "Set Primary SMTP to '$primaryEmail'")) {
try {
Set-Mailbox -Identity $identity -PrimarySmtpAddress $primaryEmail -ErrorAction Stop
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $primaryEmail -Status "Updated" -Message "Primary SMTP set"))
}
catch {
# fallback
Set-Mailbox -Identity $identity -WindowsEmailAddress $primaryEmail -ErrorAction Stop
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $primaryEmail -Status "Updated" -Message "Primary SMTP set (WindowsEmailAddress fallback)"))
}
# Refresh cache after primary change
$mbx2 = Get-Mailbox -Identity $identity -ErrorAction Stop
$newSet = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($p in ($mbx2.EmailAddresses | ForEach-Object { $_.ToString() })) {
[void]$newSet.Add($p)
}
$proxyCache[$identity] = $newSet
}
else {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $primaryEmail -Status "WhatIf" -Message "Would set Primary SMTP"))
}
break
}
'^(?i)SMTP$' {
$smtpProxy = Normalize-SmtpProxy -Value $value
if ([string]::IsNullOrWhiteSpace($smtpProxy)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedBlank" -Message "Blank SMTP value"))
break
}
if ($proxyCache[$identity].Contains($smtpProxy)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $smtpProxy -Status "SkippedAlreadyPresent" -Message "SMTP proxy already exists"))
break
}
if ($PSCmdlet.ShouldProcess($identity, "Add SMTP proxy '$smtpProxy'")) {
Set-Mailbox -Identity $identity -EmailAddresses @{Add = $smtpProxy} -ErrorAction Stop
[void]$proxyCache[$identity].Add($smtpProxy)
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $smtpProxy -Status "Added" -Message "SMTP proxy added"))
}
else {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $smtpProxy -Status "WhatIf" -Message "Would add SMTP proxy"))
}
break
}
'^(?i)X500$' {
$x500Proxy = Normalize-X500Proxy -Value $value
if ([string]::IsNullOrWhiteSpace($x500Proxy)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedBlank" -Message "Blank X500 value"))
break
}
if ($proxyCache[$identity].Contains($x500Proxy)) {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $x500Proxy -Status "SkippedAlreadyPresent" -Message "X500 proxy already exists"))
break
}
if ($PSCmdlet.ShouldProcess($identity, "Add X500 proxy '$x500Proxy'")) {
Set-Mailbox -Identity $identity -EmailAddresses @{Add = $x500Proxy} -ErrorAction Stop
[void]$proxyCache[$identity].Add($x500Proxy)
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $x500Proxy -Status "Added" -Message "X500 proxy added"))
}
else {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $x500Proxy -Status "WhatIf" -Message "Would add X500 proxy"))
}
break
}
default {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "SkippedUnknownType" -Message "Unknown Type value"))
break
}
}
}
catch {
$statusOut.Add((New-StatusRow -Identity $identity -Type $type -Value $value -Status "Error" -Message $_.Exception.Message))
continue
}
}
# Export status CSV (same shape as import, plus status columns)
$statusOut | Export-Csv -Path $StatusCsv -NoTypeInformation -Encoding UTF8
Write-Host "Import finished. Status written to: $StatusCsv" -ForegroundColor Green
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment