Developer42

2014-10-13

PowerShell Script :: Get HotFix Info from Local Machine & Web

Filed under: Microsoft, Technology — Tags: , , , , , , , , — Developer42 @ 22:02

In answering a question on Stack Overflow[1], I discovered that the Get-Hotfix cmdlet doesn’t list all hotfixes on your machine. Rather, if the fix is included in a CU, details of the installed CU will be presented, with no apparent way to drill down into the information and check for a specific fix.
So far as I can tell, there are no services / open databases which would allow you to programmatically retrieve this information. The only way currently is to manually head to the CU or fix’s related knowledge base site and read the contents.
As a very rough hack-around, I created a scraping script which will interrogate the MS knowledge-base for information on each hotfix, finding whether it’s a CU or not (based on the hotfix’s description in the page’s title) and if so try to find all related fixes. I only go down one level – to avoid the risk of an infinite loop & save time; should anyone want to go deeper, a caching mechanism could be used to store previous results and thus prevent the risk of a loop and also potentially improve performance.
As with most of the scripts on this site, this is more for me to play with PowerShell than to be of much practical use; but hopefully it could be to someone…

function get-hotfixInfo()
{
	process 
	{
		#$url = "http://support2.microsoft.com/kb/{0}" -f ($_.HotFixId -replace "KB(\d*)",'$1')
		$url = $_.Caption
		if($url -like "*.microsot.com")
		{
			$url = "{0}/{1}" -f $url,($_.HotFixId -replace "(KB)(\d*)",'$1/$2') 
		}
		try
		{
			$response = (Invoke-Webrequest $url -ea stop)
		} catch {
			$response = @{
				ParsedHTML = @{
					Title = "{0}`n`nURL: {1}" -f $error[0].Exception,$url 
				}
			}
		}
		$html = $response.ParsedHTML
		$isCU = $html.title -like "*cumulative*update*"
		$kblets = $null
		if($isCU) #this bit can be even slower than the above, hence only run if we believe we have a CU
		{
			$baseUri = $response.BaseResponse.ResponseURI
			$kblets = $html.getElementsByTagName('a') `
				| ? { ($_.parentNode.tagname -eq 'TD') -and ($_.parentNode.nextsibling.tagname = 'TD') } `
				| ? { $_.className -eq 'KBlink' } `
				| % { New-Object -TypeName PSObject -Prop @{
					Id = "KB{0}" -f $_.innerText
					Uri = (new-object System.URIBuilder($baseUri.scheme,$baseUri.dnssafehost,$baseUri.port,($_.href -replace "about:/(.*),'$1'"))).ToString()
					Title = $_.parentNode.nextsibling.innerText
				}}
		}
		New-Object -TypeName PSObject -Prop @{
			Id = $_.HotFixId
			IsCU = $isCU 
			Title = $html.title
			Source = $_.source
			Description = $_.description
			InstalledBy = $_.installedby
			InstalledOn = $_.installedon
			Uri = "http://support2.microsoft.com/kb/{0}" -f ($_.HotFixId -replace "KB(\d*)",'$1')
			KBlets = $kblets
		}
	}
}

#demos

#get the hotfix id for IE11 CU Feb 2014, and it's component hotfixes
get-hotfix -id kb2909921 | get-hotfixInfo | %{ $_; $_.KBlets | %{ $_; }} | ft -property id,title -autosize

#get the first 2 cumulative update hotfixes
get-hotfix | select -first 2 | get-hotfixInfo | ?{$_.isCU} 


2014-10-12

PowerShell Script: Demo of Calling a (StackExchange’s) Rest Web Service

Filed under: Microsoft, Technology — Tags: , , , , , , — Developer42 @ 01:25

Warning: this post is not useful unless you just like playing with scripts & APIs.

Reading a few posts on StackExchange’s Meta site, I saw that there was demand for, but no intent to, create some kind of synonym sub-type feature for tags.
e.g. Users looking for items tagged PowerShell would also see those tagged only PowerShell-1.0, PowerShell-2.0, PowerShell-ISE, etc.
Users looking for PowerShell-1.0 are being more specific though, so shouldn’t see those tagged simply PowerShell, and definitely not those tagged PowerShell-2.0.

Since SE have no intention of creating this, I figured it would be useful to have a site which could present a view over Stack Exchange sites, giving users these additional features.
Since I don’t have time to do that though, I satisfied my craving to build something by knocking up a quick script to play with the SE API.
…And since implementing synonyms would require a huge dictionary, which I’d have to create, I sufficed myself with simply playing with the API without the synonymn hierarchy feature.
So this is just me playing about with my new favourite scripting language and an API I hope one day to play more with (though then it would be with some other language).
Here’s what I came up with…

Code:

$tagList='c#','.net'
$tags = $taglist -join ';'
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
$tags = [System.Web.HttpUtility]::UrlEncode($tags)
$url = "http://api.stackexchange.com/2.2/search/advanced?page=1&pagesize=10&order=desc&sort=creation&ans
wers=0&closed=False&tagged={0}&site=stackoverflow" -f $tags
$json = Invoke-RestMethod $url
$json.items | select title,link | %{ "{0}`n{1}`n" -f [System.Web.HttpUtility]::HtmlDecode($_.title), $_.link; }

Output:

ASP.NET Web Forms and MySql Entity Framework: "Nested transactions are not supported"

http://stackoverflow.com/questions/26320679/asp-net-web-forms-and-mysql-entity-framework-nested-transactions-are-not-su

ppo

.NET C# - Both Bitmap and Image Loads Images Smaller than Expected

http://stackoverflow.com/questions/26320637/net-c-both-bitmap-and-image-loads-images-smaller-than-expected

Mono TypeLoadException in an executable

http://stackoverflow.com/questions/26320146/mono-typeloadexception-in-an-executable

Developing a music player in C# without using com component

http://stackoverflow.com/questions/26319978/developing-a-music-player-in-c-without-using-com-component

Fastest way to get directory data

http://stackoverflow.com/questions/26319973/fastest-way-to-get-directory-data

c# datagridview multi-page print

http://stackoverflow.com/questions/26319852/c-datagridview-multi-page-print

How to access an object in c sharp from outside my method?

http://stackoverflow.com/questions/26319550/how-to-access-an-object-in-c-sharp-from-outside-my-method

.NET & jQuery Jcrop Initialization Causing Double Page Load

http://stackoverflow.com/questions/26319428/net-jquery-jcrop-initialization-causing-double-page-load

Why i'm getting exception InvalidOperationException when using backgroundworker?

http://stackoverflow.com/questions/26319024/why-im-getting-exception-invalidoperationexception-when-using-backgroundwor

ker

Index was out of range error when I add a UserControl to a panel, Microsoft VS C#

http://stackoverflow.com/questions/26318864/index-was-out-of-range-error-when-i-add-a-usercontrol-to-a-panel-microsoft-

vs-c

So not that useful, but a fun play project.

2014-09-26

PowerShell Script :: Get AD Users by Email (Advanced)

Filed under: Microsoft, Technology — Tags: , , , , , , , — Developer42 @ 22:19

Here’s a script I knocked up today.

Auto.ps1 allows me to host the script on a server (or wherever), whilst others can use it by dropping input files into a queue folder, without needing to touch powershell (which may be scary to non-programmers, or may require additional setup or permissions).
ADGetUsersByEmailAdvanced.ps1 gets AD info based on email addresses; without requiring exchange modules, and includes workarounds to cope with missing information.

Auto.ps1

Monitors a folder for new text files. Once found, passes that file to a script to be processed. On completion moves the source file to the same directory & renames to begin with the same timestamp as the generated output file.

$infile = "\\myServer\myShare\Scripts\powershell\ADGetUsersByEmail\in\*.txt"

while (1 -eq 1) {
	#wait for a new file
	while(!(Test-Path $infile)) {Start-Sleep -s 30;}
	write-host "new input file found"
	Get-ChildItem $infile | %{
		$fileTimestamp = "{0:yyyy-MM-dd_HHmmss}" -f (get-date).ToUniversalTime()
		$inputFile = $_.fullname
		$exportFile = "{0}\out\{1}_output.csv" -f $PSScriptRoot,$fileTimestamp
		$inputFileMoved = "{0}\out\{1}_{2}" -f $PSScriptRoot,$fileTimestamp,$_.name
		write-host ("source: {0}" -f $inputFile)
		write-host ("output: {0}" -f $exportFile) 
		write-host ("archive: {0}" -f $inputFileMoved) 
		.\ADGetUsersByEmailAdvanced.ps1 -sourceFile $_.fullname -exportFile $exportFile 
		write-host "processed"
		Move-Item $inputFile $inputFileMoved
		write-host "archived"
	}
}

ADGetUsersByEmailAdvanced.ps1

Given a text file containing a list of email addresses, attempts to resolve those to corresponding AD users, taking advantage of email information in AD where available, then gracefully degrading to more hacky methods. Works its way through a list of domains in case the users are in the same company but on a different domain.

Param(
  [string]$sourceFile
  ,[string]$exportFile 
)
#$sourceFile = '.\sourceEmails.txt'
#$exportFile = ".\output_{0:yyyy-MM-dd_HHmmss}.csv" -f (get-date).ToUniversalTime()
$domains = 'eu','usa','myDomain','anotherDomain' # domain points to the GC; could equally list GC server names here, though this version's more user friendly

#create dummy; allows us to put in values for any unfound items (currently just using null, but we can easily amend if desired)
$adDummy = New-Object –TypeName PSObject –Prop @{
	emailSearched	= $null;
	notFound		= $true;
	sAmAccountName 	= $null;
	fullname		= $null;
	firstname		= $null;
	lastname		= $null;
	cn				= $null;
	countryCode		= $null;
	country			= $null;
	#title			= $null;
	title			= $null;
	department		= $null;
	company			= $null;
	email			= $null;	
	adEmail			= $null;
	proxyEmail		= $null;
}

function RemoveEmailDomain($email) {
  return $email -replace "(\S*)@\S*", '$1'
}
function IsFirstDotLast($name) {
	return $name -like '*.*'
}
function GetFirstName($name) {
	return $name -replace "(\S*?)\.\S*", '$1'
}
function GetLastName($name) {
	return $name -replace "\S*?\.(\S*)", '$1'
}
function GetFirstNamePartial($name) {
	return $name.substring(0,[system.math]::min(3,$name.length))
}
function GetLastNamePartial($name) {
	return $name.substring([system.math]::max($name.length-3,0),[system.math]::min(3,$name.length))
}
function GetADUserByIdentity($id, $domain) {
	#trycatch since erroraction not recognised on this type of command & I don't want error messages polluting my output
	write-host "searching for id '$id' on domain '$domain'"
	try { 
		Get-ADUser -Identity $id -Server $domain -Properties sAmAccountName, displayName, givenName, surname, distinguishedName, countryCode, c, title, department, company, emailAddress, proxyAddresses
	} catch {}
}
function GetADUserFiltered($filter, $domain) {
	Get-ADUser -Filter $filter -Server $domain -Properties sAmAccountName, displayName, givenName, surname, distinguishedName, countryCode, c, title, department, company, emailAddress, proxyAddresses
}
function GetADUserByEmailAddress($email, $domain) {
	write-host "searching for email '$email' on domain '$domain'"
	$filter = {emailAddress -eq $email} 
	GetADUserFiltered $filter $domain
}
function GetADUserByProxyAddress($email, $domain) {
	write-host "searching for proxy '$email' on domain '$domain'"
	$psBugFixSearchUser = "*:$_*"
	$filter = {proxyAddresses -like $psBugFixSearchUser}
	GetADUserFiltered $filter $domain
}
function GetADUserByFullName($name, $domain) {
	$fn = GetFirstName $name
	$ln = GetLastName $name
	write-host "searching for name '$fn', '$ln' on domain '$domain'"
	$filter = {(givenname -eq $fn) -and (surname -eq $ln)}
	GetADUserFiltered $filter $domain
}
function GetADUserByPartialName($name, $domain) {
	$fn = "{0}*" -f (GetFirstNamePartial $name)
	$ln = "*{0}" -f (GetLastNamePartial $name)
	write-host "searching for partial name '$fn', '$ln' on domain '$domain'"
	$filter = {(givenname -like $fn) -and (surname -like $ln)}
	GetADUserFiltered $filter $domain | where { ($_.givenname + $_.surname) -eq $name }
}
function FindBestMatch($email) {
	$result = $null
	#$domains | %{ $result=GetADUserByEmailAddress $email $_; if($result) {return $result;} } #doesn't play as expected
	foreach($domain in $domains) { $result=GetADUserByEmailAddress $email $domain; if($result) {return $result;} }
	foreach($domain in $domains) { $result=GetADUserByProxyAddress $email $domain; if($result) {return $result;} }
	$name = RemoveEmailDomain $email
	foreach($domain in $domains) { $result=GetADUserByIdentity $name $domain; if($result) {return $result;} }
	if(IsFirstDotLast($name)) {
		foreach($domain in $domains) { $result=GetADUserByFullName $name $domain; if($result) {return $result;} }
	} else {
		foreach($domain in $domains) { $result=GetADUserByPartialName $name $domain; if($result) {return $result;} }
	}	
	return $adDummy;
}

#define a function for later use
#get list of emails (ignore blanks)
$emails = (Get-Content $sourceFile) | where{ $_ -gt '' }

#get data from ad and stick it in an csv (or error to console if not found)
$emails | %{ 
	#get ad user by email address
	$adUser = FindBestMatch($_);
	if($adUser.notFound) {
		write-host ":(" -ForegroundColor Red
	} else {
		write-host ":)" -ForegroundColor Green
	}
	#return object replresenting results.
	New-Object –TypeName PSObject –Prop @{
		emailSearched	= $_;
		found			= if($adUser.notFound){$false} else {$true};
		sAmAccountName 	= $adUser.sAmAccountName;
		fullname		= $adUser.displayName;
		firstname		= $adUser.givenname;
		lastname		= $adUser.surname;
		cn				= $adUser.distinguishedName;
		countryCode		= $adUser.countryCode;
		country			= $adUser.c;
		#title			= $adUser.personalTitle;
		title			= $adUser.title;
		department		= $adUser.department;
		company			= $adUser.company;
		adEmail			= $adUser.emailAddress;
		proxyEmail		= [string]$adUser.proxyAddresses; #string joins the array down to a single string value
	}
} | export-csv $exportFile -notype #stick output to file

Script could be improved by allowing auto to kick off jobs so multiple instances of the worker script can be run simultaneously. Also changing the main script to make use of workflows and take advantage of the parallel foreach method should significantly improve it’s performance. However I’m still pretty new to PowerShell, so those steps will have to come later.

2014-09-17

T-SQL: Generate Series: Getting a list of numbers in a given range.

Filed under: Microsoft, SQL Server, Technology — Tags: , , , , , , , , , — Developer42 @ 01:27

I recently came across the Postgres generate_series function whilst reading a blog post.
So far as I can tell, there’s no equivalent in T-SQL. To make up for this, I coded my own, making use of the recursive nature of common table expressions:

create function dbo.generate_series
(
	  @start bigint
	, @stop bigint
	, @step bigint = 1
	, @maxResults bigint = 0 --0 = unlimited
)
returns @results table(n bigint)
as
begin

	--avoid infinite loop (i.e. where we're stepping away from stop instead of towards it)
	if @step = 0 return
	if @start > @stop and @step > 0 return
	if @start < @stop and @step < 0 return
	
	--ensure we don't overshoot
	set @stop = @stop - @step

	--treat negatives as unlimited
	set @maxResults = case when @maxResults < 0 then 0 else @maxResults end

	--generate output
	;with myCTE (n,i) as 
	(
		--start at the beginning
		select @start
		, 1
		union all
		--increment in steps
		select n + @step
		, i + 1
		from myCTE 
		--ensure we've not overshot (accounting for direction of step)
		where (@maxResults=0 or i<@maxResults)
		and 
		(
			   (@step > 0 and n <= @stop)
			or (@step < 0 and n >= @stop)
		)  
	)
	insert @results
	select n 
	from myCTE
	option (maxrecursion 0) --sadly we can't use a variable for this; however checks above should mean that we have a finite number of recursions / @maxResults gives users the ability to manually limit this 

	--all good	
	return
	
end

Example Usage:

--check we get expected results
select * from generate_series(1, 10, default, default)
select * from generate_series(10, 5, -1, default)
select * from generate_series(1, 10, 4, default)
select * from generate_series(1, 10, default, 2)
select * from generate_series(1, 10, 4, -1)

--check we don't get results if we have "invalid" input
select * from generate_series(1, 10, 0, default)
select * from generate_series(10, 1, default, default)
select * from generate_series(10, 5, 1, default)
select * from generate_series(1, 10, -4, default)

NB: Should you wish to generate a series of dates instead of a series of numbers, check my comments here: http://stackoverflow.com/questions/1478951/generate-a-resultset-of-incrementing-dates-in-tsql/25881077#25881077

2014-09-10

Powershell Script: Monitor Connection Status

Filed under: Technology, Uncategorized — Tags: , , , , , , — Developer42 @ 20:59

This script monitors a connection, reporting when the connection drops and when it’s recovered.
The script monitors by pinging an IP, or by running an HTTP Get against a defined IP/URL.
The URL option’s provided in case run from behind a firewall blocking ICMP.

function Run-ConnectionTest($site, $waitSecs, $firewall)
{
	$state = 'unknown'
	$previousState = 'unknown'

	while($true)
	{
		#get current connection state
		if(Test-MyConnection $site $firewall)
		{
			$state = 'up'
		}
		else
		{
			$state = 'down'
		}
		#report change in state
		if ($state -ne $previousState) 
		{
			$previousState = $state
			$now = Get-Date
			if($state -eq 'up')
			{
				Write-Host -ForegroundColor Green ("{0:yyyy-MM-dd hh:mm:ss}: Connection up!" -f $now)
			}
			else
			{
				Write-Host -ForegroundColor Red ("{0:yyyy-MM-dd hh:mm:ss}: Connection down..." -f $now)
			}
		}
		#wait before checking again
		Start-Sleep -Seconds $waitSecs
	}
}
function Test-MyConnection($site, $firewall)
{
	$result = $false
	if($firewall) #if there's a firewall blocking pings, pinging won't work
	{
		try 
		{ 
			$response = (Invoke-WebRequest -Uri $site)
			#write-host('OK')
			#write-host($response.StatusCode)
			if(($response.StatusCode -ge 200) -and ($response.StatusCode -lt 400)) #treat everything from 200 to 399 as connection successful
			{
				$result = $true
			}
			else #everything outside of 200-399 is treated as a connection issue
			{
				$result = $false
			}
		} 
		catch #all exceptions are treated as connection issues, regardless of http response status code
		{
			#write-host('KO')
			#write-host($_.Exception)
			#$_.Exception.Response.StatusCode.Value__
			$result = $false
		}
	}
	else
	{
		$result = Test-Connection -computer $site -count 1 -quiet
	}
	return $result
}

Clear
#Run-ConnectionTest '8.8.8.8' 5 $false
Run-ConnectionTest 'http://www.google.com' 30 $true

2014-08-22

Powershell Script: Get Server Inventory

Filed under: Microsoft — Tags: , , , , , , , , , , — Developer42 @ 16:36

The following script (heavily based on http://www.the-fays.net/blog/?p=334) polls all servers in a given text file, returning information about their disk capacity, # of cpus (sockets & cores), amount of RAM, and OS info.


$serverList = ".\Servers.txt"
$outputCSV = ".\ServerInventory.csv"


$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
pushd $dir

[System.Collections.ArrayList]$sysCollection = New-Object System.Collections.ArrayList($null)
 
foreach ($server in (Get-Content $serverList))
{
    "Collecting information from $server"
    $totCores=0
 
    try
    {
        [wmi]$sysInfo = get-wmiobject Win32_ComputerSystem -Namespace "root\CIMV2" -ComputerName $server -ErrorAction Stop
        [wmi]$bios = Get-WmiObject Win32_BIOS -Namespace "root\CIMV2" -computername $server
        [wmi]$os = Get-WmiObject Win32_OperatingSystem -Namespace "root\CIMV2" -Computername $server
		#[array]$disks = Get-WmiObject Win32_LogicalDisk -Namespace "root\CIMV2" -Filter DriveType=3 -Computername $server
		[array]$disks = Get-WmiObject Win32_LogicalDisk -Namespace "root\CIMV2" -Computername $server
		[array]$procs = Get-WmiObject Win32_Processor -Namespace "root\CIMV2" -Computername $server
        [array]$mem = Get-WmiObject Win32_PhysicalMemory -Namespace "root\CIMV2" -ComputerName $server
        [array]$nic = Get-WmiObject Win32_NetworkAdapterConfiguration -Namespace "root\CIMV2" -ComputerName $server | where{$_.IPEnabled -eq "True"}
 
        $si = @{
			Server			= [string]$server
			Manufacturer	= [string]$sysInfo.Manufacturer
			Model			= [string]$sysInfo.Model
			TotMem			= "$([string]([System.Math]::Round($sysInfo.TotalPhysicalMemory/1gb,2))) GB"
			BiosDesc		= [string]$bios.Description
			BiosVer			= [string]$bios.SMBIOSBIOSVersion+"."+$bios.SMBIOSMajorVersion+"."+$bios.SMBIOSMinorVersion
			BiosSerial		= [string]$bios.SerialNumber
			OSName			= [string]$os.Name.Substring(0,$os.Name.IndexOf("|") -1)
			Arch			= [string]$os.OSArchitecture
			Processors		= [string]@($procs).count
			Cores			= [string]$procs[0].NumberOfCores
			
		}
		
		$disks | foreach-object {$si."Drive$($_.Name -replace ':', '')"="$([string]([System.Math]::Round($_.Size/1gb,2))) GB"}
    }
    catch [Exception]
    {
        "Error communicating with $server, skipping to next"
        $si = @{
			Server			= [string]$server
			ErrorMessage	= [string]$_.Exception.Message
			ErrorItem		= [string]$_.Exception.ItemName
		}
        Continue
    }
    finally
    {
       [void]$sysCollection.Add((New-Object PSObject -Property $si))   
    }
}
 
$sysCollection `
	| select-object Server,TotMem,OSName,Arch,Processors,Cores,Manufacturer,Model,BiosDesc,BiosVer,BiosSerial,DriveA,DriveB,DriveC,DriveD,DriveE,DriveF,DriveG,DriveH,DriveI,DriveJ,DriveK,DriveL,DriveM,DriveN,DriveO,DriveP,DriveQ,DriveR,DriveS,DriveT,DriveU,DriveV,DriveW,DriveX,DriveY,DriveZ,ErrorMessage,ErrorItem `
	| sort -Property Server `
	| Export-CSV -path $outputCSV -NoTypeInformation    
	

Servers.txt should be held in the same directory as the script, & would look something like this:

MyServer1
MyServer2.fully.qualified.domain.com
YetAnotherServerName
172.24.8.1

Output looks something like this:

"Server","TotMem","OSName","Arch","Processors","Cores","Manufacturer","Model","BiosDesc","BiosVer","BiosSerial","DriveA","DriveB","DriveC","DriveD","DriveE","DriveF","DriveG","DriveH","DriveI","DriveJ","DriveK","DriveL","DriveM","DriveN","DriveO","DriveP","DriveQ","DriveR","DriveS","DriveT","DriveU","DriveV","DriveW","DriveX","DriveY","DriveZ","ErrorMessage","ErrorItem"
"MyServer1","8 GB","Microsoft Windows Server 2003 R2 Enterprise Editio","","4","","HP","ProLiant DL380 G5","Default System BIOS","P56.2.4","CZC8924LQ6      ","","","68.33 GB","0.58 GB","683.5 GB","0 GB","","","","","","","","","","","","","","","","","","","","","",""
"MyServer2.fully.qualified.domain.com","48 GB","Microsoft Windows Server 2012 Datacente","64-bit","8","1","VMware, Inc.","VMware Virtual Platform","PhoenixBIOS 4.0 Release 6.0     ","6.00.2.4","VMware-42 3b 8b d9 d3 92 b3 8e-9a 43 b1 b5 e6 a8 b2 74","0 GB","","31.66 GB","99.87 GB","3.63 GB","","","","","","","99.87 GB","","","","49.87 GB","","","","19.87 GB","","","","","","","",""
"YetAnotherServerName","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))",""
"172.24.8.1","48 GB","Microsoft Windows Server 2012 Datacente","64-bit","8","1","VMware, Inc.","VMware Virtual Platform","PhoenixBIOS 4.0 Release 6.0     ","6.00.2.4","VMware-42 3b 8b d9 d3 92 b3 8e-9a 43 b1 b5 e6 a8 b2 74","0 GB","","31.66 GB","99.87 GB","3.63 GB","","","","","","","99.87 GB","","","","49.87 GB","","","","19.87 GB","","","","","","","",""

2013-07-28

Countdown Numbers Round Solver in T-SQL

Filed under: Microsoft, SQL Server, Technology — Tags: , , , , , , , — Developer42 @ 14:00

Every now and then I try to create programs to solve puzzles as a way to introduce myself to new ideas & test my coding skills. My latest self-challenge was to write SQL code to solve the Countdown Numbers Round. The idea is; given 6 random numbers (each of which may be used at most once) and using only basic arithmetic (i.e. addition, subtraction, multiplication and division) to get to a randomly generated target number. e.g. Given the numbers 3, 7, 9, 10, 50 & 75, and the target 396 one solution would be: 3 * (75 + 50 + 7).

Here’s how I approached it in T-SQL:
Run this solution at SQL Fiddle

create procedure dbo.CountdownNumberSolver
(
	@target int output --output used to allow these to be referenced outside of the proc if rnd numbers are generated
	, @number1 int output
	, @number2 int output
	, @number3 int output
	, @number4 int output
	, @number5 int output
	, @number6 int output 
) as
begin
	
	--if the user didn't specify values randomly pick some.
	--   Small Number (1-10):			1 + 10 * rand() 
	--   Large Number (25,50,75,100):	floor(1 + 4 * rand()) * 25
	--   Target (1-999):				1 + (999 * rand())
	select @target = ISNULL(@target, 1 + (999 * rand())) --I assume 0 isn't a valid solution in countdown?
		, @number1 = ISNULL(@number1,1 + 10 * rand())
		, @number2 = ISNULL(@number2,1 + 10 * rand())
		, @number3 = ISNULL(@number3,1 + 10 * rand())
		, @number4 = ISNULL(@number4,1 + 10 * rand())
		, @number5 = ISNULL(@number5,floor(1 + 4 * rand()) * 25)
		, @number6 = ISNULL(@number6,floor(1 + 4 * rand()) * 25)

	--output question
    /* commented out as sql fiddle only returns first result set
	select @target [target]
		, @number1 [#1]
		, @number2 [#2]
		, @number3 [#3]
		, @number4 [#4]
		, @number5 [#5]
		, @number6 [#6]
	*/
	--records combinations tried so far / partial equations
	create table #solutions
	(
		id bigint not null identity(1,1) primary key clustered
		, binaryFlags tinyint not null
		, number float not null
		, equation nvarchar(256) not null
	)
	create index ix_solutions_number on #solutions(number)
	
	--start with the given values - the id stuff is just to make it easy to reuse this procedure should we want a different # of source numbers
	insert #solutions
	select power(2,id) 
	, number
	, CAST(number as nvarchar(3))
	from 
	(values 
		 (0,@number1)
		,(1,@number2)
		,(2,@number3)
		,(3,@number4)
		,(4,@number5)
		,(5,@number6)
	)x(id, number)

	declare @previousCount bigint = 0
	, @newCount bigint = (select COUNT(1) from #solutions)
	, @tempCount bigint
	
	while @previousCount < @newCount --repeat whilst there are still untried combos
		and not exists (select top 1 1 from #solutions where number=@target) --and we're still looking for a solution
	begin

		set @tempCount = @newCount
		
		insert #solutions
		select a.binaryFlags | b.binaryFlags
		, case x.o
			when '+' then a.number + b.number
			when '-' then a.number - b.number
			when '*' then a.number * b.number
			when '/' then a.number / b.number
		end
		, '(' + a.equation + ' ' + x.o + ' ' + b.equation + ')'
		from #solutions a
		inner join #solutions b
			on a.binaryFlags & b.binaryFlags = 0 --ensure we're not reusing source numbers
			and a.id != b.id --minor (potential) optimisation
			and --no point comparing things we've already checked (this may allow for some performance improvement- it doesn't affect the logic)
			(
				   a.id > @previousCount
				or b.id > @previousCount
			)
		inner join --doing things this way means we only need to find the new combinations from a&b once, not 4 times
		(
			values ('+'), ('-'), ('*'), ('/')
		) x(o)
			on  not(x.o = '/' and b.number = 0) --avoid div 0 errors
			and not(x.o = '-' and a.number - b.number = 0) --don't create 0 value entries (if 0's not an allowed solution this result just adds overhead without value)
			and not(x.o = '+' and a.number + b.number = 0) --don't create 0 value entries (if 0's not an allowed solution this result just adds overhead without value)
			and not(x.o = '+' and a.id > b.id) --commutative function optimisation
			and not(x.o = '*' and a.id > b.id) --commutative function optimisation
		
		set @previousCount = @tempCount
		select @newCount = COUNT(1) from #solutions	
		
	end

	--limited to one answer to fit with the game / avoid the assumption that all possible solutions would be listed 
	--(since we exit the above loop the moment we have an answer rather than continuing to check all combos)
	select top 1 
	  @number1 [#1]
	, @number2 [#2]
	, @number3 [#3]
	, @number4 [#4]
	, @number5 [#5]
	, @number6 [#6]
	, @target  [target] 
	, equation [solution]
	from #solutions 
	where number = @target
	order by len(equation) --show the shortest equation as that's probably simplest
	option (fast 1)	

	if object_id('tempdb..#solutions','U') is not null drop table #solutions

	return 0
end

Here’s an example of how to run it:

--run the proc using random numbers (the proc replaces nulls with random #s)
exec dbo.CountdownNumberSolver null, null, null, null, null, null, null

--run the proc on user defined values
exec dbo.CountdownNumberSolver 396, 7, 3, 10, 9, 50, 75

2013-06-05

Reverse an Array in a Windows Batch File

Filed under: Technology — Tags: , , , , , — Developer42 @ 17:58

The below script demonstrates how to reverse the order of elements in an array in a Windows batch file.

@echo off
cls
echo Script Started
setlocal enableextensions
setlocal enabledelayedexpansion

set arrayTest=("1", "2", "3", "test")

echo.Forward: %arrayTest%
for %%1 in %arrayTest% do (call:outputElement %%1)

call:reverseArray %arrayTest%

echo.Backward: %reverseArray%
for %%1 in %reverseArray% do (call:outputElement %%1)

goto:end

:reverseArray
set reverseArray=
for %%1 in %* do (call:reverseArrayX %%1)
set reverseArray=(%reverseArray%)
@goto:eof

:reverseArrayX
if "%reverseArray%"=="" (set reverseArray=%1) else (set reverseArray=%1, %reverseArray%)
@goto:eof

:outputElement
echo. – %~1
@goto:eof

:end
Echo.Completed

2013-05-02

How’re you doing? The continuous employee engagement survey.

Background
For the last few years my company’s been doing annual employee engagement surveys; a questionnaire asking how much you enjoy working at the place, what the best and worst things are, whether you’re thinking of leaving, etc. All of this data is aggregated and anonymised then a few months later presented back to us where we’re asked to help guide the company forwards to get the scores up the following year (and hopefully make the company a better place to work in the process).

Problem
The issue with this is you’re collecting data one day a year.

  • It’s pretty hard to remember what’s happened in that year, not comment on things from previous years, determine what’s still relevant to report on and mostly avoid biasing your results based on your current mood / the week’s events. As a result this kind of information isn’t very revealing.
  • As things are applied to try to resolve any issues and improve work life in general there’s no official feedback mechanism until the following year; at which point it’s hard to rate what worked and what didn’t.

Proposed Solution
A button. At the end of each day before going home employees go to a page on the intranet and press a button ["Woo" | "Meh" | "Ugh"] (number of options and their descriptions can vary per implementor’s preference). They may optionally also add a short comment (though should use this feature sparingly). This is then registered along with the date and their username (anonymised if required; but in such a way that the user cannot submit multiple answers per day) and fed into a database. Next you have a graph showing an aggregated view of all employees satisfaction ratings (depending on anonymity preferences you can do this at individual levels, for teams, for offices, or for the whole company). You can now compare these results with any activities taking place to see what’s upsetting people and what’s keeping them motivated. You can also see problem areas and upcoming issues (if there’s a long period of negative scores or everyone on a team gives a negative at the same time) and thus investigate and resolve them before they escalate. The comment function may provide additional information on why the scores are good/bad, and will also be useful if you continue with the annual detailed questionnaire as employees can look at their mood changes and the few comments they made throughout the year to remember key events to feed into this detail. Other benefits can come from including other info into your analytics (project deadlines, holidays, sick days, socials, weather (you can’t control it but can see its effect and compensate), etc).
Since it’s just a button it’s not a burden – so people will be likely to use it. You also get the satisfaction of stamping a close to your work day.

Questions

  • What do you guys think?
  • Has anyone done something like this at their place?
  • Anyone know of a LifeHacker style site for doing this kind of thing already which could be utilised (it’s pretty easy to build, but if there’s stuff out there which already includes common data sets (e.g. weather in your area, ability to log meals to allow individuals to make themselves happier by eating healthier, etc. so much the better)?

To keep discussion in one place I’ve disabled comments on this post and have left a discussion on Hacker News: https://news.ycombinator.com/item?id=5646466.

2013-02-13

Harlem Shake (XKCD Edition)

Filed under: Memes — Tags: , , , , , , , , , — Developer42 @ 23:38

I recently came across two new things. The first, an excellent bit of code by Antonin Hildebrand cmx.io. This code allows people to easily create XKCD style comics through simple markup, and also includes a useful wysiwyg editor to make tweaking the pictures simpler.
The second was a slew of videos in my YouTube feed with the latest meme: The Harlem Shake.
My instant reaction was to create my first meme whilst trying out my new toy: here’s the result (click the image to go to the animated markup version):

XKCD Harlem Shake http://cmx.io/#4949114

Harlem Shake XKCD Edition created with cmx.io

Code included below (also available on GitHub: https://gist.github.com/JohnLBevan/4949114.

Update (2013-02-14 22:12 UTC)
Code updated to include animation of second frame.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="http://cmx.io/v/0.1/cmx.css"/>
<script src="http://cmx.io/v/0.1/cmx.js"></script>
<script type="text/javascript">
<!--
  var frame2 = true;
  var vis = {};
  vis[true] = "block";
  vis[false] = "none";
  window.onload = function(){document.getElementById("scene2y").style.display = vis[false];};
  setTimeout(function(){setInterval(function(){swapFrames()},200);},3000);
  function swapFrames()
  {
    document.getElementById("scene2x").style.display = vis[frame2];
    frame2 = !frame2;
    document.getElementById("scene2y").style.display = vis[frame2];
  }
-->  
</script>
<style>.cmx-user-scene4 .cmx-text-border .cmx-path {stroke: orange}</style>
</head>
<body>
  <div style="max-width:900px; -webkit-transform:rotate(0deg);">

    <scene id="scene1">
      <label t="translate(0,346)">
        <tspan x="0" y="0em">Harlem Shake (XKCD edition)</tspan>
      </label>
      <drawing t="translate(0,31)">
        <line stroke="green">
          <point x="0" y="0"></point>
          <point x="250" y="20"></point>
        </line>
      </drawing>
      <!-- helmet; place behind head to cheat the circle -->
      <drawing t="translate(55,110)">
        <line stroke="black">
          <point x="0" y="0"></point>
          <point x="10" y="2"></point>
          <point x="-4" y="2"></point>
          <point x="12" y="5"></point>
          <point x="-7" y="4"></point>
          <point x="14" y="8"></point>
        </line>
        <line stroke="red">
          <point x="-10" y="5"></point>
          <point x="15" y="11"></point>
          <point x="-10" y="8"></point>
          <point x="14" y="15"></point>
          <point x="-10" y="11"></point>
          <point x="12" y="16"></point>
          <point x="-10" y="14"></point>
          <point x="12" y="19"></point>
          <point x="-10" y="17"></point>
          <point x="12" y="22"></point>
          <point x="-10" y="20"></point>
          <point x="12" y="25"></point>
          <point x="-10" y="23"></point>
          <!--<point x="" y=""></point>-->
        </line>
      </drawing>
      <actor t="translate(26,20) rotate(-15)" pose="-11,9|4,107|-11,99|-11,89|-18,78|-10,57|0,27|-5,2|17,29|15,15|-8,79|5,57|2,82|9,66">
        <bubble t="translate(9,11)" pose="0,0|-20,10|-81,49|14,94|-26,173|-100,127">
          <tspan x="0" y="-3em">Con los teroristas</tspan>
          <tspan x="0" y="0em">Wub wub wub wub wub wub, wub wub</tspan>
          <tspan x="0" y="1em">wub tas, wub wub, wub, wub wub wub</tspan>
          <tspan x="0" y="2em">Wub wub wub wub wub wub, wub wub wub </tspan>
          <tspan x="0" y="3em">tas wub wub, wub, wub tas wub wub tas</tspan>
          <tspan x="0" y="4em">tas wub tas wub tas</tspan>
          <tspan x="0" y="5em">wub tas wub tas tas</tspan>
        </bubble>
      </actor>
      <actor t="translate(140,22)" pose="29,1|7,103|28,69|28,59|28,71|28,51|17,32|17,2|33,26|38,1|19,45|18,64|38,52|36,30"></actor>
      <!-- table -->
      <drawing t="translate(100,10) rotate(3)">
        <line stroke="brown">
          <point x="0" y="0"></point><!-- bottom left leg bottom -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="50" y="20"></point><!-- top left leg bottom -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="140" y="20"></point><!-- top right leg bottom -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="90" y="0"></point><!-- bottom right leg bottom -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <!-- end of wireframe - begin dodgy colouring in time -->
          <point x="55" y="50"></point>
          <point x="5" y="30"></point>
          <point x="60" y="50"></point>
          <point x="10" y="30"></point>
          <point x="65" y="50"></point>
          <point x="15" y="30"></point>
          <point x="70" y="50"></point>
          <point x="20" y="30"></point>
          <point x="75" y="50"></point>
          <point x="25" y="30"></point>
          <point x="80" y="50"></point>
          <point x="30" y="30"></point>
          <point x="85" y="50"></point>
          <point x="35" y="30"></point>
          <point x="90" y="50"></point>
          <point x="40" y="30"></point>
          <point x="95" y="50"></point>
          <point x="45" y="30"></point>
          <point x="100" y="50"></point>
          <point x="50" y="30"></point>
          <point x="105" y="50"></point>
          <point x="55" y="30"></point>
          <point x="110" y="50"></point>
          <point x="60" y="30"></point>
          <point x="115" y="50"></point>
          <point x="65" y="30"></point>
          <point x="120" y="50"></point>
          <point x="70" y="30"></point>
          <point x="125" y="50"></point>
          <point x="75" y="30"></point>
          <point x="130" y="50"></point>
          <point x="80" y="30"></point>
          <point x="135" y="50"></point>
          <point x="85" y="30"></point>
          <point x="140" y="50"></point>
          <point x="90" y="30"></point>
        </line>
      </drawing>
      <actor t="translate(102,-14) rotate(2)" pose="30,1|36,118|28,72|28,62|28,67|28,47|20,35|20,25|39,32|38,22|17,61|29,58|41,59|49,60"></actor>
      <actor t="translate(187,-4)" pose="31,1|6,128|27,82|27,72|28,71|28,51|10,42|12,23|21,49|23,28|15,63|2,61|37,66|39,53"></actor>
    </scene>
    <div id="scene2x">
    <scene id="scene2">
      <label t="translate(0,346)">
        <tspan x="0" y="0em">"And do the Harlem Shake"</tspan>
      </label>
      <drawing t="translate(0,31)">
        <line stroke="green">
          <point x="0" y="0"></point>
          <point x="250" y="20"></point>
        </line>
      </drawing>
      <!-- helmet; place behind head to cheat the circle -->
      <drawing t="translate(68,129) rotate(76)">
        <line stroke="black">
          <point x="0" y="0"></point>
          <point x="10" y="2"></point>
          <point x="-4" y="2"></point>
          <point x="12" y="5"></point>
          <point x="-7" y="4"></point>
          <point x="14" y="8"></point>
        </line>
        <line stroke="red">
          <point x="-10" y="5"></point>
          <point x="15" y="11"></point>
          <point x="-10" y="8"></point>
          <point x="14" y="15"></point>
          <point x="-10" y="11"></point>
          <point x="12" y="16"></point>
          <point x="-10" y="14"></point>
          <point x="12" y="19"></point>
          <point x="-10" y="17"></point>
          <point x="12" y="22"></point>
          <point x="-10" y="20"></point>
          <point x="12" y="25"></point>
          <point x="-10" y="23"></point>
          <!--<point x="" y=""></point>-->
        </line>
      </drawing>
      <actor t="translate(43,18) rotate(-15)" pose="-11,9|-19,119|-13,101|-13,91|-17,64|-6,58|5,29|0,4|21,30|19,16|-15,79|3,59|-3,74|14,68">
        <bubble t="translate(9,11)" pose="0,0|-20,10|-40,33|-8,51|-29,79|-86,157">
          <tspan x="0" y="0em">Wub wub wub wub wub wub, wub wub</tspan>
          <tspan x="0" y="1em">Shake</tspan>
          <tspan x="0" y="2em">Wub wub wub wub wub wub, wub wub wub </tspan>
          <tspan x="0" y="3em">Shake</tspan>
          <tspan x="0" y="4em">Wub Wub tas wub tas wub tas</tspan>
          <tspan x="0" y="5em">Con los teroristas</tspan>
          <tspan x="0" y="6em">Grrrrrrr</tspan>
        </bubble>
      </actor>
      <actor t="translate(137,14)" pose="29,1|23,147|29,96|29,86|34,92|36,67|25,48|25,18|41,42|46,17|4,88|2,111|46,103|53,136"></actor>
      <!-- table -->
      <drawing t="translate(100,10) rotate(3)">
        <line stroke="brown">
          <point x="0" y="0"></point><!-- bottom left leg bottom -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="50" y="20"></point><!-- top left leg bottom -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="140" y="20"></point><!-- top right leg bottom -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="90" y="0"></point><!-- bottom right leg bottom -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <!-- end of wireframe - begin dodgy colouring in time -->
          <point x="55" y="50"></point>
          <point x="5" y="30"></point>
          <point x="60" y="50"></point>
          <point x="10" y="30"></point>
          <point x="65" y="50"></point>
          <point x="15" y="30"></point>
          <point x="70" y="50"></point>
          <point x="20" y="30"></point>
          <point x="75" y="50"></point>
          <point x="25" y="30"></point>
          <point x="80" y="50"></point>
          <point x="30" y="30"></point>
          <point x="85" y="50"></point>
          <point x="35" y="30"></point>
          <point x="90" y="50"></point>
          <point x="40" y="30"></point>
          <point x="95" y="50"></point>
          <point x="45" y="30"></point>
          <point x="100" y="50"></point>
          <point x="50" y="30"></point>
          <point x="105" y="50"></point>
          <point x="55" y="30"></point>
          <point x="110" y="50"></point>
          <point x="60" y="30"></point>
          <point x="115" y="50"></point>
          <point x="65" y="30"></point>
          <point x="120" y="50"></point>
          <point x="70" y="30"></point>
          <point x="125" y="50"></point>
          <point x="75" y="30"></point>
          <point x="130" y="50"></point>
          <point x="80" y="30"></point>
          <point x="135" y="50"></point>
          <point x="85" y="30"></point>
          <point x="140" y="50"></point>
          <point x="90" y="30"></point>
        </line>
      </drawing>
      <actor t="translate(112,26) rotate(2)" pose="30,1|32,156|16,114|16,104|8,92|8,72|19,62|14,33|29,65|26,39|5,103|22,80|29,101|37,94"></actor>
      <actor t="translate(182,33) rotate(5)" pose="31,1|59,117|23,99|23,89|20,73|27,60|7,46|16,22|17,50|24,30|12,73|3,75|33,86|5,85"></actor>
      <!-- helmet; place behind head to cheat the circle -->
      <drawing t="translate(130,124) rotate(-43)" pose="-2,-29">
        <line stroke="pink">
          <point x="0" y="0"></point>
          <point x="10" y="2"></point>
          <point x="-4" y="2"></point>
          <point x="12" y="5"></point>
          <point x="-7" y="4"></point>
          <point x="14" y="8"></point>
          <point x="-10" y="5"></point>
          <point x="15" y="11"></point>
          <point x="-10" y="8"></point>
        </line>
        <line stroke="green">
          <point x="14" y="15"></point>
          <point x="-10" y="11"></point>
          <point x="12" y="16"></point>
          <point x="-10" y="14"></point>
          <point x="12" y="19"></point>
          <point x="-10" y="17"></point>
          <point x="12" y="22"></point>
          <point x="-10" y="20"></point>
          <point x="12" y="25"></point>
          <point x="-10" y="23"></point>
          <!--<point x="" y=""></point>-->
        </line>
      </drawing>

      <actor t="translate(108,10)" pose="0,0|6,97|0,90|0,80|6,73|6,53|-10,33|-8,8|11,33|10,3|-10,70|-14,53|10,70|17,50"></actor>
      <actor t="translate(171,135) rotate(176)" pose="2,7|2,113|2,97|2,87|-9,94|3,71|-16,63|-9,34|15,55|24,48|-19,98|-23,124|27,98|30,130"></actor>
      <actor t="translate(226,18)" pose="0,0|0,89|0,82|0,72|0,70|0,50|-10,30|-10,5|13,30|11,6|-14,65|-12,50|17,66|19,50"></actor>
      <actor t="translate(225,18)" pose="-149,3|-148,96|-148,80|-148,70|-149,67|-149,47|-159,27|-153,-3|-139,27|-144,1|-166,77|-185,108|-132,62|-132,42"></actor>
      <actor t="translate(212,214)" pose="0,0|6,106|0,90|-13,64|10,71|12,55|20,49|35,25|25,75|33,59|-34,101|-42,81|26,86|35,114"></actor>
    </scene>
    </div>
    <div id="scene2y">
    <scene id="scene2b">
      <label t="translate(0,346)">
        <tspan x="0" y="0em">"And do the Harlem Shake"</tspan>
      </label>
      <drawing t="translate(0,31)">
        <line stroke="green">
          <point x="0" y="0"></point>
          <point x="250" y="20"></point>
        </line>
      </drawing>
      <!-- helmet; place behind head to cheat the circle -->
      <drawing t="translate(55,130) rotate(120)">
        <line stroke="black">
          <point x="0" y="0"></point>
          <point x="10" y="2"></point>
          <point x="-4" y="2"></point>
          <point x="12" y="5"></point>
          <point x="-7" y="4"></point>
          <point x="14" y="8"></point>
        </line>
        <line stroke="red">
          <point x="-10" y="5"></point>
          <point x="15" y="11"></point>
          <point x="-10" y="8"></point>
          <point x="14" y="15"></point>
          <point x="-10" y="11"></point>
          <point x="12" y="16"></point>
          <point x="-10" y="14"></point>
          <point x="12" y="19"></point>
          <point x="-10" y="17"></point>
          <point x="12" y="22"></point>
          <point x="-10" y="20"></point>
          <point x="12" y="25"></point>
          <point x="-10" y="23"></point>
          <!--<point x="" y=""></point>-->
        </line>
      </drawing>
      <actor t="translate(43,18) rotate(-15)" pose="-11,9|-40,108|-13,101|-13,91|10,80|-6,58|3,28|-2,3|21,30|19,16|-15,79|3,59|-3,74|14,68">
        <bubble t="translate(9,11)" pose="0,0|-20,10|-40,33|-8,51|-29,79|-60,174">
          <tspan x="0" y="0em">Wub wub wub wub wub wub, wub wub</tspan>
          <tspan x="0" y="1em">Shake</tspan>
          <tspan x="0" y="2em">Wub wub wub wub wub wub, wub wub wub </tspan>
          <tspan x="0" y="3em">Shake</tspan>
          <tspan x="0" y="4em">Wub Wub tas wub tas wub tas</tspan>
          <tspan x="0" y="5em">Con los teroristas</tspan>
          <tspan x="0" y="6em">Grrrrrrr</tspan>
        </bubble>
      </actor>
      <actor t="translate(137,14)" pose="29,1|14,145|29,96|29,86|34,92|36,67|25,48|25,18|41,42|46,17|4,88|-3,111|46,103|48,138"></actor>
      <!-- table -->
      <drawing t="translate(100,10) rotate(3)">
        <line stroke="brown">
          <point x="0" y="0"></point><!-- bottom left leg bottom -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="50" y="20"></point><!-- top left leg bottom -->
          <point x="50" y="50"></point><!-- top left leg top -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="140" y="20"></point><!-- top right leg bottom -->
          <point x="140" y="50"></point><!-- top right leg top -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="90" y="0"></point><!-- bottom right leg bottom -->
          <point x="90" y="30"></point><!-- bottom right leg top -->
          <point x="0" y="30"></point><!-- bottom left leg top -->
          <!-- end of wireframe - begin dodgy colouring in time -->
          <point x="55" y="50"></point>
          <point x="5" y="30"></point>
          <point x="60" y="50"></point>
          <point x="10" y="30"></point>
          <point x="65" y="50"></point>
          <point x="15" y="30"></point>
          <point x="70" y="50"></point>
          <point x="20" y="30"></point>
          <point x="75" y="50"></point>
          <point x="25" y="30"></point>
          <point x="80" y="50"></point>
          <point x="30" y="30"></point>
          <point x="85" y="50"></point>
          <point x="35" y="30"></point>
          <point x="90" y="50"></point>
          <point x="40" y="30"></point>
          <point x="95" y="50"></point>
          <point x="45" y="30"></point>
          <point x="100" y="50"></point>
          <point x="50" y="30"></point>
          <point x="105" y="50"></point>
          <point x="55" y="30"></point>
          <point x="110" y="50"></point>
          <point x="60" y="30"></point>
          <point x="115" y="50"></point>
          <point x="65" y="30"></point>
          <point x="120" y="50"></point>
          <point x="70" y="30"></point>
          <point x="125" y="50"></point>
          <point x="75" y="30"></point>
          <point x="130" y="50"></point>
          <point x="80" y="30"></point>
          <point x="135" y="50"></point>
          <point x="85" y="30"></point>
          <point x="140" y="50"></point>
          <point x="90" y="30"></point>
        </line>
      </drawing>
      <actor t="translate(112,26) rotate(2)" pose="30,1|38,137|14,101|14,91|8,92|8,72|15,53|10,24|25,54|22,28|3,90|20,67|27,88|31,67"></actor>
      <actor t="translate(182,33) rotate(5)" pose="31,1|38,138|23,99|23,89|24,73|31,60|11,46|13,20|21,50|25,26|12,73|3,75|33,86|7,73"></actor>
      <!-- helmet; place behind head to cheat the circle -->
      <drawing t="translate(116,127) rotate(10)" pose="-2,-29">
        <line stroke="pink">
          <point x="0" y="0"></point>
          <point x="10" y="2"></point>
          <point x="-4" y="2"></point>
          <point x="12" y="5"></point>
          <point x="-7" y="4"></point>
          <point x="14" y="8"></point>
          <point x="-10" y="5"></point>
          <point x="15" y="11"></point>
          <point x="-10" y="8"></point>
        </line>
        <line stroke="green">
          <point x="14" y="15"></point>
          <point x="-10" y="11"></point>
          <point x="12" y="16"></point>
          <point x="-10" y="14"></point>
          <point x="12" y="19"></point>
          <point x="-10" y="17"></point>
          <point x="12" y="22"></point>
          <point x="-10" y="20"></point>
          <point x="12" y="25"></point>
          <point x="-10" y="23"></point>
          <!--<point x="" y=""></point>-->
        </line>
      </drawing>

      <actor t="translate(108,10)" pose="0,0|6,97|0,90|0,80|0,70|0,50|-10,30|-5,3|10,30|10,0|-10,70|-10,50|10,70|10,50"></actor>
      <actor t="translate(171,135) rotate(176)" pose="6,7|6,113|6,97|6,87|-8,94|4,71|-15,63|-13,36|14,51|19,41|-15,98|-19,124|31,98|34,130"></actor>
      <actor t="translate(226,18)" pose="0,0|0,89|0,82|0,72|0,70|0,50|-10,30|-4,5|13,30|8,6|-13,63|-7,50|15,63|17,47"></actor>
      <actor t="translate(225,18)" pose="-149,3|-149,109|-149,93|-149,83|-149,73|-149,53|-159,33|-159,3|-139,33|-139,3|-167,90|-176,121|-139,73|-139,53"></actor>
      <actor t="translate(212,214)" pose="0,0|0,106|0,90|-13,64|10,71|12,55|20,49|35,25|25,75|33,59|-34,101|-38,135|26,86|35,114"></actor>
    </scene>
    </div>
    
    <scene id="scene3" height="160">
      <label t="translate(-2,188)" pose="0,-10|7,-30">
        <tspan x="0" y="0em">Non Web Addicts</tspan>
      </label>
      <actor t="translate(111,7) rotate(2)" pose="-41,48|-10,105|0,88|0,78|0,68|0,48|-5,23|-10,-2|5,23|10,-2|-11,70|-4,54|17,86|-4,110">
        <bubble t="translate(88,-55)" pose="-12,5|-111,42|-144,29|-129,57|-159,64|-177,75">
          <tspan x="0" y="0em" fill="red">WTF?!</tspan>
        </bubble>
      </actor>
    </scene>
    <scene id="scene4" width="300" height="150" margin-y="3" frame="no">
      <label t="translate(11,133)" pose="-10,14|-9,5">
        <tspan x="0" y="0em">~ comix markup</tspan>
        <tspan x="0" y="1em">~ can be mixed with HTML</tspan>
        <tspan x="0" y="2em">~ WYSIWYG editor</tspan>
        <tspan x="0" y="3em">~ open-source</tspan>
        <tspan x="0" y="4em">~ backed by </tspan><tspan fill="blue">gist.github.com</tspan>
        <tspan x="0" y="5em">~ xkcd harlem shake by </tspan><tspan fill="green">JohnLBevan</tspan>
      </label>
      <actor t="translate(211,44) rotate(-4)" pose="73,-56|77,58|79,38|87,22|81,12|82,1|63,-18|57,-40|89,-14|93,-38|69,23|51,30|71,15|43,19">
      </actor>
    </scene>
  </div>
</body>
</html>
Older Posts »

The WordPress Classic Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 728 other followers

%d bloggers like this: