Check if an item has been previously approved and approve again

Sometimes, you’ll have to check in an event receiver if an item has been previously approved and then do an action.

The following extension will return a boolean wether or not the item has been approved in the past or not.

public static bool HasBeenPreviouslyApproved(this SPListItem item)
{
    bool hasBeenPreviouslyApproved = false;
    if (item.Versions.Count > 1)
    {
        var prevPublishedVer = item.Versions.Cast<SPListItemVersion>().Where(v => v.Level == SPFileLevel.Published).LastOrDefault();
        if (prevPublishedVer != null)
        {
            hasBeenPreviouslyApproved = true;
        }
    }
    return hasBeenPreviouslyApproved;
}

As an example you can put the state back to approved if a modification was made. By default this always goes back to pending when an item gets edited, but in some cases you want to keep the state to approved.

if (properties.ListItem.HasBeenPreviouslyApproved())
{
    using (var web = properties.OpenWeb())
    {
        this.EventFiringEnabled = false;
        properties.ListItem.ModerationInformation.Status = SPModerationStatusType.Approved;
        properties.ListItem.SystemUpdate(false);
        this.EventFiringEnabled = true;
    }
}

Please note that when you use this code in an event receiver, it only works in the ItemUpdated event.

As a result, an announcement which was once approved, will always be approved, until a content editor decides to change the approval status back to pending or decline.

announcement approved

Advertisements

Manage your Forms Based Authentication Membership Provider using PowerShell

In SharePoint you can configure a FbaMembershipProvider so that you can store and retrieve membership data from SQL Server. Unlike AD, you don’t have a real management tool for managing FBA users. In cases like this, where you want to help people out when they locked their account or they need a password reset, you can always go to PowerShell.

The first step is of course launching PowerShell. Then you have to “prep” your session so that you can connect to the SQL database. When you’ve already configured your Web Application’s web.config file, you’re good to go. All you need to do is load in that web.config file so the connectionstrings are available.

Do this by the following statement:

[System.AppDomain]::CurrentDomain.SetData("APP_CONFIG_FILE", "C:\inetpub\wwwroot\wss\VirtualDirectories\<iisDir>\web.config")

When you’ve loaded your config file, it’s time to initialize the System.Web assembly.

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web")

When the assembly gets loaded, the config path is used to read data for the membership providers.

You can list all available membership providers:

[System.Web.Security.Membership]::Providers

FbaMembershipProvider

We’re gonna need the second one. The collection let’s us index on the “Name” property so we’ll go ahead and select the second one:

$provider = [System.Web.Security.Membership]::Providers["FbaMembershipProvider"]

Now that we have the $provider object, we can use all the useful methods it offers:

ChangePassword
ChangePasswordQuestionAndAnswer
CreateUser
DeleteUser
FindUsersByEmail
FindUsersByName
GeneratePassword
GetAllUsers
GetHashCode
GetNumberOfUsersOnline
GetPassword
GetUser
GetUserNameByEmail
ResetPassword
UnlockUser
UpdateUser
ValidateUser

Update SharePoint Timer Job’s progress bar – the easy way

Every time I develop a timer job, I always add a progress bar. Well, if the job runs long enough that is. It adds that bit more refinement to the whole project and it’s pleasant to see your job crunching through the numbers.

For this example I’ve set up a standard timer job project:

timer job project structure

  • A Web Application scoped feature which installs the timer job
  • A static class ProgressUpdater which will handle the timer job’s progress updating
  • A class service which will do something
  • A class TimerJob which is our core job definition

I’ve chosen to add a class service to demonstrate the update process from outside the JobDefinition scope.

For the timer job I have the following code:

using Microsoft.SharePoint.Administration;
using System;

namespace SharePointTimerJob
{
    public class TimerJob : SPJobDefinition
    {
        private const string JOBNAME = "Custom Timer Job";

        public TimerJob()
            : base()
        {
        }

        public TimerJob(SPWebApplication webApplication)

            : base(JOBNAME, webApplication, null, SPJobLockType.Job)
        {
            this.Title = JOBNAME;
        }

        public TimerJob(string jobName, SPService service, SPServer server, SPJobLockType targetType)

            : base(jobName, service, server, targetType)
        {
        }

        public static string JobName
        {
            get { return JOBNAME; }
        }

        public override void Execute(Guid contentDbId)
        {
            ProgressUpdater.Job = this;

            var service = new Service();
            service.Process();
        }
    }
}

As you can see it’s pretty straight forward. One note here is the ProgressUpdater.Job = this; line. This sets the Job property of the ProgressUpdater class to the executing instance.

The next piece of code is the ProgressUpdater:

namespace SharePointTimerJob
{
    public static class ProgressUpdater
    {
        public static TimerJob Job
        {
            get;
            set;
        }

        public static void Update(int percentage)
        {
            if (Job == null)
            {
                // throw or return
                return;
            }

            if (percentage >= 0 && percentage <= 100)
                Job.UpdateProgress(percentage);
        }
    }
}

And the code of the service to trigger an Update call:

using System.Threading;

namespace SharePointTimerJob
{
    public class Service
    {
        public void Process()
        {
            int[] collection = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            int count = 0;
            foreach (var item in collection)
            {
                Thread.Sleep(5000);
                count++;
                ProgressUpdater.Update((count * 100) / collection.Length);
            }
        }
    }
}

All this does is sleep the thread and update afterwards, but you get the idea.

When you run the timer job you’ll see that the progress updates nicely in ticks of 10% because of the collection, which is 10 items:

progress 10

progress 40

SharePoint 2010: PowerShell to Clear the Timer Job Cache

Great script when you need to clear the timer job cache

SharePoint Tips by Nick Hobbs

I have been clearing the Timer Job Cache manually when needed for years. A few months back I looked for a script to do this automatically and ended up writing one since it can be a slow and laborious task to perform on a multi-server SharePoint farm, and I have now finally got around to sharing it here.

The Timer Job Cache may need to be cleared for a number of reasons, however the main ones I have come across are mainly in development environments when redeploying the same or updated solutions repeatedly, where updated assemblies are not used by timer jobs, and also if for example a small development VM runs out of disk space (frugal disk allocation) because of database transaction logs or data files filling up then the timer jobs stop working. I am sure there are several other reasons you might want or need to clear the…

View original post 438 more words

Prevent items from being deleted using an event receiver

One way to prevent item deletion is creating a custom permission level, where the item deletion permission is unchecked. When you use this method to prevent item deletion, you’ll have to break the permission inheritance of your list. Plus, you’ll create the permission level for the whole site collection. If that’s a problem you can also use an event receiver.

The following code snippet will prevent item deletion and also notify the user of what happened. If you simply use the properties.Cancel = true; property, then the item is prevented from deletion, but the user isn’t notified.

By setting the properties.Status = SPEventReceiverStatus.CancelWithError; property, we tell SharePoint to generate an error message. The properties.ErrorMessage = “This item cannot be deleted; Contact your administrator”; property defines the message the user sees.

/// <summary>
/// An item is being deleted.
/// </summary>
public override void ItemDeleting(SPItemEventProperties properties)
{
    base.ItemDeleting(properties);
    properties.Status = SPEventReceiverStatus.CancelWithError;
    properties.ErrorMessage = "This item cannot be deleted; Contact your administrator";
    properties.Cancel = true;
}

The above snippet generates the following error when a user tries to delete an item in a list where the event receiver is active:

error screenshot from event receiver

Find out wether a filled in user field is a group or a user

The following method allows you to find out wether a filled in user field is an actual user, or might be an active directory group. Here I use the principalInfo variable to see if there’s an actual windows principal behind the value of “user”. I use the user’s e-mail in the first part, since there might be more persons with the same user.LookupValue value. In other words: some people have the same displayname.

For the same reason, I use the user.User.LoginName in the web.EnsureUser method.

protected static SPPrincipal GetUserOrGroup(SPWeb web, SPFieldUserValue user)
{
	if (user.User != null)
		var principalInfo = SPUtility.ResolveWindowsPrincipal(
			web.Site.WebApplication, user.User.Email, SPPrincipalType.All, true);

	// if the user has no e-mail, check the user's name. This is not failsafe when 2 persons have the same name
	if (principalInfo == null)
		principalInfo = SPUtility.ResolveWindowsPrincipal(
			web.Site.WebApplication, user.LookupValue, SPPrincipalType.All, false);

	SPPrincipal principal;
	if (principalInfo != null)
		principal = web.EnsureUser(user.User.LoginName);
	else
		principal = web.SiteGroups[user.LookupValue];
	return principal;
}

Remove items from a huge SharePoint list

At a certain client of mine, there was a huge list consisting over 67 million items. This is well over the suggested 30 million item limit. This list was filled with random information and was adding lots of data to the dbo.AllUserData table so we decided to remove it. After trying several approaches, we couldn’t get past the huge DELETE statement SQL builds in order to remove the list. This statement was so large, it even crashed the SQL server. One approach that worked was using the GUI to delete items. As you can guess, this would take a huge amount of mandays to click away the list 😉

Thankfuly SharePoint accepts batch updating items so I’ve written a script to delete those items in no time (subject to your processing power).

$web = get-spweb http://
$list = $web.lists | ? { $_.title -eq "<<ListTitle>>" }
$spQuery = New-Object Microsoft.SharePoint.SPQuery
$spQuery.ViewAttributes = "Scope='Recursive'";
$spQuery.RowLimit = 100
$caml = '<OrderBy Override="TRUE"><FieldRef Name="ID"/></OrderBy>' 
$spQuery.Query = $caml 

do
{
    $listItems = $list.GetItems($spQuery)
    $count = $listItems.Count
    $spQuery.ListItemCollectionPosition = $listItems.ListItemCollectionPosition
    $batch = "<?xml version=`"1.0`" encoding=`"UTF-8`"?><Batch>"
    $j = 0
    for ($j = 0; $j -lt $count; $j++)
    {
        $item = $listItems[$j]
        write-host "`rProcessing ID: $($item.ID) ($($j+1) of $($count))" -nonewline
        $batch += "<Method><SetList Scope=`"Request`">$($list.ID)</SetList><SetVar Name=`"ID`">$($item.ID)</SetVar><SetVar Name=`"Cmd`">Delete</SetVar><SetVar Name=`"owsfileref`">$($item.File.ServerRelativeUrl)</SetVar></Method>"
        if ($i -ge $count) { break }
    }
    $batch += "</Batch>"

    write-host

    write-host "Sending batch..."
    $result = $web.ProcessBatchData($batch)

    write-host "Emptying Recycle Bin..."
    $web.RecycleBin.DeleteAll()
}
while ($spQuery.ListItemCollectionPosition -ne $null)
$web.Dispose()