Considerations for Delivering Successful Mobile Projects

There is no denying that mobility is currently a hot topic. Staggering forecasts by various analysts have made capitalizing on the mobile applications market a top priority for many organizations’ CIOs and CTOs. However, before diving headfirst into mobility projects, it is important to consider various factors in order to define a clear strategy that addresses the many opportunities and challenges that mobility presents for your organization.

During Q4 of 2010, smartphone sales surpassed global PC sales for the first time in history. This turning point arrived quicker than most analysts predicted. By 2013, it is expected that the combined global sales of smartphones and tablets will double the sales of PCs. This estimation might be conservative. With lower-cost smartphones expected to be available on the market due to Microsoft’s partnership with Nokia and with less expensive tablets like the Kindle Fire certain to arrive in the near future as alternatives to the Apple iPad, it is likely that this turning point will arrive sooner. And these devices aren’t simply meant to supplement traditional PCs and laptops; for many users, mobile devices will be the only way they will access the internet.

Organizations should view these forecasts and trends as opportunities. The revenue potential is huge and only continues to grow. With a well-devised strategy, the mobile channel can present a unique opportunity to further engage with customers, differentiate from competitors or establish a position of leadership in your market and ultimately increase revenue. As a disruptive technology, mobile devices can even be a catalyst for organizations to create new, untapped markets (for example, location-based social network applications like Foursquare1).

In order to capitalize on these opportunities and to shape your strategy, consider the following factors:

  • Business Objectives– What are your organization’s top-level goals? It is important to align your mobility strategy to these goals. When selecting the mobile projects your organization will undertake, does your selection criteria include how the projects help your organization achieve its goals? For example, if your goals this year are to increase revenue and to increase brand awareness, are you utilizing mobile to its full potential as a revenue channel and do you have plans to integrate your mobile applications with social networks?
  • Customer Expectations – What do your high-value customers2 expect from you? Do they expect that when they browse your website from a mobile device, they’ll get a mobile-optimized view?3Do they expect applications targeted for their specific device or mobile OS? More and more smartphone and tablet users demand mobile applications that provide a rich and compelling view of content and provide the real-time information they need while on-the-go.
  • Competitive Advantage – What features do your competitions’ mobile applications provide? You can almost certainly bet that if your competition provides these features, your customers will expect similar features in your applications as well. Beyond these core features, what other features can you provide to set your applications and services apart from those of your competitors?
  • Go-to-Market Tactics – How will you launch your application? What tools will you use, which platforms and devices will you target? Can you take an agile approach and launch an application quickly (i.e., get something out on the app store or your mobile-optimized website) and do quick iterations? Or do the needs of your target customer/audience require you to have a fuller-fledged, feature-rich application from the outset? And once your application is on the market, how do you plan on driving sales? What marketing campaigns are vital and necessary to the success of the application?
  • Organizational Readiness – Is your company in a position to deliver mobile solutions? Does your company have the capacity in-house to develop and manage/support mobile-optimized web sites and/or mobile applications or will you have to establish a partnership with companies that are ready to do so? Do you have the appropriate infrastructure to support these applications or do you need to leverage cloud solution providers?

These factors are not meant to be an exhaustive list, but a starting point for developing a strategy for ensuring successful mobility projects4. However, addressing these factors and answering the related questions can help you establish a roadmap in delivering the appropriate mobile applications and services by your organization.

What other factors do you believe are important in ensuring successful mobile projects?


1 Can ‘location-based social network applications’ be even considered as a market?

2 By ‘high-value’, I don’t only mean to refer to customers who have spent the most money. You should also consider the social value of your customers as part of the valuation process.

3 Rhetorical question - of course they expect this. Browsing to a company’s website on my mobile device and not having it rendered optimized for mobile is one of my biggest pet-peeves. Every company should have a mobile-optimized version of their site.

4 These factors also don’t cover your enterprise mobility strategy (to be covered in a future blog post).

Cannot modify SharePoint list views with tracing enabled

I was debugging an issue with a heavily customized SharePoint site where users were unable to save any changes they made to SharePoint list views. Since this was a heavily customized site with several custom list definitions, my initial thought was that we may have botched a list definition or two and made these lists and list views non-modifiable. However, I also tested creating an out of the box SharePoint List and found out that its views were not modifiable as well. In order to isolate the issue, I created a second web application with a simple Team Site site collection. In this web app/site collection, the list views were all modifiable so I knew that the issue was definitely related to something in our particular web application.

The next thing I did was check the ULS logs. In the ULS logs, I noticed the following entries immediately posted after attempting to modify a list view:

09/26/2011 10:39:40.52     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Monitoring                        nasq    Medium      Entering monitored scope (Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001))     
09/26/2011 10:39:40.52     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Logging Correlation Data          xmnv    Medium      Name=Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af71    Medium      HTTP Request method: POST    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af75    Medium      Overridden HTTP request method: POST    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.60     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           af74    Medium      HTTP request URL: /_vti_bin/owssvr.dll?CS=65001    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y3    High        Failed to open the file 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.en-US.resx'.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        #20015: Cannot open "": no such file or folder.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        (#2: Cannot open "": no such file or folder.)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y9    High        Failed to read resource file "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.en-US.resx" from feature id "(null)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8e26    Medium      Failed to open the language resource keyfile wss.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y3    High        Failed to open the file 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.resx'.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        #20015: Cannot open "": no such file or folder.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y4    High        (#2: Cannot open "": no such file or folder.)    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           b9y9    High        Failed to read resource file "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\wss.resx" from feature id "(null)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8e26    Medium      Failed to open the language resource keyfile wss.    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.69     w3wp.exe (0x1790)                           0x173C    SharePoint Foundation             General                           8l3c    Medium      Localized resource for token 'multipages_direction_dir_value%>' could not be found for file with path: "(unavailable)".    9446a0f0-1577-472e-a8af-a552b1b4cc39
09/26/2011 10:39:40.73     w3wp.exe (0x1790)                           0x0EB8    SharePoint Foundation             Monitoring                        b4ly    Medium      Leaving Monitored Scope (Request (POST:http://test-sp:80/_vti_bin/owssvr.dll?CS=65001)). Execution Time=206.33069286739    9446a0f0-1577-472e-a8af-a552b1b4cc39

The key errors I noticed were the fact that, for some reason, certain resource (.resx) files couldn’t be loaded. These errors were quite strange. First, I wasn’t sure why these resource files were being loaded in the first place. Second, I could not understand why SharePoint was trying to load the files from C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Resources\. These resource files don’t reside there; they are located under the application’s App_GlobalResources directory. But even manually placing the correct resource files in the Resources directory where the application was attempting to load from didn’t resolve the issue.

A little bit of research led me to a post by Ivan Neganov entitled ‘Writing Trace Output to ULS Log in SharePoint 2010’. In the post, Ivan describes a little caveat that enabling ASP.NET tracing caused some SharePoint instability, namely the inability to create a new web part page and the inability to open up a SharePoint site with tracing enabled with SharePoint Designer. Taking that clue, I removed the system.web/tracing element from our application’s web.config and, sure enough, that resolved the issue.

So there seems to be a little laundry of things broken by ASP.NET tracing in SharePoint 2010. Have any of you had any other issues you’ve encountered?

Powershell Cmdlet for FAST Search Document Removal

On a project I’m currently on, we had a scenario where we needed to support being able to quickly remove potentially many documents from the FAST Search index. Unfortunately, the FAST web administration only allows you to delete one document at a time, which would definitely not be suitable for our scenario. We had a couple of ideas on how we were going to tackle the problem. One of the ideas we tossed around was using the FAST Content API. Although we didn’t end up using this technique for the project, I still believed that using the Content API along with Powershell to be a very useful and powerful combination. So today, I spent a little bit of time working on a Powershell cmdlet that can remove many items from the FAST index.

Visual Studio 2010 Project Setup

The first thing to do is to create a Class Library project in Visual Studio and add a reference to the Esp-Contentapi.dll from the ESP SDK. You’ll also want to add a reference to both  System.Management.Automation.dll (found in C:\Windows\assembly\GAC_MSIL\System.Management.Automation\1.0.0.0__31bf3856ad364e35) and System.Configuration.Install.dll (in C:\Windows\Microsoft.NET\Framework\v2.0.50727).

After adding the three dlls, you want to add two class files to the project, a Powershell snap-in class and a class for the cmdlet. In my project, my snap-in class is PointBridge.FAST.Cmdlets.PointBridgeFASTSnapIn and the cmdlet class is PointBridge.FAST.Cmdlets.Content.RemoveContentItem. The code and explanation of these classes follows.

PointBridge.FAST.Cmdlets.PointBridgeFASTSnapIn

This class derives from PSSnapIn and is used to register all the cmdlets in the assembly. When deriving from PSSnapIn, you need to override the following three properties: Name, Description, Vendor.

The class also is decorated with the RunInstaller attribute, in order to be able to install the assembly using installutil.exe.

 1: using System;
 2: using System.Collections.Generic;
 3: using System.Linq;
 4: using System.Text;
 5: using System.ComponentModel;
 6: using System.Configuration.Install;
 7: using System.Management.Automation;
 8:  
 9: namespace PointBridge.FAST.Cmdlets
 10: {
 11:     [RunInstaller(true)]
 12:     public class PointBridgeFASTSnapIn : PSSnapIn
 13:     {
 14:         public override string Name
 15:         {
 16:             get { return "PointBridgeFASTSnapIn"; }
 17:         }
 18:  
 19:         public override string Description
 20:         {
 21:             get { return "Various cmdlets to help with FAST management."; }
 22:         }
 23:  
 24:         public override string Vendor
 25:         {
 26:             get { return "PointBridge";  }
 27:         }
 28:     }
 29: }

 

PointBridge.FAST.Cmdlets.Content.RemoveContentItem

This class, which derives from Cmdlet, is the main class than handles the processing. When building cmdlets, you decorate the class with a Cmdlet attribute. This attribute is used to indicate the verb-noun pair used to invoke your cmdlet. In this instance, because of this attribute, my cmdlet is invoked as ‘Remove-ContentItem’ from the shell.

The RemoveContentItem class has three Powershell parameters:

  • ContentID – the ID of the content to delete from the FAST index.
  • Collection – the name of the collection in FAST where the item is in.
  • ContentDistributor – the server/port of the FAST ContentDistributor.

In the BeginProcessing() method (overridden from the Cmdlet base class), I set up an instance of an IDocumentFeeder object to be used later, when processing each record. The IDocumentFeeder is an interface that allows you to work with a FAST ESP collection for adding/removing/updating documents within that collection. You can get an instance of an IDocumentFeeder by calling the static CreateDocumentFeeder method of the Com.FastSearch.Esp.Content.Factory class.

In the ProcessRecord() method, I call the RemoveDocument() method of the IDocumentFeeder object to queue up the removal of the content item. The ProcessRecord() method is called for each ContentID passed into the cmdlet from the pipeline.

Finally, in the EndProcessing() method, I take care of reporting and clean up. The call to IDocumentFeeder.WaitForCompletion() is used to make sure all deletes that were submitted are complete (successfully or not) before we continue. After the deletes have been processed, I used the IDocumentFeederStatus object returned from IDocumentFeeder.GetStatusReport() to build up a report of the deletes that failed or executed with warnings.

 1: using System;
 2: using System.Collections;
 3: using System.Collections.Specialized;
 4: using System.Management.Automation;
 5: using Com.FastSearch.Esp.Content;
 6: using Com.FastSearch.Esp.Content.Config;
 7: using Com.FastSearch.Esp.Content.Errors;
 8: using Com.FastSearch.Esp.Content.Util;
 9:  
 10: namespace PointBridge.FAST.Cmdlets.Content
 11: {
 12:     [Cmdlet("Remove", "ContentItem")]
 13:     public class RemoveContentItem : Cmdlet
 14:     {
 15:         [Parameter(Mandatory=true, ValueFromPipeline=true, Position=0)]
 16:         public string ContentID { get; set; }
 17:  
 18:         [Parameter(Mandatory=true, Position=1)]
 19:         public string Collection { get; set; }
 20:  
 21:         [Parameter(Mandatory=true, Position=2)]
 22:         public string ContentDistributor { get; set; }
 23:  
 24:         private IDocumentFeeder _feeder = null;
 25:  
 26:         protected override void BeginProcessing()
 27:         {
 28:             base.BeginProcessing();
 29:             try
 30:             {
 31:                 _feeder = Factory.CreateDocumentFeeder(ContentDistributor, Collection);
 32:             }
 33:             catch (Exception ex)
 34:             {
 35:                 WriteError(new ErrorRecord(ex, "ContentFactoryOperationError", ErrorCategory.InvalidOperation, _feeder));
 36:             }
 37:  
 38:         }
 39:  
 40:         protected override void ProcessRecord()
 41:         {
 42:             base.ProcessRecord();
 43:  
 44:             if (_feeder == null) return;
 45:  
 46:             long opID = _feeder.RemoveDocument(ContentID);
 47:             WriteObject(string.Format("Removing item '{0}'. Operation ID: {1}", ContentID, opID));
 48:             
 49:         }
 50:  
 51:         protected override void EndProcessing()
 52:         {
 53:             base.EndProcessing();
 54:  
 55:             if (_feeder == null) return;
 56:  
 57:             _feeder.WaitForCompletion();
 58:             BuildStatusReport(_feeder.GetStatusReport());
 59:             _feeder.Dispose();
 60:         }
 61:  
 62:         private void BuildStatusReport(IDocumentFeederStatus status)
 63:         {
 64:             if (status.HasDocumentErrors())
 65:             {
 66:                 WriteObject(string.Format("Total Errors: {0}", status.NumDocumentErrors));
 67:  
 68:                 foreach (Pair p in status.AllDocumentErrors)
 69:                 {
 70:                     DocumentError error = (DocumentError)p.Second;
 71:  
 72:                     WriteObject(string.Format("Operation ID: {0} Document ID: {1} Error Code: {2} Description: {3}", 
 73:                         (long)p.First, error.DocumentId, error.ErrorCode, error.Description));
 74:                     
 75:                 }
 76:             }
 77:  
 78:             if (status.HasDocumentWarnings())
 79:             {
 80:                 WriteObject(string.Format("Total Warnings: {0}", status.NumDocumentWarnings));
 81:  
 82:                 foreach (Pair p in status.DocumentWarnings)
 83:                 {
 84:                     DocumentWarning warning = (DocumentWarning)p.Second;
 85:                     
 86:                     WriteObject(string.Format("Operation ID: {0} Document ID: {1} Warning Code: {2} Description: {3}",
 87:                         (long)p.First, warning.DocumentId, warning.WarningCode, warning.Description));
 88:  
 89:                 }
 90:  
 91:             }
 92:         }
 93:     }
 94: }

 

Using the cmdlet

In order to use the cmdlet, open up a new Powershell window and use installutil.exe to install the snap-in:

PS> CD [location of assemblies]
PS> set-alias installutil $env:windir\Microsoft.NET\Framework64\v2.0.50727\installutil
PS> installutil PointBridge.FAST.Cmdlets.dll

You only need to run installutil one time and the snap-in can be added on any subsequent Powershell sessions.

The following is an example of how to use the cmdlet:

 1: PS> add-pssnapin pointbridgefastsnapin
 2: PS> $contentids = "http://www.deviantpoint.com/category/ASPNET.aspx", "http://www.deviantpoint.com/?tag=/moss", "FAKEID", "http://www.deviantpoint.com/category/Workflow.aspx"
 3: PS> $contentids | remove-contentitem -collection "WebCollection" -contentdistributor "fsis:16100" | out-file "c:\temp\results.txt"

 

Line 1 just adds the snap-in for use in my current session. Line 2 sets up an array of the content ids I want to delete from my collection. This array (or set of records to process) can be read from a file, database, wherever. Here, I just set it up directly as an example. The third id in the example above is a fake id that doesn’t actually exist in my collection. Lastly, I take my content id array, pipe it to my remove-contentitem cmdlet and the results are sent to an output file (not necessary to push to an output file but I always like to, instead of everything dumping on the screen).

The results of running this cmdlet looks like this:

Removing item 'http://www.deviantpoint.com/category/ASPNET.aspx'. Operation ID: 1
Removing item 'http://www.deviantpoint.com/?tag=/moss'. Operation ID: 2
Removing item 'FAKEID'. Operation ID: 3
Removing item 'http://www.deviantpoint.com/category/Workflow.aspx'. Operation ID: 4
Total Errors: 1
Operation ID: 3 Document ID: FAKEID Error Code: 3 Description: Document e14c677abdbd9678dbfe1e5580de9aef_WebCollection does not exist

The nice thing about wrapping this up in a cmdlet is that I can reuse this cmdlet in my Powershell scripts so that I can easily remove any unwanted content from my collections.

So here is the shameless plug - If you want a copy of the Visual Studio solution, use the Tweet link below to tweet this post. Then send me an email (btubalinal@pointbridge.com) and I’ll send you a copy of the solution.