MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Wednesday, October 3, 2007

Publish Items

I wasn't planning on creating this command but then I ran my gl-replacefieldvalues command and forgot to pass the "-publish" switch in so now I was stuck with all these list items that needed to be published and/or approved. When this happened I thought, no problem - Andrew Connell has a PublishAllItems command so I'll just download his stuff and use that rather than create my own. Unfortunately however, Andrew's command didn't take all scenarios into account and thus I was left with the task of creating a new command to do what Andrew's attempted.

For those that are using Andrew's command without issue I decided to name mine "gl-publishitems" so as to not step on his command thus allowing both to be installed. The main problem I had with Andrew's command was that it didn't take into account items that needed approval but which did not have an SPFile object associated with it. There were also issues where he was calling SPListItem.Update() after a checkin which would throw an error because the update call requires the file to be checked out.

Andrew's code also didn't take into account workflows that would need to be canceled as a result of approving an item. And finally his code didn't allow all the scoping options that I needed (Farm, Web Application, Site Collection, Web, or List). The large bulk of the code is just a series of methods with different loops in them to handle the various scoping capabilities (similar to what I did with gl-replacefieldvalues).

The core code itself is considering all the cases in which an item may need to be either checked in, published, and/or approved. I've also added the ability to dump all changes to a log file as well as to run the command in a "test" mode where it will show you what it would publish but not actually make any changes - I'd strongly recommend you use this first to verify all the changes that will be made. The core code is shown below:

   1: /// <summary>
   2: /// Publishes the list item.
   3: /// </summary>
   4: /// <param name="item">The item.</param>
   5: /// <param name="list">The list.</param>
   6: /// <param name="settings">The settings.</param>
   7: /// <param name="source">The source.</param>
   8: internal static void PublishListItem(SPListItem item, SPList list, Settings settings, string source)
   9: {
  10:  try
  11:  {
  12:   if (item.File != null)
  13:   {
  14:    // We first need to handle the case in which we have a file which means that
  15:    // we have to deal with the possibility that the file may be checked out.
  16:    if (item.Level == SPFileLevel.Checkout)
  17:    {
  18:     // The file is checked out so we now need to check it in - we'll do a major
  19:     // checkin which will result in it being published.
  20:     if (!settings.Test)
  21:     {
  22:      item.File.CheckIn("Checked in by " + source, SPCheckinType.MajorCheckIn);
  23:      // We need to get the File's version of the SPListItem so that we get the changes.
  24:      // Calling item.Update() will fail because the file is no longer checked out.
  25:      item = item.File.Item; // If workflow is supported this should now be in a pending state.
  26:     }
  27:     TaskCounts.Checkin++;
  28:     TaskCounts.Publish++; // The major checkin causes it to be published so we'll track that as well.
  29:     Log(settings, string.Format("Checked in item: {0} ({1})", item.Title, item.Url));
  30:    }
  31:    else if (item.Level == SPFileLevel.Draft && item.ModerationInformation == null)
  32:    {
  33:     // The file isn't checked out but it is in a draft state so we need to publish it.
  34:     if (!settings.Test)
  35:     {
  36:      item.File.Publish("Published by " + source);
  37:      // We need to get the File's version of the SPListItem so that we get the changes.
  38:      // Calling item.Update() will fail because the file is no longer checked out.
  39:      item = item.File.Item; // If workflow is supported this should now be in a pending state.
  40:     }
  41:     TaskCounts.Publish++;
  42:     Log(settings, string.Format("Published item: {0} ({1})", item.Title, item.Url));
  43:    }
  44:   }
  45:  }
  46:  catch (Exception ex)
  47:  {
  48:   TaskCounts.Errors++;
  49:   Log(settings, string.Format("An error occured checking in an item:\r\n{0}", ex.Message));
  50:  }
  51:  
  52:  if (item.ModerationInformation != null)
  53:  {
  54:   // If ModerationInformation is not null then the item supports content approval.
  55:   if (item.File == null &&
  56:    (item.ModerationInformation.Status == SPModerationStatusType.Draft ||
  57:    item.ModerationInformation.Status == SPModerationStatusType.Pending))
  58:   {
  59:    // If content approval is supported but no file is associated with the item then we have
  60:    // to treat it differently.  We simply set the status information directly.
  61:    try
  62:    {
  63:     if (!settings.Test)
  64:     {
  65:      // Because the SPListItem object has no direct approval method we have to 
  66:      // set the information directly (there's no SPFile object to use).
  67:      CancelWorkflows(settings, list, item);
  68:      item.ModerationInformation.Status = SPModerationStatusType.Approved;
  69:      item.ModerationInformation.Comment = "Approved by " + source;
  70:      item.Update(); // Because there's no SPFile object we don't have to worry about the item being checkedout for this to succeed as you can't check it out.
  71:     }
  72:     TaskCounts.Approve++;
  73:     Log(settings, string.Format("Approved item: {0} ({1})", item.Title, item.Url));
  74:    }
  75:    catch (Exception ex)
  76:    {
  77:     TaskCounts.Errors++;
  78:     Log(settings, string.Format("An error occured approving an item:\r\n{0}", ex.Message));
  79:    }
  80:   }
  81:   else
  82:   {
  83:    // The item supports content approval and we have an SPFile object to work with.
  84:    try
  85:    {
  86:     if (item.ModerationInformation.Status == SPModerationStatusType.Pending)
  87:     {
  88:      // The item is pending so it's already been published - we just need to approve.
  89:      if (!settings.Test)
  90:      {
  91:       // Cancel any workflows.
  92:       CancelWorkflows(settings, list, item);
  93:       item.File.Approve("Approved by " + source);
  94:       // We don't need to re-retrieve the item as we're now done with it.
  95:      }
  96:      TaskCounts.Approve++;
  97:      Log(settings, string.Format("Approved item: {0} ({1})", item.Title, item.Url));
  98:     }
  99:    }
 100:    catch (Exception ex)
 101:    {
 102:     TaskCounts.Errors++;
 103:     Log(settings, string.Format("An error occured approving an item:\r\n{0}", ex.Message));
 104:    }
 105:  
 106:    try
 107:    {
 108:     if (item.ModerationInformation.Status == SPModerationStatusType.Draft)
 109:     {
 110:      // The item is in a draft state so we have to first publish it and then approve it.
 111:      if (!settings.Test)
 112:      {
 113:       item.File.Publish("Published by " + source);
 114:       // Cancel any workflows.
 115:       CancelWorkflows(settings, list, item);
 116:       item.File.Approve("Approved by " + source);
 117:       // We don't need to re-retrieve the item as we're now done with it.
 118:      }
 119:      TaskCounts.Publish++;
 120:      TaskCounts.Approve++;
 121:      Log(settings, string.Format("Published item: {0} ({1})", item.Title, item.Url));
 122:     }
 123:    }
 124:    catch (Exception ex)
 125:    {
 126:     TaskCounts.Errors++;
 127:     Log(settings, string.Format("An error occured approving an item:\r\n{0}", ex.Message));
 128:    }
 129:   }
 130:  }
 131: }
 132:  
 133: /// <summary>
 134: /// Cancels the workflows.  This code is a re-engineering of the code that Microsoft uses
 135: /// when approving an item via the browser.  That code is in Microsoft.SharePoint.ApplicationPages.ApprovePage.
 136: /// </summary>
 137: /// <param name="settings">The settings.</param>
 138: /// <param name="list">The list.</param>
 139: /// <param name="item">The item.</param>
 140: private static void CancelWorkflows(Settings settings, SPList list, SPListItem item)
 141: {
 142:  if (list.DefaultContentApprovalWorkflowId != Guid.Empty &&
 143:   item.DoesUserHavePermissions((SPBasePermissions.ApproveItems |
 144:            SPBasePermissions.EditListItems)))
 145:  {
 146:   // If the user has rights to do so then we need to cancel any workflows that
 147:   // are associated with the item.  This is based on how the 
 148:   SPSecurity.RunWithElevatedPrivileges(
 149:    delegate
 150:     {
 151:      foreach (SPWorkflow workflow in item.Workflows)
 152:      {
 153:       if (workflow.ParentAssociation.Id !=
 154:        list.DefaultContentApprovalWorkflowId)
 155:       {
 156:        continue;
 157:       }
 158:       SPWorkflowManager.CancelWorkflow(workflow);
 159:       Log(settings,
 160:        string.Format("Cancelling workflow {0} for item: {1} ({2})",
 161:             workflow.WebId, item.Title, item.Url));
 162:      }
 163:     });
 164:  }
 165: }
The syntax of the command I created can be seen below:

C:\>stsadm -help gl-publishitems

stsadm -o gl-publishitems

Publishes all items at a given scope.  Use -test to verify what will be published before executing.

Parameters:
        [-url <url to publish>]
        -scope <Farm | WebApplication | Site | Web | List>
        [-quiet]
        [-test]
        [-logfile <log file>]

Here's an example of how to publish all items in a given site collection:

stsadm -o gl-publishitems -url "http://intranet/hr" -scope site -logfile "c:\publish.log"

24 comments:

Juice said...

Just want to say that I greatly appreciate these extra commands and I especially appreciate all the work you've done to get them in there!

AC said...

Wow... didn't like my command huh? Sorry to hear that. I've got many of the issues you've mentioned in a build I haven't posted yet as I have other updates to my other commands... not to mention the major memory issues the current release has.

-AC

Gary Lapointe said...

Not at all - I thought your version was great and I would have preferred to have been able to use it. Unfortunately I had some specific needs that I had to address. Personally I prefer to not have to rewrite something as it's one more piece of code that I've got to maintain and test. I'll definitely be looking forward to your next release - specifically the memory management aspect of it as I've not done any profiling on any of this code - for the most part I'm looking to use it for my upgrade and then I doubt I'll use it beyond that so performance hasn't been a big concern.

AC said...

Cool... I should have an update posted around the timeframe of SharePoint Connections in Vegas in early NOV-2007 as I'm going to use the updated commands (not this one, but the other two "gen" commands) in one of my sessions.

-AC

Anonymous said...

Great .... i love your custom extensions :-)

Pablo said...

First of all thank you for you extension, it's so useful.

My question is...could it be possible to add also this functionality to approve FOLDERS inside DocumentLibraries.

The current command doesn't change the FOLDERS status at all.

Thanks in advanced

mardsja said...

Wow great tools!

It's possible to import document into folder in librarie.

Best

Gary Lapointe said...

mardsja - thanks for the feedback. I just want to make sure I'm not missing something - are you asking a question about importing documents or just stating a fact? If a question then take a look at the addlistitem command that I created - it will allow you to import a file into a folder on a list (as attachment) or document library.

Ralph said...

Hi Gary, great tool!

After running the publish items command some documents in the document libraries are checked-in, which is perfect.

However, if the document library contains folders, the documents in these folders are not checked in and still invisible to all users except the one that added the documents. The documents that were not in a folder are both checked in and visible.

This problem is probably caused by the fact that the documents were added through windows explorer (see http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1320055&SiteID=1 - post of Tod Beane - for more detail on this issue.

Do you know why the Publish Items does not check in the files that are contained in folders in the document library?

Gary Lapointe said...

Ralph - I've not seen this particular issue before but I've not done any testing using explorer view. If I have time during my flight today I'll try and see if I can replicate the problem.

Kodiak said...

This tool looks quite useful, and I can't wait to get it working. Currently whenever I execute a command I get a

Processing Site: /
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

I didn't see any credential arguments in the command. Am I missing something?

Gary Lapointe said...

You need to run the command using your site admin account. This is true with virtually all of my commands (as it is with the out of the box commands).

kodiak said...

That did the trick :)

I'm still a little new to the IT world.

Cheers!

ralph rivas said...

A suggestion for an update ... to allow for a new scope called "folder" so that we can publish list items within a specific folder as opposed to all list items in the list. (one level further would be ListParameter where there could be a -fieldname and a -fieldvalue bit but I guess that would be really heavy ;-) )

Joseph S said...

I Love you man! it worked!.. This one is way better then Andrews!!! Thank you

Anonymous said...

Amazing stuff Gary! This command is so useful.

However, we've encountered a small problem. We've created a custom doc library. (Nothing fancy, just some custom columns and views.)

In Doc library settings > Document Library Versioning Settings > Draft Item Security, we've configured "Who should see draft items in this document library?" to "Only users who can edit items".

This means that even if we run your command using a site admin account, it still won't see checked-out documents.

Am I missing something obvious here? Or do you know of a workaround?

Pritish said...

I hope you get to the top of google's result set soon. It took too long to find this module! what a click saver - folder approving / publishing seems to evade many other people's utilities.

Gary Lapointe said...

Regarding the draft items - unfortunately this is a limitation of the API - I've not yet found a way around this.

Anonymous said...

Please change your description for this command where you state that "The core code itself is considering all the cases in which an item may need to be either checked in.." I was under the impression that it would check-in items based on that statement.

This command does not check-in items in document libraries, regardless of the scope used and regardless if the item that is checked-out is new or pre-existing.

Gary Lapointe said...

I'm not following what your concern is - this command works on both list items and library items - I use it all the time to publish both documents and web part pages. It will not work on items that have been added by other users and never checked in - there's no way to see these until they've been checked in at least once (this is a SharePoint limitation and one that I cannot work around).

Dirk Schulz said...

Gary, thank you so very much for your tools. I do really appreciate your work.
Saved me a lot of hassle when implementing our sharepoint environment.

Kind regards,
Dirk Schulz

Dirk said...

Ralph and Gary,

I had the same issue where I copied documents to a doc lib using the Explorer View and is having the same issue where this command does not actually check in the documents. I think I managed to crack it. It was because the Doc Lib had custom content types with certain columns (such as Title) as compulsory fields. So when you try and bulk check in, it simply cannot because those compulsory fields aren't filled in. I temporarily changed the column settings on the content type and viola! Now you only need to manually complete those fields...thanks data sheet view and keyboard!!!!

I hope this helps!

mStar said...

This command saved me 1500 hours...THANK YOU!

asembler said...

I tried running this command using Farm admin account which is at the same item a site collection admin for the site I'm running this command on, however I got an error saying that the document is checked out to another user and cant be published. Isn't the idea of using this command is to enable a admin user to override checked-out items?

12/3/2010 3:16:05 PM: An error occured checking in an item:
The file "Documents/car-insurance-star-ratings-feb-10.pdf" is checked out or locked for editing by DOMAIN\user1.