Generate a Secret Santa List with PowerShell

It's supposed to be the most wonderful time of the year and while you might buy multiple Christmas gifts for everyone in your immediate family, often times buying for everyone in your extended family or for all of your co-workers is cost prohibitive.

I originally started out with a simple idea to create a PowerShell script to take a list of names and generate a second random list of names based off of the first one while making sure the corresponding name on the second list doesn't match the first one. Sounds simple enough, right?

Everything seemed well and fine until I figured out there was a problem with my logic because if the last entry in both lists are the same, there's no way to prevent a duplicate other than not performing a match at all which means someone would be left out.

 1$Users = Get-ADUser -Filter * -SearchBase 'OU=AdventureWorks Users,OU=Users,OU=Test,DC=mikefrobbins,DC=com' |
 2Select-Object -First 10 -ExpandProperty Name
 4$Match = $Users
 6foreach($User in $Users) {
 8    $Result = $Match.Where({$_ -ne $User}) | Get-Random
10    [pscustomobject]@{
11        Name = $User
12        Match = $Result
13    }
15    $Match = $Match.Where({$_ -ne $Result})


It took running the code a number of times for the problem to occur. As you can see in the previous example, Alan Brewer doesn't have a match because the only one left in the second list was himself.

I decided to take a different approach while at the same time checking to see if the last person in the two lists matched and just regenerate the second list if they did.

 1$Gifters = Get-ADUser -Filter * -SearchBase 'OU=AdventureWorks Users,OU=Users,OU=Test,DC=mikefrobbins,DC=com' |
 2Select-Object -First 10 -ExpandProperty Name
 4do {
 5    $Giftees = $Gifters | Sort-Object {Get-Random}
 7while ($Gifters[$Gifters.Length -1] -eq $Giftees[$Giftees.Length -1] )
 9for ($i = 0; $i -lt ($Gifters.Length); $i += 1) {
10    [pscustomobject]@{
11        Gifter = $Gifters[$i]
12        Giftee = $Giftees[$i]
13    }


The problem with my second approach is that it didn't prevent a person from being matched to themselves in the middle of the list.

Clearly this was going to be a little more difficult than I initially thought. If I'm going to put more effort into this, I'll just create a function for it.

 1#Requires -Version 3.0
 2function Get-MrSecretSantaList {
 5    Generates a unique list of gift givers and gift receivers based on a single list of names.
 7    Get-MrSecretSantaList is an advanced function that generates a unique list of gift givers
 8    and gift receivers based on a single list of names.
10    The name of the person. A minimum of 2 names must be provided.
12    Get-MrSecretSantaList -Name 'Mike Robbins', 'Joe Doe'
14    Get-MrSecretSantaList -Name (Get-ADUser -Filter "Enabled -eq '$true'" | Select-Object -ExpandProperty Name)
16    None
18    PSCustomObject
20    Author:  Mike F Robbins
21    Website:
22    Twitter: @mikefrobbins
24    [CmdletBinding()]
25    param (
26        [Parameter(Mandatory)]
27        [ValidateCount(2,32768)]
28        [string[]]$Name
29    )
30    if ($Name.Length % 2 -ne 0) {
31        Throw 'An even number of Names must be specified in order for matching to occur.'
32    }
33    do {
34        $Giftee = $Name | Sort-Object {Get-Random}
35    }
36    while (
37        $(for ($i = 0; $i -lt ($Name.Length); $i += 1) {
38            if ($Name[$i] -eq $Giftee[$i]) {
39                Write-Verbose -Message "A duplicate has occured in loop $i Name: $($Name[$i]) cannot match Giftee: $($Giftee[$i])"
40                $true
41                break
42            }
43        })
44    )
45    for ($i = 0; $i -lt ($Name.Length); $i += 1) {
46        [pscustomobject]@{
47            Gifter = $Name[$i]
48            Giftee = $Giftee[$i]
49        }
50    }

I attempted to use ValidateScript to make sure there were an even number of items provided, but it appears that you're unable to retrieve the count or length property in the param block.

I nested a for loop inside the do/while loop to check each entry and if it matches itself, the break statement causes it to immediately exit the loop, regenerate the second list, and start over.

The function generates two lists with the same names that are matched randomly while making sure no one is matched to themselves.

1$Gifters = Get-ADUser -Filter * -SearchBase 'OU=AdventureWorks Users,OU=Users,OU=Test,DC=mikefrobbins,DC=com' |
2Select-Object -First 10 -ExpandProperty Name
4Get-MrSecretSantaList -Name $Gifters


If less than two names are provided or if an odd number of names is provided, an error is generated.

1Get-MrSecretSantaList -Name 'Mike Robbins', 'John Doe', 'Jane Doe'


While duplicates don't seem to occur that often, they do occur. The verbose parameter can be used to see the duplicates.

1$null = Get-MrSecretSantaList -Name (1..32768) -Verbose


You could query Active Directory similarly to what I've done in the examples shown in this blog article and add their email address. Then the Send-MailMessage cmdlet could be used to automatically email each of the users with the name of the person they're buying a gift for.

The other possibility that I thought about is simply generating a random offset less than the number of names in the list and offsetting the matching list by that number, but it wouldn't purely random like the examples shown in this blog article. I'd love to hear your thoughts and know if there's a simpler way to accomplish this task?