Powershell to Validate Email addresses

17,969

Solution 1

Each of the current top 2 answers here has one significant deficiency:

@Trevor's answer would do just fine, until you supply it this:

John Doe <[email protected]>

@Mathias' answer preaches about accommodating exceptional (yet valid) addresses such as those with non-ASCII or no TLD suffix. The following addresses all validate successfully with the [mailaddress] casting:

olly@somewhere | olly@somewhere. | [email protected]  etc

If, like me, you will not be entertaining these edge cases into your email databases, then a combination of both ideas might prove more useful, like so:

function IsValidEmail { 
    param([string]$Email)
    $Regex = '^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$'

   try {
        $obj = [mailaddress]$Email
        if($obj.Address -match $Regex){
            return $True
        }
        return $False
    }
    catch {
        return $False
    } 
}

Perhaps there is a performance overhead with creating $obj for every email address on a possibly long mailing list. But I guess that's another matter.

Solution 2

I'm trying to get Powershell to validate email addresses using Regex

Don't!

I would recommend against this. Accurately validating email addresses using regular expressions can be much more difficult than you might think.

Let's have a look at your regex pattern:

^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$

In it's current form it incorrectly validates [email protected].

On the other hand, it doesn't validate unicode-encoded internationalized domain names, like user@☎.com (yes, that's a valid email address)


Instead of trying to find or construct a perfect email validation regex pattern, I would use the MailAddress class for validation instead:

function IsValidEmail { 
    param([string]$EmailAddress)

    try {
        $null = [mailaddress]$EmailAddress
        return $true
    }
    catch {
        return $false
    }
}

If the input string is a valid email address, the cast to [mailaddress] will succeed and the function return $true - if not, the cast will result in an exception, and it returns $false.


When exporting the data, I'd consider collecting all the results at once in memory and then writing it to file once, at the end.

If you're using PowerShell version 2 or 3, you can do the same with two passes of Where-Object:

$EmailAddresses = Import-Csv "C:\Emails\OriginalEmails\emailAddresses.csv" -Header FirstName, LastName, Email, Address, City, State, ZipCode  | Select -Skip 1

$valid   = $list |Where-Object {IsValidEmail $_.Email}
$invalid = $list |Where-Object {-not(IsValidEmail $_.Email)}

If you're using PowerShell version 4.0 or newer, I'd suggest using the .Where() extension method in Split mode:

$EmailAddresses = Import-Csv "C:\Emails\OriginalEmails\emailAddresses.csv" -Header FirstName, LastName, Email, Address, City, State, ZipCode  | Select -Skip 1

$valid,$invalid = $list.Where({IsValidEmail $_.Email}, 'Split')

before exporting to file:

if($valid.Count -gt 0){ 
    $valid |Export-Csv "C:\Emails\ValidEmails\ValidEmails.csv" -NoTypeInformation
}
if($invalid.Count -gt 0){ 
    $invalid |Export-Csv "C:\Emails\ValidEmails\InvalidEmails.csv" -NoTypeInformation
}

Solution 3

You can use the mailaddress type to ensure it meets RFC, but you will likely still want to make sure the domain is valid:

Resolve-DnsName -Name ('[email protected]' -as [mailaddress]).Host -Type 'MX'

Works well as a validation script for a function parameter:

function Assert-FromEmail {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateScript({ Resolve-DnsName -Name $_.Host -Type 'MX' })]
        [mailaddress]
        $From
    )

    Write-Output $From
}

Output examples of that function on success:

PS > Assert-FromEmail -From [email protected]

DisplayName User       Host        Address
----------- ----       ----        -------
            vertigoray example.com [email protected]

Output examples of that function on failure:

PS > Assert-FromEmail -From [email protected]
Assert-FromEmail : Cannot validate argument on parameter 'From'. The " Resolve-DnsName -Name $_.Host -Type 'MX' "validation script for the argument with value "[email protected]" did not return a result of True. Determine why the validation script failed, and then try the command again.
At line:1 char:24
+ Assert-FromEmail -From [email protected]
+                        ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Assert-FromEmail], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Assert-FromEmail
Share:
17,969
Chris Singleton
Author by

Chris Singleton

Bellevue College graduate with an Associate in Applied Science of Information Systems Business Intelligence Developer with an emphasis in .Net Programming (T-SQL, C#, HTML, AJAX, J-Query and Java Script). Currently, working at Microsoft on Contracts as a Software Developer Engineer / Migration Engineer, Sr. BI Developer using Kusto, T-SQL, C Sharp, DAX (Visual Studio.net, Azure Data Studio, Lens Explorer). I never claim to know enough, that is why I am always continuing to learn and understand more.

Updated on June 08, 2022

Comments

  • Chris Singleton
    Chris Singleton about 2 years

    I'm trying to get Powershell to validate email addresses using Regex and put email addresses into good and bad csv files. I can get it to skip one line and write to file, but cannot get it to target the email addresses and validate them, then write lines to good and bad files. I can do it in C# and JavaScript, but have never done it in Powershell. I know this can be done, but not sure how.

    Here is what I have so far:

    Function IsValidEmail { 
        Param ([string] $In) 
        # Returns true if In is in valid e-mail format. 
        [system.Text.RegularExpressions.Regex]::IsMatch($In,  
            "^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|
        (([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");  
    } 
    
    
    ## Now we need to check the original file for invalid and valid emails.**
    
    $list = Get-Content C:\Emails\OriginalEmails\emailAddresses.csv
    
    # This way we also use the foreach loop.
    ##======= Test to see if the file exists ===========
    
    if (!(Test-Path "C:\Emails\ValidEmails\ValidEmails.csv")) {
        New-Item -path C:\Emails\ValidEmails -name ValidEmails.csv -type 
        "file" # -value "my new text"
        Write-Host "Created new file and text content added"
    }
    else {
        ## Add-Content -path C:\Share\sample.txt -value "new text content"
        Write-Host "File already exists and new text content added"
    }
    
    
    if (!(Test-Path "C:\Emails\InValidEmails\InValidEmails.csv")) {
        New-Item -path C:\Emails\InValidEmails -name InValidEmails.csv -type 
        "file" # -value "my new text"
        Write-Host "Created new file and text content added"
    }
    else {
        # Add-Content -path C:\Emails\ValidEmails -value "new text content"
        Write-Host "File already exists and new text content added"
    }
    
    #$Addresses = Import-Csv "C:\Data\Addresses.csv" -Header 
    Name, Address, PhoneNumber | Select -Skip 1
    $EmailAddressImp = Import-Csv 
    "C:\Emails\OriginalEmails\emailAddresses.csv" -Header 
    FirstName, LastName, Email, Address, City, State, ZipCode  | Select  
    FirstName, LastName, Email, Address, City, State, ZipCode -Skip 1
    

    I'm validating the third column "Email" in the original csv file and trying to write out the whole row to file (good file, bad file). Not sure how to buffer either doing this.

    ForEach ($emailAddress in $list) { 
        if (IsValidEmail($emailAddress)) { 
            "Valid: {0}" -f $emailAddress
            Out-File -Append C:\Emails\ValidEmails\ValidEmails.csv -Encoding UTF8
            $EmailAddressImp | Export-Csv "C:\Emails\ValidEmails\ValidEmails.csv" 
            -NoTypeInformation
        } 
        else { 
            "Invalid: {0}" -f $emailAddress 
            Out-File -Append C:\Emails\InValidEmails\InValidEmails.csv -
            Encoding UTF8
            $EmailAddressImp | Export-Csv 
            "C:\Emails\InValidEmails\InValidEmails.csv" -NoTypeInformation
        }     
    }