Thursday, June 25, 2009

Windows 7 Kerberos updates and foreign realms


I bit the bullet, bought a new 500Gb Seagate drive for my laptop, VM’d my Vista install, swapped the drive and changed my main laptop to Windows 7 over the weekend, and apart from Sony PC Suite, which just sits in the system tray looking dead, everything is running better than ever… 4Gb of ram certainly helps things along but this is faster than XP Sp3 in my FITA (Finger In The Air) opinion and light years ahead of Vista aka Windows Me 2007 …


So I added the laptop to my AD domain e.g. lingpopo.net and then while at a client site mapped a drive to a server in their AD domain e.g. bank.corp.com, saved the credentials and all worked as expected… but later while I was debugging a kerberos issue with some users on XP I forgot I was on 7 and ran klist (now an OS standard tool at least in 7 RC1) and noticed something strange but very pleasing. Rather than just seeing a TGT from my domain e.g. me @ lingpopo.net I now saw TGTs for the me @ bank.corp.com and cifs TGS tickets for the server in bank.corp.com! Sweetness! I’m getting TGTs and tickets for resource from an untrusted realm / forest / domain, no more failover to NTLM outside the forest / trust boundary, brilliant! but how? I need to know…This did not happen in XP or Vista, so what have Microsoft done? I’ve only noticed this behaviour today and have started digging out details on Kerberos improvements in Windows 7 but thus far i’ve found zip, nada, nowt… once I do I’ll be sure to post…

Anyway random stuff and quite interesting, at least to me…

Got a response from Microsoft Response from MS which says the functionality was always there but imho it wasn't... but going to dig out an xp and vista vpc and retest just to be sure...

Update 7/1/09 :- I recompiled and used this old tool of mine http://www.brianhehir.com/ktickets.exe to try and validate the tickets under Windows 7 and Windows XP. Running this tool under Windows 7 gives totally different results to running KLIST.EXE under Windows 7 but the results from kticket.exe are consistent between Windows 7 and Windows XP. So is it just Klist.exe that is different between Windows 7 and Windows XP or is there some underlying fundamental difference with Kerberos in Windows 7... Only way to be sure is going to take a network trace and compare....

Show Wireless networks with details including strength

Useful for getting the strength and options of all available wireless networks for Vista, Windows 7 and Windows 2008:

netsh wlan show networks mode=bssid

Gives the following type of info:

SSID 20 : linksys_SES_35580
Network type : Infrastructure
Authentication : WPA2-Personal
Encryption : CCMP
BSSID 1 : 00:14:bf:79:41:c8
Signal : 15%
Radio type : 802.11g
Channel : 6
Basic rates (Mbps) : 1 2 5.5 11
Other rates (Mbps) : 6 9 12 18 24 36 48 54

Wednesday, June 24, 2009

SQL CLR & LDAP

Follow the link for an example of using Novell LDAP under SQL 2005 CLR, you'll need the Mono libraries installed, the certificate installed in the Mono certificate manager if using SSL, the db set to trustworthy or all the libraries re-compiled as signed etc. then create the novell assemblies then deploy this via VS...

but I don't like spam

Humanity has triumphed over the robots, spam is off the menu!

Monday, June 22, 2009

Spam, eggs, spam, spam and spam...

So the robots are calling me spam!

I received a notice that this blog had been classified as spam and I had to post a request for a human being to review and take me off the list... strange but I think I know why... I think it was because I posted the same information in two posts and then the About Me section while I was creating the site... why? well because I tried to import the blog from wordpress but the conversion program here http://wordpress2blogger.appspot.com/ just did not work no matter what I did.. so as there were only half a dozen posts I wanted anyway I manually updated them and voila balls'd it up...

but it's weird the robots caught that and classified it so quickly, less than an hour. They must scan the blogs on a much higher schedule than any other indexer, which is interesting, or I was just unlucky, which is a pain in the aris... anyway hopefully I'll be reclassified as non-spam pdq...

Windows 7 Shell:SendTo

Need to update the Send To list in Windows 7? Start – Run (Windows+R) Shell:SendTo brings up your SendTo list; just add what you need e.g. notepad.lnk.. easy!

To see all possible options hold down SHIFT and Right-click the item then navigate to SendTo… too many options…

Other useful Shell: commands:-

shell:Libraries
shell:DocumentsLibrary
shell:User Pinned
shell:UsersLibrariesFolder
shell:PicturesLibrary
shell:ImplicitAppShortcuts
shell:Ringtones
shell:MusicLibrary
shell:VideosLibrary
shell:OtherUsersFolder
shell:Device Metadata Store
shell:PublicSuggestedLocations
shell:CommonRingtones

Will post more when I find them

Directory Intelligence Services

Started working on a new product for BMA Data that easily allows you to import all your data from AD, Novell or other LDAP directory directly into mySQL or SQL 2005 / 2008 for offline heavy-duty data crunching. We’re going to call this Directory Intelligence Services and it should be available in some form within the next couple of months…. yes I know there are other products out there that do this (ILM / MIIS etc.) but these are all geared to Identity and Lifecycle Management and imho are a bit clunky and sometimes, ILM 2.0, require products you just don’t or may not have e.g. SQL 2005 / Exchange… plus there’s always room for competition… plus it’ll stop the brain from seizing… plus, well who cares it’s my life… so watch this space…

Sunday, June 21, 2009

Create cached credentials without an interactive logon using runas or logonusera

Useful tidbit I used in a migration once upon a time…. you can create the cached credentials for a domain user by using runas.exe or coding with logonusera in some code then you’ll be able to logon interactively as that user from ctrl+alt+del without the domain being avaialble at logon. This works as long as the machine’s domain is not changed. Changing the domain can change the cert / hashing using to hash and store the credentials in the registry and also can force the OS to clear all cached credentials. I’ve found this useful when migrating users where the VPN solution is only available after logon and not at the logon screen. Obviously if you can see the domain at the logon screen then it doesn’t matter… also if you want to create an admin back door, for use when a user is roaming for remote debugging and support, using a domain account and not a local account you can send the code to create the cached credentials and not have to logon interactivel

Here’s a little collection that sets up some Event Consumers on bad logon events and will launch a script to take action on the event. It can be used to take action on logon attempts using cached credentials. If used with the Purge Tickets code it can clear a users kerberos tickets if an intruder logon attempt is detected while disconnected from a domain. Kinda overkill but it was an interesting exercise in understanding event consumers, kerberos tickets and logon providers….

%SYSTEMROOT%\SYSTEM32\WBEM\MOFCOMP.EXE -N:root\default %SYSTEMROOT%\SYSTEM32\WBEM\scrcons.mof

%SYSTEMROOT%\SYSTEM32\WBEM\MOFCOMP.EXE “%HERE%\528SecEventTrig.mof”
%SYSTEMROOT%\SYSTEM32\WBEM\MOFCOMP.EXE “%HERE%\529SecEventTrig.mof”
%SYSTEMROOT%\SYSTEM32\WBEM\MOFCOMP.EXE “%HERE%\539SecEventTrig.mof”

529SecEventTrig.mof:

#pragma namespace (”\\\\.\\root\\subscription”)

instance of ActiveScriptEventConsumer as $Cons529
{
Name = “BadLogonConsumer”;
ScriptingEngine = “VBScript”;
ScriptFileName = “C:\\WINDOWS\\SYSTEM32\\DRIVERS\\HERACLES.VBS”;
KillTimeout = 1;
};

instance of __EventFilter as $Filt529
{
Name = “BadLogonFilter”;
Query = “SELECT * FROM __InstanceCreationEvent “
“WHERE TargetInstance ISA \”Win32_NTLogEvent\” “
“AND TargetInstance.LogFile = \”Security\” “
“AND TargetInstance.SourceName = \”Security\” “
“AND TargetInstance.EventCode = 529″;
QueryLanguage = “WQL”;
EventNamespace = “\\\\.\\root\\cimv2″;
};

instance of __FilterToConsumerBinding
{
Filter = $Filt529;
Consumer = $Cons529;
};

528SecEventtrig.mof:

#pragma namespace (”\\\\.\\root\\subscription”)

instance of ActiveScriptEventConsumer as $Cons539
{
Name = “AccountLockedConsumer”;
ScriptingEngine = “VBScript”;
ScriptFileName = “C:\\WINDOWS\\SYSTEM32\\DRIVERS\\HERACLES.VBS”;
KillTimeout = 1;
};

instance of __EventFilter as $Filt539
{
Name = “AccountLockedFilter”;
Query = “SELECT * FROM __InstanceCreationEvent “
“WHERE TargetInstance ISA \”Win32_NTLogEvent\” “
“AND TargetInstance.LogFile = \”Security\” “
“AND TargetInstance.SourceName = \”Security\” “
“AND TargetInstance.EventCode = 539″;
QueryLanguage = “WQL”;
EventNamespace = “\\\\.\\root\\cimv2″;
};

instance of __FilterToConsumerBinding
{
Filter = $Filt539;
Consumer = $Cons539;
};

539SecEventTrig.mof:

#pragma namespace (”\\\\.\\root\\subscription”)

instance of ActiveScriptEventConsumer as $Cons539
{
Name = “AccountLockedConsumer”;
ScriptingEngine = “VBScript”;
ScriptFileName = “C:\\WINDOWS\\SYSTEM32\\DRIVERS\\HERACLES.VBS”;
KillTimeout = 1;
};

instance of __EventFilter as $Filt539
{
Name = “AccountLockedFilter”;
Query = “SELECT * FROM __InstanceCreationEvent “
“WHERE TargetInstance ISA \”Win32_NTLogEvent\” “
“AND TargetInstance.LogFile = \”Security\” “
“AND TargetInstance.SourceName = \”Security\” “
“AND TargetInstance.EventCode = 539″;
QueryLanguage = “WQL”;
EventNamespace = “\\\\.\\root\\cimv2″;
};

instance of __FilterToConsumerBinding
{
Filter = $Filt539;
Consumer = $Cons539;
};

HERACLES.VBS:


‘Check logon type from TargetEvent.TargetInstance.Message – 11 CachedInteractive, 7 Unlock,
‘Check the User name from TargetEvent.TargetInstance.Message against current interactive user
‘Get lockout count from RSOP
‘Increment consecutive failure count for user
‘On a good logon check the lockout counter for the user and launch chimaera.exe to purge tickets and lock account
‘ Constants:
Const strComputer = “.”
Const HKEY_LOCAL_MACHINE = &H80000002
Const path = “C:\WINDOWS\SYSTEM32\DRIVERS”
Dim strKeyPath : strKeyPath = “SYSTEM\CurrentControlSet\Control\MSReports”

‘Quit if run without the TargetEvent passed from the ActiveScriptEventConsumer
If (Not IsObject(TargetEvent)) Then WScript.Quit

‘Quit if Server
Dim objWMIService : Set objWMIService = GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\CIMV2″)
Dim colItem :Set colItems = objWMIService.ExecQuery(”Select * From Win32_OperatingSystem”)
Dim objItem
For each objItem in colItems
if (instr(UCase(objItem.Caption),”SERVER”) > 0) then Wscript.Quit
Next
Set colItem = Nothing
Set objWMIService = Nothing

Dim objReg : Set objReg=GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\default:StdRegProv”)

‘Run routine based on Event Code
Select Case TargetEvent.TargetInstance.EventCode
Case 528
Call GoodLogonEvent
Case 529
Call BadLogonEvent
Case 4201
Call TCPIPEvent
Case 539
Call AccountLockedEvent
End Select
Wscript.Quit

Sub GoodLogonEvent
On Error Resume Next
‘Quit if Network Service or SYSTEM
If InStr(UCase(TargetEvent.TargetInstance.Message),”NETWORK SERVICE”) > 0 Then WScript.Quit
If InStr(UCase(TargetEvent.TargetInstance.Message),”SYSTEM”) > 0 Then WScript.Quit

Dim arrMessage, arrUserName, objWMIService, objProcess, test
Dim strUserName, intLockout, errReturn, strValueName, strLogonGUID, arrLogonGUID

‘Parse message for required information
arrMessage = Split(TargetEvent.TargetInstance.Message,vbCrLf)

If IsArray(arrMessage) Then
arrUserName = Split(arrMessage(2),vbTab)
If IsArray(arrUserName) then
strUserName = arrUserName(2)
End If
arrLogonDomainName = Split(arrMessage(4),vbTab)
If IsArray(arrLogonDomainName) Then
strLogonDomainName = arrLogonDomainName(3)
End If
arrLogonComputerName = Split(arrMessage(14),vbTab)
If IsArray(arrLogonComputerName) Then
strLogonComputerName = arrLogonComputerName(UBound(arrLogonComputerName))
End If
arrLogonType = Split(arrMessage(8),vbTab)
If IsArray(arrLogonType) Then
intLogonType = arrLogonType(UBound(arrLogonType))
End If
arrLogonGUID = Split(arrMessage(16),vbTab)
If IsArray(arrLogonGUID) Then
strLogonGUID = arrLogonGUID(2)
End if
Else
WScript.Quit
End If

‘Set initial lockout counter
intLockout = &H0

‘Create user key, if not exist
strKeyPath = strKeyPath & “\” & strUserName
errReturn = objReg.CreateKey(HKEY_LOCAL_MACHINE,strKeyPath)
‘Read lockout trigger information
strValueName = strUserName & “-Lockout”
errReturn = objReg.GetDWORDValue(HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intLockout)
If errReturn <> 0 Then
‘Lockout trigger not set so delete user key
objReg.DeleteKey HKEY_LOCAL_MACHINE,strKeyPath ‘,strValueName
Else
‘Lockout trigger set so launch chimaera.exe if it is not already running
Set objWMIService = GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\CIMV2″)
Set objProcess = objWMIService.Get(”win32_Process”)
Set colProcesses = objWMIService.ExecQuery(”SELECT * FROM Win32_Process WHERE Name LIKE ‘%CHIMAERA.EXE%’”)
if colProcesses.Count = 0 then
‘errReturn = objProcess.Create(path & “\HYDRA.EXE /DOMAIN=” & strLogonDomainName & ” /USERID=” & strUserName, Null, Null, intProcessID)
‘Chimaera.exe not running so launch it to purge tickets and lock acocunt
errReturn = objProcess.Create(path & “\CHIMAERA.EXE /LOCK /DOMAIN=” & strLogonDomainName & ” /USERID=” & strUserName, Null, Null, intProcessID)
strValueName = strUserName & “-PurgeTktsErrReturn”
if errReturn = 1 then test = &H1 Else test = &H0
objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,test
End if
Set objWMIService = Nothing
Set objProcess = Nothing

End If
Set objReg = Nothing
WScript.quit
End Sub

Sub BadLogonEvent
On Error Resume Next
Dim objreg, arrMessage, arrUserName, objWMIService, objProcess, colItems, objItems, test
Dim strUserName, intLockout, errReturn, strValueName, intLockoutBadCount, intBadLogonCount
Dim arrLogonDomainName, strLogonDomainName, strComputerName, arrLogonType
Dim intLogonType, arrLogonComputerName, strLogonComputerName

IntLockoutBadCount = 5
strComputerName = TargetEvent.TargetInstance.ComputerName
‘Parse message for required information
strUserName = “ERROR”
arrMessage = Split(TargetEvent.TargetInstance.Message,vbCrLf)
If IsArray(arrMessage) Then
arrUserName = Split(arrMessage(4),vbTab)
If IsArray(arrUserName) then
strUserName = arrUserName(2)
End If
arrLogonDomainName = Split(arrMessage(6),vbTab)
If IsArray(arrLogonDomainName) Then
strLogonDomainName = arrLogonDomainName(3)
End If
arrLogonComputerName = Split(arrMessage(14),vbTab)
If IsArray(arrLogonComputerName) Then
strLogonComputerName = arrLogonComputerName(UBound(arrLogonComputerName))
End If
arrLogonType = Split(arrMessage(8),vbTab)
If IsArray(arrLogonType) Then
intLogonType = arrLogonType(UBound(arrLogonType))
End If
Else
WScript.Quit
End If

‘If local logon exit
If UCase(strLogonDomainName) = UCase(strComputerName) Then WScript.Quit
If UCase(strLogonComputerName) <> UCase(strComputerName) Then WScript.Quit

strKeyPath = strKeyPath & “\” & strUserName

Set objReg=GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\default:StdRegProv”)

‘Create user key if not exist and write data from message
errReturn = objReg.CreateKey(HKEY_LOCAL_MACHINE,strKeyPath)
errReturn = objReg.SetStringValue(HKEY_LOCAL_MACHINE,strKeyPath,”strLogonComputerName”,strLogonComputerName)
errReturn = objReg.SetStringValue(HKEY_LOCAL_MACHINE,strKeyPath,”strLogonDomainName”,strLogonDomainName)
errReturn = objReg.SetStringValue(HKEY_LOCAL_MACHINE,strKeyPath,”strUserName”,strUserName)
errReturn = objReg.SetStringValue(HKEY_LOCAL_MACHINE,strKeyPath,”strLogonType”,intLogonType)
errReturn = objReg.SetStringValue(HKEY_LOCAL_MACHINE,strKeyPath,”strComputerName”,strComputerName)

‘ ‘XP records logontype 11 first even if on the network so if logontype is 2 the reduce the counter by 1
‘ If (intLogonType = 2) Then
‘ strValueName = strUserName & “-BadLogonCount”
‘ errReturn = objReg.GetDWORDValue(HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intBadLogonCount)
‘ If errReturn <> 0 Then
‘ intBadLogonCount = 0
‘ Else
‘ intBadLogonCount = intBadLogonCount – 1
‘ objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intBadLogonCount
‘ End If
‘ End if

‘ End if
‘Write and increment bad logon counter if unlock or cached
‘If not unlock or cached logon quit
If (intLogonType = 7) Or (intLogonType = 11) Then
strValueName = strUserName & “-BadLogonCount”
errReturn = objReg.GetDWORDValue(HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intBadLogonCount)
If errReturn <> 0 Then
intBadLogonCount = 1
Else
intBadLogonCount = intBadLogonCount + 1
End If
objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intBadLogonCount

‘Read Account lockout threshold from RSOP
Set objWMIRSOPService = GetObject(”winmgmts:\\” & strComputer & “\root\rsop\computer”)
Set colItems = objWMIRSOPService.ExecQuery(”Select * from RSOP_SecuritySettingNumeric”)
For Each objItem in colItems
If objItem.KeyName = “LockoutBadCount” Then
intLockoutBadCount = objItem.Setting
End If
Next
Set ColItems = Nothing
Set objWMIRSOPService = Nothing

‘Write trigger if bad logon counter higher than threshold and purge user’s ticket cache
If (intBadLogonCount => intLockoutBadCount) Then
intLockout = 1
strValueName = strUserName & “-Lockout”
objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intLockout

Set objWMIService = GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\CIMV2″)
Set objProcess = objWMIService.Get(”win32_Process”)
Set colProcesses = objWMIService.ExecQuery(”SELECT * FROM Win32_Process WHERE Name LIKE ‘%CHIMAERA.EXE%’”)
if colProcesses.Count = 0 then
‘errReturn = objProcess.Create(path & “\HYDRA.EXE /DOMAIN=” & strLogonDomainName & _
‘ ” /USERID=” & strUserName, Null, Null, intProcessID)
’strValueName = strUserName & “-PopupErrReturn”
‘if errReturn = 1 then test = &H1 Else test = &H0
‘objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,test

errReturn = objProcess.Create(path & “\CHIMAERA.EXE /DOMAIN=” & strLogonDomainName & ” /USERID=” & strUserName, Null, Null, intProcessID)
strValueName = strUserName & “-PurgeTktsErrReturn”
if errReturn = 1 then test = &H1 Else test = &H0
objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,test
End if
Set objWMIService = Nothing
Set objProcess = Nothing

End If
Set objReg = Nothing
End if
WScript.Quit
End Sub

Sub TCPIPEvent
WScript.quit
End Sub

Sub AccountLockedEvent
On Error Resume Next
If InStr(UCase(TargetEvent.TargetInstance.Message),”NETWORK SERVICE”) > 0 Then WScript.Quit
If InStr(UCase(TargetEvent.TargetInstance.Message),”SYSTEM”) > 0 Then WScript.Quit
Dim objreg, arrMessage, arrUserName, objWMIService, objProcess, test
Dim strUserName, intLockout, errReturn, strValueName
Dim objStartup, objConfig

‘Parse message for required information
arrMessage = Split(TargetEvent.TargetInstance.Message,vbCrLf)
If IsArray(arrMessage) Then
arrUserName = Split(arrMessage(4),vbTab)

If IsArray(arrUserName) then

strUserName = arrUserName(2)
‘As domain account is locked delete user’s counter key
strKeyPath = strKeyPath & “\” & strUserName
Set objReg=GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” & strComputer & “\root\default:StdRegProv”)
errReturn = objReg.DeleteKey(HKEY_LOCAL_MACHINE,strKeyPath)
Set objReg = Nothing

‘Launch CHIMAERA.EXE and purge user’s ticket cache
Set objWMIService = GetObject(”winmgmts:{impersonationLevel=impersonate}!\\” _
& strComputer & “\root\CIMV2″)
Set objProcess = objWMIService.Get(”win32_Process”)
Set colProcesses = objWMIService.ExecQuery(”SELECT * FROM Win32_Process WHERE Name LIKE ‘%CHIMAERA.EXE%’”)
if colProcesses.Count = 0 then
errReturn = objProcess.Create(path & “\CHIMAERA.EXE /USERID=” & strUserName, Null, Null, intProcessID)
end if
Set objWMIService = Nothing
Set objProcess = Nothing

End If
Else
WScript.Quit
End If

Wscript.Quit

End Sub

‘ errReturn = objProcess.Create(”c:\windows\system32\rundll32.exe user32.dll, LockWorkStation”, Null, Null, intProcessID)
‘ strValueName = strUserName & “-LockErrReturn”
‘ if errReturn = 1 then test = &H1 Else test = &H0
‘ objReg.SetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,test

‘Dim i
‘For i = 0 To UBound(arrMessage)’ thing In arrMessage
‘If arrMessage <> “” then errReturn = objReg.SetDWORDValue(HKEY_LOCAL_MACHINE,strKeyPath,arrMessage(i),i)
‘errReturn = objReg.GetDWORDValue(HKEY_LOCAL_MACHINE,strKeyPath,strValueName,intBadLogonCount)
‘Next

Friday, June 19, 2009

Brian Hehir – Technologist

Brian Hehir is the lead technologist and principal consultant at HHS International Ltd, part of the BMA Data group a full service IT Consulting company based in New York and Hoboken. Brian enables engineered competitive advantage through structured systems design while developing reusable patterns based system tools for deployment, management and migration; always beating SLAs through his proven skills in complex problem solving.

Brian Hehir has been developing and working with Information Technology and computing systems for over 30 years, from Micros, Minis, Unix, Xenix to Novell, Linux and Windows. Brian’s core skills include engineering reusable bespoke enterprise tools and solutions, as well as augmenting third-party solutions, for large scale organizations with many tens of thousands of global users in a mixed operating environment. Brian has a proven ability to diagnose, isolate and resolve complex application and network issues, down to the packet level, in mission critical real time applications.

With over 15 years proven industry experience, 12 of which consulting to the world’s largest Investment Banks there is no project too complex or business critical, no technology too sophisticated, environment too exacting or invoice too large for Brian Hehir’s unique abilities and comprehensive experience.

Please review Brian’s resume for details of his experience. Email Brian Hehir: websitecontact@brianhehir.com

“One machine can do the work of 50 ordinary men. No machine can do the work of one extraordinary man."— Elbert Hubbard

Brian Hehir – Resume

View My Resume

Tuesday, June 2, 2009

Brian Hehir - Technologist

Thought I’d start a blog and randomly doodle down some stuff I find interesting or tricky…

Search Brian Hehir's sites

Loading