Use Find/Replace in PowerShell to fix InfoPath Forms after moving to a new Site Collection


I know I know, that’s a really long title.

I found some pretty good PowerShell how-to’s which described how to use Get-ChildItem to iterate through a folder – checking each item for a string and then using -replace to fix the string.

However, my situation was slightly different as I wanted to walk through a SharePoint 2010 Form Library – fixing InfoPath forms which had been moved into a new site collection and thus did not open in the browser and would not save due to the old reference to the XSN.

I’m sure there may be better methods on moving XSNs and XMLs across site collections where maybe this wouldn’t be necessary, but if you’re reading this I’ll assume you don’t care.

In any case, what I ended up doing (after obviously re-publishing the XSN template to the new site collection so new IP forms wouold work) is:

  1. Copy all of the forms using Windows Explorer (we didn’t care about versions or modified by metadata, just the content type metadata) into the new Form Lib.
  2. Run the following PowerShell code to fix each XML file – replacing the link to the old XSN with a link to the new XSN (Note: I ran this on my desktop, NOT on the server as I didn’t need any SP cmdlets):
$files = Get-ChildItem \\sitecollection\davwwwroot\FormLib -Filter "*.xml"
foreach ($file in $files) {
(Get-Content $file.fullname) | ForEach-Object {$_ -replace "http://sitecollection/library/OLDXSNPATH.xsn", "http://newsitecollection/library/NEWXSNPATH.xsn"} | Set-Content $file.fullname
}

Voila, all IP forms are now fixed and will not only open in the browser as expected – but I can now update these forms in the new location as they now point to the correct XSN template.

If you’re a frequent reader you’re probably thinking “Wow, normally he gives us these nice functions with parameters, help, etc.”… You’re right, this time it was a quick and dirty chunk of code. However, chances are I’ll need to do this again – so I did actually write a function for future needs:

function Reset-InfoPathTemplateLink {
Param(
[string]$FilePath,
[string]$FileExtension,
[string]$OldPath,
[string]$NewPath
)
$files = Get-ChildItem $FilePath -Filter $FileExtension
	foreach ($file in $files) {
		(Get-Content $file.fullname) |
		ForEach-Object {$_ -replace $OldPath,$NewPath} |
		Set-Content $file.fullname
	} #end foreach
} #end function

To run the function, first dot-source it and then use the following syntax:

Reset-InfoPathTemplateLink -FilePath "\\sitecollection\subsite\formlib" -FileExtension "*.xml" -OldPath "http://sitecollection/doclib/template.xsn" -NewPath "http://newsitecollection/newdoclib/newtemplate.xsn"

As always, happy PowerShelling!

Making SharePoint 2010 PowerShell Scripts Backward-Compatible with 2007


Recently I received an e-mail from one of my ICC SharePoint team-mates asking for some PowerShell code to create and then delete a lot of list items. Like 20000. Well, I already had code to create the list items – so it was easy to add a single line to call the delete() method immediately after the item was added.

I provided the updated function to my teammate, only to find out later that they wanted to do this in MOSS 2007 – not SP2010. Oops! I hadn’t considered that, I assumed since they were asking me for PowerShell code that they were dabbling in a 2010 environment. Not the case. However, this particular person is one of our better SharePoint Developers and quickly modified the code to work in MOSS.

After having that discussion, it became clear to me that it would be REALLY cool to create PowerShell Scripts and Functions that would work both in V4 as well as V3. And the journey began..

After some experimentation, googling, trial & error – it became apparent that this isn’t rocket science. In SharePoint 2010 we use the following PowerShell cmdlet to add the SharePoint Snap-in:

Add-PSSnapIn Microsoft.SharePoint.PowerShell

There’s no equivalent for MOSS environments, but we can load the SharePoint assemblies by using the following line of code:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

Once we’ve done that, we can do other things such as get the farm build version and tell us what version we’re working with:

function Get-SPFarmBuildVersion {
# using the [microsoft.sharepoint.administration.spfarm] line to get the local farm regardless of version #
# this works in 2007 and 2010 #
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
$farmBuild = $farm.BuildVersion.ToString()

      if ($farmBuild.StartsWith("12")) {
      Write-Host "This is WSS or MOSS"
      } 
      
      elseif ($farmBuild.StartsWith("14")) {
      Write-Host "This is SP2010"
      }

}

How cool is that!?

Now that we know that simple tidbit, we can easily create our scripts and functions with backward compatibility in mind by doing something like if/elseif as seen above. Developers may chime in with a better way, I’m sure there are better – perhaps more elegant approaches to this same theory – but this works for me for now.

Anyhow, back to the story – my developer friend Aaron wanted to create list items in MOSS using PowerShell. Here’s the code to do that – and it’s backwards compatible!

function Add-MultipleListItems {
[CmdletBinding()]
Param(
[string]$ListName=(Read-Host "Please enter the name of the list you wish to add to."),
[string]$Amount=(Read-Host "Please enter a number of list items to create."),
[string]$Choices=(Read-Host "Please enter choices separated by semicolons, enclosed in double quotes."),
[string]$ListItem=(Read-Host "Please enter a string for the title of each list item (Example: Added by Powershell)"),
[string]$WebUrl=(Read-Host "Please enter a URL of a SharePoint site (Example: http://intranet)")
)

$choicesForCategory = $Choices -split ";"

$assemblies = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
$farmBuild = $farm.BuildVersion.ToString()
	
	if ($farmBuild.StartsWith("14")) {
		Start-SPAssignment -Global
		$mylist = (Get-SPWeb -identity $webUrl -AssignmentCollection $StartSpAssignment).Lists[$listName]
		Write-Host "Creating $amount list items in $listName" -ForegroundColor Green
		$i = 1
		do 
		{
		    $newItem = $mylist.Items.Add()
		    $newItem["Title"] = $listItem
		    $newItem["Category"] = $choicesForCategory | Get-Random
		    $newItem.Update()
		    $i++
		}
		while ($i -le $amount)

		Write-Host "Finished!" -ForegroundColor Green
		Stop-SPAssignment -Global
	}
	elseif ($farmBuild.StartsWith("12")) {
		$SPWeb = Get-SPWeb $webUrl
		$mylist = $SPWeb.Lists[$listName]
		Write-Host "Creating $amount list items in $listName" -ForegroundColor Green
		$i = 1
		do 
		{
		    $newItem = $mylist.Items.Add()
		    $newItem["Title"] = $listItem
		    $newItem["Category"] = $choicesForCategory | Get-Random
		    $newItem.Update()
		    $i++
		}
		while ($i -le $amount)
		$SPWeb.dispose()
		Write-Host "Finished!" -ForegroundColor Green
	}
}

If you read this line by line, you’ll notice that they are fundamentally identical. Really the only difference is in V3 we don’t get any cmdlets, so we don’t have Start-SPAssignment and Stop-SPAssignment. This just means we have to make sure we’re disciplined to use dispose() methods for any SPWeb and SPSite objects.

With all that in mind, my goal from here on out is to try and make any and all PowerShell code backwards compatible.

RD

Get a SharePoint Publishing Content Inventory using PowerShell


There may be a time when you want to get an inventory of all publishing content (Sites, Documents, Pages, etc.) for an entire site collection. I needed to do exactly that, so I wrote a handy little PowerShell function that walks through a Publishing Site Collection – counting the webs, pages, images and documents.

Here is the function!

function Get-SPPublishingSiteInventory {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)][string]$SiteUrl
)
    Start-SPAssignment -Global
    $Site = New-Object Microsoft.SharePoint.SPSite($SiteUrl)
	$PubSite = New-Object Microsoft.SharePoint.Publishing.PublishingSite($Site)
	$PubPageCount = 0
	$ImageCount = 0
	$DocCount = 0
	foreach ($Web in $Site.AllWebs) {
        $PubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($Web)
		$Pages = $PubWeb.GetPublishingPages($PubWeb)
		$PageCount = $Pages.Count
		$PubPageCount = $PubPageCount + $PageCount
		$Images = $PubWeb.ImagesLibrary.ItemCount
		$ImageCount = $ImageCount + $Images
		$Documents = $PubWeb.DocumentsLibrary.ItemCount
		$DocCount = $DocCount + $Documents
		$Web.Dispose()		
    }
    $Site.Dispose()
    Write-Host "Number of Sites: "     $site.allwebs.count
    Write-Host "Number of Pages: "     $PubPageCount
	Write-Host "Number of Images: "    $ImageCount
	Write-Host "Number of Documents: " $DocCount
    Stop-SPAssignment -Global
}

Convert SharePoint List Items to Lowercase using PowerShell


Sorry for the lack of blogging lately, it’s not that I haven’t had PLENTY of material – I just frankly haven’t had time to post anything.

However, I thought I’d share a recent function I threw together – if for no other reason than to make sure I don’t forget how I did this…

I’m currently working on a big migration from a legacy HTML website to SharePoint 2010 FIS-E. During this migration, I’ve used PowerShell a LOT to automate tasks and cleanup things. An example of something I needed to clean up is links used for top navigation. We are using a 3rd party web part for our top navigation – it is list driven.

The links in the list all work, but some were entered as lowercase, some camel-case, some with no rhyme or reason at all. I wanted consistency, and I’m sure my client would too…

I wrote a simple helper function to walk through the list and convert every item in a specified column to lowercase. Pretty easy, not much to it… Also, this could easily be modified to do other things like convert to uppercase. I just wanted lowercase. Here it is!

function Convert-SPListItemStringsToLowercase {
Param(
[string]$WebUrl,
[string]$ListName,
[string]$ColumnName
)
$web = Get-SPWeb $WebUrl                                                                    
$list = $web.Lists[$ListName]                                                                                             
$items = $list.Items                                                                                                   
	foreach ($item in $items){
		$item[$ColumnName] = $item[$ColumnName].toLower()
		$item.Update()
		$list.Update()
		}#end Foreach
	$web.Update()
	$web.Dispose()
}#end function