Publishing and Consuming SharePoint Service Application with PowerShell

Below are my PowerShell scripts for publishing and consuming service applications between SharePoint farms (Service Application Federation). Before I introduce the scripts let be briefly explain the process involved.

The are several reasons why you might want to share service applications between farms and I’m not planning on going into those details here but the overall process of setting up service application federation is illustrated below:

image

The SharePoint farm on the left is the consuming farm. It will use the service applications from the SharePoint farm on the right – the publishing farm. Practically this means that the publishing farm contains your service instances, service applications and service application proxies and your consuming farm contains just service application proxies that ‘point’ to the publishing farm. Loads more details on service application federation is available on technet including which service applications can be federated: http://technet.microsoft.com/en-us/library/ff621100.aspx

The scripts I have follow the pattern shown in the diagram above. First on the consuming farm, I export the farm root certificate, the security token service certificate and I write the farm ID to a text file:

Contents of 1_Consumer_ExportCerts.ps1 – run this on the CONSUMING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# export consumer root certificate
Write-Host "Exporting Consumer Root Certificate…" -nonewline
$rootCert = (Get-SPCertificateAuthority).RootCertificate
$rootCert.Export("Cert") | Set-Content ConsumingFarmRoot.cer -Encoding byte
Write-Host "Done" -Foreground Green

# export consumer sts certificate
Write-Host "Exporting Consumer STS Certificate…" -nonewline
$stsCert = (Get-SPSecurityTokenServiceConfig).LocalLoginProvider.SigningCertificate
$stsCert.Export("Cert") | Set-Content ConsumingFarmSTS.cer -Encoding byte
Write-Host "Done" -Foreground Green

# export consumer farm id
Write-Host "Exporting Consumer Farm Id…" -nonewline
$farmID = Get-SPFarm | select Id
set-content -path ConsumerFarmID.txt $farmID
Write-Host "Done" -Foreground Green

Write-Host "Now copy ConsumingFarmRoot.cer, ConsumingFarmSTS.cer & ConsumerFarmID.txt to the publishing farm." -Foreground Yellow

You’ll notice this script ends with a prompt to guide you to the next step – copy the consuming farm certificate, consuming sts certificate and the consuming farm id txt file we’ve just created to the publishing farm. Next, switch over to the publishing farm.

Contents of 2_Publisher_ExportCerts.ps1 – run this on the PUBLISHING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# export publisher root certificate
Write-Host "Exporting Publisher Root Certificate…" -nonewline
$rootCert = (Get-SPCertificateAuthority).RootCertificate
$rootCert.Export("Cert") | Set-Content PublishingFarmRoot.cer -Encoding byte
Write-Host "Done" -Foreground Green

Write-Host "Exporting Publisher Topology Url…" -nonewline
$topologyUrl = Get-SPTopologyServiceApplication | Select LoadBalancerUrl
$url = $topologyUrl.LoadBalancerUrl.OriginalString
Set-Content -path PublishingFarm.Url.txt -Value $url
Write-Host "Done" -Foreground Green

write-host
Write-Host "Now copy PublishingFarmRoot.cer & PublishingFarm.Url.txt to the consuming farm." -Foreground Yellow
write-host

Now copy the publishing farm certificate and publishing farm url txt file that have just been generated to the consuming farm. Now switch back to the consuming farm.

Contents of 3_Consumer_ImportCerts.ps1 – run this on the CONSUMING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# import publisher root certificate
Write-Host "Importing Publisher Root Certificate…" -nonewline
$trustCert = Get-PfxCertificate PublishingFarmRoot.cer
New-SPTrustedRootAuthority PublishingFarm -Certificate $trustCert
Write-Host "Done" -Foreground Green

Write-Host "Now import certificates on the publishing farm." -Foreground Yellow

You’ve now imported the publishing farm root certificate into the consuming farm, Next switch back to the publishing farm and import the consuming farm certificate and consuming sts certificate into the publishing farm with the following PowerShell:

Contents of 4_Publisher_ImportCerts.ps1 – run this on the PUBLISHING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# import consumer root certificate
Write-Host "Importing Consumer Root Certificate…" -nonewline
$trustCert = Get-PfxCertificate ConsumingFarmRoot.cer
New-SPTrustedRootAuthority ConsumingFarm -Certificate $trustCert
Write-Host "Done" -Foreground Green

# import consumer sts certificate
Write-Host "Importing Consumer STS Certificate…" -nonewline
$stsCert = Get-PfxCertificate ConsumingFarmSTS.cer
New-SPTrustedServiceTokenIssuer ConsumingFarm -Certificate $stsCert
Write-Host "Done" -Foreground Green

Write-Host "Now set permissions for application discovery on the publishing farm." -Foreground Yellow

Note: At this point I would recommend you access Central Admin on both the CONSUMING and PUBLISHING farms and verify that the trusts are in place as expected. To do this, from central admin select Security > Manage Trust.

Once you have verified your trusts are in place you’re ready to start sharing the service applications between farms. Now switch to the publishing farm.

Contents of 5_Publisher_SetPermissions.ps1– run this on the PUBLISHING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# get consumer farm id
Write-Host "Reading Consumer Farm ID…" -nonewline
$consumerId = Get-Content -path ConsumerFarmID.txt
$consumerId = $consumerId.Replace("@{Id=","").Replace("}","")
Write-Host "Done" -Foreground Green

# set application discovery permissions
Write-Host "Set Application Discovery Permissions…" -nonewline
$security=Get-SPTopologyServiceApplication | Get-SPServiceApplicationSecurity
$claimprovider=(Get-SPClaimProvider System).ClaimProvider
$principal=New-SPClaimsPrincipal -ClaimType "http://schemas.microsoft.com/sharepoint/2009/08/claims/farmid" -ClaimProvider $claimprovider -ClaimValue $consumerId
Grant-SPObjectSecurity -Identity $security -Principal $principal -Rights "Full Control"
Get-SPTopologyServiceApplication | Set-SPServiceApplicationSecurity -ObjectSecurity $security
Write-Host "Done" -Foreground Green

# list the available service applications and prompt for one to be selected
$serviceAppList = @{"0"="DummyServiceApp"}
$serviceApps = Get-SPServiceApplication
$count = 1
$serviceWarning = ""
Write-Host
Write-Host "The following service applications are available for publishing:"
foreach ($serviceApp in $serviceApps)
{
    # ensure only service applications that can be shared are listed
    $type = $serviceApp.TypeName
    $serviceSharable = 0

    Switch ($type)
    {
        ("Business Data Connectivity Service Application") {$serviceSharable = 1}
           ("Managed Metadata Service")                       {$serviceSharable = 1}
           ("User Profile Service Application")               {$serviceSharable = 1}
           ("Search Service Application")                     {$serviceSharable = 1}
           ("Secure Store Service Application")               {$serviceSharable = 1}
           ("Web Analytics Service Application")              {$serviceSharable = 1}
           ("Microsoft SharePoint Foundation Subscription Settings Service Application") {$serviceSharable = 1}
    }
    if ($serviceSharable -gt 0)
    {
        $serviceAppList.Add("$count",$serviceApp.Id)
        Write-host "$count. " -nonewline -foregroundcolor White
        Write-host $serviceApp.DisplayName -foregroundcolor gray
        $count++
    }

}
Write-Host
$serviceAppNum = Read-Host -Prompt " – Please enter the id of the service application to be shared"
Write-Host
Write-Host "Getting Service Application…" -nonewline
$serviceAppId = $serviceAppList.Get_Item($serviceAppNum)
$serviceApp = Get-SPServiceApplication $serviceAppId
Write-Host "Done" -Foreground Green

# warn about domain trusts
$serviceWarning = ""
$type = $serviceApp.TypeName
Switch ($type)
{
    ("Business Data Connectivity Service Application") {Write-Host; Write-Host "Note: Publishing domain must trust Consuming domain." -Foreground Yellow; Write-Host;}
       ("User Profile Service Application") {Write-Host; Write-Host "Note: A two-way trust must exist between the Publishing and Consuming domains." -Foreground Yellow; Write-Host;}
       ("Secure Store Service Application") {Write-Host; Write-Host "Note: Publishing domain must trust Consuming domain." -Foreground Yellow; Write-Host;}
}

# list the service rights for the specified service application
write-host
$rightsList = @{"0"="DummyServiceApp"}
$count = 1
$serviceAppSecurity = Get-SPServiceApplicationSecurity $serviceApp
foreach ($right in $serviceAppSecurity.NamedAccessRights)
{
        $rightsList.Add("$count",$right.Name)
        Write-host "$count. " -nonewline -foregroundcolor White
        Write-host $right.Name -foregroundcolor gray
        $count++
}
write-host
$serviceAppRight = Read-Host -Prompt " – Please enter the right to be granted"
$serviceAppRight = $rightsList.Get_Item($serviceAppRight)

Write-Host "Granting ‘$serviceAppright’ to service application…" -nonewline
$security=Get-SPServiceApplication $serviceApp| Get-SPServiceApplicationSecurity
$claimprovider=(Get-SPClaimProvider System).ClaimProvider

if ($type -eq "User Profile Service Application")
{
    $consumFarmAcc= Read-Host -Prompt " – Please enter the consuming farm account e.g. DOMAIN\account"
    $principal=New-SPClaimsPrincipal -Identity $consumFarmAcc -IdentityType WindowsSamAccountName   
}
else
{
    $principal=New-SPClaimsPrincipal -ClaimType "http://schemas.microsoft.com/sharepoint/2009/08/claims/farmid" -ClaimProvider $claimprovider -ClaimValue $consumerId
}
Grant-SPObjectSecurity -Identity $security -Principal $principal -Rights $serviceAppRight
Set-SPServiceApplicationSecurity $serviceApp -ObjectSecurity $security
Write-Host "Done" -Foreground Green

Write-Host "Publishing service application…" -nonewline
Publish-SPServiceApplication -Identity $serviceApp
Write-Host "Done" -Foreground Green

$lbUrl = Get-SPserviceApplication $serviceApp | Select Uri
Set-Content -path $serviceAppId -Value $lbUrl
$cleanUrl = Get-Content -path $serviceAppId
del $serviceAppId
$cleanUrl = $cleanUrl.Replace("@{Uri=","").Replace("}","")

write-Host
Write-Host "Now connect to the service application from the consumer farm with the following url:" -Foreground Yellow
write-Host
Write-Host $cleanUrl -Foreground Yellow

It’s a whopper but basically this script lists all the available service applications on the PUBLISHING farm and allows you to publish these to the CONSUMING farm whilst choosing the service application specific permissions to grant to the consuming farm:

image

Simply enter the the number for the service application you wish to publish and then the number associated with the permissions you wish to grant to the consuming farm.

Now, switch to the consuming farm.

Contents of 6_Consumer_ConnectService.ps1 – run this on the CONSUMING farm

Add-PSSnapIn "Microsoft.SharePoint.PowerShell" -EA 0

# get url from user
Write-Host
Write-Host "Reading topology service url…" -nonewline
$topologyUrlShort = get-content -path PublishingFarm.Url.txt
Write-Host "Done" -Foreground Green
Write-Host

#get available published services:
Write-Host "Connecting to topology service $topologyUrlShort…" -nonewline
$publishedServices = Receive-SPServiceApplicationConnectionInfo -FarmUrl $topologyUrlShort
Write-Host "Done" -Foreground Green
Write-Host

# list the published services
Write-Host "The following service applications are available for consumption:"
$serviceAppList = @{"0"="DummyServiceApp"}
$count = 1
foreach ($publishedService in $publishedServices)
{
    Write-host "$count. " -nonewline -foregroundcolor White
    Write-host $publishedService.DisplayName -foregroundcolor gray
        $serviceAppList.Add("$count",$publishedService.Uri)
    $count++

}

Write-Host
$serviceAppNum = Read-Host -Prompt " – Please enter the id of the service application to be consumed"

Write-Host
$serviceAppProxyName= Read-Host -Prompt " – Please enter the service application proxy name"
Write-Host

#get the selected published service app
$count = 1
foreach ($publishedService in $publishedServices)
{
    if ($count.ToString() -eq $serviceAppNum )
    {

        #we’ve found our service application – let go create it based on the type
        $type = $publishedService.SupportingProxy
        $serviceUrl =  $serviceAppList.Get_Item($serviceAppNum)
       
        Switch ($type)
        {
            ("BdcServiceApplicationProxy"){
                    Write-Host "Creating new Business Data Connectivity Service Application Proxy…" -nonewline
                    New-SPBusinessDataCatalogServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
            ("MetadataWebServiceApplicationProxy"){
                    Write-Host "Creating new Managed Metadata Service Proxy…" -nonewline
                    New-SPMetadataServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
            ("UserProfileApplicationProxy"){
                    Write-Host "User Profile Service Application Proxy…" -nonewline
                    New-SPProfileServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
            ("SearchServiceApplicationProxy"){
                    Write-Host "Search Service Application Proxy…" -nonewline
                    New-SPEnterpriseSearchServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
            ("SecureStoreServiceApplicationProxy"){
                    Write-Host "Secure Store Service Application Proxy…" -nonewline
                    New-SPSecureStoreServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
            ("WebAnalyticsServiceApplicationProxy"){
                    Write-Host "Web Analytics Service Application Proxy…" -nonewline
                    New-SPWebAnalyticsServiceApplicationProxy -Uri "$serviceUrl" -Name "$serviceAppProxyName"
                }
        }
        Write-Host "Complete." -Foreground Yellow

    }
    $count++

}

This final script connects to the topology service of the publishing farm and lists all the published services applications. Simply select the number of the service application you wish to consume

image

This has saved me loads of time in the past and has proved to be very reliable. However, please note the following points:

  • The script assumes the files copied between the farms are copied to the same location as the PowerShell scripts.
  • If you want to consume partitioned service applications you’ll need to update the final script to include the –PartitionMode switch (or the –Partitioned switch in the case of the New-SPEnterpriseSearchServiceApplicationProxy cmdlet)

I hope this helps…

Upload Multiple Documents to SharePoint Library

Here’s my script to upload all the documents found in a particular file system folder to a SharePoint document library. You can specify the destination URL and local directory path as parameters to the script or simple enter them at runtime.

WARN16 WARNING: This script first deletes the library if it already exists – please remove this step if this is not what you want. It’s there in my script so I can rebuild my environment rapidly after ever demo…

param ([String]$spPath, [String]$localPath)

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

# get the SharePoint path if not specified
if ($spPath -eq "")
{
    Write-Host
    $spPath = Read-Host -Prompt " – Please enter the fully qualified url to the destination document library"
}
# get the local path if not specified
if ($localPath -eq "")
{
    Write-Host
    $localPath = Read-Host -Prompt " – Please enter the full path to the local directory that contains the documents to be uploaded"
}
Write-Host

## Define our library name and web url
$LibraryName = $spPath.Substring($spPath.LastIndexOf("/")+1)
$webUrl = $spPath.Substring(0,$spPath.LastIndexOf("/"))

## Define a function to return the destination file name
Function BuildFileName($filename)
{
    "$webUrl/$LibraryName/" + $(split-path -leaf $filename)
}

## Delete our library – by name
$OpenWeb = Get-SPWeb $webUrl
$OpenList = $OpenWeb.Lists[$LibraryName]
if ($OpenList -ne $null)
{
    Write-Host "Deleting $LibraryName Library…."
    $OpenList.Delete()
    $OpenWeb.Update()
}

## Create our library
Write-Host "Creating new $LibraryName Library…."
$OpenWeb.Lists.Add($LibraryName, "A SharePoint document library created via PowerShell", 101) | Out-Null
$NewList = $OpenWeb.Lists[$LibraryName]
$NewList.OnQuickLaunch = 1
$NewList.Update()
$OpenWeb.Update()

## Tidy up
$OpenWeb.Dispose()

## Upload all the documents found in the specified location
Write-Host "Uploading all documents from $sourceDocs"
$webClient = New-Object System.Net.WebClient
$webClient.Credentials = [System.Net.CredentialCache]::DefaultCredentials
dir $localPath | % {
    Write-Host "Uploading: $_" -nonewline
    $fName = BuildFileName $_
    $webClient.UploadFile($fName,"PUT", $_.FullName)
    Write-Host " done" -ForegroundColor Green
    }

Create Random or Demo SharePoint Content with PowerShell

One of the things we need to a lot whilst creating SharePoint solutions or demonstrating its capabilities is to work with example data. However, creating reams of sample data can be quite laborious to say the least!

That’s where PowerShell comes in… We can rapidly create dozens of sample items from a very simple script. Best of all – the scripts can be used to create the document libraries or lists that hold the sample data. With a little bit of effort, the scripts can be used to prepopulate Libraries and Lists throughout an entire site collection.

The script example below is used to create two SharePoint Calendars and populate each with a dozen or so events. As we use this script in demonstrations, we’ve set it up to create the events within a 30 day window of the current date. That way, when the calendar is viewed, you’ll always see some events in the current month and some in the preceding and subsequent months.

Here’s what the script creates, two calendars populated with events:

image

Note the two calendars in the Quick Launch – Team Calendar and My Calendar – this is what we’ll be creating.

The Script

If you’re not familiar with PowerShell a great article on getting started can be found here: http://www.somewhere.com

The script example used in this article can be downloaded here: Create Calendars Script

First of all, the script is started with the Add-PSSnapin Microsoft.SharePoint.PowerShell command. This loads the SharePoint PowerShell cmdlets and allows the script to be executed directly from the PowerShell Editor.

image

Without this the PowerShell editor with throw the following error as it tries to execute SharePoint commands:

The term ‘Get-SPWeb’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Documents\SP2010\Create Calendars.ps1:42 char:21
+ $OpenWeb = Get-SPWeb <<<< $WebUrl
+ CategoryInfo : ObjectNotFound: (Get-SPWeb:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Now we’ve loaded the SharePoint cmdlets, the next thing we do is create a random number object; this will be used latter when we insert items into the list.

image

Next, the script sets up a list of titles to be used for the events, add as many as you want; I’ve added 26 separate items:

image

Finally, having created all our event titles, we connect to SharePoint (line 42) and delete the list defined by the $LibraryName variable if it already exists (line 43). You may not wish to do this in your scripts, but for us in a demonstration environment it’s nice to be able to drop and recreate the required lists simply by executing the same script again. Additionally, by leaving the list delete in place (line 43) you can see the speed that PowerShell runs at but simple re-running the script over and over again. 😉

image

Having deleted any existing ‘Team Calendar’ list we simply recreate it with the following;

image

Line 47 adds the new list to the opened web and uses list template number 106 (calendar list). For a complete list of available template numbers, see: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splisttemplatetype.aspx. Once added, line 48 opens the list we’ve just added and line 49 ensures the list is displayed on the Quick Launch bar.

Now we’ve successfully added our new list, it’s time to insert some random items. This is done very simply by the following code:

image

Line 53 sets up our loop, and in our example loads the first 13 events only (the other 13 are loaded into a second calendar list).

The title for our event is taken from the list of event titles we defined at the start of the script. The start date for the event (EventDate) is created by taking the current date from the Get-Date function and then adding a random number of days to it (line 57). In this example, we add events anywhere from 30 days prior to 30 days subsequent from the current date. The end date for the event is calculated by adding a random number of minutes to the start date (line 59). Here we are assuming each event we add in anywhere between 30 and 90 minutes in duration. Finally, line 60 saves our new item to the calendar list.

Because our script is setup to create two calendars so we can demonstrate Calendar Overlays, I’ve skipped over lines 62 to 87 as this is just a repeat of what we’ve seen above. The only difference with the second iteration of list operations is that when we run the same commands again we change the $LibraryName to ‘My Calendar’ and there is no need to re-open the web again:

image

Finally, we need to dispose of the SPWeb object we opened earlier (line 42) to release its resources; this is done simply by calling its Dispose method:

image

The complete script is available for download here: Create Calendar Script. Feel free to modify and use it to meet you own requirements and enjoy not having to manually create sample data ever again!

I hope this helps…

Update ‘System Account’ user information on a stand alone SharePoint installation

If you’ve ever installed SharePoint in a stand alone SharePoint configuration, e.g. on a development machine on Windows 7, you’ll have noticed the user properties drop down by default shows ‘System Account’ as the current logged on user:

image

Although this does not cause any problems with the operation of SharePoint in a stand-alone install, when demonstrating SharePoint this can sometimes require explanation to the audience why System Account is displayed. It is actually displayed because the current user is logged on with the account used by the SharePoint app pool.

To change the display name show on the user properties drop down to something more friendly is easy with PowerShell:

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

## get the default web app
$web = Get-SPWeb http://bc01

## get the user info list
$l = $web.Lists | where { $_.Title -eq "User Information List" }

## get the system account item
$i = $l.Items | where { $_["Account"] -eq "SHAREPOINT\system" }

## update to a friendly name
$i["Title"] = "Brian Cartmel"

## update e-mail address
$i["Work e-mail"] = "your.email@your.domain.com"

## update the item
$i.Update()

This results in the above being shown…

image

…and the following being updated in the into the (hidden) User Information list:

image

I hope this helps.

Wait for a Solution to deploy with PowerShell – like execadmsvcjobs used to…

With previous versions of SharePoint is was usually possible to wait for a solution deployment to execute by running the STSADM –o execadmsvcjobs command.

With SharePoint 2010 and PowerShell, things can be done a little differently. The script snippet below can be used to wait for a Solution deployment to complete before the script continues and usefully shows deployment progress whilst waiting…

## Get the solution
$Solution = Get-SPSolution -identity $SolutionFileName

$lastStatus = ""

## loop whilst there is a Job attached to the Solution
while ($Solution.JobExists -eq $True)
{
    $currentStatus = $Solution.JobStatus
    if ($currentStatus -ne $lastStatus)
    {
            Write-Host "$currentStatus…" -foreground green -nonewline
            $lastStatus = $currentStatus
    }
    Write-Host "." -foreground green -nonewline
    sleep 1
}
## completed
Write-Host ""
Write-Host "     " $Solution.LastOperationDetails -foreground green

…the above can be used as the equivalent to the old stsadm –o execadmsvcjobs command.

I hope this helps…

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&quot;

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