I'm using MOSS (SharePoint 2007 Enterprise), 32-bit, SP2.
I've been noticing some odd problems with Custom Workflow Activities which I've developed and am using in SharePoint Designer (SPD) workflows. These actions seem to work correctly, but don't "play nice" with the rest of the workflow (specifically, the root issue was posted at: Timing concerns with Custom WF Activity changing permissions , since that hasn't gotten any responses I've been digging deeper...)
To help pin down the problem, I've developed a very simple test, which I will outline in detail below, and am noticing even more odd behavior with this test, which is what I'll get into first...
So, at the end of this test I've got a simple SPD WF which I can start manually on a list I created for this test, which contains a Single Line of Text field/column named "TextField". The WF contains 1 step which performs 4 actions:
Set Field to Value (uses my custom WFA to assign "1" to the TextField column)
Log "Set 1" to the workflow history list
Set Field in Current Item (uses the OOTB action to assign "2" to the TextField column)
Log "Set 2" to the workflow history list
This workflow runs perfectly, completing successfully with the workflow messages in order, and TextField==2.
However, if I move the last 2 actions to the top of the actions list, making the WF's single step then look like:
Set Field in Current Item (uses the OOTB action to assign "2" to the TextField column)
Log "Set 2" to the workflow history list
Set Field to Value (uses my custom WFA to assign "1" to the TextField column)
Log "Set 1" to the workflow history list
In this case, the workflow status is "Error Occurred", and even though TextField==1 (the second assignment) the only items in the workflow history are:
Error updating a list item
An error has occured in Set Field Test.
("Set Field Test" is the name of my SPD WF)
So, that's what the problem looks like: The WF works 100% if my custom WFA happens first, but fails every time (even though the field does get updated correctly) if my custom WFA happens second. I've repeated this test many times, including to perform the action reversal multiple times.
I might be doing something dumb in my custom WFA, so here it is (I've replaced my company's acronym with public agency - getting my tax dollars' worth) in its entirety:
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
namespace NASA.workflowActivity {
public partial class TestSetFieldValueActivity : Activity {
#region Dependency Properties
public static DependencyProperty itemFieldProperty = DependencyProperty.Register("itemField", typeof(String), typeof(TestSetFieldValueActivity));
public static DependencyProperty newValueProperty = DependencyProperty.Register("newValue", typeof(String), typeof(TestSetFieldValueActivity));
public static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register("__ActivationProperties", typeof(Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties), typeof(TestSetFieldValueActivity));
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
public String itemField {
get { return base.GetValue(TestSetFieldValueActivity.itemFieldProperty).ToString(); }
set { base.SetValue(TestSetFieldValueActivity.itemFieldProperty, value); }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
[Browsable(true)]
public String newValue {
get { return base.GetValue(TestSetFieldValueActivity.newValueProperty).ToString(); }
set { base.SetValue(TestSetFieldValueActivity.newValueProperty, value); }
}
[ValidationOption(ValidationOption.Required)]
public Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties __ActivationProperties {
get { return (Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties)base.GetValue(TestSetFieldValueActivity.__ActivationPropertiesProperty); }
set { base.SetValue(TestSetFieldValueActivity.__ActivationPropertiesProperty, value); }
}
#endregion
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {
try {
SPListItem listItem = this.__ActivationProperties.Item;
SPField field = listItem.Fields[this.itemField];
listItem[field.Id] = this.newValue;
listItem.SystemUpdate();
} catch {
return ActivityExecutionStatus.Faulting;
}
return ActivityExecutionStatus.Closed;
}
}
}
And my .ACTIONS file (saved to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\Workflow ) contains this entry for this action:
<Action
Name="Set field in current item (Custom WFA)"
ClassName="NASA.workflowActivity.TestSetFieldValueActivity"
Assembly="NASA.workflowActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f579ebeb24170bf5"
AppliesTo="all"
Category="NASA WFA: Test">
<RuleDesigner Sentence="Set %1 to %2 (Custom WFA)">
<FieldBind Id="1" Field="itemField" DesignerType="FieldNames" text="field" />
<FieldBind Id="2" Field="newValue" DesignerType="Text" text="value" />
</RuleDesigner>
<Parameters>
<Parameter Name="itemField" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="newValue" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="__ActivationProperties" Type="Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePoint" Direction="In" />
</Parameters>
</Action>
And finally, of course, my web.config contains an entry for my assembly.
This is a very simple action of course, but even it isn't working right in my SPD WF. I must be doing something wrong, but all the documentation I can find on creating a custom WFA makes this look correct. Can anyone see a problem in my code, or even try this out in your own environment?
I have acually solved this problem in my open source project. Download the source code from http://spdactivities.codeplex.com/ and look at the source for one of the permission activities. The reason your code does not work is because the OOTB activities participate in a transaction and your custom activity does not. That is why your custom activity runs before all OOTB activities.
To participate in the workflow you need to implement IPendingWork and submit to WorkflowEnvironment.WorkBatch. HTH
Related
I have a custom module getting executed right after the PDFGenerator finished. I followed this guide on how to create a custom module
https://stackoverflow.com/a/55799101/9945420
When processing a batch document I want to manipulate the generated PDF file and add a footer to that file. The content of that footer needs to get configured in the Administration module.
So within my project called "StampOnScanProcess" I added a Folder called "Setup" with two files. A Form called "FrmSetup"
public partial class FrmSetup : Form
{
private IBatchClass batchClass;
public FrmSetup()
{
InitializeComponent();
}
public DialogResult ShowDialog(IBatchClass batchClass)
{
this.batchClass = batchClass;
// Load previous Settings ...
return this.ShowDialog();
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnSave_Click(object sender, EventArgs e)
{
// Save ...
this.Close();
}
}
and a UserControl called "UserCtrlSetup"
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISetupForm
{
[DispId(1)]
AdminApplication Application { set; }
[DispId(2)]
void ActionEvent(int EventNumber, object Argument, out int Cancel);
}
[ClassInterface(ClassInterfaceType.None)]
[ProgId(CUSTOM_MODULE_NAME_SETUP)]
public partial class UserCtrlSetup : UserControl, ISetupForm
{
private const string CUSTOM_MODULE_NAME_SETUP = "StampOnScanProcess.Setup";
private AdminApplication adminApplication;
public AdminApplication Application
{
set
{
value.AddMenu(CUSTOM_MODULE_NAME_SETUP, CUSTOM_MODULE_NAME_SETUP, "BatchClass");
adminApplication = value;
}
}
public void ActionEvent(int EventNumber, object Argument, out int Cancel)
{
Cancel = 0;
if ((KfxOcxEvent)EventNumber == KfxOcxEvent.KfxOcxEventMenuClicked && (string)Argument == CUSTOM_MODULE_NAME_SETUP)
{
FrmSetup form = new FrmSetup();
form.ShowDialog(adminApplication.ActiveBatchClass);
}
}
}
I modified my registration file and added the setup form to it
[Modules]
StampOnScanProcess
[StampOnScanProcess]
RuntimeProgram=StampOnScanProcess.exe
ModuleID=StampOnScanProcess.exe
Description=...
Version=10.2
SupportsNonImageFiles=True
SupportsTableFields=True
SetupProgram=StampOnScanProcess.Setup
[Setup Programs]
StampOnScanProcess.Setup
[StampOnScanProcess.Setup]
Visible=0
OCXFile=StampOnScanProcess.exe
ProgID=StampOnScanProcess.Setup
When launching the Administration module I head over to the Batch Class Properties => Queues and want to call this setup form by clicking the Properties button in the middle.
Unfortunately the properties button is disabled so I can't open the setup form. This form gets added to the context menu of the batch class
How can I bind this form to the properties button instead? And what is the best way to store configured data and access it when the runtime application gets executed?
I need to think about how to store data because some users have user profiles
and the runtime application currently logs in with no credentials.
public void LoginToRuntimeSession()
{
login = new Login();
login.EnableSecurityBoost = true;
login.Login();
login.ApplicationName = CUSTOM_MODULE_ID;
login.Version = "1.0";
login.ValidateUser($"{CUSTOM_MODULE_ID}.exe", false, "", "");
session = login.RuntimeSession;
}
So it might happen that I have to store the credentials on setup too.
How can I bind this form to the properties button instead?
All interactions with menu entries are handled by ISetupForm.ActionEvent. New entries are added with the AddMenu method of the AdminApplication object. Kofax differentiates between multiple entries by name - imagine that you could have multiple menu entries at the same time, one on batch class level, another one on document class level, and another one in the ribbon - just to name a few examples. Kofax uses the same approach in any component that integrates into Administration (e.g. Custom Modules or Workflow Agents).
This is an example from one of our components. Note that three entries are added on BatchClass level and two more on DocumentClass level.
value.AddMenu("BatchClass.GeneralConfig", "Field Panel - General Configuration", "BatchClass");
value.AddMenu("BatchClass.FieldEditor", "Field Panel - Configure Batch Fields", "BatchClass");
value.AddMenu("DocumentClass.FieldEditor", "Field Panel - Configure Index Fields", "DocumentClass");
value.AddMenu("CopyBatchFieldConfig", "Field Panel - Copy Batch Field Configuration", "BatchClass");
value.AddMenu("PasteBatchFieldConfig", "Field Panel - Paste Batch Field Configuration", "BatchClass");
value.AddMenu("CopyIndexFieldConfig", "Field Panel - Copy Index Field Configuration", "DocumentClass");
value.AddMenu("PasteIndexFieldConfig", "Field Panel - Paste Index Field Configuration", "DocumentClass");
Each entry is no identified by its event text, the first parameter. For example, BatchClass.GeneralConfig is intended to open up a generic configuration dialog - on batch class level.
Now, back to our ActionEvent - this is how I distinguish between the entry selected by the user:
if ((KfxOcxEvent)EventNumber == KfxOcxEvent.KfxOcxEventMenuClicked)
{
AdminForm form = new AdminForm();
switch ((string)Argument)
{
case "BatchClass.GeneralConfig":
ConfigureGeneral(kcApp.ActiveBatchClass);
break;
[I] want to call this setup form by clicking the Properties button in
the middle.
I don't know if you can use this button - I would assume yes - yet personally I tend to put settings either on batch or document class level. For example - your PDF annotation settings may different from document class to class - having an entry on this level seems more natural.
And what is the best way to store configured data and access it when
the runtime application gets executed?
Custom Storage Strings, and you can let your imagination run wild here. The most simplistic approach is to store key-value pairs during setup, and retrieve them in runtime. Here's a generic call (BatchClass is an IBatchClass object, i.e. a pointer to the ActiveBatchClass property of the AdminApplication object):
// set a CSS
BatchClass.set_CustomStorageString(name, value);
// get a CSS
BatchClass.get_CustomStorageString(name)
I usually use a single custom storage string only and store custom object - the object is a base64-encoded serialized XML using XmlSerializer - but again, that's up to you. The only recommendation is to rely on CSS only - don't use external files to store configuration parameters. A CSS is an integral part of your batch class - so, when exporting said class and importing it on a different system, your entire configuration will be there.
I need to think about how to store data because some users have user
profiles
Usually, you don't need to worry about that. The properties for user and password in ValidateUser are entirely optional - and since you're planning to write an unattended module - ideally a Windows Service, credentials should be maintained there. Kofax and Windows would automatically make sure the credentials are passed on, and your module will run under this user's context. Just make sure the user has permissions for the module and all associated batch classes. It's different if you're planning to write an attended module, for example an enhanced Validation module.
I'm developing a feature that will be provisioned to a SharePoint 2007 web. The feature config files are below.
What I want to happen when the feature is installed and activated:
A list named xxx to be created under the web where the feature is
activated.
A folder named yyy to be created under that list.
The file page1.aspx to be placed under that folder.
For now I get errors when trying to activate the feature, but if I create the list and the folder manually then the file is placed there.
So the question is, how do I make sure the list and the folder are created automatically?
feature.xml
<?xml version="1.0" encoding="utf-8"?>
<Feature Id="5EAAAAD9-E885-43f8-B2FD-4C63271E7BAA"
Title="ABC"
Description="ABC"
Version="1.0.0.0"
Hidden="FALSE"
Scope="Web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml"/>
<ElementFile Location="CustomPages/yyy/page1.aspx" />
</ElementManifests>
</Feature>
elements.xml
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Module1" Url="xxx/yyy" RootWebOnly="TRUE" Path="CustomPages/yyy">
<File IgnoreIfAlreadyExists="TRUE" Type="GhostableInLibrary" Url="page1.aspx"></File>
</Module>
</Elements>
If you can use custom code you can override the FeatureActivated event receiver of the feature to create the list and folder.
You will need to create another feature to run before the feature that provisions the files and give the file provision feature a dependency on the first to ensure that the list is available.
Alternatively you can add a list definition and instance via xml. I personally avoid this route whenever possible but you can find guidance for this at MSDN - Creating List Definitions with Custom List Columns for SharePoint Server 2007
Example for adding a list via code (caveat - I don't have 2007 to test against but this does work on 2010 and I don't think I've used any 2010 specific code)
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWeb web = properties.Feature.Parent as SPWeb; // Assuming web scoped feature
SPList customPagesList;
bool listExists = true;
//Check to see if the list exists, this method sucks but 2007 doesn't have web.TryGetList()
try
{
customPagesList = web.GetList("/CustomPages"); // server relative url of the list
}
catch (FileNotFoundException e)
{
listExists = false;
}
if (!listExists)
{
// Create list and record returned guid
Guid customPagesListGuid = web.Lists.Add("CustomPages",
"Library to store web pages used in the site", SPListTemplateType.DocumentLibrary);
//Get list from stored guid
customPagesList = web.Lists[customPagesListGuid];
// Set list properties and add required content types
customPagesList.Title = "CustomPages";
customPagesList.OnQuickLaunch = false; // Set to true to display on the quick launch
customPagesList.ContentTypesEnabled = true;
customPagesList.NoCrawl = true; // Set to false if you want pages indexed by search
customPagesList.EnableFolderCreation = true;
customPagesList.EnableSyndication = false; // Turn off rss
SPContentType webPartPageCT = web.AvailableContentTypes[SPBuiltInContentTypeId.WebPartPage];
SPContentType basicPageCT = web.AvailableContentTypes[SPBuiltInContentTypeId.BasicPage];
customPagesList.ContentTypes.Add(webPartPageCT);
customPagesList.ContentTypes.Add(basicPageCT);
// Remove the default content type added on list creation if it is not needed
DeleteContentType(customPagesList.ContentTypes, "Document");
// Commit changes
customPagesList.Update();
//Get library from stored guid
SPDocumentLibrary customPagesLibrary = (SPDocumentLibrary)web.Lists[customPagesListGuid];
customPagesLibrary.Folders.Add("/Lists/CustomPages/yyy", SPFileSystemObjectType.Folder);
string rootFolderUrl = customPagesLibrary.RootFolder.ServerRelativeUrl;
SPListItem newFolder = customPagesLibrary.Folders.Add(rootFolderUrl, SPFileSystemObjectType.Folder, "yyy");
newFolder.Update();
}
}
private void DeleteContentType(SPContentTypeCollection ctCollection, string ctName)
{
foreach (SPContentType ct in ctCollection)
{
if (ct.Name.Equals(ctName))
{
ct.Delete();
}
}
}
After upgrading some of our external websites running on SharePoint 2007 to 2010, we ran a link checker to find problems. We noticed the log showed requests for a file called spsdisco.aspx. Indeed, when examining the source of our web pages, SharePoint is adding the following link element to the page HEAD:
<link href="_vti_bin/spsdisco.aspx" rel="alternate" type="text/xml" />
This is a web service discovery file listing out the names and locations of all of SharePoint's web service endpoints. Even worse, this file is starting to show up in search indexes. At best it is embarrassing; at worst it's a potential vulnerability (these are external websites). Because it's a virtual file, it shows up under every site and subsite, so a manual approach to "hiding" each one is difficult and clumsy.
I can't seem to find any actual documentation about it -- a few references on updating it to include a custom web service, but that's about it. How might we approach a reliable, top-down approach to disabling access to these pages? I think we can find a way to suppress the LINK element in the page, but that's just obscuring the problem.
Is there a location in SharePoint (Site or Central Admin) to turn it off? Would you just add some request filtering to IIS to disallow access to SPSdisco.aspx and the ASMX files?
Update: On Kev's suggestion, I've cross-posted to sharepoint.stackexchange.com.
Update 2: See, I hadn't abandoned this question. We finally had time to get some MS guidance and build a deployable SharePoint solution to address the issue.
As a quick fix I would add a request filtering rule to deny access to SPSDisco.aspx.
But you might want to ask on the new SharePoint Stack Exchange site about a more robust fix:
https://sharepoint.stackexchange.com/
Here is the solution that we arrived at. It was in part based on recommendations by our Microsoft representative, so you might consider this an unofficial, "official" approach.
First, we need keep SharePoint from advertising the disco file to the world (i.e. Google). Simply remove the following line in your master pages:
<SharePoint:SoapDiscoveryLink runat="server"/>
This will suppress the <link href="/_vti_bin/spsdisco.aspx" rel="alternate" type="text/xml"> reference in the HEAD of your pages.
Next, we want to make sure that unauthorized users don't have access to the web services described by the disco file, or anything in _vti_bin for that matter. If your site only runs internal to your firewall (an intranet, for example), then this isn't as important. But if you've got anonymous endpoints that can be accessed externally, you want them locked down.
This is an excellent application for an HttpModule. We'll build one that intercepts any request containing _vti_bin in the path, and if the current user is unauthorized will return a 404 NOT FOUND status code. I chose to return a 404 rather than a 401 UNAUTHORIZED because I don't just want to lock those paths down, I want to hide the fact that anything even exists at those paths.
Our HttpModule looks like this:
using System;
using System.Web;
namespace Custom.SharePoint.HttpModule.SpSecureVtiBin {
public class SpSecureVtiBinModule : IHttpModule {
#region IHttpModule Members
public void Dispose() { }
public void Init( HttpApplication context ) {
context.AuthorizeRequest += new EventHandler( context_AuthorizeRequest );
}
protected virtual void context_AuthorizeRequest( object sender, EventArgs e ) {
HttpApplication app = (HttpApplication)sender;
string requestedPath = app.Request.Path;
if ( requestedPath.ToLowerInvariant().Contains( "_vti_bin" ) ) {
if ( !app.Request.IsAuthenticated ) {
app.Response.StatusCode = 404;
app.Response.StatusDescription = "Not Found";
app.Response.Write( "404 NOT FOUND" );
app.Response.End();
}
}
}
#endregion
}
}
Simple enough. To use the HttpModule, it needs to be registered in the site's web.config file with an entry under \configuration\system.webServer\modules:
<add name="SpSecureVtiBinModule" type="Custom.SharePoint.HttpModule.SpSecureVtiBin.SpSecureVtiBinModule, Custom.SharePoint.HttpModule.SpSecureVtiBin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[your_public_key_token]" />
Of course, we don't want to modify a SharePoint application's web.config file manually. We'll create an SPFeatureReceiver to do the job:
using System.Collections.ObjectModel;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace Custom.SharePoint.HttpModule.SpSecureVtiBin {
public class ModuleFeatureReceiver : SPFeatureReceiver {
private static string _owner = "SpSecureVtiBinModule";
public override void FeatureActivated( SPFeatureReceiverProperties properties ) {
SPWebApplication app = (SPWebApplication)properties.Feature.Parent;
app.WebConfigModifications.Add( GetModificationForSystemWebServer() );
app.WebService.ApplyWebConfigModifications();
app.Update();
}
public override void FeatureDeactivating( SPFeatureReceiverProperties properties ) {
SPWebApplication app = (SPWebApplication)properties.Feature.Parent;
Collection<SPWebConfigModification> mods = app.WebConfigModifications;
int modCount = mods.Count;
bool modRemoved = false;
for ( int i = modCount - 1; i >= 0; i-- ) {
SPWebConfigModification mod = mods[i];
if ( mod.Owner.Equals( _owner ) || mod.Owner.Equals( "CHK.SharePoint.HttpModule.SpSecureVtiBin.SpSecureVtiBinModule" ) ) {
app.WebConfigModifications.Remove( mod );
modRemoved = true;
}
}
if ( modRemoved ) {
app.WebService.ApplyWebConfigModifications();
app.Update();
}
}
private SPWebConfigModification GetModificationForSystemWebServer() {
return new SPWebConfigModification {
Name = "add[#name='SpSecureVtiBinModule']",
Owner = _owner,
Path = "configuration/system.webServer/modules",
Value = #"<add name=""SpSecureVtiBinModule"" type=""Custom.SharePoint.HttpModule.SpSecureVtiBin.SpSecureVtiBinModule, Custom.SharePoint.HttpModule.SpSecureVtiBin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=[your_public_key_token]"" />",
Sequence = 0
};
}
}
}
Now all that's left is to package up the HttpModule. You'll need to define a Feature in the package and reference the SPFeatureReceiver class. This will cause the web.config entry to be added when the Feature is activated, and the entry to be removed when the Feature is deactivated. Target the Feature for a WebApplication and the assembly deployment target to GlobalAssemblyCache.
I'm logged in as the System Account, so it's probably not a "real access denied"!
What I've done :
- A custom master page
- A custom page layout from a custom content type (with custom fields)
If I add a custom field (aka "content field" in the tools in SPD) in my page layout, I get an access denied when I try to edit a page that comes from that page layout.
So, for example, if I add in my page layout this line in a "asp:content" tag :
I get an access denied. If I remove it, everyting is fine. (the field "test" is a field that comes from the content type).
Any idea?
UPDATE
Well, I tried in a blank site and it worked fine, so there must be something wrong with my web application :(
UPDATE #2
Looks like this line in the master page gives me the access denied :
<SharePoint:DelegateControl runat="server" ControlId="PublishingConsole" Visible="false"
PrefixHtml="<tr><td colspan="0" id="mpdmconsole" class="s2i-consolemptablerow">"
SuffixHtml="</td></tr>"></SharePoint:DelegateControl>
UPDATE #3
I Found http://odole.wordpress.com/2009/01/30/access-denied-error-message-while-editing-properties-of-any-document-in-a-moss-document-library/
Looks like a similar issue. But our Sharepoint versions are with the latest updates. I'll try to use the code that's supposed to fix the lists and post another update.
** UPDATE #4**
OK... I tried the code that I found on the page above (see link) and it seems to fix the thing. I haven't tested the solution at 100% but so far, so good. Here's the code I made for a feature receiver (I used the code posted from the link above) :
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Xml;
namespace MyWebsite.FixAccessDenied
{
class FixAccessDenied : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
FixWebField(SPContext.Current.Web);
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
static void FixWebField(SPWeb currentWeb)
{
string RenderXMLPattenAttribute = "RenderXMLUsingPattern";
SPSite site = new SPSite(currentWeb.Url);
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
web.Update();
SPField f = web.Fields.GetFieldByInternalName("PermMask");
string s = f.SchemaXml;
Console.WriteLine("schemaXml before: " + s);
XmlDocument xd = new XmlDocument();
xd.LoadXml(s);
XmlElement xe = xd.DocumentElement;
if (xe.Attributes[RenderXMLPattenAttribute] == null)
{
XmlAttribute attr = xd.CreateAttribute(RenderXMLPattenAttribute);
attr.Value = "TRUE";
xe.Attributes.Append(attr);
}
string strXml = xe.OuterXml;
Console.WriteLine("schemaXml after: " + strXml);
f.SchemaXml = strXml;
foreach (SPWeb sites in site.AllWebs)
{
FixField(sites.Url);
}
}
static void FixField(string weburl)
{
string RenderXMLPattenAttribute = "RenderXMLUsingPattern";
SPSite site = new SPSite(weburl);
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
web.Update();
System.Collections.Generic.IList<Guid> guidArrayList = new System.Collections.Generic.List<Guid>();
foreach (SPList list in web.Lists)
{
guidArrayList.Add(list.ID);
}
foreach (Guid guid in guidArrayList)
{
SPList list = web.Lists[guid];
SPField f = list.Fields.GetFieldByInternalName("PermMask");
string s = f.SchemaXml;
Console.WriteLine("schemaXml before: " + s);
XmlDocument xd = new XmlDocument();
xd.LoadXml(s);
XmlElement xe = xd.DocumentElement;
if (xe.Attributes[RenderXMLPattenAttribute] == null)
{
XmlAttribute attr = xd.CreateAttribute(RenderXMLPattenAttribute);
attr.Value = "TRUE";
xe.Attributes.Append(attr);
}
string strXml = xe.OuterXml;
Console.WriteLine("schemaXml after: " + strXml);
f.SchemaXml = strXml;
}
}
}
}
Just put that code as a Feature Receiver, and activate it at the root site, it should loop trough all the subsites and fix the lists.
SUMMARY
You get an ACCESS DENIED when editing a PAGE or an ITEM
You still get the error even if you're logged in as the Super Admin of the f****in world (sorry, I spent 3 days on that bug)
For me, it happened after an import from another site definition (a cmp file)
Actually, it's supposed to be a known bug and it's supposed to be fixed since February 2009, but it looks like it's not.
The code I posted above should fix the thing.
Try to publish your MasterPage and Page Layouts, this is the most common reason. Since the system account is godmode, it wont get that error.
In SharePoint Designer you cannot do the last step in the publishing workflow (Approval), so you:
SharePoint Designer:
CheckIn => Publish Major Version, hit the OK button or go to /_catalogs/masterpage on the site .
Then and use the Context Menu to Approve the MasterPage and Layouts.
Some ideas:
Check if any web parts in your custom Page Layout and Master Page are not registered as safe.
Did you define your own custom field type, like write a class which extends SPField? If so, are you using a custom Field Control? If you are, check if it is doing anything which may need elevated privileges.
Likewise, check for any edit mode panels containing web parts of web controls which might be trying to do something which needs elevated privileges.
See the code I've posted in the edit of the post. It fixed my problem.
The problem appears to be caused by an error in the stsadm -o export function in certain versions of SharePoint (I got it doing an export from a 2007 RTM MOSS server). Importing the bogus export file causes the "edit-denied-access" problem in all NEWLY-CREATED lists. The patches for later version from Microsoft fix stsadm -o export, but DO NOT FIX the broken lists; that requires a procedure like tinky05's.
We are trying to create a custom event handler that would fire on ItemAdded event. The event handler then updates the document with a unique ID value to a column in this document library.
Our solution works fine, except when a user on Vista is trying to save a new document from Office 2007. In this scenario the document is stored to document library but Unique ID column is empty, and there is no exception.
Vista Users can upload document(s) to library without a problem. Everything else works fine on XP and Win2k3 operating systems.
Has anyone seen something similar, and what might be the problem here? To demonstrate the problem we are using DateTime.Now as unique ID.
using Microsoft.SharePoint;
public class TestReciever : SPItemEventReceiver
{
public override void ItemAdded(Microsoft.SharePoint.SPItemEventProperties properties)
{
try {
DisableEventFiring();
properties.ListItem("UniqueID Column") = DateTime.Now.ToString();
properties.ListItem.SystemUpdate();
EnableEventFiring();
}
catch (Exception ex) {
// handle exception
}
}
}
We have noticed exactly the same thing happening. When the 2007 doc is added to the library, the properties from the list are added to it (blank).
Then the (synchronous) event handler is called, updating the list with the correct values for the UniqueID column.
Then the inbuilt property mapping to 2007 docs kicks in and overwrites your values with those stored in the 2007 doc (not raising an item updated event again).
This means that the list now has a blank for your column.
If you change to an asynchronous event you may see what we did and that the slight delay for asynchronous meant that the property mapping to 2007 docs happened first (we think) meaning the value was stored correctly.
It is possible to break the property mapping between the list and office, but that is only a workaround.
I cannot for the life of me find where this info is on an MS site, but it is a known problem. Perhaps you can hang in there for SP2 (may even be fixed in SP1, but am unsure).
At the end we contacted Microsoft and they provided us with the following workaround solution. The key here is to delay item update in a separate thread.
private Guid listID;
private Guid itemID;
private Guid siteID;
public override void ItemAdded(Microsoft.SharePoint.SPItemEventProperties properties)
{
DisableEventFiring();
item = properties.ListItem;
listID = properties.ListId;
itemID = properties.ListItem.UniqueId;
siteID = properties.SiteId;
Threading.Thread setDocumentInternalIDThread = new Threading.Thread(SetInternalID);
setDocumentInternalIDThread.Start();
EnableEventFiring();
}
private void SetInternalID()
{
try {
Threading.Thread.Sleep(10000);
using (SPSite site = new SPSite(siteID)) {
using (SPWeb web = site.OpenWeb()) {
SPList list = web.Lists(listID);
SPListItem item = list.Items(itemID);
item(Common.CustomID) = Common.GetAlphaPrefix() + Common.GetDocNumber();
item.SystemUpdate();
}
}
}
catch (Exception ex) {
Log(ex.Message);
}
}
Another workaround is to set the custom field that is being updated in the event receiver to be read only (set the fields ReadOnly property to TRUE). This has a few pros and cons. This is ideal for a unique id solution, because there is no way for the user to modify the value. However, the field is hidden to the UI, so managing it becomes more troublesome, and the field can't be used in labels, or quick parts. You can set the ShowInDisplayForm property of the field to TRUE to have the field display in the view form, but not the edit form. The field can also be added to your views. This is the best workaround I have found for this issue to date.
This is an issue with Office 2007 and the WSS 3.0 xml parser. When the properties are promoted from the Office 2007 document, the parser attempts to assign document properties to the list properties in SharePoint (property promotion). The problem is that this event occurs after any ItemAdded, or ItemUpdated events, so the property gets overwritten with the value in the document, which is of course, blank. Microsoft has raised this as a bug, and I was hoping for a fix in SP2, but no such luck.