Setting AutoCleanupDays With PowerShell in SharePoint 2010

The Problem

In it’s default configuration, SharePoint (2007 and 2010) will delete workflow associations after 60 days (e.g. the information found on the Workflow Status page). There are times when you may want to alter this functionality and retain the association for a longer period of time.

In SharePoint 2007, the recommendation from Microsoft (per TechNet), is to disable the Workflow Auto Cleanup timer job. The Workflow Auto Cleanup timer job is a web application scoped timer job. As such, disabling the job is often not the optimal solution; as disabling the job will ensure that cleanup is not occurring for all of the site collections within the web application for which the timer job was disabled.

The Solution

Thankfully, there is a property (SPWorkflowAssociation.AutoCleanupDays) that is exposed with the workflows associated with a given list/content type. And once again, PowerShell comes to the rescue, giving us the ability to manipulate the cleanup days at a more granular level. This ensures that we do not disable the timer job for an entire web application.

Presented as two functions, we can manipulate the workflow association settings for either the workflows associated with a list or the workflows associated with a content type attached to a list.

Set-SPListWorkflowAssocationCleanup

The following function allows us to set the AutoCleanupDays for all of the workflows associated with a given list.

Set-SPListWorkflowAssocationCleanup -WebUrl "http://intranet" -ListName "Shared Documents" -CleanupDays 365 -ReportOnly:$false
function Set-SPListWorkflowAssocationCleanup {
    param (
        [string] $WebUrl = $(Read-Host -prompt "Enter a Url"),
        [string] $ListName = $(Read-Host -prompt "Enter a List Name"),
        [int32] $CleanupDays = $(Read-Host -prompt "Enter the number of Cleanup Days"),
        [switch] $ReportOnly = $true
        )        

    $web = Get-SPWeb $WebUrl;
    if ($web -eq $null) {
        Write-Error -message "Error: Web Not Found" -category InvalidArgument
    } else {
        $list = $web.Lists[$ListName];
        if ($list -eq $null) {
            Write-Error -message "Error: List Not Found" -category InvalidArgument
        } else {
            [Microsoft.SharePoint.Workflow.SPWorkflowAssociation[]] $wfaMods = @();
            foreach ($wfa in $list.WorkflowAssociations) {
                $message = "Found Workflow Association for " + $wfa.Name + " with AutoCleanupDays set to " + $wfa.AutoCleanupDays
                Write-Verbose -message $message -verbose

                if ($ReportOnly -eq $false) {
                    $wfa.AutoCleanupDays = $CleanupDays;
                    $wfaMods = $wfaMods + $wfa;
                }
            }

            if ($ReportOnly -eq $false) {
                foreach ($wfa in $wfaMods) {
                    $message = "Setting AutoCleanupDays for " + $wfa.Name + " to " + $CleanupDays
                    Write-Verbose -message $message -verbose
                    $list.WorkflowAssociations.Update($wfa);
                }
            }
        }
    }
}

Set-SPListContentTypeWorkflowAssocationCleanup

The following function allows us to set the AutoCleanupDays for all of the workflows associated with content type for a given list.

Set-SPListContentTypeWorkflowAssocationCleanup -WebUrl "http://intranet" -ListName "Shared Documents" -ContentTypeName "Document Content Type" -CleanupDays 365 -ReportOnly:$false
function Set-SPListContentTypeWorkflowAssocationCleanup {
    param (
        [string] $WebUrl = $(Read-Host -prompt "Enter a Url"),
        [string] $ListName = $(Read-Host -prompt "Enter a List Name"),
        [string] $ContentTypeName = $(Read-Host -prompt "Enter a Content Type Name"),
        [int32] $CleanupDays = $(Read-Host -prompt "Enter the number of Cleanup Days"),
        [switch] $ReportOnly = $true
        )        

    $web = Get-SPWeb $WebUrl;
    if ($web -eq $null) {
        Write-Error -message "Error: Web Not Found" -category InvalidArgument
    } else {
        $list = $web.Lists[$ListName];
        if ($list -eq $null) {
            Write-Error -message "Error: List Not Found" -category InvalidArgument
        } else {
            $ct = $list.ContentTypes[$ContentTypeName];
            if ($ct -eq $null) {
                Write-Error -message "Error: Content Type Not Found" - category InvalidArgument
            } else {
                [Microsoft.SharePoint.Workflow.SPWorkflowAssociation[]] $wfaMods = @();
                foreach ($wfa in $ct.WorkflowAssociations) {
                    $message = "Found Workflow Association for " + $wfa.Name + " with AutoCleanupDays set to " + $wfa.AutoCleanupDays
                    Write-Verbose -message $message -verbose

                    if ($ReportOnly -eq $false) {
                        $wfa.AutoCleanupDays = $CleanupDays;
                        $wfaMods = $wfaMods + $wfa;
                    }
                }

                if ($ReportOnly -eq $false) {
                    foreach ($wfa in $wfaMods) {
                        $message = "Setting AutoCleanupDays for " + $wfa.Name + " to " + $CleanupDays
                        Write-Verbose -message $message -verbose
                        $ct.WorkflowAssociations.Update($wfa);
                    }
                }
            }
        }
    }
}

Conclusion

By manipulating the workflow associations for a list at the list/content type level the default configuration of a SharePoint farm can be retained (e.g. Workflow associations removed after 60 days) and in those cases where there is a business need, the workflow associations can be maintained for a longer period of time.

Reference

UrlAction Tokens in SharePoint 2010

When creating CustomAction Elements, a number of tokens are available for use within the UrlAction Element in SharePoint 2010. The list of UrlAction tokens in SharePoint 2010 has grown ever so slightly (For a SharePoint 2007 reference, see UrlAction Tokens Of The CustomAction Feature).

Tokens

Key
Available in 2007
Available in 2010
Token Replacement
~site/ SPContext.Current.Web.ServerRelativeUrl
~sitecollection/ SPContext.Current.Site.ServerRelativeUrl
{ItemId} SPListItem.ID.ToString() or SPListItem["BdcIdentity"] (external data source)
{ItemUrl} SPListItem.Url
{SiteUrl} SPWeb.Url
{ListId} SPList.ID.ToString(“B”)
{RecurrenceId} SPListItem.RecurrenceID
{ListUrlDir} SPList.RootFolder.Url
{Source} Current Request Url

Microsoft.SharePoint.SPCustomActionElement.ReplaceUrlTokens

internal static string ReplaceUrlTokens(string urlAction, SPWeb web, SPList list, SPListItem item)
{
	if (string.IsNullOrEmpty(urlAction))
	{
		return urlAction;
	}
	if (item != null)
	{
		int d = item.ID;
		string str1 = d.ToString(CultureInfo.InvariantCulture);
		if (list.HasExternalDataSource)
		{
			str1 = item["BdcIdentity"] as string;
		}
		urlAction = urlAction.Replace("{ItemId}", str1);
		urlAction = urlAction.Replace("{ItemUrl}", item.Url);
		string recurrenceID = str1;
		if (!string.IsNullOrEmpty(item.RecurrenceID))
		{
			recurrenceID = item.RecurrenceID;
		}
		urlAction = urlAction.Replace("{RecurrenceId}", recurrenceID);
	}
	if (web != null)
	{
		urlAction = urlAction.Replace("{SiteUrl}", web.Url);
	}
	if (list != null)
	{
		urlAction = "{ListId}".Replace(Guid guid = list.ID, guid.ToString("B"));
		if (list.RootFolder != null)
		{
			urlAction = urlAction.Replace("{ListUrlDir}", list.RootFolder.Url);
		}
	}
	HttpContext current = HttpContext.Current;
	if (current != null && current.Request != null)
	{
		string rawUrl = current.Request.RawUrl;
		Uri contextUri = SPAlternateUrl.ContextUri;
		if (!string.IsNullOrEmpty(rawUrl) && null != contextUri)
		{
			string str2 = null;
			if (!SPUtility.StsStartsWith(rawUrl, "/"))
			{
				str2 = string.Concat(contextUri.GetLeftPart(UriPartial.Authority), "/", rawUrl);
			}
			else
			{
				str2 = string.Concat(contextUri.GetLeftPart(UriPartial.Authority), rawUrl);
			}
			urlAction = urlAction.Replace("{Source}", SPHttpUtility.UrlKeyValueEncode(str2));
		}
	}
	urlAction = SPUtility.GetServerRelativeUrlFromPrefixedUrl(urlAction);
	return urlAction;
}

Reference

SharePoint Saturday DC – The Conference – Session Deck

SharePoint Saturday DC – The Conference has kicked off and seems to be running full steam ahead. As promised, here is my deck from SharePoint Saturday DC – The Conference. Thanks to all who came out to my session on Managed Metadata – The Good, the Bad, and the Ugly.

SharePoint 2010 Information Worker FAST Search Certificate

In the SharePoint 2010 Information Worker, the FASTSearchCert deployed in the image expired in April 2011 and needs to be updated.

The following script (credit to Bryan Hart) will update the certicate in a single elevated PowerShell prompt.

###################################
# Apply Certificate to FAST
###################################
write-host "Applying Certificate to FAST" -ForegroundColor Yellow

Add-PSSnapin AdminSnapIn
Add-PSSnapin Microsoft.FASTSearch.PowerShell
Add-PSSnapin Microsoft.SharePoint.PowerShell

stop-service FAST*

$installerdir = $env:FASTSEARCH + "installer\scripts"

cd $installerdir

$pw = ConvertTo-SecureString -AsPlainText -force pass@word1

.\ReplaceDefaultCertificate.ps1 -generateNewCertificate $true -certificatePassword $pw

$cert = @(dir cert:\LocalMachine\My -recurse | ? { $_.Subject -eq 'CN=FASTSearchCert' })[0]

$thumb = $cert.Thumbprint

Start-service FAST*

.\SecureFASTSearchConnector.ps1 -certThumbprint $thumb -ssaName "FASTContent" -username "contoso\administrator"

Reference

Performance Optimization WordPress Plugins by W3 EDGE