+menu-


  • Installing Exchange 2007 Mgmt tools with PowerShell CTP’s

    When you try to install Exchange 2007 mgmt tools on a machine running anything else than PowerShell V1 (yah yah I know, shouldn’t run “beta” software in production, but I want my remoting).
    So I guessed it was checking some registry value, did a bit of digging and found it it was looking for this :

    “HKLM:\SOFTWARE\Microsoft\PowerShell\1″ PID “89383-100-0001260-04309″

    So using PowerShell’s Set-item it looks something like this

    Set-Item “HKLM:\SOFTWARE\Microsoft\PowerShell\1″ PID “89383-100-0001260-04309″


  • Get Exchange rates

    I stumbled on a post about getting Exchange rates from a bank, from an online XML file.. So I thought I would try that with a local bank, I figured out the Danish national bank, has an XML file online with the current rates…

    #Get Exchange Rates from The Danish National Bank
    $wc = New-Object System.Net.WebClient
    $rates = 1$wc.DownloadString(“http://www.nationalbanken.dk/dndk/valuta.nsf/valuta.xml”)
    $MonCode = @{Name=”ValutaKode”;Expression = {$_.Code}}
    $Description = @{Name=”Valuta”;Expression = {“100 ” + $_.desc + ” = “}}
    $Kurs = @{Name=”Kurs”;Expression = {$_.Rate + ” KR”}}
    $rates.exchangerates.dailyrates.currency | Select-Object $MonCode,$Description,$Kurs | ft -AutoSize

    Another option would be to use Out-Gridview as well, then you would get the result in a GUI form, instead of in the command prompt


  • Create Excel Spreadsheet

    The other day I was tasked with creating a spreadsheet containing a list of all servers in our network, manager wanted a “sheet” for each computer, and an “index” sheet with links to all the other sheets.

    Since we have more than 150 servers, there was no way I was going to create this list by hand… So I wrote a quick and dirty little Powershell function.

    Function Fill-Excel {
    BEGIN {
    $xl = new-object -comobject excel.application
    $xl.Visible = $true
    #I specify an existing filename, since creating the link in Excel requires a filename
    $wb = $xl.Workbooks.Open(“C:\Temp\test.xlsx”)
    $ws = $wb.Worksheets.Item(1)
    $xl.ActiveSheet.Name = “List”
    $row = 1
    }

    Process {
    $_
    $q = $_
    $z = $q.ToUpper()
    $xl.Sheets.Add()
    $xl.ActiveSheet.Name = $z
    $wh = $xl.ActiveSheet
    $wh.Cells.Item(1,1) = “=HYPERLINK(`”[test.xlsx]List!A1`”;`”Back To List`”)”
    $ws.Cells.Item($row,1) = “=HYPERLINK(`”[test.xlsx]$z!A1`”;`”$z`”)”
    $row++
    }
    }

    $s= “Computer1″,”CompPUTER2″,”comPUTer3″
    $s | Fill-Excel

    This will open the xslx filed called test.xlsx under c:\temp and create an index sheet called “list”, and a sheet for each object that is passed to the function, it also creates a Link in A1 that refers back to the index sheet. On the Index sheet a link to each new sheet is created.

    In the above example I “manually” pipe in some text string, but it could also be something like this:

    #
    #
    $s = Get-QADComputer | Where {$_.OSname -match “Server”} | select name

    So since it is a function, you can pass virtually any string to it, and it will populate the excel sheet for you.


  • Arrived safely in Washington Dulles airport

    I landed in Dulles on time, or actually earlier that we were supposed to…

    I am meeting up with Wes, who is flying in from California, we are sharing a ride down to Virgina, unfortunately his flight was delayed, so I have to wait 4 hours here.. It is almost midnight now (local Danish time), and he isn’t landing for another 3 hours… Best of all I only slept 3½ hours last night… So I can barely stay awake (Glad I am not driving tonight )

    We will make a stopover in Richmond, so we don’t have to drive all the way down to VA tonight, about 400 km, then we will drive down to VA tomorrow around noon..

    Tomorrow there is a meeting between me and the rest of the team behind this years event, to get all last minute issues settled… Really looking forward to this years event, stay tuned for more post from the most exiting conference in 2009..


  • Deleting empty folders in Outlook

    A friend of mine called me, and asked me for help… He had had a migration go wrong and had ended up with a bunch of .pst files containing 1000′s of emtpy folders. He had started cleaning up the .pst’s manually, after having spent several hours on one .pst file he decided on another approach, and this is where I came in.

    He asked if it was possible to write a script that would delete all the empty folders for him, I said to him that I bet it is possible, just not sure if I can do it, I have never really “scripted” Outlook before..

    So I started looking for ways to manipulate data in Outlook and found out that the Outlook.Application COM object allowed me to manipulate folders and messages within Outlook.. Did some quick testing, told my friend that it should be possible, and that I would get back to him…

    After some mucking around here is what I ended up with.

    $olApp = new-object -com Outlook.Application
    $namespace = $olApp.GetNamespace("MAPI")
    #Root folder to match, look in your outlook and look at what folders you have.
    #RooFolders could be Mailbox - <username>, Public folders, or other mapped in PST's or mailboxes.
    $RootfolderToMatch = "Inbox"
    
    $Global:StopValue = 1
    
    Function ListEmpty {
    Param ($infolder)
      Foreach ($fldr in $infolder.Folders) {
    
        If ($fldr.Items.Count -eq 0 -and $fldr.Folders.Count -eq 0) {
        
    	  # I have had to check for system folders, these cannot be deleted via the script, so to prevent the script for erroring out, I check for it.
        if ($fldr.name -match "Slettet" -or $fldr.name -match "Deleted" -or $fldr.name -match "journal" -or $fldr.name -match "rss"`
    	    -or $fldr.name -match "note" -or $fldr.name -match "kalender" -or $fldr.name -match "kontakt" -or $fldr.name -match "udbakke" `
    		-or $fldr.name -match "Sync" -or $fldr.name -match "Server" -or $fldr.name -match "contacts" -or $fldr.name -match "Outbox" ) 
          {"Cannot delete systemfolder :  """ + $fldr.FolderPath + ""
    	  
    	  }
    	  
    
        Else {
          if ($fldr.Parent.name -notmatch "slettet" -and $fldr.Parent.name -notmatch "Deleted" ) {
    	  #Remove # from the below line, to actually delete Outlook folders.
          #$fldr.delete()
          $fldr.FolderPath + " has been deleted!!"
          
    	  $Global:StopValue = 0
    	  }
             }
     }
     
        Else {
          if ($fldr.name.length -gt 0) {
      
           ListEmpty($fldr)}
    	   }
      	    
     }
    
    }
    
    
    $RootFolders = $namespace.Folders  | ?{$_.name -match $RootfolderToMatch}
    
    Listempty($RootFolders)
    
    While ($Global:StopValue -eq 0) {
    $Global:StopValue = 1
    Listempty($RootFolders)
    }
    
    

    I start by initializing the Outlook COM object, and declaring some variables

    $olApp = new-object -com Outlook.Application
    $namespace = $olApp.GetNamespace("MAPI")
    #Root folder to match, look in your outlook and look at what folders you have.
    #RooFolders could be Mailbox - </username><username>, Public folders, or other mapped in PST's or mailboxes.
    $RootfolderToMatch = "Inbox"
    
    $Global:StopValue = 1
    

    I then create a function that takes an Outlook folder as a parameter.
    I then loop through each subfolder, and checks to see if each folder contains any files or other folders, if it finds a folder that does not contain any files/folders it then checks to see if the folder name is something like “outbox, sent items, Journal etc etc.” The reason I do this is because these are system folders, and cannot be deleted, so if the script tried to delete one of them, it would come back with an error.

    So if the folder that is currently being checked is empty and is not a system folder it will be deleted.(Actually the line where the folder is deleted is commented out, just to make sure there is not accidental deletions) The script then recurses through all subfolders to check for empty folders.

    
    Function ListEmpty {
    Param ($infolder)
      Foreach ($fldr in $infolder.Folders) {
    
        If ($fldr.Items.Count -eq 0 -and $fldr.Folders.Count -eq 0) {
        
    	  # I have had to check for system folders, these cannot be deleted via the script, so to prevent the script for erroring out, I check for it.
        if ($fldr.name -match "Slettet" -or $fldr.name -match "Deleted" -or $fldr.name -match "journal" -or $fldr.name -match "rss"`
    	    -or $fldr.name -match "note" -or $fldr.name -match "kalender" -or $fldr.name -match "kontakt" -or $fldr.name -match "udbakke" `
    		-or $fldr.name -match "Sync" -or $fldr.name -match "Server" -or $fldr.name -match "contacts" -or $fldr.name -match "Outbox" ) 
          {"Cannot delete systemfolder :  """ + $fldr.FolderPath + ""
    	  
    	  }
    	  
    
        Else {
          if ($fldr.Parent.name -notmatch "slettet" -and $fldr.Parent.name -notmatch "Deleted" ) {
    	  #Remove # from the below line, to actually delete Outlook folders.
          #$fldr.delete()
          $fldr.FolderPath + " has been deleted!!"
          
    	  $Global:StopValue = 0
    	  }
             }
     }
     
        Else {
          if ($fldr.name.length -gt 0) {
      
           ListEmpty($fldr)}
    	   }
      	    
     }
    
    }
    
    [/PS]
    
    
    Rootfolders is the initial "path"/folder that the scripts starts to look for empty folders from.
    $namespace.folders is the highest folder level in Outlook, it contains the user mailbox, public folders and any mapped in .pst files, so what I do here is match a single top level folder.
    
    Here is an example, lets say I have opened a .pst file called  BusinessCases09, I would set $RootfolderToMatch equal to <em>BusinessCases09</em> (This is set at the beginning of the script), that means when the ListEmpty function is called for the first time, it will scan through all subfolders of the .pst file.
    1
    
    $RootFolders = $namespace.Folders  | ?{$_.name -match $RootfolderToMatch}
    
    Listempty($RootFolders)
    
    

    In order to make sure the script runs to all empty folders is deleted, I have set up a while loop to call function until no more empty folders are found.

    While ($Global:StopValue -eq 0) {
    $Global:StopValue = 1
    Listempty($RootFolders)
    

  • Get-Mailboxinfo Powershell script

    My CEO asked me for a report about mailbox usage in the company I work for, so I thought this has to be a job for powershell.

    So I sat down and tried to figure out how to get info from Exchange, I know there is a WMI class that I can query about information about the different mailboxes, so I thought I would give it a try to write a script to output things like Number of items in each mailbox, the size, deleted size etc.
    I stumbled on a script by Michael B. Smith, who had made a similar script, and really done some nice work on the output of the script, so I decided not to reinvent the wheel and use his formatting.

    # Get-MailBoxSize.ps1
    # Claus T N 2009
    # This script connects to an Exchange server(s) and returns all mailbox sizes
    # number of items, plus a summary.
    # All formatting is “stolen” from a script by Michael B. Smith

    $day = Get-Date -UFormat “%Y%m%d”
    #File to output results to
    $OutPutFile = “c:\scripts\MailBoxInfo-$day.txt”

    [System.Int64]$script:totalSize = 0
    [System.Int64]$script:totalItems = 0
    [System.Int64]$script:totalDeleted = 0
    [System.Int64]$script:totalMailboxes = 0

    # Gets data through WMI from specified Exchange mailbox servers
    $ExchangeServers = “dksrv01″

    Function Get-MailboxInfo {
    foreach ($computer in $ExchangeServers) {
    $Mailboxes = Get-Wmiobject -namespace root\MicrosoftExchangeV2 -class Exchange_Mailbox -computer $computer | sort-object Size `

    Foreach ($mailbox in $mailboxes){
    If ($mailbox.MailBoxDisplayName -like “SMTP (*” -or $mailbox.MailBoxDisplayName -like “SystemMailbox*”){
    }
    else {
    [string]$str = ([string]$Mailbox.MailboxDisplayname).PadRight(40)
    $str += ([System.Int64]$Mailbox.Size).ToString(“N0″).PadLeft(12)
    $str += ” KB”
    $str += ([System.Int32]$MailBox.TotalItems).ToString(“N0″).PadLeft(9)
    $str += ” ”
    $str += ([System.Int64]$Mailbox.DeletedMessageSizeExtended).ToString(“N0″).PadLeft(12)
    $str += ” KB”
    $str | Out-File $OutPutFile -append
    #”———————————————————————————-” | Out-File $OutPutFile -append

    $script:totalSize += ([System.Int64]$mailbox.Size)
    $script:totalItems += ([System.Int32]$mailbox.TotalItems)
    $script:totalDeleted += ([System.Int64]$mailbox.DeletedMessageSizeExtended)
    $script:totalMailboxes += 1
    }
    }
    }
    }

    “Mailbox name Mailbox size Item count Deleted size” | Out-File $OutPutFile -append
    “==================================================================================” | Out-File $OutPutFile -append
    Get-MailBoxInfo

    “==================================================================================” | Out-File $OutPutFile -append
    $string = ($script:totalMailboxes.ToString(“N0″) + ” mailboxes”).PadRight(40)
    $string += $script:totalSize.ToString(“N0″).PadLeft(12) + ” KB”
    $string += $script:totalItems.ToString(“N0″).PadLeft(9) + ” ”
    $string += $script:totalDeleted.ToString(“N0″).PadLeft(12) + ” KB”
    $string | Out-File $OutPutFile -append
    “==================================================================================” | Out-File $OutPutFile -append


  • New Powershell brushes for Syntax Highlighter plus

    Since I once in a while post powershell scripts on my site, I like that fact that I can have them color highlighted for better readability, so I played around with several different syntax highlighters, but none of them was able to do powershell, so I created a brush for Syntax highligther plus.

    This is my initial attempt, updated to work with Syntax Highlighter plus 2
    [Sourcecode lang='posh']
    #
    # Author: Claus T Nielsen
    # http://www.xipher.dk
    #
    # This program is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, version 3 of the License.
    #
    # This program is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this program. If not, see .

    SyntaxHighlighter.Brushes.PoSh = function()
    {
    var builtins = ‘ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem ‘ +
    ‘Disable-PSBreakpoint Disable-QADUser Disconnect-EventListener Disconnect-QADService Enable-PSBreakpoint Enable-QADUser Export-Alias Export-Clixml Export-Console ‘ +
    ‘Get-Content Get-Credential Get-Culture Get-Date Get-Event Get-EventBinding Get-EventLog Get-ExecutionPolicy Get-Help ‘ +
    ‘Get-PSJob Get-PSProvider Get-PSSnapin Get-QADComputer Get-QADGroup Get-QADGroupMember Get-QADObject Get-QADPasswordSettingsObject Get-QADPSSnapinSettings ‘ +
    ‘Get-Runspace Get-Service Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object Import-Alias ‘ +
    ‘Import-Csv Import-LocalizedData Invoke-Expression Invoke-History Invoke-Item Invoke-WmiMethod Join-Path Measure-Command Measure-Object ‘ +
    ‘Move-ItemProperty Move-QADObject New-Alias New-Event New-Item New-ItemProperty New-Object New-PSBreakpoint New-PSDrive ‘ +
    ‘New-QADObject New-QADPasswordSettingsObject New-QADUser New-Runspace New-Service New-TimeSpan New-Variable Out-Default Out-File ‘ +
    ‘Out-Host Out-Null Out-Printer Out-String Pop-Location Push-Location Read-Host Receive-PSJob Remove-Item ‘ +
    ‘Remove-PSBreakpoint Remove-PSDrive Remove-PSJob Remove-PSSnapin Remove-QADGroupMember Remove-QADObject Remove-QADPasswordSettingsObjectAppliesTo Remove-Runspace Remove-Variable ‘ +
    ‘Rename-Item Rename-ItemProperty Rename-QADObject Resolve-Path Restart-Service Resume-Service Select-Object Select-String Set-Acl ‘ +
    ‘Set-AuthenticodeSignature Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug Set-QADGroup ‘ +
    ‘Set-QADPSSnapinSettings Set-QADUser Set-Service Set-TraceSource Set-Variable Set-WmiInstance Sort-Object Split-Path Start-KeyHandler ‘ +
    ‘Start-Service Start-Sleep Start-Transcript Step-Into Step-Out Step-Over Stop-KeyHandler Stop-Process Stop-PSJob ‘ +
    ‘Stop-Transcript Suspend-Service Tee-Object Test-Path Trace-Command Unlock-QADUser Update-FormatData Update-TypeData Wait-PSJob ‘ +
    ‘Write-Debug Write-Error Write-Host Write-Output Write-Progress Write-Verbose Write-Warning ‘ +
    ‘suspend test times trap type typeset ulimit umask unalias unset wait’;

    var keywords = ‘case do done elif elseif esac fi for function if in select then ‘ +
    ‘-band -bor -bnot -match -notmatch -like -notlike -eq -ne -gt -ge -lt -le -is ‘ +
    ‘-imatch -inotmatch -ilike -inotlike -ieq -ine -igt -ige -ilt -ile ‘ +
    ‘time until while filter foreach not ft sort’;

    this.regexList = [
    /* comments */
    {regex: SyntaxHighlighter.RegexLib.SingleLinePerlComments, css: 'comment'},
    /* text qualifiers */
    {regex: SyntaxHighlighter.RegexLib.DoubleQuotedString, css: 'string'},
    {regex: SyntaxHighlighter.RegexLib.SingleQuotedString, css: 'string'},
    /* deliminators */
    {regex: new RegExp('[()[\\]{}]’, ‘g’), css: ‘delim’},
    /* variables */
    {regex: new RegExp(‘\\$\\w+’, ‘g’), css: ‘vars’},
    {regex: new RegExp(‘\\w+=’, ‘g’), css: ‘vars’},
    /* flags */
    {regex: new RegExp(‘\\s-\\w+’, ‘g’), css: ‘test’},
    /* builtins */
    {regex: new RegExp(this.GetKeywords(builtins), ‘igm’), css: ‘builtin’},
    /* keywords */
    {regex: new RegExp(this.GetKeywords(keywords), ‘igm’), css: ‘keyword’}
    ];

    };

    SyntaxHighlighter.Brushes.PoSh.prototype = new SyntaxHighlighter.Highlighter();
    SyntaxHighlighter.Brushes.PoSh.Aliases = ['PoSh', 'ps'];

    [/sourcecode]

    I modified a version made by Joel Bennet to work with version 2 as well.

    /*Updated to v2 by Claus T Nielsen (Xipher.dk */
    /* Powershell 1.0 syntax contributed by Joel Bennett */
    SyntaxHighlighter.brushes.PowerShell = function()
    {
    var keywords = ‘while until trap switch return ref process param in if global: function foreach for finally filter end elseif else do default continue break begin \\? % #script #private #local #global’;

    var operators = ‘and as band bnot bor bxor casesensitive ccontains ceq cge cgt cle clike clt cmatch cne cnotcontains cnotlike cnotmatch contains creplace eq exact f file ge gt icontains ieq ige igt ile ilike ilt imatch ine inotcontains inotlike inotmatch ireplace is isnot le like lt match ne not notcontains notlike notmatch or regex replace wildcard’;

    var verbs = ‘Write Wrap Width Where Whatif Warning Wait Version Verify Verbose Value UserName URL Update Unlock Unique Uninstall TypeName TrustLevel Trusted Truncate Trace TimeStamp TimeOut TID Test Temp Tee Suspend Strict Stop Statistic State Start Split Speed SortBy Sort Size SID Shortname Set Send Select Scope SaveCred Role Retry Resume Restore Restart Resolve Resize Repair Rename Remove Regex Recurse Receive Reason ReadOnly Read Push Property Prompt Privilege Priority PrinterName PrincipalName PortName Pop Ping Password PassThru ParentID Parameter Owner Overwrite Output Out Operation Notify NewLine New Name Move Most Modified Minimum Migrate Merge Measure Maximum MacName LogName Log Lock Location LineCount Limit KeyLength KeyContainerName KeyAlgorithm Join JobName IpAddress Invoke Interval InterfaceName Interactive Install Insert Input Initialize Incremental Include Import Ignore Id Group Get From Format ForEach Force Follow Filter FileName Fast Export Exclude Exact EventName ErrorLimit ErrorLevel Erase Encrypt Encoding Enable Elapsed DriveName Drain DomainName Disconnect Disable DirectoryName Description Descending Delete Default Debug Data Verbs CSPType CSPName Created Create Count Copy ConvertTo ConvertFrom Convert Continuous Connect Confirm ComputerName Compress Compatible Compare Command ClusterName Clear ClassName Checkpoint Char CertUsage CertSubjectName CertStoreLocation CertSerialNumber CertRequestFileName CertIssuerName CertFile CaseSensitive Cache BlockCount Binary Before AttributeName AssemblyName Ascending As ApplicationName Allocation All After Add ACL Accessed’;

    this.regexList = [
    { regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'color4' }, // one line comments
    { regex: new RegExp('@"\\n[\\s\\S]*?\\n”@’, ‘gm’), css: ‘string’ }, // double quoted here-strings
    { regex: new RegExp(“@’\\n[\\s\\S]*?\\n’@”, ‘gm’), css: ‘string’ }, // single quoted here-strings
    { regex: new RegExp(‘”(?:\\$\\([^\\)]*\\)|[^"]|`”)*[^`]“‘,’g'), css: ‘string’ }, // double quoted strings
    { regex: new RegExp(“‘[^']*’”, ‘g’), css: ‘string’ }, // single quoted strings
    { regex: new RegExp(‘(?:’+verbs.replace(/ /g, ‘\\b|\\b’)+’)-[a-zA-Z_][a-zA-Z0-9_]*’, ‘gmi’), css: ‘cmdlet’ }, // cmdlets
    { regex: new RegExp(‘\\[[A-Z_\\[][A-Z0-9_. ,\\[\\]]*\\]*’, ‘gi’), css: ‘color2′ }, // .Net [Type]s
    { regex: new RegExp(‘\\$(?:(?:global|script|private|env):)?[A-Z0-9_]+’, ‘gi’), css: ‘variable’ },// $variables
    { regex: new RegExp(this.getKeywords(keywords), ‘gmi’), css: ‘keyword’ }, // keywords
    { regex: new RegExp(‘-’+this.getKeywords(operators), ‘gmi’), css: ‘constants’ }, // operators
    { regex: new RegExp(‘\\s+-[a-zA-Z_][a-zA-Z0-9_]*’, ‘gmi’), css: ‘color1′ } // operators
    ]

    };

    SyntaxHighlighter.brushes.PowerShell.prototype = new SyntaxHighlighter.Highlighter();
    SyntaxHighlighter.brushes.PowerShell.aliases = ['powershell', 'posh', 'ps'];

    Be aware that compared to version 1 of the brushes you can no longer have specific css settings in the brush (previously you could define font/colors for different datatypes/keywords in the brush), this has been changed inversion 2 because of a new theme options.


  • Set file permissions with Set-ACL

    One of the guys at the minasi forum had a problem with setting permissions on multiple .ini files (Had to add the Deny setting to read for a certain group), he joked that I was probably able to write a powershell script to do it.. So I took the challenge, and wrote this little script.

    This script will lrecurse through the InitialFolder and all of its subfolders for Desktop.Ini files, it will then loop through each of the files, and add the DENY setting on the read permission for the local administrators group.

    $InitialFolder = “c:\scripts\test\”

    $Files = Get-ChildItem $InitialFolder -include desktop.ini -recurse -force

    Foreach ($File in $Files) {
    $colRights = [System.Security.AccessControl.FileSystemRights]“Read”

    $InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::None
    $PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None

    $objType =[System.Security.AccessControl.AccessControlType]::DENY

    $objUser = New-Object System.Security.Principal.NTAccount(“Administrators”)

    $objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($objUser, $colRights, $InheritanceFlag, $PropagationFlag,$objType)

    $objACL = Get-ACL $File
    $objACL.AddAccessRule($objACE)

    Set-ACL $file $objACL
    }


  • AArrggghhh

    This is going to be a short post, I fractured at least one bone in my hand playing basketball the other night, after sitting 6 hours in the emergency room until 4.30 in the morning, they decided that since at least one bone was fractured both vertically and horisontally I would need a cast. So the next 3 weeks it is back to the one handed 4 finger system. *sigh*