Customizing AccessDenied, SignOut and other pages in SharePoint 2010 using PowerShell

When building a custom solution in SharePoint, whether intranet or public facing, you often need to balance out-of-the-box functionality with the custom look and feel your customer is asking for.
Page Content

 

When building a custom solution in SharePoint, whether intranet or public facing, you often need to balance out-of-the-box functionality with the custom look and feel your customer is asking for.

There are number of out-of-the-box system pages which SharePoint uses to display upon various events. For example; AccessDenied.aspx page is displayed when a user is trying to access the resource they don’t have permission to. Signout.aspx is a common one – displayed when user chooses to sign out.

A few others include: Confirmation.aspx Error.aspx Login.aspx ReqAcc.aspx WebDeleted.aspx

In SharePoint 2010 there is an elegant way to customize what user’s will see without changing the original files. Here are the steps:

1. First you can copy an existing page from the Template/Layouts, in our case we’ll use AccessDenied.aspx.

You can give it any name. If you’re using a Visual Studio solution simply add this new file to your solution structure under Layouts mapped folder.

2. Make any desired changes to the newly created page. If you made a copy of an existing page, remember that it’s referencing the original assembly and you still need to follow the structure outlined in the page placeholders to avoid the page erroring out.

Alternately, you can create your own page in VS2010 which can reference its own assembly.

3. Assuming the page has been deployed to the layouts folder. Execute the following PowerShell to set it as a default page for the role, in our case AccessDenied page role.

1 PS C:\Users\Administrator> $site = get-spsite "http://intranet.contoso.com"
2 PS C:\Users\Administrator> $webApp = $site.WebApplication
3 PS C:\Users\Administrator> $webapp.UpdateMappedPage(1, "/_layouts/AccessDeniedNew.aspx")
4 True
5 PS C:\Users\Administrator> $webapp.Update()
6 PS C:\Users\Administrator>

In here, the value of “1″ in UpdateMappedPage, specifies a reference to an enumeration value for other page roles available, see SPCustomPage for more info.

4. We’re set. Since we’re testing AccessDenied page, I’m going to log in as a reader and access “Site collection administrators” page by URL.

As a result I am getting my customized AccessDenied page:

2011-10-11-CustomAccessDenied-01.png

Notice that the URL of the page still remains the same AccessDenied.aspx and not our AccessDeniedNew.aspx – however, the content is different.

5. To reverse the customization, execute:

1 PS C:\Users\Administrator> $webapp.UpdateMappedPage(1, $null)
2 True
3 PS C:\Users\Administrator> $webapp.Update()
4 PS C:\Users\Administrator>

NOTE: The custom page must always be hosted in _layouts, otherwise the UpdateMappedPage will fail. If you need to show your content page as a result – you can execute a redirect from the custom page in _layouts to your content page.

Advertisements

How to Add a Custom Visual Studio Workflow to SharePoint Designer

This article explains, step by step, how to add a custom Visual Studio work flow to SharePoint designer in SharePoint 2010 using Visual Studio 2010.
Page Content

This article explains, step by step, how to add a custom Visual Studio work flow to SharePoint designer in SharePoint 2010 using Visual Studio 2010.

Create an Empty Sequence Work Flow project.

2011-11-02-VSWorflowSPD-01.png

Select .Net Framework 3.5 and select the Sequential Workflow Project.

2011-11-02-VSWorflowSPD-02.png

In this example we are going to add a custom string functionality to SharePoint designer. After creating the project you will get a blank activity as follows.

2011-11-02-VSWorflowSPD-03.png

Delete that activity and add the Activity item to the project as shown above. I named it subStringAfterText.cs

2011-11-02-VSWorflowSPD-04.png

You will then get a blank Sequence Activity.

Add references to the Project

You will then need to add Microsoft.SharePoint.dll and Microsoft.SharePoint.WorkflowActions.dll to the project. Those dlls can be found in the following paths.

  • C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.dll
  • C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.SharePoint.WorkflowActions.dll

Coding

My task is to develop a simple string function that will return the text after the search text. This is very simple and you can use this method to develop any Custom SPD workflows.

001 using System;
002 using System.ComponentModel;
003 using System.ComponentModel.Design;
004 using System.Collections;
005 using System.Linq;
006 using System.Workflow.ComponentModel;
007 using System.Workflow.ComponentModel.Design;
008 using System.Workflow.ComponentModel.Compiler;
009 using System.Workflow.ComponentModel.Serialization;
010 using System.Workflow.Runtime;
011 using System.Workflow.Activities;
012 using System.Workflow.Activities.Rules;
013 using System.Diagnostics;
014 using Microsoft.SharePoint;
015 using Microsoft.SharePoint.WorkflowActions;
016 using Microsoft.SharePoint.Workflow;
017   
018 namespace getSubStringAfter
019 {
020 public partial class subStringAfterText : SequenceActivity
021 {
022 // Declare event log
023 private EventLog eventLog;
024   
025   
026 // Declare dependency propoerties to get and set values to the activity
027   
028 public static DependencyProperty SourceStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register("SourceString", typeof(string), typeof(subStringAfterText));
029 public static DependencyProperty SearchStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register("SearchString", typeof(string), typeof(subStringAfterText));
030 public static DependencyProperty FoundStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register("FoundString", typeof(string), typeof(subStringAfterText));
031 // Specify validations to validate inputs
032 [Description("")]
033 [ValidationOption(ValidationOption.Required)]
034 [Browsable(true)]
035 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
036 public string SourceString
037 {
038 get
039 {
040 return (string)base.GetValue(SourceStringProperty);
041 }
042 set
043 {
044 base.SetValue(SourceStringProperty, value);
045 }
046 }
047   
048 [Description("")]
049 [ValidationOption(ValidationOption.Required)]
050 [Browsable(true)]
051 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
052 public string SearchString
053 {
054 get
055 {
056 return (string)base.GetValue(SearchStringProperty);
057 }
058 set
059 {
060 base.SetValue(SearchStringProperty, value);
061 }
062 }
063
064 [Browsable(true)]
065 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
066 public string FoundString
067 {
068 get
069 {
070 return (string)base.GetValue(FoundStringProperty);
071 }
072 set
073 {
074 base.SetValue(FoundStringProperty, value);
075 }
076 }
077   
078 //The SharePoint workflow properties activation object
079 public static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register("__ActivationProperties", typeof(SPWorkflowActivationProperties), typeof(subStringAfterText));
080   
081 // Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty. This enables animation, styling, binding, etc...
082 public static DependencyProperty WorkflowInstanceIdVariableProperty = DependencyProperty.Register("WorkflowInstanceIdVariable", typeof(string), typeof(subStringAfterText));
083   
084 // Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty. This enables animation, styling, binding, etc...
085 public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(subStringAfterText));
086   
087 [Description("The WorkflowInstanceId for the workflow.")]
088 [ValidationOption(ValidationOption.Required)]
089 public string WorkflowInstanceIdVariable
090 {
091 get { return (string)(base.GetValue(WorkflowInstanceIdVariableProperty)); }
092 set { base.SetValue(WorkflowInstanceIdVariableProperty, value); }
093 }
094   
095 [Description("The Workflow Properties")]
096 [ValidationOption(ValidationOption.Required)]
097 public SPWorkflowActivationProperties __ActivationProperties
098 {
099 get { return (SPWorkflowActivationProperties)(base.GetValue(__ActivationPropertiesProperty)); }
100 set { base.SetValue(__ActivationPropertiesProperty, value); }
101 }
102   
103 [Description("The Workflow Context object")]
104 [ValidationOption(ValidationOption.Optional)]
105 [Browsable(false)]
106 [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
107 public WorkflowContext __Context
108 {
109 get
110 {
111 return (WorkflowContext)(base.GetValue(__ContextProperty));
112 }
113   
114 set
115 {
116 base.SetValue(__ContextProperty, value);
117 }
118 }
119   
120 // Override the activity execution
121 protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
122
123 //Set up the Event Logging object 
124 eventLog = new EventLog("Workflow");
125 eventLog.Source = "SharePoint Workflow";
126   
127 try
128 {
129 // Your Method
130 getTheString();
131 }
132 finally
133 {
134 //Dispose of the Event Logging object 
135 eventLog.Dispose();
136 }
137   
138 //Indicate the activity has closed 
139 return ActivityExecutionStatus.Closed;
140 }
141   
142   
143 private void getTheString()
144 {
145 this.FoundString = SourceString.Substring(SourceString.IndexOf(SearchString) + SearchString.Length -1);
146 }
147   
148 }
149 }

Code Explanation

Dependency properties are used to exchange data from a workflow instance. Therefore the following properties needed to exchange primary data.

//The SharePoint workflow properties activation object // Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty. This enables animation, styling, binding, etc…

public static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register(“__ActivationProperties”, typeof(SPWorkflowActivationProperties), typeof(subStringAfterText));

public static DependencyProperty WorkflowInstanceIdVariableProperty = DependencyProperty.Register(“WorkflowInstanceIdVariable”, typeof(string), typeof(subStringAfterText));

// Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty. This enables animation, styling, binding, etc… public static DependencyProperty __ContextProperty = DependencyProperty.Register(“__Context”, typeof(WorkflowContext), typeof(subStringAfterText));

//These properties should have getter setter methods. attributes define the visibility , validation and etc.. Ex :-

[Description(“The WorkflowInstanceId for the workflow.”)] [ValidationOption(ValidationOption.Required)] public string WorkflowInstanceIdVariable { get { return (string)(base.GetValue(WorkflowInstanceIdVariableProperty)); } set { base.SetValue(WorkflowInstanceIdVariableProperty, value); } }

// Then you need your own properties and getters setters

public staticDependencyProperty SourceStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register(“SourceString”,typeof(string),typeof(subStringAfterText));

public static DependencyPropertySearchStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register(“SearchString”,typeof(string),typeof(subStringAfterText));

public static DependencyPropertyFoundStringProperty = System.Workflow.ComponentModel.DependencyProperty.Register(“FoundString”,typeof(string),typeof(subStringAfterText));

// Override the activity execution protected overrideActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { //Set up the Event Logging object eventLog =new EventLog(“Workflow”); eventLog.Source =”SharePoint Workflow”;
try { // Your Method getTheString(); } finally { //Dispose of the Event Logging object eventLog.Dispose(); }
//Indicate the activity has closed
returnActivityExecutionStatus.Closed; }

Sign the application

Sign the application using a strong key. This way you can go to project properties and sign the application as follows.

2011-11-02-VSWorflowSPD-05.png

2011-11-02-VSWorflowSPD-06.png

Build your application and place the assembly to the GAC. In here there are are two GAC maintaining with framework 4.0

  • C:\Windows\Microsoft.NET\assembly
  • C:\Windows\assembly

To work the assembly, this should be registered under “C:\Windows\assembly” if you use Target Framework as 4.0 then it will register the assembly in C:\Windows\Microsoft.NET\assembly. Therefore use target framework as 3.5

Register Assembly Under “C:\Windows\assembly”

2011-11-02-VSWorflowSPD-07.png

Make sure the target framework is 3.5 of the project. if not it will get add to the location “C:\Windows\assembly

For that

  • Go to the Visual Studio Command Prompt as administrator
  • Go to your bin/debug folder (place where the dll is build)
  • Type gacutil /i getSubStringAfter.dll

If you did it correctly you can see your assembly in the GAC. To view the GAC you can type “C:\Windows\assembly” in the Run Window.

2011-11-02-VSWorflowSPD-08.png

Creating work flow template actions file.

You then need to create an .ACTIONS file. This is the file that is used by SPD to get the workflow information. Therefore create a file named getSubStringAfter.ACTIONS and put the following code there. you need to put the correct public key token, namespace and class name.

2011-11-02-VSWorflowSPD-09.png

01 <?xml version="1.0" encoding="utf-8"?>
02 <WorkflowInfo Language="en-us" >
03    <Actions Sequential="then" Parallel="and">
04      <Action Name="get the substring after the text"
05          ClassName="getSubStringAfter.subStringAfterText"
06          Assembly="getSubStringAfter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d98a44afbf8ccb86"
07          AppliesTo="all"
08          Category="Custom String Actions">
09        <RuleDesigner Sentence="Search %1 from %2 and store in %3.">
10          <FieldBind Field="SearchString" Text="this text" DesignerType="stringbuilder" Id="1"/>
11          <FieldBind Field="SourceString" Text="this address" Id="2" DesignerType="stringbuilder" />
12          <FieldBind Field="FoundString" Text="variable" Id="3" DesignerType="ParameterNames" />
13        </RuleDesigner>
14        <Parameters>
15          <Parameter Name="SearchString" Type="System.String, mscorlib" Direction="In" />
16          <Parameter Name="SourceString" Type="System.String, mscorlib" Direction="In" />
17          <Parameter Name="FoundString" Type="System.String, mscorlib" Direction="Out" />
18        </Parameters>
19      </Action>
20    </Actions>
21 </WorkflowInfo>

then copy this file and put in to the following SharePoint server location C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\1033\Workflow

Modify the web Config

Modify the web.config of your site. You’ll need to put SafeControl and authorizedType in here. Also please make sure public key token , namespace , version and class is correct otherwise it will not work. For example it may not show in SPD, or it will not get added to the work flow step even though it showed in SPD.

2011-11-02-VSWorflowSPD-10.png

<authorizedTypeAssembly=”getSubStringAfter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d98a44afbf8ccb86″Namespace=”getSubStringAfter”TypeName=”subStringAfterText”Authorized=”True”/> <SafeControlAssembly=”getSubStringAfter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d98a44afbf8ccb86″Namespace=”getSubStringAfter”TypeName=”*”Safe=”True”/>

Finally ..

  • reset the IIS
  • Now open SPD

2011-11-02-VSWorflowSPD-11.png

I took nearly one week to figure this out. Anyway, finally I’ve got a solution. 🙂