The PowerCLI Book Raffle – Behind the scenes

Now that our PowerCLI Book Raffle has ended and the lucky winner is published, I want to give you a full disclosure of the drawing process. Because our PowerCLI Book is about PowerCLI, what other tool than PowerShell could we use to perform the drawing process?

Twitter Fun

So first we need to retrieve all the twitter messages that people re-tweeted on Twitter. In order to do that we need to call the HTTP based Twitter search API. For more information on how to use the Twitter search API have a look at http://search.twitter.com/api/ This page shows us that the Twitter search API supports the Atom format through the Atom service URL “http://search.twitter.com/search.atom?q=<query>”. It also shows us the supported URL parameters. The most important parameters are:

  • rpp: the number of tweets to return per page, up to a max of 100
  • page: the page number to return, up to a max of roughly 1500 results (based on rpp * page

In order to perform a simple search, we only need to fill in the query parameter. We are interested in all messages that contain both “powerclibook” and “magictweet”. In order to search for tweets that contain both words, we need to concatenate them with the plus (+) sign, like http://search.twitter.com/search.atom?q=powerclibook+magictweet. To see the results you could simply paste that URL into your browser.
   

To capture the result in PowerShell, we need to create a WebClient object using:

$WebClient = New-Object System.Net.WebClient

To download the Twitter search results we use the DownloadString method like:

$WebClient.DownloadString("http://search.twitter.com/search.atom?q=powerclibook+magictweet")

The downloaded Atom result is actually an XML formatted string. In order to process the result as actual XML instead of a string we need to convert the string into the XML type. This is also referred to as type casting.

$searchResult = [xml]$WebClient.DownloadString("http://search.twitter.com/search.atom?q=powerclibook+magictweet")

So far so good. We now have our Twitter search results. We can retrieve the individual tweets through the .feed.entry property, however there are only 15 tweets present by default in our search results. This is where the additional -rpp parameter kicks in. Using this parameter we can override the default of 15 tweets per page to a maximum of 100 tweets per page. The returned number of tweets per page can also be found in the .feed.itemsPerPage property.  Let’s try the search again using the -rpp parameter:

$searchResult = [xml]$WebClient.DownloadString("http://search.twitter.com/search.atom?q=powerclibook+magictweet&rpp=100")

[vSphere PowerCLI] C:\scripts> $searchResult.feed.itemsPerPage

100What if we want to retrieve more than 100 results? This is where we get to use the -page parameter. With the -page parameter you define the page number that gets returned. The number of pages is determined by the total number of search results (maximum is 1500) divided by the results per page (rpp). Fortunately we don’t have to determine how many pages are available to us, because Atom provides a link model that allows us to easily browse pages. The Atom links are stored under the .feed.link property.

[vSphere PowerCLI] C:\scripts> $searchResult.feed.link

type                       href                       rel
----                       ----                       ---
text/html                  http://search.twitter.c... alternate
application/atom+xml       http://search.twitter.c... self
application/opensearchd... http://search.twitter.c... search
application/atom+xml       http://search.twitter.c... refresh

In order to retrieve the next page we only have to retrieve the appropriate URL from the .feed.link property and use that URL with the .DownloadString method of our WebClient.

$url = ($searchResult.feed.link | ?{$_.rel -eq "next"}).href  
$nextResult = [xml]$WebClient.DownloadString($url)  

Now we only need to put all the pieces together to retrieve all re-tweeted magic tweets.  

$WebClient = New-Object System.Net.WebClient  
$url = "http://search.twitter.com/search.atom?q=powerclibook+magictweet&rpp=100"  
$tweets=@()  
while ($url) {  
    $searchResult = [xml]$WebClient.DownloadString($url)  
    $tweets += $searchResult.feed.entry  
    $url = ($searchResult.feed.link | ?{$_.rel -eq "next"}).href  
}  

To make things easier and reusable, I’ve created a Search-Twitter function using this code.  

function Search-Twitter {
<#
.SYNOPSIS
  Finds tweets on Twitter
.DESCRIPTION
  This function finds tweets on Twitter that match the query provided by the -query parameter.
  A WebClient needs to be defined in the $Global:WebClient variable. If no WebClient is found, a default WebClient will be created.
.NOTES
  Author:  Arnim van Lieshout
.PARAMETER Query
  The search query
.PARAMETER All
  An optional switch parameter to retrieve the maximum number of tweets. By default only the first page will be returned
.EXAMPLE
  PS> Search-Twitter -Query "PowerCLI"
.EXAMPLE
  PS> Search-Twitter -Query "PowerCLI" -All
#>

	param(
		[Parameter(Mandatory = $true, Position = 0, HelpMessage = "Enter the search query")]
			[String]$Query,
			[Switch]$All
	)

	$url = "http://search.twitter.com/search.atom?rpp=100&q=$Query"

	if (!$WebClient) {$Global:WebClient = New-Object System.Net.WebClient}

	$messages = @()
	if ($All) {
		while ($url) {
			$searchResult = [xml]$WebClient.DownloadString($url)
			$messages += $searchResult.feed.entry
			$url = ($searchResult.feed.link | ?{$_.rel -eq "next"}).href
		}
	}
	else {
		$searchResult = [xml]$WebClient.DownloadString($url)
		$messages += $searchResult.feed.entry
	}

	$messages
}

Collecting The Magic Tweets

Using our newly crafted Search-Twitter function we collect the re-tweeted magic tweets and save some information about the tweet and the user in a custom object. Because the maximum number of returned search results is 1500 and search results aren’t kept forever on Twitter (the Twitter history only goes back to approximately four days), we run our script on a daily basis and export the results to an XML file.  

#COLLECT TWEETS
$WebClient = New-Object System.Net.WebClient  

$tweets = @()
foreach ($item in (Search-Twitter "powerclibook+magictweet" -All)) {
	$obj = "" | Select user_id, user, user_name, user_uri, profile_image_url, published, text, source

	$obj.user = $item.author.uri.split('/')[-1]
	$userInfo = [xml]$WebClient.DownloadString("http://api.twitter.com/1/users/show.xml?screen_name=$($obj.user)")
	$obj.user_id = $userInfo.user.id
	$obj.user_name = $userInfo.user.name
	$obj.user_uri = $item.author.uri
	$obj.profile_image_url = ($item.link | ?{$_.rel -eq "image"}).href
	$obj.published = [DateTime]$item.published
	$obj.text = $item.title
	$obj.source = $item.source

	$tweets += $obj
}

$tweets | Export-Clixml C:\Scripts\PowerCLIBookRaffle_$(get-date -Format yyy-MM-dd).xml

Notice that we also used the Twitter API to retrieve user information. We decided to use the Twitter user id as the user’s ticket number for our raffle. This way we can ensure that only one ticket per user is issued, regardless of the number of times the user re-tweeted the magic tweet.

The Draw

After collecting all the re-tweeted magic tweets, it’s time to draw one lucky winner. First we need to read all saved search results. Because we collected the tweets on a daily basis and some users re-tweeted the magic tweet multiple times, there are duplicates and we need to de-duplicate the collected tweets first before we can actually draw the winner. To keep the raffle fair, we also decided to exclude the authors from entering the draw.  

In order the retrieve our saved search results, we can simply import then using: 

#Import tweets
$tweets = @()
$tweets = Get-ChildItem "C:\Scripts\PowerCLIBookRaffle*" | Import-Clixml

Because of the duplicates, we need to de-duplicate the $tweets array. This can easily be achieved using the Sort-Object cmdlet. 

#Dedup tweets
$tweets = $tweets | Sort-Object -Property user_id -Unique

Next task is removing the authors.   

#Remove authors
$authors = @("alanrenouf","LucD22","avlieshout","jonathanmedd","glnsize")
$tweets = $tweets | ?{$authors -notcontains $_.user}

To simulate the process of mixing the tickets in the jar, we switched 2 random objects in the array multiple times. Using the code below we switched tickets 10.000 times:  

#Mix tickets
1..10000 | %{
	$random1 = Get-Random -Minimum 0 -Maximum $tweets.count
	$random2 = Get-Random -Minimum 0 -Maximum $tweets.count
	$tempObject = $tweets[$random1]
	$tweets[$random1] = $tweets[$random2]
	$tweets[$random2] = $tempObject
}

The last task to do is to actually draw the lucky winner:     

#Draw the lucky winner
$winner = $tweets | Get-Random
$winner

References

Twitter Search API Documentation – http://search.twitter.com/api/
Twitter Developer Documentation – http://dev.twitter.com/doc
The Atom Link model – http://www.xml.com/lpt/a/1434

Related posts:

  1. PowerCLI Book Update Tweet As you might know, I was asked to co-author a PowerCLI book. Now that the book is nearly finished we posted an update on our book’s website at www.powerclibook.com. ...
  2. PowerShell / PowerCLI linkage Tweet Since I started looking into PowerShell and PowerCLI, I gathered a couple of links which I found interesting and useful. I needed a way for them to be accessible...
  3. Rescan VMware vSphere Hypervisor (ESXi) using PowerCLI simplified Tweet While preparing some disaster recovery (DR) tests, I had to add and remove a couple of LUNS from several ESX hosts in different clusters. Doing so,  I had to...
  4. PowerCLI 4.1 namespace changes Tweet Output type changes in PowerCLI 4.1In PowerCLI 4.1 VMware changed the namespaces of the output types. According to VMware, this was done to improve the internal structure and enable...
  5. PowerCLI: Get WMI info from isolated guests Tweet A few weeks back I posted an article on matching Windows and VMware disks. Unfortunately this would work only if you could remotely query WMI information from that VM....

0 Comments on “The PowerCLI Book Raffle – Behind the scenes”

Leave a Comment