Paul Galvin's (old) SharePoint space [SharePoint

Just another WordPress.com site

Monthly Archives: November 2007

MOSS Tells Me My Column Name is Reserved or In Use … But It’s Not

UPDATE 12/04/07: See this Microsoft KB (http://support.microsoft.com/kb/923589) for related information.

Actually, it turns out it is, but tricksy MOSS had to make it difficult.

My customer does some development work on his MOSS site over the weekend.  It’s a bit of a jumble as to what he actually did, but the end result is this:

  • He tries to add a site column called "Quantity" and MOSS replies: "The column name that you entered is already in use or reserved.  Choose another name."
  • He attempts to add it to another environment and that works.  Therefore, "Quantity" is not a reserved name.
  • He tries to find an existing site column named "Quantity" in that site collection.  He cannot find it.

I did some research,  and even some coding, waxed philosophical and finally found that a column named Quantity did, in fact, exist.  It was in the "_Hidden" group.  Hence, we could not find it via the SharePoint user interface.

How did it get there?  I do not know, but I have a theory (or as my wife would call it, "blah blah blah").  Somewhere along the line, a fabulous forty template was added and probably activated at a site in the site collection.  It was then deactivated (or the site removed).  The site column, however, remained but in the "_Hidden" group.  If someone knows better, please let me know via email or post in the comments.

SharePoint was telling the truth.  It’s hardly worth pointing out that that message is not as helpful as it could be.  It would be nice to see that message fork into two different messages in the future: 1) Say that the column name is reserved or it is not. 2) If it’s not reserved, show the site, or at least the group, where the column name is already used.

</end>

Present OM Data Via a Custom List (or, Yet Another OM Data Displayor [like YACC, but different])

Today, I spent a handful of hours tracking down the root cause behind the message "The column name that you entered is already in use or reserved.  Choose another name."

The column in question could be created, deleted and re-created in another environment, so I knew it wasn’t a reserved name.  However, I simply couldn’t find the column anywhere via the standard SharePoint user interface at any site in the site collection.

I posted to MSDN forums here and the indomitable Andrew Woodward pointed me in the direction of the underlying object model data.

I went off to codeplex to find some tools that would help me peer into the underlying OM data and help me locate the trouble.

I tried several tools and they were very cool and interesting but in the end, the UI wasn’t good enough for my purpose.  I’m not criticizing them by any means, but clearly the tool-makers didn’t have my problem in mind when they created their UI :).  Most people seem to be investing a fair amount of time and effort in creating workstation / client applications that provide tree views, right-click context menus and so forth.  These are nice and all, but it’s a lot of work to create a top-of-the-line user experience that is also very flexible.

I really needed an answer to this problem. It occurred to me that if I could get all of the site columns in the site collection into a custom list, I could filter, sort and create views that would help me find this supposedly existing column (which it did, BTW).  I went ahead and did that and an hour or two later, had all my site columns loaded into a custom list with grouping, sorting and so forth.  I found my answer five minutes later.

If and when I successfully take over the world, I think I will decree that all SharePoint tools providers must seriously consider surfacing their object model data in a custom list.  That way, I have the power to search any way I want (constrained, of course, by standard sharepoint features).

SharePoint Designer Workflow Custom Action — Observation About

Just a quick observation that there’s a very important difference between these two definitions:

<FieldBind Field="InParam1" DesignerType="StringBuilder" Id="2" Text="Input parameter #1"/>

versus:

<FieldBind Field="InParam1" Id="2" Text="Input parameter #1"/>

The first shows like this in SPD:

while the latter shows like this:

I’m not sure how helpful these screen shots are but I put in the effort to make them so you have to view them 🙂

The observation is this: StringBuilder allows you to build a string (obviously) by mixing together string literals and workflow data (via the "Add Lookup" button in the lower left-hand corner).  When you use the Add Lookup button, it inserts a token in the form "[%token%]".  When SharePoint invokes your custom action, (C# code in my case), SharePoint passes the token itself, not the value of the token.  If you use the default designer type (the second type), SharePoint expands the token and passes actual value of the token to your action.

StringBuilder = BAD, default designer type = GOOD.

Of course, that’s not what I really mean.  Just don’t try and pass a parameter to your custom action when the designer type = StringBuilder.  Use the default  designer type and chain a StringBuilder to it up front if you need to build complex strings in your workflow (which incidentally is exactly what one does to create a dynamic subject for the email action, but that’s a subject for another blog entry, har har).

<end/>

Premature Workflow Activation — A Non-medical Solution

UPDATE: See this MSDN discussion, especially the last entry: http://forums.microsoft.com/MSDN/showpost.aspx?postid=2631057&siteid=1.  It describes a condition that may short circuit this whole thing.  In short, it may be as simple as making at least one of the fields mandatory.

I have a document library that supports eight content types.

I have a SharePoint Designer workflow that wants to calculate and assign a "reminder date" by simply subtracting 30 days from another column, "due date".  This should only happen for one of the content types, "Insurance".  The business objective is to produce a KPI that shows two categories of insurance documents: "about to expire" and "expired."  (You can read more about this kind of KPI and more substantial drill-down here).

I have configured the workflow to fire when a new item is created and when an item is modified.  The idea is that when an insurance document is uploaded, we calculate a "warning date" based on the expiration date.  A pair of views work in connection with a KPI List to highlight these conditions when users hit their home page.

This strategy does not work when I upload a document.

I upload the document and I am presented with the meta data entry screen.  At this point, I’m already in trouble.  SharePoint has already, prematurely from my perspective, fired the workflow.  I haven’t had a chance to pick the correct content type nor assign a due date.  At the same time, the workflow does not fire when I hit the submit button at this time.  There’s some built-in logic that "believes" that first submit is part of the "create" event.  So … my workflow has fired and when it executed, it was passed default meta data values. 

The best work-around I know of is to insert a "pause until" activity in the workflow.  I have the workflow pause for 1 minute.  While it’s pausing, I select the correct content type, enter the meta data and submit.  The pause completes and the workflow proceeds as needed.  (Note that in my environment, timer workflow activities from SPD do not work out of the box.  You may have the same trouble.  See here for more details).

I don’t like "magic delay" work-around.  What happens if the user uploads a document and the phone rings and the ensuing conversation outlasts the pause?  I can make the pause longer, but I still don’t like it.

I wrote about this on the MSDN forums here: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2430725&SiteID=1 

SharePoint Security Fundamentals Primer / Avoid Common Pitfalls

UPDATE 12/18/07: See Paul Liebrand’s article for some technical consequences of removing or modifying the default group names (see his comment below as well). 

Overview:

SharePoint security is easy to configure and manage.  However, it has proven to be difficult for some first-time administrators to really wrap their hands around it.  Not only that, I have seen some administrators come to a perfect understanding on Monday only to have lost it by Friday because they didn’t have to do any configuration in the intervening time.  (I admit to having this problem myself).  This blog entry hopefully provides a useful SharePoint security primer and points towards some security configuration best practices.

Important Note:

This description is based on out of the box SharePoint security.  My personal experience is oriented around MOSS so there may be some MOSS specific stuff here, but I believe it’s accurate for WSS.  I hope that anyone seeing any errors or omissions will point that out in comments or email me.  I’ll make corrections post haste. 

Fundamentals:

For the purposes of this overview, there are four fundamental aspects to security: users/groups, securable objects, permission levels and inheritance.

Users and Groups break down to:

  • Individual users: Pulled from active directory or created directly in SharePoint.
  • Groups: Mapped directly from active directory or created in SharePoint.  Groups are a collection of users.  Groups are global in a site collection.  They are never "tied" to a specific securable object.

Securable objects break down to at least:

  • Sites
  • Document libraries
  • Individual items in lists and document libraries
  • Folders
  • Various BDC settings.

There other securable objects, but you get the picture.

Permission levels: A bundle of granular / low level access rights that include such things as create/read/delete entries in lists. 

Inheritance: By default entities inherit security settings from their containing object.  Sub-sites inherit permission from their parent.  Document libraries inherit from their site.  So on and so forth.

Users and groups relate to securable objects via permission levels and inheritance.

The Most Important Security Rules To Understand, Ever 🙂 : 

  1. Groups are simply collections of users.
  2. Groups are global within a site collection (i.e. there is no such thing as a group defined at a site level).
  3. Group name not withstanding, groups do not, in and of themselves, have any particular level of security. 
  4. Groups have security in the context of a specific securable object. 
  5. You may assign different permission levels to the same group for every securable object.
  6. Web application policies trump all of this (see below).

Security administrators lost in a sea of group and user listings can always rely on these axioms to manage and understand their security configuration.

Common Pitfalls:

  • Group names falsely imply permission: Out of the box, SharePoint defines a set of groups whose names imply an inherent level of security.  Consider the group "Contributor".  One unfamiliar with SharePoint security may well look at that name and assume that any member of that group can "contribute" to any site/list/library in the portal.  That may be true but not because the group’s name happens to be "contributor".  This is only true out of the box because the group has been provided a permission level that enables them to add/edit/delete content at the root site.  Through inheritance, the "contributors" group may also add/edit/delete content at every sub-site.  One can "break" the inheritance chain and change the permission level of a sub-site such that members of the so-called "Contributor" group cannot contribute at all, but only read (for example).  This would not be a good idea, obviously, since it would be very confusing.
  • Groups are not defined at a site level.  It’s easy to be confused by the user interface.  Microsoft provides a convenient link to user/group management via every site’s "People and Groups" link.  It’s easy to believe that when I’m at site "xyzzy" and I create a group through xyzzy’s People and Groups link that I’ve just created a group that only exists at xyzzy.  That is not the case.  I’ve actually created a group for the whole site collection.
  • Groups membership does not vary by site (i.e. it is the same everywhere the group is used): Consider the group "Owner" and two sites, "HR" and "Logistics".  It would be normal to think that two separate individuals would own those sites — an HR owner and a Logistics owner.  The user interface makes it easy for a security administrator to mishandle this scenario.  If I didn’t know better, I might access the People and Groups links via the HR site, select the "Owners" group and add my HR owner to that group.  A month later, Logistics comes on line.  I access People and Groups from the Logistics site, add pull up the "Owners" group.  I see the HR owner there and remove her, thinking that I’m removing her from Owners at the Logistics site.  In fact, I’m removing her from the global Owners group.  Hilarity ensues.
  • Failing to name groups based on specific role:  The "Approvers" group is a perfect example.  What can members of this group approve?  Where can they approve it?  Do I really want people Logistics department to be able to approve HR documents?  Of course not.  Always name groups based on their role within the organization.  This will reduce the risk that the group is assigned an inappropriate permission level for a particular securable object.  Name groups based on their intended role.  In the previous HR/Logistics scenario, I should have created two new groups: "HR Owners" and "Logistics Owners" and assign sensible permission levels for each and the minimum amount required for those users to do their job.

Other Useful References:

If you’ve made it this far:

Please let me know your thoughts via the comments or email me.  If you know other good references, please do the same!

Technorati Tags:

Quick and Easy: Create a Data View Web Part (DVWP)

There is a wealth of great information on the WSS 3.0 Data View Web Part (DVWP) on the web from several sources.  However, I found it to be surprisingly difficult to find information on this first very basic step.  Here is another article in the "quick and easy" series to address it.

Follow these steps to create a data view web part (DVWP).  They are based on an "Announcements" web part, but apply to most lists.

  1. Create an Announcements web part and add it to a site.
  2. Open the site in SharePoint Designer.
  3. Open the site’s default.aspx.
  4. Select the Announcements web part and right-click. 
  5. From the context menu, select "Convert to XSLT Data View".

SharePoint Designer notifies you that this site is now customized from its site definition. That’s not necessarily bad, but there are important implications (performance, upgrade, others) which are beyond the scope of this little "Quick and Easy" entry.  To get more information on this subject, I recommend both books here as well as your favorite Internet search.

Confirm that you did it correctly:

  1. Close and re-open the web browser (to avoid accidentally re-posting the original "add a new web part").
  2. Select the web part’s arrow drop-down and choose "Modify Shared Web Part" from the menu.
  3. The tool panel opens to the right.
  4. The panel has changed from its usual set options to this:
 

 

 

At this point, modify the XSL to your heart’s content.

 

“Cannot get the list schema column property from the SharePoint list” — description/work-arounds

This week, we finally reproduced a problem that had been reported by a remote user: When she tried to export the contents of a list to excel, things would seem to start working, but then Excel would pop up an error: "Cannot get the list schema column property from the SharePoint list".  She was running office 2003, windows XP and connecting to MOSS. 

I searched the Internets and saw some speculation but nothing 100% definitive.  Hence, this post.

The problem: Exporting a view to excel that contains a date (date = the data type of the column).

What worked for us: Convert the date to a "single line of text".  Then, convert it back to a date.

That solved it.  It was nice to see that the conversion worked, actually.  It was quite nervous that converting things this way would fail, but it did not. 

This bug has thrown a huge shadow over the date data type in the client’s mind, so we’re going to be seeking out a definitive answer from Microsoft and hopefully I’ll post and update here in the next short period of time with their official answer and hotfix information.

Other references:

http://www.kevincornwell.com/blog/index.php/cannot-get-the-list-schema-column-property-from-the-sharepoint-list/

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2383611&SiteID=1

<end>

Subscribe to my blog.  

 

Technorati Tags: ,

Quick and Simple: Send an email with embedded hyperlink from SharePoint Designer workflow

Once or twice month, someone posts a forum question: "How do I include hyperlinks to URL’s that are clickable from a SharePoint Designer email?"

Presented without further comment:  (well, actually there is further comment after the image):

Becky Isserman follows up with a helpful explanation on how to embed a link to an item in the email: http://www.sharepointblogs.com/mosslover/archive/2007/11/20/addition-to-paul-galvin-s-post-about-sending-an-e-mail-with-hyperlinks-in-spd.aspx

 

New release: SharePoint Designer Workflow Extensions (string manipulation functions)

UPDATE: See here for my thoughts on commercializing this project: http://paulgalvin.spaces.live.com/blog/cns!1CC1EDB3DAA9B8AA!569.entry

I’ve been busy working on my Codeplex project which is presently focused on providing string manipulation extensions to workflows created via SharePoint Designer.

See here for details:

Project home: http://www.codeplex.com/spdwfextensions

Release: https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=spdwfextensions&ReleaseId=8280

Version 1.0 includes the following new features:

Function

Description (if not same as .Net function)

Num-entries()

Returns the number "entries" in a string as per a specified delimiter. 

For example: Num-entries in a string "a,b,c" with delimiter "," = 3.

Entry()

Returns the nth token in a string as per a specified delimiter. 

Length

String.Length

Replace()

String.Replace()

Contains()

String.Contains()
Returns the word "true" or the word "false".

Substring(start)

String.Substring(start)

Substring(start,length)

String.Substring(start,length)

ToUpper()

String.ToUpper()

ToLower()

String.ToLower()

StartsWith()

String.StartsWith()
Returns the word "true" or the word "false".

EndsWith()

String.EndsWith()
Returns the word "true" or the word "false".

I’ve put together a series of screen shots that show the functions and what they look like when used in SharePoint Designer (both the design and the runtime result).  The screen shots show the individual functions as invoked from SharePoint Designer and then the history of a workflow that shows them after-the-fact.

Overall test:

Replace():

ToUpper():

Contains():

StartsWith():

EndsWith():

Length():

Substring(start,length)

Substring(start)

Entry:

  

 

 

A BDC runtime error explained

I caused a BDC error this week that manifested itself on the user interface and in the 12 hive log at runtime.

First, this appeared in the user interface:

Could not find fields to insert all the Identifier Values to correctly execute a SpecificFinder MethodInstance with Name … Ensure input Parameters have TypeDescriptors associated with every Identifier defined for this Entity.

Here’s a screen shot:

 

I could also cause this message to appear in the 12 hive log at will (using my patented high-tech-don’t-try-this-at-home  "mysterious errors" method):

11/14/2007 09:24:41.27         w3wp.exe (0x080C)         0x0B8C        SharePoint Portal Server         Business Data         6q4x        High         Exception in BusinessDataWebPart.OnPreRender: System.InvalidOperationException: The Identifier value ”, of Type ”, is invalid. Expected Identifier value of Type ‘System.String’. at Microsoft.Office.Server.ApplicationRegistry.MetadataModel.Entity.FindSpecific(Object[] subIdentifierValues, LobSystemInstance lobSystemInstance) at Microsoft.SharePoint.Portal.WebControls.BdcClientUtil.FindEntity(Entity entity, Object[] userValues, LobSystemInstance lobSystemInstance) at Microsoft.SharePoint.Portal.WebControls.BusinessDataItemBuilder.GetEntityInstance(View desiredView) at Microsoft.SharePoint.Portal.WebControls.BusinessDataDetailsWebPart.GetEntityInstance() at Microsoft.SharePoint.Portal.WebControls.BusinessDataDetailsWebPart.SetDataSourceProperties()

I searched around and found some leads in the MSDN forum, but they weren’t enough for me to understand what I was doing wrong.  I watched a webcast by Ted Pattison that my company has squirreled away on a server and came to realize my problem.

In my ADF, I’m connecting to a SQL database as shown:

            <Property Name="RdbCommandText" Type="System.String">
              <![CDATA[
                SELECT     
                      SETID, CARRIER_ID, EFFDT, DESCR, EFF_STATUS, TAXPAYER_ID, NETWORK_ID, FRT_FORWARD_FLG, ALT_NAME1, ALT_NAME2, LANGUAGE_CD, 
                      COUNTRY, ADDRESS1, ADDRESS2, ADDRESS3, ADDRESS4, CITY, NUM1, NUM2, HOUSE_TYPE, ADDR_FIELD1, ADDR_FIELD2, ADDR_FIELD3, 
                      COUNTY, STATE, POSTAL, GEO_CODE, IN_CITY_LIMIT, COUNTRY_CODE, PHONE, EXTENSION, FAX, LAST_EXP_CHK_DTTM, FREIGHT_VENDOR, 
                      INTERLINK_DLL, TMS_EXCLUDE_FLG
                FROM         
                      dbo.PS_CARRIER_ID_VW WITH (nolock)
                WHERE     
                  (SETID <> 'SHARE') and 
                  (lower(CARRIER_ID) >= lower(@MinId)) and
                  (lower(CARRIER_ID) <= lower(@MaxId)) and
                  (lower(DESCR) LIKE lower(@InputDescr))
                ]]>
            </Property>

I was provided that SQL from a DBA person and I’m given to understand that it’s a special view they created just for me.  The unique key there is CARRIER_ID.

Here is the bug I introduced:

      <Identifiers>
        <Identifier Name="CARRIER_ID" TypeName="System.String" />
        <Identifier Name="DESCR" TypeName="System.String" /> 
</Identifiers>

Somewhere along the line, I had managed to confuse myself over the meaning of <Identifiers> and added DESCR even though it’s not actually an identifier.  I took DESCR out of the identifiers set and presto! It all worked.

I hope this saves someone some grief 🙂

Technorati Tags: , , ,