Wednesday, June 20, 2012

Configurable SharePoint solution for Sending Email Alerts upon Document/List Item Expiration


Problem: need to develop a configurable sharepoint solution that would do the following:
- be deployed to any SharePoint Site as an Event receiver 
- Once the Event Receiver (ER) is attached to a List/Document Library, it will send Email Alerts a certain number of days before the Document/List Item Expiration Date added by User upon Document Upload/List Item Creation

Why Event Receiver? Explained at the end of the post.

Solution: The Event Receiver, once fired after a list item has been added, will do the following:
1) Fire a connection to a configuration list, lets call it DocumentExpirationConfig List.
2) Find out the list name it is currently fired on using properties.ListTitle, and then using a CAML Query, find the List Item in DocumentExpirationConfig List that corresponds to that List.
3) Once a record has been obtained the ER can find the name of the Column it has to read for the Document Expiration Date, subtract the number of days (once again read from the ConfigurationList) before which it should send the Alert from the Document Expiration Date, , and then set the value of the Destination Column(let's call it, for instance, EmailAlertDate) with that Date (once again read from the ConfigurationList) 

for example: Document Expiration Date - 90 (number of days to send the Alert before expiration)  = EmailAlertDate
The Document Expiration Config List is as shown in the screenshot below:


As you can tell, the DocumentExpirationConfig makes the ER configurable and deployable to any list in the site, without requiring any code changes.

After a record has been added, and the ER fired on it, it would look something like the screenshot below:


The code for the ER is shown below, simply call this method in the ItemAdded and ItemUpdated Event Handlers:
 1:  public void UpdateExpirationAlertColumn(SPItemEventProperties properties, bool isadded)
 2:  {
 3:  try
 4:  { 
 5:  SPList _configlist = properties.Web.Lists.TryGetList("DocumentExpirationConfig");
 6:  string _documentexpirationcolumn = string.Empty;
 7:  string _expirationalertcolumn = string.Empty;
 8:  if (_configlist != null)
 9:  {
 10:  string _camlQuery = "" +properties.ListTitle + "";
 11:  SPQuery _query = new SPQuery();
 12:  _query.Query = _camlQuery;
 13:  SPListItemCollection _listitems = _configlist.GetItems(_query);
 14:  
 15:  if (_listitems.Count > 0)
 16:  {
 17:  _documentexpirationcolumn = _listitems[0]["DocumentExpirationColumn"].ToString();
 18:  _expirationalertcolumn = _listitems[0]["ExpirationAlertColumn"].ToString();
 19:  
 20:  if (properties.ListItem[_documentexpirationcolumn] != null)
 21:  {
 22:  DateTime _documentexpiratedate = DateTime.Parse(properties.ListItem[_documentexpirationcolumn].ToString());
 23:  properties.ListItem[_expirationalertcolumn] = _documentexpiratedate.AddDays(-60).ToShortDateString();
 24:  properties.ListItem.SystemUpdate();
 25:  }
 26:  
 27:  }
 28:  
 29:  }
 30:  }
 31:  catch (Exception ex)
 32:  {
 33:  EventLog m_EventLog = new EventLog("");
 34:  m_EventLog.Source = "Error updating Document Expiration Date";
 35:  m_EventLog.WriteEntry("Error updating Document Expiration Date" + ex.Message + ex.StackTrace,
 36:  EventLogEntryType.FailureAudit);
 37:  }
 38:  
 39:  }

- Once the ER has finished updating the EmailAlertDate, the next step is find a way to send an alert regarding the expiration of the document/list item to the User who created the document. This could be done in either of two ways

Approach #1) Create a Timerjob that runs each night and figures out all the documents/list items that are expiring on that day, and send an Alert to users.
Approach #2) Create a Retention Policy that kicks off a Workflow, that checks if Today's Date = EmailAlertDate, then sends the Alert to users, informing them about the Contract Expiration.

I chose the second approach, as shown in the screenshot below:


Why EventReceiver?: In case you are wondering why I chose to add an Event Receiver, here are the reasons why:
First of all, SharePoint does not allow one to kickoff a Retention policy a certain number of days before a certain datetime value (read from a column such as the Document Expiration Date column described above). As you can see, in the screenshot above, there is a '+' sign but no '-' sign. Thus, for example, we cannot kick off this policy  30 days before Document Expiration Date ( I wish that was true :-)).
So you might ask, why not reate a calculated column with the same value set by the Event Receiver. Well, the answer to that is, yes, you could very well set the value of a calculated column without any need of an ER, but SharePoint will not allow you to kick off a Retention policy on it. It does not show up in the drop down list of columns on the Retention Stage Properties page, as shown in the screenshot above. 
Thus, one way around the above two problems is to use the Event Receiver as described above.

Sunday, June 3, 2012

Powershell Script for Item Level permissions

Requirement: Have a large list (tens of thousands of items). Each List item has a unique set of permissions, assigned dynamically through a SPD workflow Activity at the time when the list item is added and the workflow kicked off. A new user arrives in the middle of the workflow approval process ( maybe a reorganization or a supervisor change), and that user needs to be assigned to all items with a certain common property (could range from 1 item to potentially 1000's of list items).


Solution: Need 2 powershell scripts, one that will take the itemId (if only 1 item needs to be reassigned) and reassign permissions, and the second that will take a View Name (if more than 1 item with a common property needs to be reassigned)/filter through a CAML Query and reassign permissions.


Script #1) Powershell script for reassignment of a large number of list item permissions, each sharing a common property that can be used to create a view/apply a filter is as follows:


You can get the CAML Query for the View or the filter and pass it as the Caml parameter:


The script takes in the following parameters:


- Url: Url of the Web that you want to run the script on.
- ListName: ListName of the List you want to run the script on
- Caml: Query to be passed into the script to apply the filter
- UserName: UserName of the user whose permissions need to be assigned to the List Item, for example: "domain\kevin"
- PermissionLevel: The permission level that needs to be assigned to the UserName on the List Item, for example: "Contribute"


   1:  #Load SharePoint Snap In
   2:  Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
   3:   
   4:  function Add-SPPermissionToListItemUserConditional
   5:  {
   6:   param ($Url, $ListName, $UserName, $Caml, $PermissionLevel)
   7:   $web = Get-SPWeb -Identity $Url
   8:   $list = $web.Lists.TryGetList($ListName)
   9:   if ($list -ne $null)
  10:   {
  11:          $spQuery = New-Object Microsoft.SharePoint.SPQuery        
  12:          $spQuery.Query = $Caml
  13:          $spQuery.RowLimit = 10000
  14:          $listItems = $list.GetItems($spQuery)        
  15:          $listItems.Count
  16:        foreach ($item in $listItems) 
  17:        {
  18:             if ($item.HasUniqueRoleAssignments -eq $False)
  19:             {
  20:              $item.BreakRoleInheritance($True)
  21:                  $user = $web.AllUsers[$UserName]
  22:                  $roleDefinition = $web.RoleDefinitions[$PermissionLevel]
  23:                  $roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($user)
  24:                  $roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
  25:                  $item.RoleAssignments.Add($roleAssignment)
  26:                  $item.Update()
  27:                       Write-Host "Successfully added $PermissionLevel permission to $UserName in $ListName list. " -foregroundcolor Green
  28:             }
  29:             else
  30:             {
  31:                  $user = $web.AllUsers[$UserName]
  32:                  $roleDefinition = $web.RoleDefinitions[$PermissionLevel]
  33:                  $roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($user)
  34:                  $roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
  35:                  $item.RoleAssignments.Add($roleAssignment)
  36:                  $item.Update()
  37:                       Write-Host "Successfully added $PermissionLevel permission to $UserName in $ListName list. " -foregroundcolor Green
  38:              }
  39:         }
  40:   }
  41:   
  42:   $web.Dispose()
  43:  }
  44:   
  45:  Usage: Add-SPPermissionToListItemUserConditional "Url" "ListName" "username" "<Where><Eq><FieldRef Name='Status' /><Value Type='Text'>Yes</Value></Eq></Where>" "PermissionLevel"




Script #2) Powershell script for reassignment of ONLY 1 list item permission is as follows:
The script takes in the following parameters:


- Url: Url of the Web that you want to run the script on.
- ListName: ListName of the List you want to run the script on
- ItemId: ID of the Item that needs to have the permissions reassigned upon
- UserName: UserName of the user whose permissions need to be assigned to the List Item, for example: "domain\kevin"
- PermissionLevel: The permission level that needs to be assigned to the UserName on the List Item, for example: "Contribute"




   1:  #Load SharePoint Snap In
   2:  Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
   3:  function Add-SPPermissionToListItemUser
   4:  {
   5:   param ($Url, $ListName, $ItemID, $UserName, $PermissionLevel)
   6:   $web = Get-SPWeb -Identity $Url
   7:   $list = $web.Lists.TryGetList($ListName)
   8:   if ($list -ne $null)
   9:   {
  10:    $item = $list.Items.GetItemByID($ItemID)
  11:     if ($item.HasUniqueRoleAssignments -eq $False)
  12:     {
  13:      $item.BreakRoleInheritance($True)
  14:          $user = $web.AllUsers[$UserName]
  15:          $roleDefinition = $web.RoleDefinitions[$PermissionLevel]
  16:          $roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($user)
  17:          $roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
  18:          $item.RoleAssignments.Add($roleAssignment)
  19:          $item.Update()
  20:               Write-Host "Successfully added $PermissionLevel permission to $UserName in $ListName list. " -foregroundcolor Yellow
  21:     }
  22:         else
  23:         {
  24:             $user = $web.AllUsers[$UserName]
  25:          $roleDefinition = $web.RoleDefinitions[$PermissionLevel]
  26:          $roleAssignment = New-Object Microsoft.SharePoint.SPRoleAssignment($user)
  27:          $roleAssignment.RoleDefinitionBindings.Add($roleDefinition)
  28:          $item.RoleAssignments.Add($roleAssignment)
  29:          $item.Update()
  30:               Write-Host "Successfully added $PermissionLevel permission to $UserName in $ListName list. " -foregroundcolor Yellow
  31:         }
  32:    }
  33:   $web.Dispose()
  34:  }
  35:   
  36:   
  37:  Usage: Add-SPPermissionToListItemUser "Url" "ListName" "ID" "username" "permission level"