Quick Links

When you need a data set for testing or demonstration, and that set needs to represent Personally Identifiable Information (PII), you generally don't want to use real data that represents actual people. Here, we'll walk you through how you can use PowerShell to generate a list of random names and phone numbers for just such an occasion.

What You Need

Before you get started, there's some tools and information you should have:

PowerShell

This script was developed using PowerShell 4.0, and has also been tested for compatibility with PowerShell 2.0. PowerShell 2.0 or later has been built-in to Windows since Windows 7. It is also available for Windows XP and Vista as part of the Windows Management Framework (WMF). Some further details, and links for downloads, are below.

  • PowerShell 2.0 comes with Windows 7. Windows XP SP3 and Vista (SP1 or later) users can download the appropriate WMF version from Microsoft in KB968929. It is not supported on XP SP2 or below, or Vista without SP1.
  • PowerShell 4.0 comes with Windows 8.1. Windows 7 SP1 users can upgrade to it as part of a WMF update from the Microsoft Download Center. It is not available for XP or Vista.

Names

You'll need some lists of names to feed into the random generator. A great source for a lot of names, and information regarding their popularity (though that will not be used for this script), is the United States Census Bureau. The lists available at the links below are very large, so you might want to trim them down a bit if you plan to be generating a lot of names and numbers at once. On our test system, each name/number pair took about 1.5 seconds to generate using the full lists but your mileage will vary depending on your own system specs.

Regardless of the source you use, you will need to generate three text files that the script can use as pools for its name selection. Each file should contain only names, and only one name per line. These need to be stored in the same folder as your PowerShell script.

Surnames.txt should contain the surnames you want the script to select from. Example:

Smith

Johnson

Williams

Jones

Brown

Males.txt should contain the male first names you want the script to select from. Example:

James

John

Robert

Michael

William

Females.txt should contain the female first names you want the script to select from. Example:

Mary

Patricia

Linda

Barbara

Elizabeth

Rules for Phone Numbers

If you want to be sure your phone numbers don't match up with anyone's real phone number, the easiest way is to use the well-known "555" Exchange Code. But if you're going to be showing a data set with a lot of phone numbers, that 555 will start to look pretty monotonous real fast. To make things more interesting, we'll generate other phone numbers that violate the North American Numbering Plan (NANP) rules. Below are some sample invalid phone numbers, representing each class of number that will be generated by this script:

  • (157) 836-8167This number is invalid because Area Codes cannot begin with a 1 or 0.
  • (298) 731-6185This number is invalid because the NANP is not assigning area codes with 9 as the second digit.
  • (678) 035-7598This number is invalid because Exchange Codes cannot begin with a 1 or 0.
  • (752) 811-1375This number is invalid because Exchange Codes cannot end with two 1s.
  • (265) 555-0128This number is invalid because the Exchange Code is 555, and the Subscriber ID is within the range reserved for fictitious numbers.
  • (800) 555-0199This number is the only 800 number with a 555 Exchange Code which is reserved for use as a fictitious number.

Note that the above rules are subject to change and may vary by jurisdiction. You should do your own research to verify the current rules which are applicable to the locale for which you will be generating phone numbers.

Common Commands

There are some fairly common commands that are going to be used throughout this script, so you should get a basic idea of what these mean before we dive into actually writing it.

  • ForEach-Object takes an array, or list, of objects and performs the specified operation on each of them. Within a ForEach-Object script block, the $_ variable is used to refer to the current item being processed.
  • if ... else statements allow you to perform an operation only if certain conditions are met, and (optionally) specify what should be done when that condition is not met.
  • switch statements are like if statements with more choices. Switch will check an object against several conditions, and run whatever script blocks are specified for conditions that the object matches. You can also, optionally, specify a default block which will only run if no other conditions are matched. Switch statements also use the $_ variable to refer to the current item being processed.
  • while statements allow you to continuously repeat a script block so long as a certain condition is met. Once something happens that causes the condition to no longer be true when the script block is finished, the loop exits.
  • try ... catch statements help with error handling. If anything goes wrong with the script block specified for try, the catch block will run.
  • Get-Content does what it says on the tin. It gets the contents of a specified object - usually a file. This can be used to display the contents of a text file at the console or, as in this script, pass the contents along the pipeline to be used with other commands.
  • Write-Host puts stuff in the console. This is used to present messages to the user, and is not included in the script's output if the output gets redirected.
  • Write-Output actually generates output. Normally, this is dumped to the console but it can also be redirected by other commands.

There are other commands in the script, but we'll explain those as we go.

Building the Script

Now it's time to get our hands dirty.

Part 1: Getting Ready to Go

If you like your script to start running from a clean console, here's the first line you want in it.

Clear-Host

Now that we have a clean screen, the next thing we want to do is have the script check to make sure everything it needs is in place. To do that, we need to start by telling it where to look, and what to look for.

$ScriptFolder = Split-Path $MyInvocation.MyCommand.Definition -Parent

$RequiredFiles = ('Males.txt','Females.txt','Surnames.txt')

The first line there is very useful for any script. It defines a variable that points to the folder containing the script. This is essential if your script needs other files that are located in the same directory as itself (or a known relative path from that directory), because you will otherwise encounter errors if and when you try to run the script while you're in another working directory.

The second line creates an array of file names that are required for the script to run properly. We'll use this, along with the $ScriptFolder variable, in the next piece where we check to be sure those files are present.

$RequiredFiles | ForEach-Object {

if (!(Test-Path "$ScriptFolder\$_"))

{

Write-Host "$_ not found." -ForegroundColor Red

$MissingFiles++

}

}

This chunk of script sends the $RequiredFiles array into a ForEach-Object block. Within that script block, the if statement uses Test-Path to see if the file we're looking for is where it belongs. Test-Path is a simple command that, when given a file path, returns a basic true or false response to tell us if the path points to something that exists. The exclamation point in there is a not operator, which reverses the response of Test-Path before passing it on to the if statement. So if Test-Path returns false (that is, the file we're looking for does not exist), it will be converted to true so that the if statement will execute its script block.

Another thing to note here, which will be used often in this script, is the use of double-quotes instead of single-quotes. When you put something in single-quotes, PowerShell treats it as a static string. Whatever is in the single quotes will be passed along exactly as-is. Double-quotes tells PowerShell to translate the variables and some other special items within the string before passing it along. Here, the double-quotes mean that instead of running Test-Path '$ScriptFolder\$_'  we'll actually be doing something more like Test-Path 'C:\Scripts\Surnames.txt' (assuming your script is in C:\Scripts, and ForEach-Object is currently working on 'Surnames.txt').

For each file not found, Write-Host will post an error message in red to tell you which file is missing. Then it increments the $MissingFiles variable which will be used in the next piece, to error and quit if there were any files missing.

if ($MissingFiles)

{

    Write-Host "Could not find $MissingFiles source file(s). Aborting script." -ForegroundColor Red

    Remove-Variable ScriptFolder,RequiredFiles,MissingFiles

    Exit

}

Here's another neat trick you can do with if statements. Most guides you'll see about if statements will tell you to use an operator to check for a matching condition. For example, here we could use if ($MissingFiles -gt 0) to see if $MissingFiles is greater than zero. However, if you're already using commands that return a boolean value (as in the previous block where we were using Test-Path) that's not necessary. You can also do without it in cases like this, when you're just testing to see if a number is non-zero. Any non-zero number (positive or negative) gets treated as true, while zero (or, as may happen here, a non-existent variable) will be treated as false.

If $MissingFiles exists, and is non-zero, Write-Host will post a message telling you how many files were missing and that the script will abort. Then, Remove-Variable will clean up all the variables we've created and Exit will quit the script. At the regular PowerShell console, Remove-Variable is not really needed for this particular purpose because variables set by scripts are normally discarded when the script exits. However, the PowerShell ISE behaves a bit differently so you may want to keep this in if you plan on running the script from there.

If all things are in order, the script will continue on. One more preparation to make is an alias that we'll be really glad to have later on.

New-Alias g Get-Random

Aliases are used to create alternate names for commands. These can be useful to help us get acquainted with the new interface (e.g.: PowerShell has built-in aliases like dir -> Get-ChildItem and cat -> Get-Content) or to make short-hand references for commonly-used commands. Here, we're making a very short-hand reference for the Get-Random command which is going to be used a lot later on.

Get-Random pretty much does what its name implies. Given an array (like a list of names) as input, it picks a random item from the array and spits it out. It can also be used to generate random numbers. The thing to remember about Get-Random and numbers though is that, like many other computer operations, it starts counting from zero. So instead of Get-Random 10 meaning the more natural "give me a number from 1 to 10" it really means "give me a number from 0 to 9." You can be more specific about the number selection, so that Get-Random behaves more like you'd naturally expect, but we won't need that in this script.

Part 2: Getting User Input and Getting to Work

While a script that generates just one random name & phone number is great, it is much better if the script allows the user to specify how many names & numbers they want to get in one batch. Unfortunately, we can't really trust users to always give valid input. So, there's a tiny bit more to this than just $UserInput = Read-Host.

while (!$ValidInput)

{

    try

    {

        [int]$UserInput = Read-Host -Prompt 'Items to be generated'

        $ValidInput = $true

    }

    catch

    {

        Write-Host 'Invalid input. Enter a number only.' -ForegroundColor Red

    }

}

The while statement above checks for, and negates, the value of $ValidInput. So long as $ValidInput is false, or does not exist, it will keep looping through its script block.

The try statement takes user input, via Read-Host, and attempts to convert it to an integer value. (That's the [int] before Read-Host.) If it's successful, it will set $ValidInput to true so that the while loop can exit. If not successful, the catch block posts an error and, because $ValidInput didn't get set, the while loop will come back around and prompt the user again.

Once the user has properly given a number as input, we want the script to announce that it's about to start actually doing its work and then get about doing it.

Write-Host "`nGenerating $UserInput names & phone numbers. Please be patient.`n"

1..$UserInput | ForEach-Object {

    <# INSERT RANDOM NAME & NUMBER GENERATOR HERE #>

}

Don't worry, we're not going to leave you on your own to figure out the random name & number generator code. That's just a placeholder comment to show you where the next section (where the real work gets done) is going to fit.

The Write-Host line is pretty straightforward. It simply says how many names and phone numbers the script is going to generate, and asks the user to be patient while the script does its work. The`n at the start and end of the string is to insert a blank line before and after that output, just to give it some visual separation between the input line and the list of names & numbers. Be aware that that's a back-tick (AKA "grave accent" - usually the key above tab, to the left of 1) and not an apostrophe or single-quote in front of each n.

The next part shows a different way you can use a ForEach-Object loop. Typically, when you want a script block to run a certain number of times, you'll set up a regular for loop like for ($x = 1; $x -le $UserInput; $x++) {<# INSERT SCRIPT HERE #>}. ForEach-Object lets us simplify this by feeding it a list of integers and, instead of telling it to actually do anything with those integers, we just give it a static script block to run until it runs out of integers to do it for.

Part 3: Generating a Random Name

Generating the name is the simplest bit of the rest of this process. It only consists of three steps: Picking a surname, picking a gender, and picking a first name. Remember that alias we made for Get-Random awhile back? Time to start putting that to use.

    $Surname = Get-Content "$ScriptFolder\Surnames.txt" | g

    $Male = g 2

    if ($Male)

    {$FirstName = Get-Content "$ScriptFolder\Males.txt" | g}

    else

    {$FirstName = Get-Content "$ScriptFolder\Females.txt" | g}

The first line takes our list of surnames, feeds it into the random picker, and assigns the chosen name to $Surname.

The second line picks our person's gender. Remember how Get-Random starts counting from zero, and how zero is false and everything else is true? That's how we're using Get-Random 2 (or the much shorter g 2 thanks to our alias - both result in a choice between zero or one) to decide whether our person is male or not. The if/else statement afterwards randomly chooses a male or female first name accordingly.

Part 4: Generating a Random Phone Number

Here's the really fun part. Earlier on, we showed you how there's several ways you can make an invalid or fictitious phone number. Since we don't want all our numbers looking too similar to each other, we'll randomly pick an invalid number format every time. The randomly chosen formats will be defined by their Area Code and Exchange Code, which will collectively be stored as $Prefix.

    $NumberFormat = g 5

    switch ($NumberFormat)

    {

        0 {$Prefix = "($(g 2)$(g 10)$(g 10)) $(g 10)$(g 10)$(g 10)"}

        1 {$Prefix = "($(g 10)9$(g 10)) $(g 10)$(g 10)$(g 10)"}

        2 {$Prefix = "($(g 10)$(g 10)$(g 10)) $(g 2)$(g 10)$(g 10)"}

        3 {$Prefix = "($(g 10)$(g 10)$(g 10)) $(g 10)11"}

        4 {$Prefix = "($(g 10)$(g 10)$(g 10)) 555"}

    }

The first line is a straightforward random number generation to pick which format we're going to follow for the phone number. Then, the switch statement takes that random choice and generates a $Prefix accordingly. Remember that list of invalid phone number types? The $NumberFormat values 0-3 correspond to the first four in that list. Value 4 can generate one of the last two, since both use the "555" Exchange Code.

Here, you can also see we're using another trick with double-quotes. Double-quotes don't just let you interpret variables before a string gets output - they also let you process script blocks. To do that, you wrap the script block like this: "$(<#SCRIPT HERE#>)". So what you have above is a lot of individually randomized digits, with some of them either limited in their range or set statically according to the rules we need to follow. Each string also has parenthesis and spacing as you'd normally expect to see in an Area Code and Exchange Code pair.

The last thing we need to do before we're ready to output our name & phone number is to generate a Subscriber ID, which will be stored as $Suffix.

    switch ($NumberFormat)

    {

        {$_ -lt 4} {$Suffix = "$(g 10)$(g 10)$(g 10)$(g 10)"}

        4 {

            switch ($Prefix)

            {

                '(800) 555' {$Suffix = '0199'}

                default {$Suffix = "01$(g 10)$(g 10)"}

            }

        }

    }

Because of the special rules for 555 numbers, we can't just generate four random digits for the end of every phone number our script is going to make. So, the first switch checks to see if we're dealing with a 555 number. If not, it generates four random digits. If it is a 555 number, the second switch checks for the 800 area code. If that matches, there's only one valid $Suffix we can use. Otherwise, it's allowed to pick from anything between 0100-0199.

Note that there are a few different ways this block could have been written, instead of the way it is. Both switch statements could have been replaced with if/else statements, since they each only handle two choices. Also, instead of specifically calling out "4" as an option for the first switch statement, "default" could have been used similar to how it was done in the second since it was the only option left. The choice between if/else vs. switch, or where to use the default keyword instead of specific values, often comes down to a matter of personal preference. So long as it works, use whatever you're most comfortable with.

Now, it's time for output.

    Write-Output "$FirstName $Surname $Prefix-$Suffix"

}

This one's pretty much as simple as it gets in the script. It just outputs the first and last name separated by spaces, then another space before the phone number. Here's where the standard dash between Exchange Code and Subscriber ID gets added as well.

That closing bracket at the bottom is the end of the ForEach-Object loop from earlier - omit this if you've already got it.

Part 5: Cleanup and Running the Script

After all the work is done, a good script knows how to clean up after itself. Again, the variable removal below isn't really needed if you're only going to run the script from the console but you will want it if you ever plan to run it in the ISE.

Remove-Item alias:\g

Remove-Variable ScriptFolder,RequiredFiles,Surname,Male,FirstName,NumberFormat,Prefix,Suffix,ValidInput,UserInput

After you've got it all done, save the script with a ".ps1" extension in the same folder as your names files. Make sure your ExecutionPolicy is set so that the script can run, and give it a whirl.

Here's a screenshot of the script in action:

You can also download a ZIP file containing this PowerShell script, and text files with name lists, from the link below.

Random Name & Phone Number Generator for PowerShell