.NET, BizTalk, Business, InfoPath, Mobile, Personal, Photography, Politics, SharePoint, SQL Server, Technical, Windows Workflow

First WordPress Post

I finally moved off of Community Server as my blog engine and migrated to this site using WordPress.  It’s all new to me so I’m still figuring out the settings, but with the help of my hosting company (WebHost4Life.com) and Vincent (support guy), I was able to get all the content over (I think.)

I decided to move off CS for a couple reasons.  First, I was on an older version of the software and to upgrade would’ve been a pain in the ass.  Second, I initially created the site thinking it would be used to host blogs for other coworkers and friends, and post pictures.  It seemed like folks I ran into that were interested in blogging already had a blog, so it became my personal site.  As for pictures, I’m still trying to figure that solution out, but I know CS isn’t part of it.  Perhaps a newer version would’ve been useful, but see previous first point.  Lastly, although the CS setting for allowing comments was set, no one could comment on my posts.  Not what I wanted.  I realize I don’t have a lot of readers, but I’d like to have the option for communication with anyone that stops by.

Hopefully WordPress will last a while for me.  I’d, of course, like to get on a SharePoint platform some day, but that will have to wait.  If anyone has any WordPress tips, feel free to leave them in the comments (because you can do that now!)

SharePoint, Technical, Windows Workflow

Could not establish trust relationship for the SSL/TLS secure channel

On a recent project, we had a requirement to use a SharePoint Designer workflow to call an external web service.  Instead of writing my own custom Activity, I did a search to see what was out there and found the iLove SharePoint Designer Actions.  One of the actions he has in there is one that can call a SOAP 1.1 or 1.2 web service, which is exactly what I needed.  Although it was written for SharePoint 2007, I pulled it into SharePoint 2010 and tested it out and found it to work just fine.

The end point for our test web service was an http address.  The end point for production was https.  As soon as we switched to the production end point, we began getting the error:

System.Net.WebException: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

—> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.

That’s is the error we saw in the ULS logs.  The nice error we saw in the workflow history was something useless like “An exception occurred”.

The error boils down to the code not trusting the secure certificate.  I tried a couple non-code solutions first, one modifying the web.config:

   1: <servicePointManager checkCertificateName="false" checkCertificateRevocationList="false"/>

And another importing the security certificate on the server.  Neither option worked for me, although some postings I found on the Internet suggested one or the other worked for some folks.

So after exhausting the non-code solutions, I dove into the code of the iLove SharePoint Actions to add some code to tell it to ignore the certificate check.  Some searching pointed me to here and here.  So I pulled down the iLove SharePoint source from codeplex, opened it up in VS2010 and upgraded it.  I had to change the Microsoft.SharePoint.dll references since I was using SharePoint 2010, and there were some build commands that needed to be updated as well.  Once those were done, I added the line:

   1: ServicePointManager.ServerCertificateValidationCallback = (obj,certificate,chain,errors) => true;

Right before the call to the web service.  Once that was built and deployed, problem solved.

Looking at it from a security perspective, probably not the best solution as it opens up the possibility to call services on sites with truly invalid certificates, but being in a closed environment it was acceptable.

.NET, BizTalk, Business, Mobile, Personal, Photography, Politics, SharePoint, SQL Server, Technical, Windows Workflow

Test Twitter Notifier from Live Writer

SharePoint, Windows Workflow

Authorized Types

I ran into this when using SharePoint Designer to create a simple workflow.  When I went to deploy the workflow to my site, I got a bunch of errors that looked like this:

(0, 0) Type System.Workflow.ComponentModel.DependencyProperty, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 is not marked as authorized in the application configuration file.)

The solution is pretty simple:  Add a new authorizedType entry to the System.Workflow.ComponentModel.WorkflowCompiler section in the web.config for the type in question.  For the error above I needed to add:

<authorizedType Assembly=”System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″ Namespace=”System.Workflow.*” TypeName=”*” Authorized=”True” />

Kind of odd the System.Workflow types aren’t in there already, but I am running in a minimal trust mode.  Maybe it’s different for full.

.NET, SharePoint, Windows Workflow

How to Deploy a Custom Activity as a Feature

Henry asked me to figure out a way to easily deploy the custom activity I wrote, so with a suggestion and a pointer from him I was on my way.  The end result is deployment of the activity as a feature.  Here’s how it’s done.

The concept is simple.  Use the normal Feature deployment framework to do the three things needed to deploy the activity.  Those are:

  1. Deploy the dll to the GAC
  2. Add the .ACTIONS file to the 12 hive
  3. Update the authorizedTypes in the web.config

The first two are pretty simple, but I’ll show the manifest so you get an idea if you’re not sure how that works.

<Solution SolutionId="153F5D62-5662-4288-B915-903F5B950E39" xmlns="http://schemas.microsoft.com/sharepoint/" ResetWebServer="TRUE">

<
FeatureManifests>
<
FeatureManifest Location="CustomActivitiesFeature.xml"/>
</
FeatureManifests>

<
RootFiles>
<
RootFile Location="TEMPLATE1033WorkflowFindManager.ACTIONS" />
</
RootFiles>

<
Assemblies>
<
Assembly DeploymentTarget="GlobalAssemblyCache" Location="MyAssembly.dll">
<
SafeControls>
<
SafeControl Assembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4" Namespace="MyNamespace" TypeName="*" />
</
SafeControls>
</
Assembly>
</
Assemblies>
</
Solution>

To update the authorizedTypes in the web.config I needed some help (remember, I’m new!)  Henry pointed me to the SPWebConfigModification class and the concept of an SPFeatureReceiver.  The theory was to use an SPFeatureReceiver and override the FeatureActivated method and use that to call into SPWebConfigModification to update the web.config file with the new authorizedType entry.  So with that, I was off!!

Other than the MSDN documentation, I found a couple other valuable resources.  The first was a post by Tony Bierman on Using SPWebConfigModification to modify a SharePoint web application’s web.config for all servers in the farm.  Sounds like what I need, right?!  Just about.  The biggest thing missing was how to get the right SPWebApplication reference.  His example was using a console app in which the web app was passed in as a parameter.  I played around with SPContext (not sure why, since there’s no http context during deployment of a wsp), as well as different options using the Parent of the Feature.  I never quite figured it out.  Then I ran into Daniel Larson’s posting on the same subject.  (I think he and I ran into each other a few years ago on a project in Parker, by the way.  I’m not sure.)

Dan was grabbing the web app using the following:

        SPWebApplication app = null;
        SPSiteCollection site = properties.Feature.Parent as SPSiteCollection;
        if (site == null)
        {
            SPWeb web = properties.Feature.Parent as SPWeb;
            if (web != null)
                app = web.Site.WebApplication;
        }
        else
            app = site.WebApplication;

Basically, using properties.Feature.Parent, as I was attempting.  He first checks to see if it’s a Site Collection and then, if not, assumes it’s a Web.  Well, guess what?  I wasn’t deploying my feature as either!  Maybe I need to rethink that, but I’m deploying it to a specific Site.  This helped me get to the right web application by doing:

        SPSite site = (SPSite)properties.Feature.Parent;
        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

After that I needed to spend some time reviewing how SPWebConfigModification works.  I started out by creating a modification object for my authorizedTypes entry and calling webApp.WebConfigModifications.Add, but that didn’t work.  If you take a look at the MSDN docs you’ll see why I started there.  I was missing a few steps before I could call .Add.  I needed to add a few more settings to the modification, as you can see in the code below.  That’s about it for the tricky stuff, so I’ll post the code.  One more thing to notice, though, is that I also overrode FeatureDeactivating to remove the .config entry.

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPSite site = (SPSite)properties.Feature.Parent;
                SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

                if (webApp != null)
                    UpdateWebConfig(webApp, false);
                else
                    throw new ApplicationException(“Could not locate a web application”);
               
                //Write log entry for success
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(“Activation complete.”);
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(ex.Message);
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPSite site = (SPSite)properties.Feature.Parent;
                SPWebApplication webApp = SPWebApplication.Lookup(new Uri(site.Url));

                if (webApp != null)
                    UpdateWebConfig(webApp, true);
                else
                    throw new ApplicationException(“Could not locate a web application”);

                //Write log entry for success
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(“De-Activation complete.”);
            }
            catch (Exception ex)
            {
                System.Diagnostics.EventLog el = new System.Diagnostics.EventLog();
                el.Source = “WebConfigFeature”;
                el.WriteEntry(ex.Message);
            }
        }

        private static void UpdateWebConfig(SPWebApplication webApp, bool removeModification)
        {
            SPWebConfigModification modification =
                new SPWebConfigModification(“authorizedType[@Assembly=”MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4″][@Namespace=”MyNamespace”][@TypeName=”*”][@Authorized=”True”]”, “configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes”);
         
            modification.Owner = “Ryan”;
            modification.Sequence = 0;
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value =
                 string.Format(CultureInfo.InvariantCulture,
                 “<authorizedType Assembly=”{0}” Namespace=”{1}” TypeName=”{2}” Authorized=”{3}”/>”,
                 new object[] { “MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9eed2245513232a4”, “MyNamespace”, “*”, “True” });

            if (removeModification)
                webApp.WebConfigModifications.Remove(modification);
            else
                webApp.WebConfigModifications.Add(modification);

            SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
        }

SharePoint, Windows Workflow

Custom Workflow Activity for SharePoint Designer to Retrieve Manager

My recent task was to create a custom workflow activity that users could access in SharePoint Designer which would be used to retrieve a users manager from Active Directory.  No, you don’t get this out of the box.  But you almost do!  And with this post it should be taken care of.

I won’t cover the basics for how to create a custom activity and deploy it for use with SPD.  There are many other posts that cover those details.  And speaking of being lazy, turns out there is a code sample in the ECM Starter Kit that is a great starting point for exactly what I was trying to accomplish (download it, install it, sample located in “ECM Starter KitCode SamplesWorkflowECMActivities”).  Through a lot of trial and error (no debug capability in clients dev site) I was able to modify it to be a bit more stable.  I added logic to handle things like AD properties not existing and manager name being in a couple different formats ({First Name} {Last Name} and {Last Name}, {First Name}). 

I’ll post the code for the SetManagerFields method as that’s where all the customization takes place.

    public void SetManagerFields(object sender, EventArgs e)
    {
        try
        {
            SearchSuccessful = false;

            //Set up the AD objects
            DirectoryEntry dirEntry = new DirectoryEntry("GC:");
            if (null == dirEntry)
            {
                Outcome = "Could not connect to Directory";
                return;
            }

            System.Collections.IEnumerator gcChildrenEnum = dirEntry.Children.GetEnumerator();
            gcChildrenEnum.MoveNext();

            //Search for Manager of AccountName
            DirectorySearcher searcher = new DirectorySearcher((DirectoryEntry)gcChildrenEnum.Current);
            string filterString = "(samAccountName= " + AccountName.Substring(AccountName.IndexOf("") + 1) + ")";
            searcher.Filter = filterString;
            SearchResult result = searcher.FindOne();

            if (result == null)
            {
                Outcome = "Could not find user account.";
                return;
            }

            if (result.Properties.Contains("manager"))
            {
                //Parse manager display name from search result
                ManagerDisplayName = result.Properties["manager"][0].ToString();
                int index;
                int indexComma = ManagerDisplayName.IndexOf(',');
                int indexSlash = ManagerDisplayName.IndexOf('');
                //If a comma is escaped, grab the second comma to get the full display name
                if (indexSlash != -1 && indexComma > indexSlash)
                    index = ManagerDisplayName.IndexOf(',', indexComma + 1);
                else
                    index = indexComma;

                ManagerDisplayName = ManagerDisplayName.Substring(3, index  3);
                //If there's a slash, remove it (.Replace("", "") doesn't work)
                if (indexSlash != -1)
                {
                    //Find it again cause it moved
                    indexSlash = ManagerDisplayName.IndexOf('');
                    indexComma = ManagerDisplayName.IndexOf(',');

                    ManagerDisplayName = ManagerDisplayName.Substring(0, indexSlash) +
                        ManagerDisplayName.Substring(indexComma);
                }

                searcher.Filter = "(displayName= " + ManagerDisplayName + ")";
                result = searcher.FindOne();
                if (result == null)
                {
                    Outcome = "Could not find manager " + ManagerDisplayName + ".";
                    return;
                }

                //Propagate data to the promoted properties
                if (result.Properties.Contains("samaccountname"))
                    ManagerAccountName = result.Properties["samaccountname"][0].ToString();
                if (result.Properties.Contains("mail"))
                    ManagerEmailAddress = result.Properties["mail"][0].ToString();
                Outcome = "Manager retrieved successfully.";
                SearchSuccessful = true;
            }
            else
            {
                Outcome = "AD does not contain a manager property";
                return;
            }
        }
        catch (Exception ex)
        {
            Outcome = "Error: " + ex.Message;
        }
    }

Pretty nice, right?  I know, most of that I didn’t even write.  I’ll shut up.  But…Now when a user uses this activity in their workflow in SPD, they get a nice response back even if the manager property isn’t even there (which is possible depending on the quality of the directory.)  After running the action, they can check the IsSuccess variable to determine if the manager was found.  Then act accordingly.

.NET, Windows Workflow

ASP.NET/WF Integration

Along the lines of my previous post, and discovered through the same source (ServerSide.net), Jon “stole” an idea I had on how to use WF in an ASP.NET app to manage page flow.  I say “stole” because he had no idea that I’ve had the same thoughts, let alone an idea of who I am!  Anyway, it looks like a great starting point to implement a workflow to manage page flow.  The main key to the solution is that the workflow and ASP.NET app have no idea about each other.  I like that.


When I get some time I’ll see if I can build on his solution to do things like have alternate storage areas (other than Session) and optional integration with a security model, like the Microsoft Application Blocks, to route the workflow based on permissions.

.NET, Windows Workflow

Debugging WF Workflows Hosted in ASP.NET

I came across these tips on Debugging WF-based workflows hosted in ASP.NET from a link on ServerSide.net. Chances are, I would’ve struggled with this for a few hours before figuring it out. Thanks Christian!