Mystery of the _Hidden Event Content Type

Earlier this week I was putting together a demo for an upcoming user group meeting about lists and content types where I was creating a new content type that would support some contact information and some calendaring information. The content type is used in two different lists. One is a contact list that can be connected to Outlook as a contact view and the other is a calendar that can be connected to Outlook as a calendar.

I started out by creating a new content type based on Contact, added a couple of custom site columns, and then attempted to add Start Date, End Date, and Location from the Event contact type. But, to my surprise, they were not listed!

After a little investigation I realized that the Event content type is in the _Hidden group! Now, I don’t know why this is, but I suspect the reason rhymes with ‘hug’. First, because it doesn’t make any sense and I couldn’t find any explanation of why this might be on the web. The second is because the WSS SDK says that it is part of the List Content Types group, not the _Hidden group. Whatever the reason, I needed those fields!

Fortunately, you can put the Event content type into the List Content Types Group pretty easily via the UI without resorting to writing any code or altering the feature or site definitions.

  1. Create a new Calendar list by choosing Create from the Site Actions menu.

    and choosing Calendar

  2. Fill out the form and click OK
  3. Open the list settings by choosing List Settings from the Settings menu.
  4. By default, Lists do not allow management of the content types they are based on. Which is a double edged sword because while it makes things easier for the user it also increases the chances that someone will edit a field in a list, like a calculation, that later gets stomped by an update to the content type. But I digress… To access the Event content type we need to configure the list to allow management of content types. Click advanced settings under General Settings.

  5. On the Advanced Settings page select Yes for ‘Allow management of content types?’ and click OK.
  6. A new section is now displayed on the list settings page for the content types used by the list. To access the Event type click the Event link.

  7. On the subsequent page click the Event link beside the Parent: label.

  8. On the Content Type Information Screen follow the Name, description, and group link
  9. Change the Group to List Content Types and click OK

That’s it. Now you can get at the event columns when creating your own content types and choose Event as the parent if you want to create new Site Content Types based on Event.

If you want to rehide the type, simply edit it from the Site Content Type gallery on Site Settings and assign it to the _Hidden group.

Author: Doug Ware

Easy WSS Forms with Workflow and Anonymous Access

I am just having loads of fun with the anonymous membership provider. I mentioned in a previous post that the provider approach solves a wider range of issues than merely allowing anonymous types to upload files to a document library.

For example, you can’t create a workflow that is invoked by an anonymous user in WSS. Here is a thread on the MSDN forums about the issue. The solution the developer came up with was to muck about with the list forms on the list in question, just as I was contemplating before Søren Nielsen bought me a ticket on the clue train with his suggestion of a custom provider.

As I announced a few days ago, we are now signing up folks for the SharePoint developer core skills classes in November and December. I thought it would be nice to create a form to allow folks to request more information. Now that I have the ability to do work flows without authentication, I thought it would be fun to create the form using plain old WSS workflow via SharePoint Designer instead of creating a web part or user control. As it turns out, this is a lot easier than creating, deploying, and building a web part page using a control.

You can see the result here. If you fill out the form and hit ‘Submit Request’ I will get an email.

It’s worth noting that this technique works well for creating a form quickly even when the anonymous scenario does not exist. If you need a quick and easy data entry form for WSS, follow the steps below and ignore the bits about Anon. Just make sure your users have permissions to contribute to the list that starts the work flow and if the work flow adds items to any other lists then those as well!!!

How it’s done

The first thing you need to do is add Anon to the site’s Visitors group. WSS workflow is, to put it charitably, picky about permissions. Even though I had given Anon specific access to the list where the workflow was attached, it didn’t work until I added Anon to the site. It occurs to me I probably should have done that anyway. And so, even though, it was one of the last things I did, just save yourself some trouble if you set the provider up previously and do that now if you haven’t already.

Anyway…

Workflow in WSS is designed to work against list items. A workflow can be started manually, when an item is created, or when an item changes. Whichever way, if you want a workflow you need a list item and to have a list item, of course, you need a list. Therefore step one in this exercise is to create a custom list and add an item.

  • Create a new custom list

  • Add an item to the list

  • Give Anon ‘Contribute’ permissions to the list


  • Open the site in SharePoint Designer
  • Form the File menu, choose New | Workflow
  • The Workflow Designer will open. Fill the form out as follows:
  • Click the Initiation button. This is where we will define the fields for our form.
  • Add the following fields:

Your Name

Single line of text

 

Company

Single line of text

 

eMail

Single line of text

 

Phone

Single line of text

 

Contact Preference

Choice (menu to choose from)

Class

Choice (menu to choose from)

The parameters form should now look like this:

Click OK

  • Click Next on the Workflow Designer and then click Actions and choose "Send an email" to add an action. Because we want this workflow to happen every time we will not create any conditions.
  • Click the this message link and fill out the email form as follows except using your own email address!:
  • Add fields from the form the user filled out by clicking Add Lookup to Body. Choose ‘Workflow Data’ from the Source dropdown to populate the Field with the fields from step 8.

    Create an email body that has all of the information and wind up with something like this:

    and click OK.

  • Finally click Finish in the Workflow Designer to deploy the new workflow.
  • At this point you may want to tidy up the workflow form a bit. In my case I wanted to change the text on the buttons and do some styling. You can find it in the folder list in SharePoint Designer and edit it as you would any form. However, you will want to back up any edits you make because if you edit the workflow SPD will regenerate the form and stomp your changes!
  • Great! So now we have all the pieces we need. To finish it up we need to create a link to our new form and put it somewhere. We can’t simply link to the form itself however because the workflow requires a list item to attach itself to. To get the link, navigate to the list created in step 1 and choose Workflows from the item dropdown.

  • Right click the Request Workflow link and choose Properties
  • Copy the address, in this case, /Workflows/Request%20Workflow/Request%20Workflow.aspx?List=ea10f5a1-5401-4c96-bcbf-d91db261dd76&ID=1&TemplateID={d4d1f324-bfcf-44f6-8f10-efdd31400650}&Source=http%3A%2F%2Finternal%2Eskinner%2Edemo%2FLists%2FWorkflow%2520List%2FAllItems%2Easpx. That is the url you will use to access the form and it will always work unless you delete the item! I recommend hiding the list to keep anyone from doing it accidentally. If someone is savvy enough and wants to be a jerk they still can get to the item by parsing out the link. So, this does have the disadvantage of not being as robust as a handmade control, but it is way easier. This is one case where SharePoints seemingly arbitrary need to force certain levels of permissions actually make it less secure in my opinion.
  • Test your form by clicking the link and filling it out. If you did everything right you will get an email with all the nice email with all of the information, but you also wound up redirected to that list and that is no good at all! So there is one last thing we need to do to make this solution complete!
  • We can’t fix the redirect on the workflow form because it is actually taking place on the server and we have no way to change it. So instead we have to use some simple java script to do the redirect on the client side.
    Return to SharePoint Designer and edit the AllItems.aspx form for the list.
  • Find the PlaceHolderAdditionalPageHead place holder and change it to include the script.
    <asp:content contentplaceholderid="PlaceHolderAdditionalPageHead" runat="server">

        <SharePoint:RssLink runat="server"/>

        <script>

        alert(‘Thank you for your interest. We will contact you shortly.’);

        window.location = ‘http://www.someotherUrl/’;

        </script>

    </asp:content>

  • Save your work!
  • Finally, (shew!) hide the list from the browser and from the all items list by right clicking the list and choosing properties.
  • Check Hide from Browsers and hit OK

That’s it! Enjoy!!!

Now get out there and build some forms.

Author: Doug Ware

Good Week

I’m having a good week!

On Monday I attended the Halo 3 launch party at Microsoft. Everyone had a great time although I am a better target than a killer in a death match.

That same day, Skinner crossed the 1000 download mark in just under one month! I knew WSS needed it, but when I put it up I said I’d be happy with a 1000 downloads ever. I’ve gotten a few good feature requests that I am working on. Look for a new release soon!

Finally, my new ecommerce site, RealNiceRugs, officially launched. If you need something to wipe your feet on, check it out! Below is one of my wares, the most excellent weather forecast rug! Get one… it’s never wrong.

Anonymous Access to Add Items to Document Libraries – Solution

Yesterday I received a comment from Søren Nielsen on my earlier post on this subject:

"Hi

We have had the same problem as you – SharePoint don’t seem to be designed to have any interaction with anonymous users.

We did:
1. created an ordinary SharePoint site
2. extended the site and enabled forms authentication on the new site, using our very own custom build forms authentication, that just handles one "Guest" account
3. Gave the new Guest account proper rights to the list in question
4. Create a auto login page
5. Connected the dots, and the anonymous user is now signed in as Guest (through our own forms authentication provider) and can do whatever we grant the Guest the liberty to.

I’m not (yet) at liberty of sharing the exact solution.

Hope it helps.

soerennielsen.wordpress.com"

This is a much better solution than the one I was contemplating because it solves a wider range of issues than simply mucking about with the lists in question!

I was able to refine the approach slightly by simply using an event in global.asax instead of creating an auto login page as Søren suggests for step 4. You can now upload themes to the theme library!

Here is a brief overview of the solution. It assumes a basic understanding of membership providers and forms based authentication as it relates to SharePoint. And it assumes you have already created a site and extended it so that you have two zones, one for the public and one for authoring. If you don’t know what I mean by that, you need to do some reading before going any further. Here is an excellent article on the subject by my friend Dan Attis.

You can download the code and example configuration files here.

Step 1. Create a simple membership provider

All we want to do is create a named identity for our anonymous web friends that we can use to assign permissions. This makes the membership provider very simple. Aside from telling the site that the name is valid we need it to also return a list containing the anonymous user in a few methods so that the people picker works and so we can actually add the user to the site.

The complete code is available in the downloads section, but here are the applicable snippets.

public class AnonymousMembershipProvider : MembershipProvider

{

//If the user is named "Anon" it is valid. Otherwise something funny is going on!

public override bool ValidateUser(string username, string password)

{

if (username == "Anon")

return true;

else

return false;

}

 

//Create a MembershipUserCollection consisting of our single user.

private MembershipUserCollection GetMembers()

{

MembershipUserCollection users = new MembershipUserCollection();

users.Add(new MembershipUser("AnonymousMembershipProvider", "Anon",

"Anon", string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue));

return users;

}

 

//These mthods are used by SharePoint to get names for People Picker

//and to validate the name when adding it to People and Groups.

public override MembershipUserCollection FindUsersByName(string usernameToMatch,

int pageIndex, int pageSize, out int totalRecords)

{

totalRecords = 1;

return GetMembers();

}

 

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)

{

totalRecords = 1;

return GetMembers();

}

 

public override MembershipUser GetUser(string username, bool userIsOnline)

{

return new MembershipUser("AnonymousMembershipProvider", "Anon", "Anon",

string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue);

}

 

public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)

{

return new MembershipUser("AnonymousMembershipProvider", "Anon", "Anon",

string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue);

}

Step 2. Install the AnonymousMembershipProvider to the Global Assembly Cache

 

1.

2.

3.

4.

 

Step 3. Configure the Web.Config of the FBA site

In the <SharePoint> section configure a key for the people picker:
<PeoplePickerWildcards>

<clear />

<add key="AnonymousMembershipProvider" value="%" />

</PeoplePickerWildcards>

Configure <system.web> as so:

<authentication mode="Forms">

<forms loginUrl="/_layouts/login.aspx" />

</authentication>

<identity impersonate="true" />

<authorization>

<allow users="*" />

</authorization>

<membership defaultProvider="AnonymousMembershipProvider">

<providers>

<add name="AnonymousMembershipProvider" type="AnonymousMembershipProvider, AnonymousMembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=701fdd64fcd5ceb2" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

</providers>

</membership>

If you know your web.config you may have noticed that this site is still configured for anonymous access. The next step will assign our named "Anon" user during the authentication.

Step 4. Modify Global.asax on the FBA site to Log in as Anon

<%@ Assembly Name="Microsoft.SharePoint"%>

<%@ Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication" %>

 

<script RunAt=’server’>

 

public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)

{

if (Membership.ValidateUser("Anon", ""))

{

FormsAuthentication.SetAuthCookie("Anon", true);

}

}

 

</script>

Step 5. Configure the Web.Config of the Authoring Site

In the <SharePoint> section configure a key for the people picker:
<PeoplePickerWildcards>

<clear />

<add key="AnonymousMembershipProvider" value="%" />

</PeoplePickerWildcards>

Configure <system.web> as so:

<authentication mode="Windows" />

<identity impersonate="true" />

<authorization>

<deny users="?" />

<allow users="*" />

</authorization>

<membership defaultProvider="AnonymousMembershipProvider">

<providers>

<add name="AnonymousMembershipProvider" type="AnonymousMembershipProvider, AnonymousMembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=701fdd64fcd5ceb2" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

</providers>

</membership>

Even though the authoring site is configure to use Windows authentication, we still need access to the provider so that permissions can be assigned.

Step 6. Configure the Permissions

On the authoring site, do the following.

1.

2.

3.

4.

 

That’s it! If the SharePoint gods are smiling on you, you can now allow anonymous type folks to upload files, add attachments to lists, and generally have more control over your anonymous permissions!

Author: Doug Ware

Anonymous Access to Add Items to Document Libraries – Solution

Yesterday I received a comment from Søren Nielsen on my earlier post on this subject:

"Hi

We have had the same problem as you – SharePoint don’t seem to be designed to have any interaction with anonymous users.

We did:
1. created an ordinary SharePoint site
2. extended the site and enabled forms authentication on the new site, using our very own custom build forms authentication, that just handles one "Guest" account
3. Gave the new Guest account proper rights to the list in question
4. Create a auto login page
5. Connected the dots, and the anonymous user is now signed in as Guest (through our own forms authentication provider) and can do whatever we grant the Guest the liberty to.

I’m not (yet) at liberty of sharing the exact solution.

Hope it helps.

soerennielsen.wordpress.com"

This is a much better solution than the one I was contemplating because it solves a wider range of issues than simply mucking about with the lists in question!

I was able to refine the approach slightly by simply using an event in global.asax instead of creating an auto login page as Søren suggests for step 4. You can now upload themes to the theme library!

Here is a brief overview of the solution. It assumes a basic understanding of membership providers and forms based authentication as it relates to SharePoint. And it assumes you have already created a site and extended it so that you have two zones, one for the public and one for authoring. If you don’t know what I mean by that, you need to do some reading before going any further. Here is an excellent article on the subject by my friend Dan Attis.

You can download the code and example configuration files here.

Step 1. Create a simple membership provider

All we want to do is create a named identity for our anonymous web friends that we can use to assign permissions. This makes the membership provider very simple. Aside from telling the site that the name is valid we need it to also return a list containing the anonymous user in a few methods so that the people picker works and so we can actually add the user to the site.

The complete code is available in the downloads section, but here are the applicable snippets.

public class AnonymousMembershipProvider : MembershipProvider

{

//If the user is named "Anon" it is valid. Otherwise something funny is going on!

public override bool ValidateUser(string username, string password)

{

if (username == "Anon")

return true;

else

return false;

}

 

//Create a MembershipUserCollection consisting of our single user.

private MembershipUserCollection GetMembers()

{

MembershipUserCollection users = new MembershipUserCollection();

users.Add(new MembershipUser("AnonymousMembershipProvider", "Anon",

"Anon", string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue));

return users;

}

 

//These mthods are used by SharePoint to get names for People Picker

//and to validate the name when adding it to People and Groups.

public override MembershipUserCollection FindUsersByName(string usernameToMatch,

int pageIndex, int pageSize, out int totalRecords)

{

totalRecords = 1;

return GetMembers();

}

 

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)

{

totalRecords = 1;

return GetMembers();

}

 

public override MembershipUser GetUser(string username, bool userIsOnline)

{

return new MembershipUser("AnonymousMembershipProvider", "Anon", "Anon",

string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue);

}

 

public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)

{

return new MembershipUser("AnonymousMembershipProvider", "Anon", "Anon",

string.Empty, string.Empty, string.Empty, true, false,

DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,

DateTime.MinValue, DateTime.MinValue);

}

Step 2. Install the AnonymousMembershipProvider to the Global Assembly Cache

 

1.

2.

3.

4.

 

Step 3. Configure the Web.Config of the FBA site

In the <SharePoint> section configure a key for the people picker:
<PeoplePickerWildcards>

<clear />

<add key="AnonymousMembershipProvider" value="%" />

</PeoplePickerWildcards>

Configure <system.web> as so:

<authentication mode="Forms">

<forms loginUrl="/_layouts/login.aspx" />

</authentication>

<identity impersonate="true" />

<authorization>

<allow users="*" />

</authorization>

<membership defaultProvider="AnonymousMembershipProvider">

<providers>

<add name="AnonymousMembershipProvider" type="AnonymousMembershipProvider, AnonymousMembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=701fdd64fcd5ceb2" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

</providers>

</membership>

If you know your web.config you may have noticed that this site is still configured for anonymous access. The next step will assign our named "Anon" user during the authentication.

Step 4. Modify Global.asax on the FBA site to Log in as Anon

<%@ Assembly Name="Microsoft.SharePoint"%>

<%@ Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication" %>

 

<script RunAt=’server’>

 

public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)

{

if (Membership.ValidateUser("Anon", ""))

{

FormsAuthentication.SetAuthCookie("Anon", true);

}

}

 

</script>

Step 5. Configure the Web.Config of the Authoring Site

In the <SharePoint> section configure a key for the people picker:
<PeoplePickerWildcards>

<clear />

<add key="AnonymousMembershipProvider" value="%" />

</PeoplePickerWildcards>

Configure <system.web> as so:

<authentication mode="Windows" />

<identity impersonate="true" />

<authorization>

<deny users="?" />

<allow users="*" />

</authorization>

<membership defaultProvider="AnonymousMembershipProvider">

<providers>

<add name="AnonymousMembershipProvider" type="AnonymousMembershipProvider, AnonymousMembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=701fdd64fcd5ceb2" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" />

</providers>

</membership>

Even though the authoring site is configure to use Windows authentication, we still need access to the provider so that permissions can be assigned.

Step 6. Configure the Permissions

On the authoring site, do the following.

1.

2.

3.

4.

 

That’s it! If the SharePoint gods are smiling on you, you can now allow anonymous type folks to upload files, add attachments to lists, and generally have more control over your anonymous permissions!

Author: Doug Ware

SharePoint 2007 List Membership Provider

I recently completed a very cool project where I had the pleasure of working with Matt Ranlett. A big element of the project is a membership provider that Matt built that uses a SharePoint list as the user store. The client, Preparis, has very graciously allowed Matt to put the result of on CodePlex here.

Project Description
This project contains a custom Membership and Role provider for SharePoint 2007 which allows you to store your users in a SharePoint list on the site. This gives you the advantage of being able to maintain your users via the simple SharePoint list UI without requiring custom SQL access web parts

Custom SharePoint 2007 List-based Membership and Role Provider

This custom SharePoint 2007 forms based authentication provider was created to fit the following customer requirements:

  • on a single server (farm), use forms based authentication to validate external users to prevent the need for Active Directory accounts
  • on the single server (farm), host 20 to 30 or more site collections, each exclusively relying on forms based authentication user validation
  • each SharePoint site collection must maintain a private set of users
  • grant the ability to independently back up and restore each individual site collection, including the user base
  • include easy to use and cheap to build membership list maintenance tools

Here is a link to Matt’s blog post on the topic. Congratulations Matt!

Screen Clippings

One of my favorite pieces of software is Microsoft OneNote. If you’ve never used it, you are really missing out!

Aside from using it to take and manage notes, I also am using it more and more often to produce quick documentation. The tutorials in this blog started in OneNote!

I especially like the screen clipping tool.

If you have OneNote installed, press the Windows Key + S to activate the tool and create a clipping. The result goes into OneNote as an unfiled note.