Friday, 20 August 2010

Show and hide columns in SharePoint list forms with PowerShell

One of the great things about PowerShell with SharePoint 2010 is the ability to configure settings that are not available through the browser interface by writing a simple script, rather than having to load up Visual Studio and develop a custom application to access the Object Model. This article covers how to set properties on columns in order to show or hide them from specific list forms.

The following table taken from MSDN shows the properties available to us:

ShowInDisplayForm Gets or sets a Boolean value that specifies whether the field is displayed in the form for displaying list items.
ShowInEditForm Gets or sets a Boolean value that specifies whether the field is displayed in the form that is used to edit list items.
ShowInListSettings Gets or sets a Boolean value that specifies whether the field is displayed in the page for customizing list settings.
ShowInNewForm Gets or sets a Boolean value that specifies whether the field is displayed in the form that is used to create list items.
ShowInVersionHistory Gets or sets a Boolean value that specifies whether the field is displayed in the page for viewing list item versions.
ShowInViewForms Gets or sets a Boolean value that specifies whether the field is displayed in pages that are used to view list data.

For this example, I am going to disable the ShowInEditForm properties for a column called “Test Column” that I have created directly in a document library (I’ll deal with site columns later). Here is a screenshot of the edit form when I upload a new document to the library or edit the properties of an existing document:

TestColumnThere

I then run the following PowerShell Script:

#Get the web, list and column objects
$web = Get-SPWeb http://portal
$list = $web.Lists["Shared Documents"]
$column = $list.Fields["Test Column"]

#Change the ShowInEditForm property and update objects
$column.ShowInEditForm = $false
$column.Update()
$list.Update()
$web.Dispose()

Now when I edit the properties of the document, the Test Column is no longer there:

TestColumnThere

However, because the ShowInDisplayForm property is still set to true, the column does appear when I view the properties of a document:

TestColumnThere

The script is different if you want to achieve the same thing with a site column. I used the following script to modify the ShowInEditForm property of a site column called “Test Site Column”, which can be added to document libraries and lists directly, or through a content type. Note that the new ShowInEditForm property setting will apply to all document libraries, lists and content types where the site column has been added:

#Get the web and site column objects
$web = Get-SPWeb http://portal
$column = $web.Fields["Test Site Column"]

#Set the PushChangesToLists property for the changes to be applied
#to lists where the column has already been added
$column.PushChangesToLists = $true

#Change the ShowInEditForm property and update objects
$column.ShowInEditForm = $false
$column.Update()
$web.Update()
$web.Dispose()

Thursday, 19 August 2010

Bulk deactivate feature and remove farm solution in PowerShell

As a follow up to my article back in June on how to install a farm solution and bulk activate site or site collection features in SharePoint 2010 using PowerShell, here I am doing the opposite by bulk deactivating a feature before removing the solution from the farm.

First, we get the feature from the farm with this line:

$feature = Get-SPFeature "FeatureName"

Then we have one of four scenarios to use for deactivation of the feature:

  • Deactivate site scoped feature in one site collection

$site = Get-SPSite http://portal
Disable-SPFeature $feature -Url $site.Url -Force -Confirm:$false
$site.Dispose()

  • Deactivate site scoped feature for all site collections in a Web Application (checks to see if the feature is activated before attempting to deactivate)

$webApp = Get-SPWebApplication -Identity http://portal
$webApp | Get-SPSite -limit all | ForEach-Object {
    if ($_.Features[$feature.ID]) {
        Disable-SPFeature $feature -Url $_.Url -Force -Confirm:$false
    }
}

  • Deactivate web scoped feature in one site

$web = Get-SPWeb http://portal
Disable-SPFeature $feature -Url $web.Url -Force -Confirm:$false
$web.Dispose()

  • Deactivate web scoped feature for all sites in a site collection (checks to see if the feature is activated before attempting to deactivate)

$site = Get-SPSite http://portal
$site | Get-SPWeb -limit all | ForEach-Object {
    if ($_.Features[$feature.ID]) {
        Disable-SPFeature $feature -Url $_.Url -Force -Confirm:$false
    }
}
$site.Dispose()

To remove the solution from the farm, use the script below. Note that there is a pause in the script whilst it waits for the solution to be uninstalled from the farm before attempting to remove it:

#Set up Web Application variable
#Only needed if solution contains Web Application scoped resources
$webApp = Get-SPWebApplication -Identity http://portal

#Get Solution from the farm
$solution = Get-SPSolution -Identity "Solution.wsp"

#Uninstall solution
#Add -WebApplication $webApp if solution contains Web Application scoped resources
Uninstall-SPSolution $solution -Confirm:$true

#Wait for solution to be uninstalled
do {Start-Sleep -s 1} while ($solution.Deployed -eq $true)

#Remove solution from the farm
Remove-SPSolution $solution -Confirm:$true

Tuesday, 17 August 2010

Check the size of SharePoint 2010 sites using PowerShell

One of those “it may come in useful one day” scripts. It walks through a site hierarchy in SharePoint 2010, reports the size of each one, and then displays a total size for all sites reported. It uses the concepts of the code specified in this article, which calculates the size of a site by adding up the contents of each folder within it.

UPDATE 29th October 2010: Modified the script to include all subfolders – thanks to Steven Wu for pointing it out.

To use, first run the following script:

function GetWebSizes ($StartWeb)
{
    $web = Get-SPWeb $StartWeb
    [long]$total = 0
    $total += GetWebSize -Web $web
    $total += GetSubWebSizes -Web $web
    $totalInMb = ($total/1024)/1024
    $totalInMb = "{0:N2}" -f $totalInMb
    $totalInGb = (($total/1024)/1024)/1024
    $totalInGb = "{0:N2}" -f $totalInGb
    write-host "Total size of all sites below" $StartWeb "is" $total "Bytes,"
    write-host "which is" $totalInMb "MB or" $totalInGb "GB"
    $web.Dispose()
}

function GetWebSize ($Web)
{
    [long]$subtotal = 0
    foreach ($folder in $Web.Folders)
    {
        $subtotal += GetFolderSize -Folder $folder
    }
    write-host "Site" $Web.Title "is" $subtotal "KB"
    return $subtotal
}

function GetSubWebSizes ($Web)
{
    [long]$subtotal = 0
    foreach ($subweb in $Web.GetSubwebsForCurrentUser())
    {
        [long]$webtotal = 0
        foreach ($folder in $subweb.Folders)
        {
            $webtotal += GetFolderSize -Folder $folder
        }
        write-host "Site" $subweb.Title "is" $webtotal "Bytes"
        $subtotal += $webtotal
        $subtotal += GetSubWebSizes -Web $subweb
    }
    return $subtotal
}

function GetFolderSize ($Folder)
{
    [long]$folderSize = 0 
    foreach ($file in $Folder.Files)
    {
        $folderSize += $file.Length;
    }
    foreach ($fd in $Folder.SubFolders)
    {
        $folderSize += GetFolderSize -Folder $fd
    }
    return $folderSize
}

Once you have run the script, you can call it with the following PowerShell command:

GetWebSizes -StartWeb <StartURL>

Using a start URL allows you to perform a size check from any site in the hierarchy – not just the top level site. In the example below, I ran it against the URL http://portal, but could also choose to run it from a sub-site, if required:

PS C:\> GetWebSizes -StartWeb http://portal
Site Intranet is 12763801 KB
Site Hello is 581759 Bytes
Site Team Site is 604175 Bytes
Total size of all sites below http://portal is 13949735 Bytes,
which is 13.30 MB or 0.01 GB

Thursday, 12 August 2010

Useful PowerShell for SharePoint links and resources #1

Just a few links to resources I have found over the past weeks. Whilst I hope you find them useful, I’m really blogging these for my own benefit to remind me of them later :-)

Wednesday, 11 August 2010

Add a workflow to a SharePoint list in all sites of a site collection using PowerShell

This script sets up a function to add a workflow association to a specifically named list or document library in all sites of a site collection. For example, adding a workflow to the “Pages” document library in all publishing sites. You use the function by first running the script and then calling it using the following command:

AddWorkflowToLibraries -SiteCollection http://portal -ListName Pages -WfName "Collect Feedback - SharePoint 2010" -WfAssociationName "Collect Feedback"

The example above adds the out-of-box SharePoint 2010 Collect Feedback workflow to the Pages library in each site of the http://portal site collection, calling it “Collect Feedback”. If a site does not have a Pages library (e.g., a Team site), the script will pass over it and continue on to the next site. The script will also continue to the next site if the workflow already exists in the list and I have added an optional delete section (commented out in the script below) which you can use to delete a workflow associated with each list – this will allow you to bulk delete workflow associations before creating new ones.

Run this script first before typing the AddWorkflowToLibraries command above:

function AddWorkflowToLibraries ($SiteCollection, $ListName, $WfName, $WfAssociationName)
{

    #Get site object and create blank guid to store workflow template ID
    $site = Get-SPSite $SiteCollection
    [Guid]$wfTemplateId = New-Object Guid

    #Step through each web in site collection
    $site | Get-SPWeb -limit all | ForEach-Object {

        #Do the following if a list exists with the name specified by the user - e.g., Pages
        if ($_.Lists[$ListName]) {

            write-host $_.Title"has a list called"$ListName -ForegroundColor green

            $list = $_.Lists[$ListName]
            #Go through each workflow installed in the site to find the correct ID           
            foreach ($wfTemplate in $_.WorkflowTemplates) {
                if ($wfTemplate.Name -eq $WfName) {
                $wfTemplateId = $wfTemplate.Id
                }
            }
            #Set up the template from the ID found
            $wfTemplate = $_.WorkflowTemplates[$wfTemplateId]
            #Check if the site already has a workflow history list - if not, create it
            if(!$_.Lists["Workflow History"])
            {
                $_.Lists.Add("Workflow History", "A system library used to store workflow history information that is created in this site.  It is created by the Publishing feature.",
                "WorkflowHistory", "00BFEA71-4EA5-48D4-A4AD-305CF7030140", 140, "100")
                if (!$_.Features["00BFEA71-4EA5-48D4-A4AD-305CF7030140"]) {
                    Enable-SPFeature -Identity WorkflowHistoryList -Url $_.Url
                }
                $wfHistory = $_.Lists["Workflow History"]
                $wfHistory.Hidden = $true
                $wfHistory.Update()
            }
            else
            {
                $wfHistory = $_.Lists["Workflow History"]
            }
            #Check if the site already has a workflow tasks list - if not, create it
            if(!$_.Lists["Workflow Tasks"])
            {
                $_.Lists.Add("Workflow Tasks", "This system library was created by the Publishing feature to store workflow tasks that are created in this site.", "WorkflowTasks", "BF611337-1591-49f4-BF42-CE7BE53852D8", 107, "100")
            }
            $wfTasks = $_.Lists["Workflow Tasks"]
            #Set up workflow association (extra associated data can be added if you have it)
            $wfAssociation = [Microsoft.SharePoint.Workflow.SPWorkflowAssociation]::CreateListAssociation($wfTemplate, $WfAssociationName, $wfTasks, $wfhistory)
            #Check to see if the association has already been added to the list
            [guid]$wfId = New-Object Guid
            [bool]$wfFound = $false
            foreach ($wf in $list.WorkflowAssociations) {
                if ($wf.Name -eq $wfAssociation.Name) {
                    $wfId = $wf.Id
                    write-host "Workflow"$wf.Name"already exists on"$list.Title"list in site"$_.Title -ForegroundColor green
                    $wfFound = $true
                }
            }
            #If association is already there, ignore the add (and optionally delete it)
            if ($wfFound -eq $true) {
                #Command to remove existing workflow from list before adding new one, if required
                #$list.WorkflowAssociations.Remove($wfId)
                #write-host "Removed workflow"$wfAssociation.Name"from"$list.Title"in site"$_.Title -ForegroundColor green
            }
            #else, add it to the workflow association to the list
            else
            {
                $list.WorkflowAssociations.Add($wfAssociation)
                write-host "Added workflow"$wfAssociation.Name"to"$list.Title"in site"$_.Title -ForegroundColor green
            }
        }
        #Report if the site does not have the list specified by the user
        else {
            write-host $_.Title"does not have a list called"$listName -ForegroundColor green
        }
    }
    #Dispose of Site object
    $site.Dispose()
}

Another thing to note here are the names of the history and task lists associated with the workflow. I have gone with “Workflow History” and “Workflow Tasks” but you can change these as required. The script will create these lists if they do not exist already on a site (e.g., when a Publishing site template is used without the approval workflow).

Monday, 9 August 2010

Find GUID of a site column in SharePoint using PowerShell

Whilst a simple script, this illustrates the real advantages of PowerShell. I needed to find the ID’s of some site columns I had created in SharePoint 2010. In the days of MOSS 2007, I would have had to either work out the GUID from looking at the URL of the column in the browser or write a custom application in Visual Studio to get the information I needed by accessing the Object Model. Now, I spend a minute writing a script in PowerShell and it’s reusable time and time again.

The script asks the user to input the display name of the site column and then gets the name and GUID of the column and outputs it to the console. I have configured the URL as a variable, but you could also ask the user for this if you wanted to:

$web = Get-SPWeb http://portal
$columnName = Read-Host "Enter the column title (display name)"
write-host $web.Fields[$columnName].Title -nonewline
write-host " "$web.Fields[$columnName].Id -foregroundcolor Red
$web.Dispose()

You could use the essence of this script to find a whole bunch of information about the column, if required. A full list of properties can be found on MSDN here.

Monday, 2 August 2010

Use PowerShell to push changes from Content Type Hub to Site Collections in SharePoint 2010

Quite a simple one this, but also a time saver. If you are in the process of adding site columns and content types to a Content Type Hub, it can take up to one hour for the timer jobs to run to distribute the changes to your site collections, which is sensible for maintaining performance, but a pain if you are making lots of changes and want to see them in your site collections straight away.

In SharePoint 2010, you can now run your timer jobs manually from the UI in Central Administration, but this can still be pain as you have to run the Content Type Hub job, followed by the Content Type Subscriber job for each Web Application receiving the changes – quite a few clicks!

The PowerShell script below allows you to do this a lot quicker as you can just keep running it every time you make a change:

#Run the Content Type Hub timer job
$ctHubTJ = Get-SPTimerJob "MetadataHubTimerJob"
$ctHubTJ.RunNow()

#Run the Content Type Subscriber timer job for a specific Web Application
$ctSubTJ = Get-SPTimerJob "MetadataSubscriberTimerJob" -WebApplication http://portal
$ctSubTJ.RunNow()

I’ll be following this with a script in a future post to show you how you can publish and republish content types inside the Content Type Hub site collection itself.