# Tuesday, September 30, 2008

This is just a post containing all the issues I’ve run into over the last six months while working with MOSS Workflows, InfoPath and Visual Studio 2008/VSTO 3.0. I’ve been using these tools in this combination for a while now, but it’s only the last six months where I decided to take a note every time I ran into something. Don’t get me wrong though. This is more of a post aimed at helping others in their quest for abdominal hull-integrity while working with these tools, not attacking the toolset. Ultimately, the Office Suite and the corresponding tools are working together better than ever before, but unfortunately with large teams working relatively independently you’re just going to get these kind of problems. To be brutally honest, I think VSTO 3.0 is a saddle-sore on the otherwise supple and leathery hide of Visual Studio 2008 and the disparity in their respective product quality is way too pronounced to be ignored.

Anyway, I’m not going to make much attempt to explain each case for non-SharePoint people, but if you’re involved in this area, you’ll know where I’m coming from. The following list contains Glitches and Gotchas.

Glitches

First though, the Glitches, in no particular order. These are the lovingly hand-crafted defects and “product quality issues” that will first cause your stomach muscles to contract somewhat, then violently spasm so hard that that first loop of intestine will pop out to nestle amongst the fat cells and veins in the fleshy meadow of your lower-epidermis. Don’t say I didn’t warn you.

Publish an Installable Form Template (requires Visual Studio)

vs2008-vs-not-installed-properly

If you try to publish a form as "an installable template (requires visual studio)" from within Visual Studio 2008 (even with sp1), VSTO, in its infinite wisdom tries to use visual studio 2005 devenv.exe executable to do it! And it fails spectacularly 'cos it's not installed, or if it is installed it's part of some vs-shell install for some other office app like office macros IDE, or Internet Explorer’s “script debugger” shell for example.

vs2008-infopath-vs2005

Publishing a Form to an explicitly-included Managed Path

Trying to publish a form to a SiteCollection sitting on an explicitly-included managed path fails with "getting the site content type columns failed." Workaround? Don’t publish forms to Managed Paths. It’s b0rked.

“An exception occurred during loading of business logic” - Publishing XSNs with CodeBehind to SharePoint

If you perform a cursory search for the title, you’ll find lots of herniated VSTO users having issues with this. InfoPath Forms Services on MOSS Enterprise is a fickle beast; a beast that will have you tucking those intestinal loops back into your torso with a pencil before you can say “FileNotFoundException, whuh?”

XSN files are actually CAB files. When Forms Services loads them up to be rendered into Browser Forms, it has to load the embedded DLL into memory. It has an extremely hard time doing this sometimes. Particularly when you store your XSN files in a subdirectory under your workflow feature, say like one called Forms. It appears that the form loads ok, but Forms Services is looking for the DLL in the root of your feature. Even sometimes if you have the XSN in the root of your feature, it still won’t find and extract/load the embedded DLL. The only way to avoid this it appears is to manually extract the DLL and place it in the root of the feature yourself after you have deployed the workflow solution.

Some strange things I’ve observed about Forms Services on MOSS Enterprise:

  • Uploading a codebehind-XSN  file via central admin automatically extracts dll/pdb into feature directory but...
  • Deploying a codebehind-XSN with  workflow feature does not extract dll/pdb.
  • Upgrading a codebehind-XSN with central admin creates new subdir in feature folder, modifies feature.xml to point to it but does not extract the form's codebehind dll/pdb into new subdir either.

These seem to be some rather serious looking issues to me.

VSTO Designer ToolPane Content Shrinks

Occasionally the Design ToolPane in VS2008/VSTO inexplicably changes size, with the contents of it being set at about 1cm wide.b0rked-vsto-toolpane

A restart of Visual Studio or close/reopen of the project usually fixes this.
Workflow Designer Screws Up Layout of Nested States

The workflow designer in VS 2008 (even sp1) will screw up the formatting of nested states in a state machine workflow - they get randomly offset to the left or right so they are half occluded by the containing state. Yay.

Views ToolPane Is Blank For a Form that has Views

Views created in infopath sometimes do not appear in vs2008 project built around that imported XSN. Weirdly, the toolpane is completely white/blank. A restart of Visual Studio or close/reopen of the project usually fixes this.

Manipulating Files In “InfoPath Form Template” Project Folder Is a Bad Idea

Deleting/modifying/renaming a file in the "infopath form template" meta-folder in Visual Studio 2008 does not fix the reference in the manifest.xsf file. The form is then impossible to open until you fix the references within the manifest by closing the form designer and opening the XSF with the XML handler through a right-click. Why does VSTO even let you modify embedded resources if it won’t update the manifest for you?

The magical secondary DataSource ItemMetadata.xml

The secondary DataSource (for receive data) ItemMetadata.xml has _two_ ways of being added as a resource - the second way (prompted at end of import). You can either add it as a resource when you initially point VSTO/InfoPath at it, or if you don’t do it then you get a second chance at the end of import to add it as a resource. This is confusing.

Converting an embedded DataSource to site-relative format and placing into a  Data Connection Library can leave the required field "Title" empty

This enforces the UDCX files to be permanently checked out - trying to update the title field by hand does not work - it remains blank at every attempt to change it when done directly or via bulk edit DataGrid view. It sometimes exclaims that the files are checked out to another user even though they are plainly checked out to me. Worst of all, only *I* can use the InfoPath form that uses this DataSource so it’s starts out a looking like “it works for me” issue.  Anyone else cannot use the form - DataSource loading fails - ULS log says "root element is missing" - after much debugging, it appears that anyone (apart from me the author) attempting to download the UDCX file gets a 0 length file - you can test this by getting the poor user to try to download the UDCX from the connection library. This is because only an author of a checked-out file can read it. 

Hacky Fix: Remove ReadOnly attribute from the list-level UDCX content type and mark the Title column as optional, then check-in the connections. Title be damned!

This all started when I enabled Versioning for the connection library before converting my data sources. Not sure if that’s relevant.

BDC randomly losing ServerContext while inside Event Receiver triggered from Workflow context

The TLS slot for the resource provider SqlSessionProvider sometimes ends up being null on the Event Receiver's thread. If you’re not following me, you probably don’t need to understand this any further.

Gotchas

These are not so much glitches are they are things that can trip you up and/or be considered “non-obvious” behavioural quirks. While they may give you an upset tummy, you are not going to be trussed up in a Spigelian torso-bangle any time soon.

A Workflow Continually Spawns New Instances of Itself

When you have a workflow added to a document library and the workflow updates the list item when it is running, a new workflow will spawn once the .Update() method is executed on the list item. To update the item without having a new worklfow started, use .SystemUpdate(). Duh.

"The database returns an error. Failed to enable constraints." When InfoPath DataBinds

Changing the query directly in a UDCX file inside a data connection library may cause "The database returns an error. Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints" errors from live InfoPath forms because the data source schema in InfoPath no longer matches (especially if you increase the length of a returned field) - after you change the UDCX, you must go through the "manage data connections" and choose "modify" for that connection and go through the wizard without changing anything. This will cause InfoPath to re-sync the schema.

How The Hell Can I Render The Form Version On The Form Itself?

Tip: insert form version with the expression: substring-before( substring-after( /processing-instruction()[local-name(.) = "mso-infoPathSolution"], 'solutionVersion="'), '"')

I can’t remember where I got this, I didn’t write it myself. Thanks to the original author.

Why Can’t SharePoint Find My Forms for my Workflow?

The Form URN will change if you don't name your form exactly as you did (e.g. accept the default) in "file > form properties" when publishing it with the publishing wizard.

Why Can’t My Workflow Forms Find My Data Connections?

Site relative data connections stored in a data connection library work fine for InfoPath published forms bound to a content type in a site; also form security will stay at domain. But they can’t be found if these same forms are used in a workflow. Use “centrally managed” data connections instead.

Why Does Visual Studio Put Referenced DLLs Bound For the GAC Into My InfoPath XSN?

If you reference other feature projects in the same solution that deploy into GAC, be sure that they exist in the GAC before building the InfoPath project's XSN - if they are not in the GAC, they will be copied into the XSN by default. Duh.

How Do I Uninstall My Workflow I was Debugging In Visual Studio?

Workflow features deployed via vs2008 F5/start debugging have no obvious means of being "undeployed." you must use VSTO 1.0 PowerTools "Feature Sweeper" to remove it; this is a dependency for workflow forms registered in central admin.

Why Are The TaskProperties Hashtables So Weird?

ExtendedProperties Hashtables leaves root XML fields as keys, and nested xml as my:schema formatted XML for Hashtable values. Strangely, the Workflow API has a public XmlToHashtable method on a utility class, but not the inverse to pull it back out of ExtendedProperties. Duh.

Where The Hell Did The Form Publish Menu Disappear to?

You cannot publish the InfoPath form unless you have the designer open, even though you can only have one form per project and you might just have a single project in the solution. This is annoying when your forms are large and sluggish to open.

Why do some VS SharePoint Activities have a User (string) property and others a UserID (int) property?

I wish I knew. This is just another silly design (in)decision. All SPUsers in SharePoint have both a Login (string) and a PrincipalID (int). If you want to set the Logging Activity’s UserId (int) to the id of a person who performed a Workflow Modification User (string), you have to convert, persist and expose these properties yourselves before you can bind to an activity using the designer. Duh.

I use a method like this:

id = Contact.FromName(executor, this.workflowProperties.Web).PrincipalID;
Why is Visual Studio 2008  SP1’s Workflow Designer Host still such a dog?

You and thousands of other people would love to know the answer. IMHO, it’s too complex to have been written in Managed Code (e.g. C#). It should have been native C++ from day one. All that synchronizing intellisense, XOML and the visual designer is just too much for it. God help you if you throw in any real-time refactoring tools on top of that.

Summary

Despite all this moaning, SharePoint in conjunction with Visual Studio 2008 is a powerful combination. Nothing to be sniffed at all; I actually love it (most of the time.)

Have fun.

posted on Tuesday, September 30, 2008 2:19:44 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, March 27, 2008

PowerShell has several "type accelerators" which are used exactly like a casting operation. Examples of these special operators are [xml] and [wmi]. The former is used for quickly converting a string of xml into a fully-fledged System.Xml.XmlDocument object.

Often I find myself converting things to and from hexadecimal using the -f operator, but this always seemed like just a little too much typing for me. Enter the [hex] accelerator type:

image

As you can see from the source below, there's no magic here. This is just a straight cast, but I have no namespace. If I had a namespace, say, like "Nivot.PowerShell", we'd have to cast using [nivot.powershell.hex] instead of just [hex]. All of the trickery is done using operator overloads in C#. These tells .NET (and in turn, powershell) how to behave should someone try to add, subtract, remove or divide our instances.

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.  
  5. public class Hex  
  6. {  
  7.     private readonly int _value = 0;  
  8.  
  9.     private Hex(int value)  
  10.     {  
  11.         _value = value;  
  12.     }  
  13.  
  14.     private Hex(string value)  
  15.     {  
  16.         _value = Convert.ToInt32(value, 16);  
  17.     }  
  18.  
  19.     public static implicit operator Hex(int value)  
  20.     {  
  21.         return new Hex(value);  
  22.     }  
  23.  
  24.     public static implicit operator int(Hex value)  
  25.     {  
  26.         return value._value;  
  27.     }  
  28.  
  29.     public static explicit operator Hex(string value)  
  30.     {  
  31.         return new Hex(value);  
  32.     }  
  33.  
  34.     public static Hex operator +(Hex op1, Hex op2)  
  35.     {  
  36.         return new Hex(op1._value + op2._value);  
  37.     }  
  38.  
  39.     public static Hex operator -(Hex op1, Hex op2)  
  40.     {  
  41.         return new Hex(op1._value - op2._value);  
  42.     }  
  43.  
  44.     public static Hex operator *(Hex op1, Hex op2)  
  45.     {  
  46.         return new Hex(op1._value * op2._value);  
  47.     }  
  48.  
  49.     public static Hex operator /(Hex op1, Hex op2)  
  50.     {  
  51.         return new Hex(op1._value / op2._value);  
  52.     }  
  53.  
  54.     public override string ToString()  
  55.     {  
  56.         return "0x" + _value.ToString("X");
  57.     }  

The next step is to tell PowerShell's formatter what to do with the new type. Here's a simple format definition that tells the formatter to call ToString() on the Hex instance. This is the method that does the conversion by calling ToString("X") on the integer field. "X" means format the integer as hexadecimal using upper case. A lower-case "x" would output the value using lower-case (if you couldn't guess ;-)).

  1. <Configuration> 
  2.   <ViewDefinitions> 
  3.     <View> 
  4.       <Name>Hex</Name> 
  5.       <ViewSelectedBy> 
  6.         <TypeName>Hex</TypeName> 
  7.       </ViewSelectedBy> 
  8.       <CustomControl> 
  9.         <CustomEntries> 
  10.           <CustomEntry> 
  11.             <CustomItem> 
  12.               <ExpressionBinding> 
  13.                 <ScriptBlock>$_.ToString()</ScriptBlock> 
  14.               </ExpressionBinding> 
  15.             </CustomItem> 
  16.           </CustomEntry> 
  17.         </CustomEntries> 
  18.       </CustomControl> 
  19.     </View> 
  20.   </ViewDefinitions> 
  21. </Configuration> 

If we don't load this format file, PowerShell just emits a couple of blank lines when you try to use it.  You'll notice from the screenshot above that the blank lines still appear. I'm not sure how to remove these - it looks pretty ugly compared to the output of the [int] object. If you want to play with this, you can download the zip file below and unzip the contents into a single folder and run "hex.ps1". I didn't bother with a full Snap-In, it just loads the DLL using reflection. It also loads the format ps1xml too. Have fun.

HexAccelerator1.zip (2.4 KB)
posted on Thursday, March 27, 2008 8:19:15 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Thursday, March 06, 2008

Just a minor thing, but this is a filter I've been using for a while now as a replacement for Get-Member. It's always annoyed me that the Get_ and Set_ methods are returned along with the actual properties. The only place that's actual useful is when you're using an object wrapped in the [xml] adapter since those objects do not expose the XmlDocument's properties in the adapted member set.

update: if you use (of course you do!) MoW's PowerTab, you can disable display of the accessor methods with $PowerTabConfig.ShowAccessorMethods = $false.

  1. filter get-memberex {   
  2.     $_ | gm | ? { -not($_.name -match "^[gs]et_.+") }   
  3. }   
  4. new-alias gmx get-memberex  
posted on Thursday, March 06, 2008 11:35:21 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Friday, February 29, 2008
Someone on the PowerShell usenet group asked if it was possible to interact with SharePoint lists through our favourite little shell. Marco Shaw responded and put the pressure on by saying this was my bag of tricks. Who am I to say otherwise? so lets take a look at the recipe.
posted on Friday, February 29, 2008 10:11:39 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0] Trackback
# Tuesday, August 07, 2007

I noticed that my last script could not handle more complex webservices like the MSDN ContentService (unlike WSDL.EXE) for example, so I rewrote it from the ground up. This time, it supports Basic Profile 1.1 and can generate proxy instances for simple ASMX services and more complicated multi-schema services like the MSDN/TechNet Publishing System (MTPS) Content Service.

What's New:

  • No need for the .NET 2.0 SDK, just PowerShell
  • Auto discovery of all referenced schema
  • No need to point it at page.asmx?wsdl any more, just the asmx
  • Validation against WS-I Basic Profile 1.1

get-webservice2.ps1.txt (4.28 KB)  (updated 2007/12/19: errant comma in write-progress fixed)

  1. #
  2. # Get-WebService.ps1 (v2.0 Aug 6, 2007)
  3. #
  4. # Oisin Grehan <oising@gmail.com> (x0n)
  5. #
  6. # Usage:
  7. #   $proxy = .\get-webservice2.ps1 [-Url] http://site/service.asmx [-Anonymous] [[-SoapProtocol] <Soap | Soap12>]
  8. #
  9. # to see available webmethods:
  10. # $proxy | gm
  11. #
  12.  
  13. # $url = "http://services.msdn.microsoft.com/contentservices/contentservice.asmx?wsdl"
  14.  
  15. param($url = $(throw "need `$url"), [switch]$Anonymous, [string]$protocol = "Soap")
  16.  
  17. [void][system.Reflection.Assembly]::LoadWithPartialName("system.web.services")
  18.  
  19. trap {
  20.         "Error:`n`n $error";
  21.         break;
  22. }
  23.  
  24. #$request = [System.Net.WebRequest]::Create($url);
  25. $dcp = new-object system.web.services.discovery.discoveryclientprotocol
  26.  
  27. if (! $Anonymous) {
  28.     Write-Progress "Network Credentials" "Awaiting input..."
  29.     $dcp.Credentials = (Get-Credential).GetNetworkCredential()
  30. }
  31.  
  32. Write-Progress "Discovery" "Searching..."
  33. $dcp.AllowAutoRedirect = $true
  34. [void]$dcp.DiscoverAny($url)
  35. $dcp.ResolveAll()
  36.  
  37. # get service name
  38. foreach ($entry in $dcp.Documents.GetEnumerator()) { # needed for Dictionary
  39.     if ($entry.Value -is [System.Web.Services.Description.ServiceDescription]) {
  40.         $script:serviceName = $entry.Value.Services[0].Name
  41.         Write-Verbose "Service: $serviceName"
  42.     }
  43. }
  44.  
  45. Write-Progress "WS-I Basic Profile 1.1" "Validating..."
  46. $ns = new-Object System.CodeDom.CodeNamespace # "WebServices"
  47.  
  48. $wref = new-object System.Web.Services.Description.WebReference $dcp.Documents, $ns
  49. $wrefs = new-object system.web.services.description.webreferencecollection
  50. [void]$wrefs.Add($wref)
  51.  
  52. $ccUnit = new-object System.CodeDom.CodeCompileUnit
  53. [void]$ccUnit.Namespaces.Add($ns)
  54.  
  55. $violations = new-object system.web.Services.Description.BasicProfileViolationCollection
  56. $wsi11 = [system.web.services.WsiProfiles]::BasicProfile1_1
  57.  
  58. if ([system.web.Services.Description.WebServicesInteroperability]::CheckConformance($wsi11, $wref, $violations)) {
  59.     Write-Progress "Proxy Generation" "Compiling..."
  60.    
  61.     $webRefOpts = new-object System.Web.Services.Description.WebReferenceOptions
  62.         $webRefOpts.CodeGenerationOptions = "GenerateNewAsync","GenerateProperties" #,"GenerateOldAsync"
  63.  
  64.         #StringCollection strings = ServiceDescriptionImporter.GenerateWebReferences(
  65.         #       webReferences, codeProvider, codeCompileUnit, parameters.GetWebReferenceOptions());
  66.  
  67.     $csprovider = new-object Microsoft.CSharp.CSharpCodeProvider
  68.         $warnings = [System.Web.Services.Description.ServiceDescriptionImporter]::GenerateWebReferences(
  69.                 $wrefs, $csprovider, $ccunit, $webRefOpts)
  70.        
  71.     if ($warnings.Count -eq 0) {
  72.         $param = new-object system.CodeDom.Compiler.CompilerParameters
  73.         [void]$param.ReferencedAssemblies.Add("System.Xml.dll")
  74.         [void]$param.ReferencedAssemblies.Add("System.Web.Services.dll")       
  75.         $param.GenerateInMemory = $true;
  76.         #$param.TempFiles = (new-object System.CodeDom.Compiler.TempFileCollection "c:\temp", $true)
  77.         $param.GenerateExecutable = $false;
  78.         #$param.OutputAssembly = "$($ns.Name)_$($sdname).dll"
  79.         $param.TreatWarningsAsErrors = $false;
  80.         $param.WarningLevel = 4;
  81.        
  82.         # do it
  83.         $compileResults = $csprovider.CompileAssemblyFromDom($param, $ccUnit);
  84.  
  85.         if ($compileResults.Errors.Count -gt 0) {
  86.             Write-Progress "Proxy Generation" "Failed."
  87.             foreach ($output in $compileResults.Output) { write-host $output }
  88.             foreach ($err in $compileResults.Errors) { write-warning $err }           
  89.         } else {           
  90.             $assembly = $compileResults.CompiledAssembly
  91.  
  92.             if ($assembly) {
  93.                 $serviceType = $assembly.GetType($serviceName)               
  94.                 $assembly.GetTypes() | % { Write-Verbose $_.FullName }
  95.             } else {
  96.                 Write-Warning "Failed: `$assembly is null"
  97.                                 return
  98.             }
  99.            
  100.             # return proxy instance
  101.             $proxy = new-object $serviceType.FullName
  102.             if (! $Anonymous) {
  103.                 $proxy.Credentials = $dcp.Credentials
  104.             }
  105.             $proxy # dump instance to pipeline
  106.         }
  107.     } else {
  108.         Write-Progress "Proxy Generation" "Failed."       
  109.         Write-Warning $warnings
  110.     }
  111.     #Write-Progress -Completed
  112. }
posted on Tuesday, August 07, 2007 12:44:03 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback
# Thursday, August 02, 2007

For me, this opens up a whole new world of PowerShell interaction with remote servers. No more constructing soap packets - just point it at a WSDL url, and you get back a first-class object with methods that you can call corresponding to remote webmethods. PowerShell's [xml] accelerator automatically wraps the responses so you can easily manipulate the results.

Have fun!

UPDATE: newer more robust version 2.0 available!

posted on Thursday, August 02, 2007 1:56:44 PM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] Trackback