Thursday, April 10, 2008

Comparing InfoPath Fields - Case Insensitively

Currently have a form deployed which has 4 different views:
  1. User
  2. Manager
  3. Process Owner
  4. DBA
Each of the first three users (User, Manager, Process Owner) have an authorization field with an automatically filled in date field. Each user can only read/write for their authorization, and have read access to the authorizations below them (User cannot see anyone else's, Manager can see User's, Process Owner can see Manager's and User's, DBA can see all).

A problem arose where the Process Owner was actually the user's manager as well, but my rules for determining which view is presented to the user only showed this person the Process Owner view. I did not want to create a new view just for the case of Process Owner == Manager, so I did the following:
  1. Set the Manager's authorization field to Read/Write on the Process Owner view
  2. Added a conditional formatting element to the Manager's authorization field to set it to read only when the managerID field does not equal the InfoPath function "userName()"
This was great, except everything is case sensitive, and there is no standard "toUpper" or "toLower" functions to standardize multiple fields on one case...enter translate.

The resulting expression used in my conditional formatting is (broken onto multiple lines for ease of reading):
translate(
substring-after(
/my:myFields/my:grpEmployeeInformation/my:managerID,
"\"),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz") !=
translate(
xdUser:get-UserName(),
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz")

Rather long and cumbersome, but you can see both the left and right hand sides of the "!=" translate all capital letters to their lower case equivalents. The substring-after() bit was just an artifact of the structure of my form, where the managerID field is in the format of "domain\username".

Note: Since this form uses the "userName()" function, it will probably require full trust.

Enjoy!
--andrew

Wednesday, April 2, 2008

DataView Web Parts and Page Layouts

The DataView Web Part is a nice tool to customize the way information is presented on a page. Unfortunately, this web part is not available through the browser's "Add Web Part" interface, and must be added through SharePoint Designer. The problem I ran into was that pages attached to a page layout cannot be directly modified within SharePoint Designer.

I decided to detach the page from its layout and add the DataView Web Part. Everything seemed to work, except I started to receive errors stating "This Web Part Page has been modified since you opened it." I figured this was related to detaching from the Page Layout (I may have hosed something up unknowingly). I then reattached the page to its layout and everything ended up working fine.

Therefore, if you need to add a DataView Web Part to a page attached to a layout, I suggest doing the following(all done within SharePoint Designer):
  1. Detach the Page from the Page Layout
  2. Go through the necessary steps to add a DataView Web Part to an existing zone on the page
  3. Reattach the Page to it's Page Layout
This was fantastic since detaching/reattaching to layouts does not change any web parts or customizations made to those web parts. Also, once the DataView Web Part is on a page, you can simply modify it's display through the browser using the XSL editor.

Note: I have not attempted to reattach a page to a layout after new zones have been added and do not know exactly what will happen.

Enjoy!
--andrew

Wednesday, March 26, 2008

InfoPath Property Promotion Woes

Note: I experienced this issue with Administrator Approved templates only. I am unaware if this is a problem with either of the other publishing methods or if it will fix those problems.

I have been having issues lately with property promotion in InfoPath 2007 forms. When I create, publish, and deploy a form to a SharePoint site, promoted properties function correctly. If I add new structural elements and property promotion settings to a form after it has been deployed to a SharePoint site and is in use in a form library, then the new properties do not appear in SharePoint at all.

This has surfaced as a concern within my company as we have a few forms in use which require structural/property promotion changes. After several days of searching for any information on the capabilities and limitations of property promotion, I stumbled upon the following forum entry: Property Promotion - Full Trust Form - TechNet Forums.

In this forum post, there is some discussion of whether this is a bug or by design and possible resolutions.

Jasbury summed up the thread as:
The workarounds presented in this thread suggest the following:
  • Rebuild the library or site

  • Replace InfoPath’s “Automatically determine security level” with “Domain Security”

  • Deactivate/Reactivate the InfoPath Form Template from the Site Collections

The first suggestion was not an option for my situation.
The second suggestion did not apply to me as I experienced problems with automatic, domain, and full trust permission levels.
The third option, however, was right on the mark. Once I deactivated/reactivated the template, the new properties were promoted successfully.

Thanks to bobchauvin for originally suggesting this:
I also notice when using the Publish to Sharepoint as a content type that a change to the promoted cols wont take effect until you disable and then re-enable the content type for the site collection.

With a little investigation, I discovered what was causing Jasbury's problem here:
I was able to deactivate/reactivate with some success (thanks for this work-around!!!). The missing content type columns were added. However, changes to InfoPath property promotion were not reflected...huge bummer!! That leaves me with no other options that to start over…again!

This is due to the order in which the steps were completed rather than a bug within SharePoint/InfoPath. Field values within an InfoPath form are copied into the corresponding SharePoint columns when the form is saved/submitted. If people have been filling out forms prior to fixing the missing columns in the content type, these values will not be stored anywhere. Once the content type is correct, simply open any forms and save them again for the values to be copied.

I recommend the following order for updating a form which has property promotion/structural changes:
  1. Deactivate the form from all site collections it is active on

  2. Upload the new version of the form

  3. Reactivate the form on all necessary site collections
When you go to your SharePoint site, the content type will now reflect the correct promoted properties, and these columns will be available for use withn your form library views.

Enjoy!
--andrew

Thursday, March 20, 2008

Automatic Login with Current User Name and Password....Doesn't

Recently, I had to change my network password at work. This caused a problem which I was struggling to solve: every time I attempted to access the corporate intranet (with the IE setting "Logon automatically with current username/password"), it would prompt me for my password. It worked correctly before my password change, so I figured my password was being cached somewhere.

I attempted to empty the browser cache, restart the machine, relocate the intranet URL to another security zone; all to no avail.
Just today I found a blog post by Nick Porter entitled: SharePoint prompt for password after changing network password. In his post, he states:

Cause:
What seemed to happen was it was storing the old information in the
Password list under the Manage Passwords section under "User Accounts". (Start > Settings > Control Panel > User Accounts)

His resolution was:

1. Click "Start" > "Settings" > "Control Panel"
2. Double click "User Accounts"
3. Click on the "Advanced" tab
4. In the "Passwords and .NET Passports" area click "Manage Passwords"
5. Remove everything there.

Albeit, you do not need to remove *everything* from the managed passwords if other sites are working correctly for you (and you want them to), but I removed the intranet URL, opened a new browser and navigated to the site...and it automatically logged me in!

Thanks Nick Porter.

Enjoy!
--andrew

Wednesday, March 5, 2008

Programmatically Inherit Master Page and CSS in MOSS 2007

So recently I have been working on two small MOSS features:

  • Change the master page and stylesheet settings for a site

  • Staple the first feature to all site templates, so it is activated upon site creation

I was able to create both features and deploy them with little difficulty. See the following resources for tips on solutions, features, and stapling:

The problem that I ran into was this little radio button:





Brief rundown of SharePoint Object Model:

  • SPSite is a Site Collection

  • SPWeb is a Site within a Site Collection


Within the SharePoint Object Model, there are a couple of properties of the SPWeb object which we will be using:

  • [web_object].MasterUrl

  • [web_object].CustomMasterUrl

  • [web_object].AlternateCssUrl

These values are read/write, so you can set the values to your own strings and the values in the database will be changed accordingly.



Note: all of the following is done within the FeatureActivated function of a custom class inheriting from the SPFeatureReceiver class. Now let's dive into some code (these are all different methods I tried to no avail)

  • Created some variables and hard coded the locations within the feature:

    string MasterUrl = "/_layouts/custom.master";

    string CustomMasterUrl = "/_layouts/custom.master";

    string AlternateCssUrl = "/StyleLibrary/Custom/CSS/stylesheet.css";



    SPWeb web = (SPWeb)properties.Feature.Parent;

    try {

      web.MasterUrl = MasterUrl;

      web.CustomMasterUrl = CustomMasterUrl;

      web.AlternateCssUrl = AlternateCssUrl;

      web.Update();

    }

    catch { }


  • Set the values of the current site to the values from the root site:

    SPWeb web = (SPWeb)properties.Feature.Parent;

    try {

      web.MasterUrl = web.Site.RootWeb.MasterUrl;

      web.CustomMasterUrl = web.Site.RootWeb.CustomMasterUrl;

      web.AlternateCssUrl = web.Site.RootWeb.AlternateCssUrl;

      web.Update();

    }

    catch { }


  • Set the values of the current site to the values from it's parent site:

    SPWeb web = (SPWeb)properties.Feature.Parent;

    try {

      web.MasterUrl = web.ParentWeb.MasterUrl;

      web.CustomMasterUrl = web.ParentWeb.CustomMasterUrl;

      web.AlternateCssUrl = web.ParentWeb.AlternateCssUrl;

      web.Update();

    }

    catch { }

None of these methods, upon going to the "Site Master Page Settings" page within a browser, showed the little radio button next to "Inherit site master page from parent of this site" as being checked. They did correctly set the master page and stylesheet links, but if I went to a parent site and changed the master page, the master page did not get changed on the site *unless* the "inherit" radio button was selected.



I decided to take this to the Content Database for the portal in which I was working. Looking in the dbo.Webs table, I found that if the "Inherit" radio button is selected, there are still values in the DB for AlternateCssUrl, MasterUrl, and CustomMasterUrl. Running the above code, would put the same values in these fields, but the "Inherit" radio button would not be selected. I then performed the following steps:

  • Copy a row (one site) from the database to a text file

  • Change each of the 3 settings (Site Master, System Master, and Alternate CSS) from "Inherit" to "Specify a ..."

  • Copy the same row from the database to the second row of the text file

  • Change each of the 3 settings back to "Inherit"

  • Copy the same row from the database to the third row of the text file

  • Change each of the 3 settings back to "Specify a ..."

  • Copy the same row from the database to the fourth row of the text file

I then moved the side-scroll-bar all the way to the right (to confirm something in each lines was different) and they did not end at the same character, so I went to the beginning and compared vertically until I found different characters. There were a total of 3 instances of different characters (all 3 instances had the same characters) all within the same field, 'MetaInfo'. These values were:

  • "Inherit": 547275

  • "Specify a ...": 46616C73


Going back to the SharePoint Object Model, I discovered that there are two member properties to the SPWeb object which seem to correlate to site properties: Properties and AllProperties. I then tossed together a quick console app to output all Key/Value pairings for these two collections, and this is what came out:

  • SPWeb.Properties (C# type SPPropertyBag)


    • vti_extenderversion: 12.0.0.4518

    • vti_associatevisitorgroup: 4

    • vti_defaultlanguage: en-us

    • vti_associategroups: 5;4;3;6;7;8;9;10;11;14;15;17;18;28

    • vti_associateownergroup: 3

    • vti_associatemembergroup: 5

  • SPWeb.AllProperties (C# type Hashtable)


    • vti_extenderversion: 12.0.0.4518

    • __InheritsCustomMasterUrl: False

    • vti_associatevisitorgroup: 4

    • vti_categories: Business Competition Expense\ Report Goals/Objectives Ideas In\ Process Miscellaneous Planning Schedule Travel VIP Waiting

    • vti_associatemembergroup: 5

    • vti_defaultlanguage: en-us

    • vti_associateownergroup: 3

    • vti_associategroups: 5;4;3;6;7;8;9;10;11;14;15;17;18;28

    • __InheritsAlternateCssUrl: False

    • vti_approvallevels: Approved Rejected Pending\ Review

    • __InheritsMasterUrl: False

Fancy that, there are three properties which interest me most at this point (indicated in red).



I simply set these values to "True" (note, that is a string of "True" and not a 1 or C# true), which resulted in this:



However, it did not pull the correct values to begin with. Therefore, it would appear that SharePoint uses these settings for when the parent's master pages/css are changed, and not relying on these settings for everytime the site is accessed.



All in all, here is the final code I came up with for my Feature (inside FeatureActivated function):



SPWeb web = (SPWeb)properties.Feature.Parent;

Hashtable hash = web.AllProperties;

try {

  web.MasterUrl = web.ParentWeb.MasterUrl;

  hash["__InheritsMasterUrl"] = "True";

  web.Update();

}

catch { }

try {

  web.CustomMasterUrl = web.ParentWeb.CustomMasterUrl;

  hash["__InheritsCustomMasterUrl"] = "True";

  web.Update();

}

catch { }

try {

  web.AlternateCssUrl = web.ParentWeb.AlternateCssUrl;

  hash["__InheritsAlternateCssUrl"] = "True";

  web.Update();

}

catch { }



I hope this helps you, as I know I was pulling my hair out for a long time over this.



Enjoy!

--andrew



Correction [06.11.2008]: I just realized that in my final code, I had the following line:

  web.MasterUrl = web.ParentWeb.CustomMasterUrl;

This works just fine, except I am pulling the wrong master page setting from the parent site for the current setting. This should read:

  web.MasterUrl = web.ParentWeb.MasterUrl;

I have corrected this above in the code.

Tuesday, February 26, 2008

Remotely Enable Remote Desktop

Needed to remotely enable remote desktop on a Win2k3 machine, but all of the methods I could find suggested registry editing which posed a few problems:
  1. Not a big fan of editing the registry of servers
  2. Remote Registry service was not enabled on this particular machine

Luckily, I found the following at msgoodies.blogspot.com:

From Win2k3:

wmic /node:"Computer Name (FQDN)" /USER:"username" RDTOGGLE WHERE ServerName="Computer Name (FQDN)" CALL SetAllowTSConnections 1

From WinXP:

wmic /node:"Computer Name (FQDN)" /USER:"username" PATH win32_terminalservicesetting WHERE (__Class!="") CALL SetAllowTSConnections 1

Enjoy!
--andrew

Friday, September 21, 2007

Requiring the Contact Selector in InfoPath 2007

The contact selector control in InfoPath makes implementing a user/group lookup field quite easy. However, there are a few downsides. Ben Walters has done a very nice job of listing the upsides and downsides to using this control in his post, Contact Selector the Good and the Bad.

The one aspect of InfoPath (2003) that prevented company wide deployment at my employer was the need to have the InfoPath client in order to fill out a form. Now that Microsoft has implemented Forms Server and allowed the filling of forms via a web browser, many of my colleagues have been banging at my door for custom InfoPath solutions.

The requirement that I have been presented with was to use the Contact Selector control, to make it required, and to only allow one value. After reading the above post by Ben, which references some validation code posted in a comment on the InfoPath Team blog, it became apparent there was no easy solution for browser-based forms.

Below I offer my solution:
  • Domain trust form
  • No digital certificate

public void InternalStartup()
{
 EventManager.FormEvents.Loading +=
  new LoadingEventHandler(FormEvents_Loading);
 EventManager.XmlEvents["/my:myFields/my:contactSelector"].Changed +=
  new XmlChangedEventHandler(contactSelector_Changed);
}

public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
 XPathNavigator mainDS = this.MainDataSource.CreateNavigator();
 if (this.New)
 {
  XPathNavigator contSel =
   mainDS.SelectSingleNode("/my:myFields/my:contactSelector",
    NamespaceManager);
  this.Errors.Add(contSel, "ContactSelectorError",
   "You must select a contact.");
  return;
 }
}

public void contactSelector_Changed(object sender, XmlEventArgs e)
{
 if (e.Site.SelectChildren(XPathNodeType.Element).Count == 1)
 {
  try
  {
   this.Errors.Delete("ContactSelectorError");
  }
  catch { }
 }

 if (e.Site.SelectChildren(XPathNodeType.Element).Count > 1)
  this.Errors.Add(e.Site, "ContactSelectorError",
   "Only one contact can be selected.");

 if (e.Site.SelectChildren(XPathNodeType.Element).Count < 1)
  this.Errors.Add(e.Site, "ContactSelectorError",
   "You must select a contact.");
}

Some notes:
  • "/my:myFields/my:contactSelector" is the XPath to the Contact Selector control. This is the main group, i.e. in my example, it would be:
    <contactSelector>
      <Person>
        <DisplayName />
        <AccountId />
        <AccountType />
      </Person>
    </contactSelector>
  • "ContactSelectorError" is the name of the error being added/deleted. It is not displayed to the user, but rather the internal error name for code references.
  • I only add the error in the loading event for new forms, since an existing form would already have these fields required. An alternative to this.New would be to test the Contact Selector control for a value, and add the error if it's value was equal to the empty string ("").
  • In the function "contactSelector_Changed", you must have a try/catch around the delete statement, because if you attempt to delete an error that does not exist, an error will be thrown.
  • If you have multiple contact selector controls on your form, you will need a seperate "onChanged" event for each control, and I would suggest simply changing the name of the error from "ContactSelectorError" to something like "ContactSelectorErrorEmployee", and ensure each contact selector control has a unique name for its error. You will also need as many "this.Errors.Add" lines in the loading event as you have contact selector controls you want validated.
Enjoy!
--andrew