Created
February 20, 2026 11:19
-
-
Save win2000b/593bb8427ae7273a949a9c93faf68830 to your computer and use it in GitHub Desktop.
Script to Import SMTP Aliases and X500 into Exchange Online
This file contains hidden or 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
| <# ------------------------------------------------------------ | |
| 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