Summary: Learn how to use Windows PowerShell to select random winners from a text file.
Hey, Scripting Guy! I have a question. It is neither a vital server performance issue nor is it a dramatic process optimization revelation, but it is something that will make my life a lot easier. First the background: I have been going to our local IT user group for a while, and one of the members wrote a command-line application that would accept a text file that contained our attendees for the night. Well, that member recently moved to another city, and now we are in desperate need of something that will do a random drawing for us for our prize giveaways that we have at each meeting. I guess I could send email to the departing member and ask for the command-line application, but I have been preaching Windows PowerShell for so long that I sort of feel like I should create the solution in Windows PowerShell. But I need a bit of help doing that. Do you have any ideas for doing a random sort on a text file?
—CS
Hello CS,
Microsoft Scripting Guy Ed Wilson here. Good afternoon, CS. During the 2011 Scripting Games, I wrote a script to perform a random sort of people who had submitted scripts during the event. I was doing daily drawings, and I also needed to exclude people who had previously won a prize, so my script is a bit overkill for what you need to do.
I was talking to someone the other day about this very thing. What my friend had done was rather clever. He said that he created a hash table with the key as the data to sort, and the value as a random number produced via the Get-Random cmdlet. He then used the getenumerator method from the hash table and piped it to the Sort-Object cmdlet to sort the hash table based on the random numbers. This has the effect of randomizing the list of user group names. I thought it was a clever approach to the problem, so I wrote the RandomSortUsersViaHashTable.ps1 script. The complete text of the script is shown here:
$users = Get-Content C:\fso\UserGroupNames.txt
$hash = @{}
foreach ($u in $users)
{
$hash.add($u,(Get-Random -Maximum $users.count))
} #end foreach
$hash.GetEnumerator() | Sort-Object -Property value |
Select-Object -First 5
The first thing I do is use the Get-Content cmdlet to read the contents of the user group list into an array of user names. Next, I create an empty hash table, and store it in the $hash variable. I then use the foreach statement to walk the array of user names. This portion of the script is shown here:
$users = Get-Content C:\fso\UserGroupNames.txt
$hash = @{}
foreach ($u in $users)
Inside the ScriptBlock for the foreach statement, I use the add method to add the user name and a random number to the hash table. One thing to note: I limit the random number to the maximum number of users that are listed in the UsergroupNames.txt file. There is no real need to do this because I am later going to use the Select-Object cmdlet to choose the first five users anyway. Here is the line of code that adds the user names and the random number to the hash table:
$hash.add($u,(Get-Random -Maximum $users.count))
I then use the getenumerator method from the hashtable object, and pipe the hashtable to the Sort-Object cmdlet where I sort the hashtable based on the value of the random number. Finally, I choose five people from the hashtable. This portion of the script is shown here:
$hash.GetEnumerator() | Sort-Object -Property value |
Select-Object -First 5
When the script runs, the output shown in the following figure is displayed.
While the approach is valid and makes for a relatively cool script, there is a simpler way to accomplish the task of grabbing user names in a random fashion from a text file. In fact, the nine-line script boils down to a single line. In the following command, I use the Get-Content cmdlet (gc is an alias) to read the contents of the UserGroupNames.txt file. I pipe the results to the Sort-Object cmdlet and use a script block to choose a random name from the text file stream. I then select the first five users.
gc C:\fso\UserGroupNames.txt | sort{get-random} | select -First 5
The command when run three times and the associated output are shown here.
Perhaps, the easiest way to do this is to use the Get-Content cmdlet to read the contents of the text file, and bypass the Sort-Object cmdet completely. I can tell the Get-Random cmdlet to return only five user names from the text file, so I do not really need to sort the list first. This command is shown here (gc is an alias for the Get-Content cmdlet):
gc C:\fso\UserGroupNames.txt | Get-Random -Count 5
I run the command a few times to check to see if it is returning different names. As shown in the following figure, it works.
If I do not need to select only a certain number of users, I can use the Get-Random cmdlet to randomize the entire list by using the count property of the array to read the contents of the text file into a variable. Here is one way to accomplish this task:
$users = gc C:\fso\UserGroupNames.txt
Get-Random -InputObject $users -Count $users.Count
In the following figure, I read the contents of the text file into the $users variable, and then I supply it as an inputobject to the Get-Random cmdlet. I then use the Measure-Object cmdlet to count how many items are returned by the command. The figure is shown here.
As seen in the following figure, the contents of the text file consist of 12 users. In addition, a comparison between the text file and the random output shows that the user names are in fact displayed in a random order.
If memory consumption is an issue, the command can be revised to take advantage of the pipeline. The revised command is shown here:
$users = gc C:\fso\UserGroupNames.txt
$users | Get-Random -Count $users.Count
CS, that is it for generating random user names from a text file. I invite you to join me tomorrow for another exciting episode of BATCHman.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy