Script-Based SharePoint 2007/2010 Site-Collection Backup - Part 1: The Script

With every new release of SharePoint I ask my Microsoft contacts if anything has changed in the area of backup management. The response is typically along the lines of "We have done some great things in this area and feel that our partners will be able to leverage these changes in their solutions." This means that we either have to make do with what we've got or shell out for a third party solution (which is not entirely unreasonable).

Hopefully you are doing backups of some sort. At the very least SQL Server database backups. But sometimes you might want something a little more granular. And if, for whatever reason, an investment in a commercial backup solution has not been made, you might want to consider some sort of automated script.

If that describes you, then the following may be of interest. Several years ago I put together a VBScript that will create backups for every site-collection in a web application, create an HTML log of the results which are then emailed or saved locally. Every time the script is run, the target share will be cleaned up so new backup files can be created. The assumption is that you have file system backups that can get these files to tape or disk for longer term storage.

Part 1 of this series of articles supplies you with that script and an example command line. Follow-up articles will come shortly where I will show you how I have used it in my own environments.

From a command prompt on one of the servers in your farm you would execute the script with the following options:

CScript BackupSiteCollections.vbs <Web Application> backuppath=<File Share UNC> from=<Email Address> notify=<Email Address> binpath=<Path To SharePoint BIN Folder> smtpserver=<Server Name, FQDN or IP Address>

So something a little more "real world" would look like this:

CScript BackupSiteCollections.vbs http://my.company.com backuppath=\\server\share from=admin@my.company.com notify=me@my.company.com binpath="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\BIN" smtpserver=smtp.company.com

And here is the script itself:

'================================================
' BackupSiteCollections.vbs
'------------------------------------------------
' For the automated backup of MOSS 2007/2010 site
' collections.  Includes emailed backup report 
' and resource utilization.
'================================================

Option Explicit
On Error Resume Next

Dim strErrorMessage, objShell, intShellExecStatus

strErrorMessage = ""
Set objShell = CreateObject("WScript.Shell")

Dim strLogFolder
strLogFolder = objShell.SpecialFolders("MyDocuments") & "\Site-Collection Backup Logs"

'======================================================
' Parameters and Defaults
'======================================================

Dim strBinPath, strBackupPath, strBaseURL, strNotification, strFromAddress, strSmtpServer

strBinPath      = ""
strBackupPath   = ".\"
strURL          = ""
strNotification = ""
strFromAddress  = ""
strSmtpServer   = ""

'======================================================
' Read Arguments
'======================================================

Dim objArguments
Set objArguments = WScript.Arguments

If Err.Number > 0 Then
  strErrorMessage = strErrorMessage & FormatErrorMessage( Err.Number, Err.Description & " [receiving arguments]")
  Err.Clear
End If

Dim intArgCount
intArgCount = objArguments.Count

Dim intCounter, strThisArgument, strThisOption, strThisValue

For intCounter = 0 to intArgCount - 1
  
  strThisArgument = LCase( objArguments( intCounter ) )
  
  If inStr( strThisArgument, "=" ) > 0 Then
    strThisOption = Trim(Left( strThisArgument, InStr( strThisArgument, "=" ) - 1 ))
    strThisValue = Trim(Mid( strThisArgument, InStr( strThisArgument, "=" ) + 1 ))
  Else
    strThisValue = Trim(strThisArgument)
  End If
  
  Select Case strThisOption
  Case "notify"
    strNotification = strThisValue
  Case "from"
    strFromAddress = strThisValue
  Case "baseurl"
    strBaseUrl = strThisValue
  Case "backuppath"
    strBackupPath = strThisValue
    If Right(strBackupPath,1) <> "\" Then
      strBackupPath = strBackupPath & "\"
    End If
  Case "binpath"
    strBinPath = strThisValue
  Case "smtpserver"
    strSmtpServer = strThisValue
  Case Else
    strBaseUrl = strThisValue
  End Select

Next

'======================================================
' Begins building response to be sent as an e-mail.
' Note that it's being formatted as HTML.
'======================================================

Dim datStart, strReportHeader, strReportBody, strReportFooter

datStart        = Now()
strReportHeader = ""
strReportBody   = ""
strReportFooter = ""

strReportHeader = "<!DOCTYPE HTML PUBLIC""-//IETF//DTD HTML//EN"">" & vbCrLf & _
  "<html><head>" & vbCrLf & _
  "<title>Site Collection Backup Report</title>" & vbCrLf & _
  "<style type=""text/css"">" & vbCrLf & _
    "  body {background-color:#ffffff;font-family:Arial;font-size:12px;}" & vbCrLf & _
    "  table {width:100%;border-collapse:collapse;margin:0;}" & vbCrLf & _
    "  td {padding:0;font-size:12px;}" & vbCrLf & _
    "  td.status {font-family:Consolas,Courier New;border:1px #c0c0c0 solid;background-color:#eee;color:#888;padding:5px;}" & vbCrLf & _
    "  .error {border:1px #800 solid;background-color:#ff0;color:#800;padding:4px;font-weight:bolder;}" & vbCrLf & _
    "  .log {border:1px #888 solid;}" & vbCrLf & _
    "  .log th {border:1px #888 solid;background-color:#888;font-weight:bolder;padding:4px;}" & vbCrLf & _
    "  .log td {border:1px #888 solid;font-size:10px;padding:4px;}" & vbCrLf & _
  "</style>" & vbCrLf & _
  "</head><body>"

strReportHeader = strReportHeader & "<h1>Site-Collection Backup</h1>" & vbCrLf & _
  "<table><tr><td class=""status"">" & vbCrLf & _
  "  <div>Starting Task at <em>" & CStr( datStart ) & "</em></div>" & vbCrLf & _
  "  <hr>" & vbCrLf & _
  "  <div>Base URL......: <em>" & strBaseURL & "</em></div>" & vbCrLf & _
  "  <div>Backup Path ..: <em>" & strBackupPath & "</em></div>" & vbCrLf & _
  "  <div>BIN path......: <em>" & strBinPath & "</em></div>" & vbCrLf & _
  "</td></tr><tr><td>" 

'================================================

Dim strBaseCommandLine

strBaseCommandLine = ""
If Trim(strBinPath) <> "" Then
  strBaseCommandLine = strBaseCommandLine & Trim(strBinPath)
  If Right(strBaseCommandLine,1) <> "\" Then
    strBaseCommandLine = strBaseCommandLine & "\"
  End If
End If
strBaseCommandLine = strBaseCommandLine & "stsadm.exe"

Dim objFileSystem, objFolder, objFiles, objFile, objExec, strResult, objXml, objSiteCollection, objURL, strURL, strFileName, strCommandLine

Set objFileSystem = CreateObject("Scripting.FileSystemObject")

WScript.Echo vbCrLf & "|==> Retrieving site collection list..."
WScript.Echo strBaseCommandLine & " -o enumsites -url " & strBaseURL
Set objExec = objShell.Exec(strBaseCommandLine & " -o enumsites -url " & strBaseURL)
Do While objExec.Status
  WScript.Sleep 250
Loop
strResult = objExec.StdOut.ReadAll
WScript.Echo strResult

If InStr( 1, LCase(strResult), "</sites>") = 0 Then
  strErrorMessage = strErrorMessage & "Unable to retrieve list of site collections from " & strBaseUrl
Else
  Set objFolder = objFileSystem.GetFolder(strBackupPath)
  Set objFiles = objFolder.Files

  WScript.Echo vbCrLf & "|==> Deleting old backup files..."
  For Each objFile in objFiles
    If LCase(Right(objFile.Name,9)) = ".scbackup" Then
      objFile.Delete(True)
      If Err.Number > 0 Then
        strErrorMessage = strErrorMessage & FormatErrorMessage( Err.Number, "[Cleanup] " & Err.Description & " [deleting old backup files]")
        Err.Clear
      End If
    End If
  Next

  WScript.Echo vbCrLf & "|==> Loading XML..."
  Set objXml = CreateObject("MSXML2.DOMDocument")
  objXml.LoadXML(strResult)

  WScript.Echo vbCrLf & "|==> Processing XML..."

  Dim datBackupStartTime
  
  strReportBody = strReportBody & "<table class=""log""><tr><th>Site</th><th>Size<sup>1</sup></th><th>Owner</th><th>Database</th><th>Time<sup>2</sup></th><th>Result</th></tr>" & vbCrLf 
  
  For Each objSiteCollection in objXml.DocumentElement.ChildNodes

      strUrl = objSiteCollection.Attributes.GetNamedItem("Url").Text
      strFileName = strBackupPath & Replace(Replace(Replace(Replace(strUrl, "http://", ""),"https://",""), "/", "_"),":", "-") & ".scbackup"
      strCommandLine = strBaseCommandLine & " -o backup -url """ + strUrl + """ -filename """ + strFileName + """"
      WScript.Echo vbCrLf & "|==> Backing up site collection " & strUrl 

      strReportBody = strReportBody & "<tr><td><a href=""" & strUrl & """>" & strUrl & "</a></td><td align=""right"">" & FormatNumber( objSiteCollection.Attributes.GetNamedItem("StorageUsedMB").Text, 2) & "</td><td>" & objSiteCollection.Attributes.GetNamedItem("Owner").Text & "</td><td>" & objSiteCollection.Attributes.GetNamedItem("ContentDatabase").Text & "</td>" & vbCrLf

      datBackupStartTime = Now()
      
      Set intShellExecStatus = objShell.Exec(strCommandLine)
      Do While intShellExecStatus.Status = 0
        WScript.Sleep 100
      Loop
      strResult = objExec.StdOut.ReadAll

      strReportBody = strReportBody & "<td align=""right"">" & FormatNumber( DateDiff( "s", datBackupStartTime, Now() )/60, 2 ) & "</td><td>" & strResult & "</td></tr>"

  Next
  
  strReportBody = strReportBody & "<tr><td colspan=""6""><sup>1</sup> Size shown in MB<br /><sup>2</sup> Time shown in minutes rounded to nearest hundredth</td></tr></table>"
  
End If

WScript.Echo vbCrLf & "|==> Backup of site collections completed!"

If strErrorMessage <> "" Then
  strReportBody = strReportBody & "<table><tr><td class=""error"">[ERROR MESSAGE COLLECTION]<ul>" & strErrorMessage & "</ul></td></tr></table>"
End If

strReportFooter = strReportFooter & "</td></tr><tr><td class=""status"">" & vbCrLf & _
  "Done in " & FormatNumber( DateDiff( "s", datStart, Now() )/60, 2 ) & " minutes" & vbCrLf & _
  "</td></tr></table>" & vbCrLf & _
  "</body></html>" 

'======================================================
'    Report the results of the operation. 
'======================================================
Dim strSendMailError
Dim bolMailSent
strSendMailError = ""
bolMailSent      = False

If strNotification <> "" And strFromAddress <> "" Then
  strSendMailError = SendMail( strNotification, strFromAddress, "[BackupSiteCollections] " & Replace(Replace(Replace( LCase( strBaseUrl ),"http://",""),"https://",""),"/","_") & " - " & Replace(Replace( CStr( datStart ),"/","-"),":","."), strReportHeader & strReportBody & strReportFooter, strSmtpServer )
  If strSendMailError = "" Then
    bolMailSent = True
  Else
    strReportBody = strReportBody & strSendMailError
  End If
End If

If Not bolMailSent Then
  Dim strLogName
  Dim objLogFile
  strLogName = strLogFolder & "\BackupSiteCollections [" & Replace( Replace( Replace( LCase( Replace(Replace(Replace(strBaseUrl,"http://",""),"https://",""),"/","_") ) & "] " & CStr( Year(datStart)) & Right( "0" & CStr( Month(datStart)), 2) & Right( "0" & CStr( Day(datStart)), 2), "/", "][" ), ":", "" ), "\", "][" ) + ".htm"
  If Not objFileSystem.FolderExists( strLogFolder ) Then
    objFileSystem.CreateFolder( strLogFolder )
  End If
  Set objLogFile = objFileSystem.CreateTextFile( strLogName, True )
  objLogFile.Write( strReportHeader & strReportBody & strReportFooter )
  objLogFile.Close
  Set objLogFile = Nothing
End If

'============================================================================
' FUNCTIONS
'============================================================================
'-------------------------------------------------------------
' SendMail
'-------------------------------------------------------------
Function SendMail( strTo, strFrom, strSubject, strBody, strSmtpServer )

  Dim strMailComponent
  strMailComponent = ""

  Dim strErrorMessage
  strErrorMessage = ""
  
  Dim objMail
  Set objMail = CreateObject( "CDO.Message" )
  
  If Err.Number > 0 Then
    strErrorMessage = strErrorMessage & FormatErrorMessage( Err.Number, "[SendMail] " & Err.Description & " [creating CDOSYS object]")
    Err.Clear
    
    Set objMail = CreateObject( "CDONTS.NewMail" )

    If Err.Number > 0 Then
      ' strErrorMessage = strErrorMessage & FormatErrorMessage( Err.Number, "[SendMail] " & Err.Description & " [creating CDONTS object]")
      Err.Clear
    Else
      strMailComponent = "CDONTS"
    End If
  Else
    strMailComponent = "CDOSYS"
  End If


  If strMailComponent <> "" Then

    objMail.To = strTo
    objMail.Subject = strSubject

    Select Case strMailComponent
    Case "CDONTS"
      objMail.From = strFrom
      objMail.Body = strBody
      objMail.BodyFormat = 0
      objMail.MailFormat = 0
    
      objMail.Send

      If Err.Number > 0 Then
        strErrorMessage = FormatErrorMessage( Err.Number, "[SendMail] " & Err.Description & " [sending mail]")
        Err.Clear
      End If
    Case "CDOSYS"
      objMail.Sender = strFrom
      objMail.HTMLBody = strBody
    
      If strSmtpServer <> "" Then
        objMail.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strSmtpServer
        objMail.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
        objMail.Configuration.Fields.Update
      End If

      objMail.Send
 
      If Err.Number > 0 Then
        strErrorMessage = FormatErrorMessage( Err.Number, "[SendMail] " & Err.Description & " [sending mail]")
        Err.Clear
      End If
    End Select

    Set objMail = Nothing

  End If

  SendMail = strErrorMessage

End Function

'-------------------------------------------------------------
' FormatErrorMessage
'-------------------------------------------------------------
Function FormatErrorMessage( intErrorNumber, strErrorDescription )
  FormatErrorMessage = "<li><em>ERROR:</em> (" & intErrorNumber & ") " & strErrorDescription & "</li>"
End Function

'======================================================
' EOF: BackupSiteCollections.vbs
'======================================================

I will follow up shortly with the rest of my process. Stay tuned!

A New Blog Perspective

The Blogger crew is testing some snazzy new blog templates. Check out the announcement here:

http://buzz.blogger.com/2011/03/fresh-new-perspectives-for-your-blog.html

If you would like to see these in action on Simianus Digitalus use the link below.

http://blog.thomasgehrke.com/view

How Edge-Lit LCD Monitors Work

Robert Heron (of DL.TV, HD Nation and Tekzilla fame) tweeted a link to a video where Bill Hammack (EngineerGuy) offers a very good explanation of how edge-lit LCD monitor work. If you are a display geek, it's five minutes well spent.

One Reason To Love The Netflix

Let's be realistic. Companies and services have problems. It is going to happen sooner or later. Obviously you don't want it to happen very often, but when it does the mark of a great service is how they deal with their customers.

Enter Netflix.


I have been a Netflix subscriber for a long time and have had incredibly few problems. Both with the shipment of discs and with the instant streaming service once they started doing that. So it never fails to impress me when I see them do something like sending me an email to apologize for a problem I never knew I had.

And it's not that I didn't stream any video yesterday, because I did. (To my iPad even!)

Not content with just a lip-service apology, they are willing to credit my account. Granted, 3% of $7.99 is not a huge amount, but I'm sure it adds up for them.

Well, thank you Netflix. I won't be cashing in this credit. Not only because I never noticed whatever problem you might have been having yesterday, but because you are worth every cent I'm paying you and more.

...

Um... Let's just forget the "and more" part.

Meerkat

Meerkat, originally uploaded by Thomas Gehrke.

I went to the Nashville Zoo and what did I see, but a meerkat or two.

They sure are not shy animals and since I was using my 70-300mm lens, it was very easy to get up close and personal. You can see how close we were by looking at the reflection in this guy's eye.

I'm also not sure if it was the recent repair work on my Canon 40D (maybe), my skills improving (doubtful) or my just getting lucky (probably) but I was pretty happy with how well focused my shots at the zoo were. That's unfortunate for you since that means you will get to see a lot more animal pictures. :)

What Is Different in the New PS3 Terms of Service (April 1, 2011)?

Like most people, I usually breeze by the Terms of Service (ToS) or End User License Agreement (EULA) without giving it much thought. But given the recent ... issues ... controversy ... whatever ... involving Sony, the PS3 and some intrepid hackers, I was a bit curious. Terms of Service change all the time, but how much and for what reasons?

So I grabbed a demo version of DeltaWalker for the Mac, found what I believe to be the old version of the ToS (version 8) and compared it to the new ToS (version 9). I created a PDF of the side-to-side comparison. It's not that pretty, but it is interesting.

The PDF can be found here.

Leave your thoughts in the comments area or hit me on Twitter. Especially if you know of any other diff tools for Mac OS X.

Fair To Compare? Rift Versus World of Warcraft

Rift just came out on March 1, 2011 and you are sure to see a lot of articles and commentary comparing it to the de facto king of MMO's, World of Warcraft. And with that will come a slew of comments that take one side over the other in this great debate over which is better. But is a comparison fair or even useful?

A comparison seems reasonable to me. Time and money are in limited supply for most people and when an investment in both of these are involved, it should make people stop and think.

If you were hoping for someone to tell you which is better, you probably need to look elsewhere. What I am about to do is just to share my thoughts on both games. I won't try to sway you one way or another. The hope is that my experiences might give you a starting point in determining what might work best for you.

System Requirements

The first question that people might have is whether or not they can use the hardware they already have or if they will have to invest some cash in upgrades.

World of Warcraft

There is no avoiding the fact that World of Warcraft is old. That is not necessarily a criticism. What this means is that it performs decently on all kinds of hardware. You don't need a supercharged machine to have an enjoyable experience.

The minimum requirements from Blizzard are:

Windows® System XP/XP64/Vista/Vista64/7** OS (with the latest Service Packs or updates):
  • Intel Pentium 4 1.3 GHz or AMD Athlon XP 1500+
  • 1 GB or more of RAM
  • NVIDIA® GeForce® FX or ATI Radeon™ 9500 video card or better
  • 25.0 GB available HD space
  • 4X DVD-ROM drive (Downloadable Installer also available)
  • Broadband Internet connection
  • Keyboard/mouse

Mac® OS X 10.5.8, 10.6.4, or newer:
  • Intel processor
  • 2 GB RAM or higher
  • 25.0 GB available HD space
  • 4X DVD-ROM Drive
  • Broadband Internet connection

That puts WoW within reach of a huge number of people. If you have a machine built in the past 5 years, you are good to go.

Rift

Rift is very new (obviously) and it targets more contemporary hardware. The minimum requirements from Trion are:

Windows XP, Vista or 7
  • Processor: Dual Core 2.0 GHz or better
  • Memory: 2 GB
  • Hard disk space: 15.0 GB available
  • Video: Nvidia GeForce FX 5900, ATI/AMD Radeon X300, Intel GMA X4500 or better.
  • Sound: DirectX 8.1 compliant card
  • DirectX®: 9.0c, June 2010 update
  • Broadband internet connection (DSL, cable modem or other high speed connection)

I have an older MacBook Pro and a Mac Pro. Being able to play from both is helpful. The notebook just meets Rift's requirements. Barely.

You can probably guess my other issue with Rift by looking at my hardware. There is no Mac OS X version which means I will have to Boot Camp in order to play. That is not a big deal for some people, but it's a pretty big barrier to entry for me. I hate having to shut everything I might be working on or reading in order to boot into Windows.


Visuals

World of Warcraft

Again, since WoW runs on an older engine the visuals are not all that impressive by today's standards. They appear a little cartoony sometimes. That said, I like the look. The folks at Blizzard do a great job with what they have to work with and every now and then things get a boost with updates to higher resolution textures or the addition of things like new water effects.

Here are a couple WoW shots:



Rift

Visually, Rift is gorgeous. I would expect nothing less from a brand new game. Sometimes I even get distracted while site seeing. Things are not perfect, though. While WoW might look a little dated it has the advantage of years of tweaks. Rift is version 1.0 (probably 1.01 by the time this is published) and you will occasionally run across oddities. These can be annoying and sometimes amusing. It just goes with the territory.

And a couple of Rift screenshots to compare against:



Sound and Music

This is going to be highly subjective obviously, but I felt it was worth noting.

World of Warcraft

WoW sounds kind of match the visuals. They are a little cartoony. But that is really what you want. Anything else would be jarring as your eyes register one thing and your ears another. I also heavily rely on sound to let me know what is going on and where it's going on.

Where Blizzard really shines is in the soundtrack area. The music in World of Warcraft is epic and the way it fades in and out based on where you are and what you are doing in the game is extremely well done.

Rift

In Rift the sounds also match the visuals. When you hit something it sounds like something has been hit and hit hard. The music is pretty good also, but whether it is because the game is new to me and I am concentrating on all kinds of other things or because I just haven't gotten that deep into the game yet, the music just has not had a big impact on me. It's good, just not great. Yet.

Sound in Rift does not seem to be positional. I tend to rely very much on audio cues and when I hear a wolf growling, I cannot tell where it is or how far away it is. It sounds like I'm riding the darn thing half the time. Maybe it's just me, though.

On the other hand, one really great sound cue is what you hear when a rift materializes. I have gotten in the habit of scanning the skies whenever I hear something like thunder rolling in the distance. It's a small thing, but it makes the world seem a much larger and more epic place.


Customization

World of Warcraft

There is an extremely healthy add-on community for WoW. Almost any feature or user interface change that you can imagine can be done. While that might be a slight exaggeration, you will find numerous ways to tailor the game to suit your playing style, whatever it might be.

Character customization, on the other hand, is fairly limited. There are several models per race and style, with various hair styles, colors, etc. It's a decent system.

Armor and clothing are even more locked in. Gear models will be reused in various places. Sometimes with differences in coloring and sometimes not. This is not necessarily a bad thing, however, as you are able to make judgements on an opponents (if PvP is your thing) level and ability simply by taking stock of what they are wearing.

Rift

As far as I know, there is no add-on community. I'm not even sure that there is any sort of API for developers to leverage. So what you get out of the box is pretty much it. (If I learn differently, I will update this post.)

Even with that, many of the things you need add-ons for in WoW come native in Rift. For instance, every component of the user interface is moveable. This is a big deal for me since I like faceplates closer to the center of the screen. I play on a 30" monitor most of the time and keeping as much critical information front and center is huge. Not only that, but the UI changes you make persist with the character. Which means when I log on to the notebook, the layout is the same. (Although to be fair, the difference resolutions between desktop and notebook do mean that some adjustments might need to be made.)


Conclusions

Sadly, I started this article during the public beta before the game was released. Between one thing and another, distractions of various sorts (including playing Rift) I have dragged this out way too long. The more I play, the more I find to talk about and the longer this article gets. I haven't even touched on Lore, community, public groups, quests, characters... you get the idea. But I will close this thing out and save some virtual ink for another day.

Unfortunately I really feel as if I have not met the goal I set for myself when I started which was to give fairly casual WoW players an idea of what they could expect from Rift and whether it was worth their time and money to try it out. My opinion is that if you like World of Warcraft, you will find a lot to like in Rift. It does many things as well or better and when you consider that this horse is just barely out of the gates, it bodes well for an exciting race ahead of us.

What was that noise? Oh, no! Another rift?!

Grab your gear and LET'S GO!


Throwing the eBook At Apple

I love Apple and their products, but come on, guys.

So here's the deal. I was curious to know whether there were any books on iOS development using Xcode 4 available now or sometime in the near future. So I go to Amazon and search for Xcode 4 and don't find much there. Not surprising given that Xcode 4 was only very recently released to the public.

Then it hits me. Why did I immediately go to Amazon when Apple has their own book store? I'm looking for a book on Apple development using Apple products. Feeling a bit silly, I switch over to iTunes, throw "Xcode 4" in the search box and... nothing.

Oh! I'm sure it's because it's too soon. Let me search for just "xcode". Aha! Um...


Nothing.

Wait. Can I do book searches from within iTunes? It sure seems like I should be able to. There is a section for Books in the results (empty as it might be). I can manage my books in iTunes (it's right there in the Library). I'll try it from the iPad!


Four books? Really?

Well let me go back to Amazon and do the same search there now. And I will only list Kindle books to be fair.


Forty-one (41) books. Ten times what the iBookstore offers. And Amazon is a little to a lot less expensive. Half of the Apple books have not even gotten enough reviews for them to offer to show a rating.

I have no idea who is to blame here. Maybe it's all on the publishers. Or maybe iBooks is just a hobby. All I know is that even with what amounts to a prime shopping spot in the mall of the Apple ecosystem, I'm walking right by the iBookstore, without even noticing that it's there. Instead I am going straight to the Amazon bookstore MegaMart. It may not be as pretty and polished and I might have to trade in any feelings of superiority I might have from shopping at such an ... exclusive ... establishment, but I can find what I want at the other store and the overall experience is far superior!

Step it up, Apple! If you're serious about this piece of your world (using "ecosystem" twice in one post would just be way too pretentious, in my opinion) then something has got to change soon.

Google Chrome Speaks In Tongues

I decided to roll back from a developer build to a stable release of Google Chrome today after having been forced to do a hard reset on my Windows 7 workstation for the umpteenth time when the browser decided it wanted all of the CPU to itself.

When I logged back in, I went to the main Chrome page and hit the download link. It downloads and starts to install and then...


What the... huh?!

Fortunately the link for help is in English and the information provided was clear. It wanted me to uninstall the developer build before installing a different one.

I knew that....

SharePoint 2010, User Profiles and Photos Stored In Active Directory

If you have pictures for your users stored in Active Directory and want to use them in SharePoint 2010's user profiles, you may have endured some amount of annoyance in getting from point A to point B. As great an end-user tool as Microsoft SharePoint is, administration of the environment can be incredibly frustrating. Which is probably great if you are a SharePoint consultant, but not so good when getting the infrastructure in place is just one of your many jobs in a corporate IT setting. Guess which of those describes me (hint: I'm not a consultant).

In SharePoint 2007, displaying photos with user profiles was pretty straightforward.  One common method was to throw a URL where the user's picture was being hosted into one of the extensionAttributes in Active Directory. You then mapped that to the SharePoint user profile's Picture property and voila! Piece of cake.


Now in SharePoint 2010 all of the old user profile stuff gets thrown out the window and instead they have bolted on Microsoft Forefront Identity Manager. I'm all for bulking up the underlying technology even if it adds a certain amount of complexity, but you need to provide similar levels of functionality. No such luck here. The Picture property in 2010 can only be linked to what appear to be binary AD attributes. This seems odd since the SharePoint property is a URL. And while properties can synch both ways now (SharePoint can be used to update Active Directory), the out-of-the-box behavior for Pictures is export only.

This is essentially what Microsoft is telling us that everyone should do:

  • Enable MySites for your enterprise.
  • Allow users to upload their own pictures.
  • These pictures are then resized into three different versions (small, medium and large) and stored in a centralize picture library in the My Sites site collection. (I do like this part.) The URL for the medium sized image is then what is stored in the Pictures property.
  • Allow SharePoint to update Active Directory by storing the image in a binary attribute like thumbnailPhoto.

And that is perfectly fine except for a few problems:

  • For good or ill, we are not interested in setting up an internal Facebook where people put any old picture up whether it is of them, their children or heaven only knows what.
  • We do not want to have to create, communicate and enforce policies simply to address what users are allowed to upload. Particularly items that might fall into that "heaven only knows" category.
  • To get the most useful employee directory for things like People Search, we need a majority of our user profiles to have pictures. The percentage of our users who will be able to figure this out and also take the time to do this is small.

I cannot imagine that this sits well with many Enterprise IT organizations. While seemingly minor, this sidelined my company's move from SharePoint 2007 to 2010 simply because I do not like taking away functionality from my customers.

Life got better with the October 2010 Cumulative Update where we finally got the ability to import into the Picture property.

Then life got worse when they pulled the update back.

A month later, life got better when the update was re-released.

Then life got worse again when the December 2010 Cumulative Update came out and you were no longer able to create a Synchronization Target to an Active Directory when SharePoint's NetBiosDomainNamesEnabled setting was set to "True".

Hope was revived once more when we heard that the February 2011 Cumulative Update fixed this. And once it actually did come out in March 2011, everyone cheered (or at least I did).

Even after all of that, another "gotcha" is that after the picture has been imported, it will not automatically show up on user profile pages, search results and other user listings. As mentioned previously, SharePoint will take the image and create three different sized versions that it stores in a Picture Library of your MySite site-collection. Except this only happens when the following PowerShell command is run:
Update-SPProfilePhotoStore -CreateThumbnailsForImportedPhotos 1 -MySiteHostLocation <URL of MySite web application>
My understanding is that if profile pictures get added or changed after you run this command that they will not be added to the picture library until this command is run again. In other words, there is no completely automated way to make this all work out of the box. (Someone please tell me that I'm wrong.)

But that's enough grousing. You probably want to know how to get this done. Assuming that you already have the User Profile Synchronization service running, you will need to do the following:

  • Install the February 2011 Cumulative Update for SharePoint 2010 (link can be found here).
  • Create an Import property mapping between the Picture property and the attribute in AD where your user picture is being stored (e.g. thumbnailPhoto).
  • Create a PowerShell script (name it something like "CreateUserProfileThumbnails.ps1") and put it somewhere on your SharePoint application server. I typically create a volume for general file storage and I have a "Scripts" folder specifically for things like this. Your script should contain the following two lines (remember to change paths as appropriate for your environment):
    &'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\POWERSHELL\Registration\sharepoint.ps1'
    Update-SPProfilePhotoStore -CreateThumbnailsForImportedPhotos 1 -MySiteHostLocation <URL of MySite web application>
  • In Task Scheduler, create a new task that runs as the SharePoint Farm account on whatever schedule you feel is appropriate (once a day works for me). Make sure it is set to "run whether user is logged on or not". The following shows the entries for the program/script and the command line arguments:

    Program/Script:
    %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe
    Arguments:
    -NonInteractive -NoProfile -File F:\Scripts\CreateUserProfileThumbnails.ps1
This should allow you to handle more important tasks, secure in the knowledge that your users' photographs are being automatically updated in all of these systems.

Feel free to comment below if you have problems and I'll do my best to point you in the right direction. And don't be shy to chime in if I have missed something and there is a better way. I would like to provide the best possible solution(s).

You Got Your iTunes In My Outlook!

Seen when installing iTunes on a new Windows 7 desktop:


This surprised me for some reason.

Anyone know what iTunes/QuickTime wants to do with my Outlook?

Update: Tim points out in the comments that this is likely for Calendar and Contact syncing. Thank you, Tim!

iOS 4.3 Home Sharing Is Caring

To be honest, I did not think much of what I had seen and heard on the latest iOS update (4.3). I am in the Apple iOS Developer program and had been running one of the earlier betas for quite some time and never felt compelled to keep up. I did, however, go ahead and update to the final release tonight.

After going through my usual post-update dive into the settings to see what was up, I enabled Home Sharing.

I knew what Home Sharing was. Basically, it allows you to connect to an iTunes library running on a Mac or PC and stream that content to your iDevice. It sounds so simple. But the first time you actually do it... it is an amazing feeling to see all of your music and all of your video seemingly available to you on this tiny little device.


Granted, I have a 32GB iPhone 4 and that fits quite a bit of stuff. I still find myself having to manage what goes on the phone if I know I might not be local to my iTunes. Home Sharing does not necessarily change much here and I'm sure that some of you are pointing out the fatal flaw: this all has to happen on the same WiFi network. While I will admit that it would be nice to have all of my media stored somewhere and be accessible from my devices wherever I may be, this is a great first step.

You know how Netflix talks about relatively small numbers of people who stream movies to iPhones and iPads? Yeah... well I'm that guy. I spend quite a bit of time around the house doing all kinds of house things and I watch a lot of Netflix and various podcasts on my phone and tablet. In the morning I sync up the phone (which takes forever) in order to get the latest podcasts to watch while I'm getting ready for work and making breakfast. I no longer have to do that. It's saving me time and space because I am not constantly shuffling things around. So maybe Home Sharing does change how I manage my 32GB of storage after all.

I read some articles today that said that the rules for iOS Home Sharing were the same as with OSX. The implication in a few cases seemed to be that each device would count against your activation limit. This did not really seem to make much sense to me and it does not appear that this is the case. If I find out differently, I will post an update.

Another interesting item of note. I have been bitten a couple of times when I have purchased some HD video content through iTunes, only to be stonewalled by the HDCP on my 30" Dell display.


My options were to get the standard definition version or to watch it on the 15" MacBook Pro. Well those videos play just fine on my iPhone. I guess it's possible that I am getting some reduced resolution, but I am frankly surprised that it worked at all. Regardless, it looks pretty good on the Retina display.

From Marvel's Astonishing X-Men motion comic

At least it's another option until we can finally do away with this HDCP nonsense.

If Home Sharing seems like something you would find useful, don't put the update off.

Foxy Database Nostalgia


In the past month there have been several occasions where legacy FoxPro databases and applications have been topics of discussion at work. That got me thinking about the good ol' days and made me a little misty-eyed. I started my IT career in the early 1990's as a FoxPro developer. It was something I just kind of picked up with the help of a great mentor (thanks, Randy!).

This software was ahead of its time in many ways. It was a file-based DBMS with blazing lookup speeds thanks to Rushmore optimization and sported a full development environment with a screen builder and report builder that allowed you to create and distribute powerful standalone information systems. At one point, you could develop solutions that would run on DOS, Windows, Mac and Unix. In the early 90's! Remarkable.

I actually still have the first application I ever wrote. It is in a 3-ring binder with detailed documentation including screen diagrams (no screenshots back then!) and two 3.5" floppies as the installation media. The labels on the disks are dated April 5, 1993. If I can find a machine with a floppy drive in it, I just might try installing it later.

Related Links

Blog Updates

This is just a quick note to apologize for the website weirdness (more than the usual) the past week or so. It's been a regular comedy of errors as I have tried to make adjustments which then required more adjustments until I found myself so far down a path that I could not find my way back.

What you see now is what I cobbled together in the past 24 hours in an attempt to punch through to at least a  reasonable starting point. Feel free to comment on what you like or don't like. I will be making adjustments and am more than willing to accommodate requests wherever I can.

So hang with me. I actually have a couple of articles in the works. Stay tuned.