Quickest SharePoint Upgrade Ever

I returned to a client site yesterday to help resolve some post upgrade issues that had been identified during UAT. At the top of their list of issues was the fact that SharePoint had clearly not been upgraded properly!

What caused them to think this? Their upgraded out of the box 2007 homepage that had never been modified:

image

I edited the page, changed the references to ‘2007’ to read ‘2010’ and published the page. This made the users happy and we moved on to solve some real problems. Perception is fact in the eyes of users…

I couldn’t stop laughing about this all the way home that evening.

Update all quick launch links via PowerShell

As part of the process of upgrading SharePoint or perhaps simply moving web applications to a different URL, sometimes it is necessary to update multiple quick launch links that point to an old URL so they now resolve to a new URL.

These could be links to the ‘old’ SharePoint URL that have been manually created or links to an external system that has had a URL change. Either way, the script below will scan through the entire site and update any links that contain either of the ‘from’ URLs.

NB: SharePoint does a great job of updating ‘internal’ links but this does not stop users creating fully qualified links that contain an old URL nor can SharePoint automatically update links to external URLs

Add-PSSnapin Microsoft.SharePoint.PowerShell –erroraction SilentlyContinue

## urls to search for
$fromURL = "
https://from.your.url"
$fromURL2 = "http://from.another.url"

## the url to be used as a replacement
$toURL = "http://bc01"

foreach ($web in (Get-SPWebApplication $toURL | Get-SPSite -Limit All | Get-SPWeb -Limit All))
{
    ## scan the web quick launch urls
    Write-Host "Scanning " $web.Url

    $nodes = $web.Navigation.QuickLaunch
    foreach ($node in $nodes)
    {
        $nodeUrl = $node.Url
        if ($nodeUrl.ToString().ToLower().Contains($fromURL) -or $nodeUrl.ToString().ToLower().Contains($fromURL2))
        {
            Write-Host "     found link to: " -foreground gray -nonewline; Write-Host $nodeUrl -nonewline
           
            ## update the link
            $nodeUrl = $nodeUrl.ToString().ToLower().Replace($fromUrl,$toURL).Replace($fromUrl2,$toURL)
            $node.Url = $nodeUrl
            $node.Update()
            Write-Host " – done." -foreground green

        }
    }
    $web.Dispose()
}

I hope this helps

Generate a SharePoint upgrade report with PowerShell and Test-SPContentDatabase

When running Test-SPContentDatabase to check for upgrade issues before mounting a content database to a web application the output from this command can sometimes be a little overwhelming if there are lots of issues found:

image

 

…row after row of issues. Finding the important (blocking issues) in this list can sometimes be a time consuming process.

To help analyse the test results automatically the I’ve developed the PowerShell script (included below). This script parses the test results and shows a summary and highlights (or are they lowlights?) of the test results before optionally showing you the full results. I’ve found this speeds up my repetitive testing of content databases significantly and almost makes the process of finding, tracing and fixing deployment issues a pleasure!

Test Results Summary:

image

PS C:\> .\test_db.ps1 moss_content http://bc01:2007

Testing moss_content against http://bc01:2007…Done

Analysing results…Done

Test results for moss_content against http://bc01:2007

Upgrade Blocking Issues: 0
Upgrade non-Blocking Issues: 40

Issue Summary
————-
SiteOrphan : 1
MissingFeature : 12
MissingSetupFile : 11
MissingAssembly : 1
MissingWebPart : 15

Issue Details – SiteOrphan
————————-
Database [moss_content] contains a site (Id = [37b206a2-16f8-42b2-b307-e2a6be895fa2], Url = [/]) whose url is already u
sed by a different site, in database (Id = [c6afed7f-a591-4f17-b0e1-90fb2be7a6f6], name = [dummy_upgrade]), in the same
web application. Consider deleting one of the sites which have conflicting urls.

Issue Details – MissingFeature
————————-
Database [moss_content] has reference(s) to a missing feature: Id = [1d761b30-7eed-11db-9fe1-0800200c9a67].
Database [moss_content] has reference(s) to a missing feature: Id = [99bf9746-84d2-4672-aff3-1d677d714910].
…etc, etc

Would you like to see the full details?
[Y] yes  [N] no  [?] Help (default is "N"):

Answering ‘Yes’ the question above will display the full test results as per the Test-SPContentDatabase command.

The PowerShell script to create this report is below, simply save the PowerShell script to a file and execute it:

param ([String]$dbName, [String]$webApp)

## Load the SharePoint Snapin so the script can be executed from PowerShell editor
Add-PSSnapin Microsoft.SharePoint.PowerShell –erroraction SilentlyContinue

# get the database name if not specified
if ($dbName -eq "")
{
    Write-Host
    $dbName = Read-Host -Prompt " – Please enter the name of the database to be tested"
}
# get the web application name if not specified
if ($webApp -eq "")
{
    Write-Host
    $webApp = Read-Host -Prompt " – Please enter the url of the web application to be tested"
}
Write-Host

# test the database
Write-Host "Testing database $dbName against web application $webApp…" -nonewline
$results = Test-SPContentDatabase -name $dbName -WebApplication $webApp
Write-Host "Done" -Foreground Green
Write-Host

# analyse the results
Write-Host "Analysing results…" -nonewline
$listCategory = @{"Dummy"=0}; $listCategory.Clear()
$listBlocking = @{[string]$true=0;[string]$flase=0}
foreach ($result in $results)
{   
    #count the distinct categories
    if($listCategory.ContainsKey($result.Category))
    {
        #increment the counter
        $listCategory.Set_Item($result.Category,($listCategory.Get_Item($result.Category) +1))
    }
    else
    {
        #add the counter
        $listCategory.Add($result.Category,1)
    }
   
    #count the distinct blockings
    if($listBlocking.ContainsKey([string]$result.UpgradeBlocking ))
    {
        #increment the counter
        $listBlocking.Set_Item([string]$result.UpgradeBlocking ,($listBlocking.Get_Item([string]$result.UpgradeBlocking ) +1))
    }
    else
    {
        #add the counter
        $listBlocking.Add([string]$result.UpgradeBlocking ,1)
    }
}
Write-Host "Done" -Foreground Green
Write-Host

# now write the results
Write-Host "Test results for " -nonewline ; Write-Host $dbName -nonewline -foreground Yellow; write-host " against " -nonewline; write-host $webApp -foreground Yellow
Write-Host
Write-Host "Upgrade Blocking Issues: " -nonewline
if ($listBlocking.Get_Item([string]$true) -gt 0)
{
    Write-Host $listBlocking.Get_Item([string]$true) -foreground Red
}
else
{
    Write-Host $listBlocking.Get_Item([string]$true) -foreground Green
}
Write-Host "Upgrade non-Blocking Issues: " -nonewline
if ($listBlocking.Get_Item([string]$false) -gt 0)
{
    Write-Host $listBlocking.Get_Item([string]$false) -foreground Yellow
}
else
{
    Write-Host $listBlocking.Get_Item([string]$false) -foreground Green
}

# issue summary
Write-Host
write-host "Issue Summary"
write-host "————-"
foreach ($category in $listCategory.Keys)
{
    $v = $listCategory.Get_Item($category)
    write-host $category ": " -nonewline ; write-host $v -foreground Yellow
}

# issue details
foreach ($category in $listCategory.Keys)
{
    Write-Host
    write-host "Issue Details – $category"
    write-host "————————-"
    foreach ($result in $results)
    {   
        #count the distinct categories
        if($category -eq $result.Category)
        {
            $t = $result.Message
            $t
        }
    }
}

# shall we show full details
Write-Host
$yes = ([System.Management.Automation.Host.ChoiceDescription]"&yes")
$no = ([System.Management.Automation.Host.ChoiceDescription]"&no")
$selection = [System.Management.Automation.Host.ChoiceDescription[]] `
  ($yes,$no)
$answer = $host.ui.PromptForChoice(”,
  ‘Would you like to see the full details?’,$selection,1)

Write-Host
if ($answer -eq 0)
{
   $results

… enjoy

Updating InfoPath form templates and data connections with PowerShell

Occasionally when migrating content between SharePoint sites or URLs you may come across issues with InfoPath forms that have embedded data connections. These data connection can contain URLs that allow the forms to submit or save to SharePoint.

However, what happens if you want to update all of these embedded data connections contained in dozens or hundreds of InfoPath form templates for one reason or another, perhaps during a migration, upgrade or when you copy your production content to a test or dev farm?

The answer, like most things SharePoint 2010, lies with PowerShell. Below is a script I’ve written to perform the following actions:

  • Find all the form libraries in a web application
  • Download the form templates
  • Crack open the form template xsn (cab file)
  • Update any references to the old urls
  • Rebuild the form template xsn (cab file)
  • Upload the updated form templates to the form libraries

The above script generates output similar to that shown below:

image

The script:

Add-PSSnapin Microsoft.SharePoint.PowerShell –erroraction SilentlyContinue

## the web application to be searched for form libraries
$url = "
http://bc01:2007"

## temp working directory
$dir = "C:\Documents\SharePoint\Upgrade\Forms"

## urls to search for
$fromURL = "
http://bc01:2007/sites/New"
$fromURL2 = "http://xxxxxxxxx.tttttttt.wwwwwwww"

## the url to be used as a replacement
$toURL ="
http://bc01"

function Compress-Directory ($dir, $cabFileFullPathAndFilename)
{
    ## the cablib dll can be downloaded from
http://wspbuilder.codeplex.com
    [void][reflection.assembly]::LoadFile("C:\Documents\SharePoint\Upgrade\Forms\CabLib.dll")
    $c = new-object CabLib.Compress
    $c.CompressFolder($dir, $cabFileFullPathAndFilename, $null, $null, $null, 0)
    ## thanks to
http://www.pseale.com for this function
}

Write-Host "Scanning ‘$Url’ for form templates:"

foreach ($web in (Get-SPWebApplication $toURL | Get-SPSite -Limit All| Get-SPWeb -Limit All))
{
    foreach($list in $web.Lists)
    {
        if ($list.DocumentTemplateUrl -ne $null)
        {
            ## does this list have a form template attached to it
            if ($list.DocumentTemplateUrl.ToString().ToLower().EndsWith(".xsn"))
            {
                $listUrl = $list.ParentWebUrl + ‘/’ + $list.rootFolder
                $templateUrl = $list.ParentWebUrl + ‘/’ + $list.DocumentTemplateUrl
                Write-Host "    found – " -foreground gray -nonewline; Write-Host $listUrl 
               
## get the file
                $file = $web.GetFile($templateUrl)


                ## download the form template
                $filename = $file.Name
                $fileID = $file.UniqueId.Tostring()
                $localfile = $fileID + "\" + $filename
                Write-Host "            downloading – " -foreground gray -nonewline; Write-Host $templateUrl -nonewline; Write-Host " to " -foreground gray -nonewline; Write-Host $localfile -nonewline
                $file = $web.GetFile($templateUrl)
                $bytes = $file.OpenBinary();
                # Download the file to the path
                $localfile = $dir + "\" + $localfile
                New-Item "$dir\$fileID" -type directory -force | Out-Null
                New-Item "$dir\$fileID\Extracted" -type directory -force | Out-Null
                [System.IO.FileStream] $fs = new-object System.IO.FileStream($localfile, "OpenOrCreate")
                $fs.Write($bytes, 0 , $bytes.Length)
                $fs.Close()
                Write-Host " – done." -foreground green
           
                ## crack open the form template
                Write-Host "            extracting – " -foreground gray -nonewline; Write-Host $filename -nonewline; Write-Host " to " -foreground gray -nonewline; Write-Host "$fileID\Extracted" -nonewline
                EXPAND "$localfile" -F:* "$dir\$fileID\Extracted" | Out-Null
                Write-Host " – done." -foreground green
               
                ## update the data connections
                $extractedFiles = Get-ChildItem "$dir\$fileID\Extracted" *.x*
                foreach ($extractedFile in $extractedFiles)
                {
                    $efn = $extractedFile.Name
                    Write-Host "            checking – " -foreground gray -nonewline; Write-Host $efn -nonewline
                    $f = [system.io.file]::Open("$dir\$fileID\Extracted\$efn" ,"open","read","Read")
                    $sr = New-Object system.io.streamreader $f
                    $contentBefore = $sr.ReadToEnd()
                    $sr.close()
                    $f.close()
                    
                    ## now replace the urls
                    $contentAfter = $contentBefore.ToString().Replace("$fromURL","$toURL")
                    $contentAfter = $contentAfter.ToString().Replace("$fromURL2","$toURL")
                   
                    if ($contentBefore -eq $contentAfter)
                    {
                        Write-Host " – skipped." -foreground green
                    }
                    else
                    {                       
                        ## now write the file
                        $fs = [system.io.file]::Open("$dir\$fileID\Extracted\$efn" ,"create","write","Write")
                        $sw = New-Object system.io.streamwriter $fs
                        $sw.Write($contentAfter)
                        $sw.close()
                        $fs.close()

                        Write-Host " – updated." -foreground green
                    }
                }
               
               
                ## rebuild the cabinet
                Write-Host "            building cabinet – " -foreground gray -nonewline; Write-Host $localfile -nonewline
                Move-Item "$localfile" "$dir\$fileID\$filename.old" -force
                $extractedFiles = Get-ChildItem "$dir\$fileID\Extracted" *.*
                Compress-Directory "$dir\$fileID\Extracted" "$dir\$fileID\$filename"
                Write-Host " – done." -foreground green

                
                ## upload the form template
                Write-Host "            saving – " -foreground gray -nonewline; Write-Host $filename -nonewline; Write-Host " to " -foreground gray -nonewline; Write-Host $listUrl -nonewline
                $bytes=get-content -Encoding byte "$dir\$fileID\$filename"
                $bytes=[byte[]]$bytes
                $file.SaveBinary($bytes)
                Write-Host " – done." -foreground green
            }
        }
    }
    $web.Dispose()
}

I hope this helps someone else who is facing the same challenges…