Thursday, March 14, 2013

Searches, by an International User, producing no results

This will be quick.

So I had an issue with a user from Beijing performing searches against a site, but no search results would appear. After some investigation, ie a couple of late nights of virtual troubleshooting sessions with the user, I discovered that SharePoint search was interpreting the query by way of the local language settings. Given that this user's local language was Mandarin, the English word searches he was performing did not make sense to SharePoint. To resolve his issue, it required me to adjust the default query language setup on the search results page. Here's how I did it:

I performed a search to navigate directly to the search results window. Next, the page was placed in edit mode. Under the "Search Core Results" webpart, I edited the properties. Expanding the "Results Query Options" section, I picked "English" as the default Query Language and saved my changes.

VoilĂ , it worked!

Wednesday, January 30, 2013

Universal PowerShell Script to Deploy SharePoint Solution Packages

So, I was tasked with deploying several solution packages to an SharePoint 2010 alpha environment. Obviously, task one was to gather all the packages to be deployed. Task two was going to turn into a tedious headache until I ventured off into the PowerShell scripting world. What resulted was a universal script that I could use going forward that would deploy any number of wsp files. Below follows the script that handles said deployment.

A couple of things to note:
   1. This script assumes that a ps1 script is generated and is included in the folder from which your WSPs will be deployed.
   2. You have access to a SharePoint server on the farm.


Set-ExecutionPolicy Unrestricted 


$fc = new-object -com scripting.filesystemobject
$curFolder = Split-Path ($MyInvocation.MyCommand.Path)
$folder = $fc.getfolder($curFolder)

foreach ($i in $folder.files) {
  IF ($i.name -like "*.wsp")
  {
    $fullPath = join-path $curFolder $i.name
    Add-SPSolution $fullPath
    Install-SPSolution -Identity $i.name -GACDeployment
  }
}


Write-host "SharePoint 2010 Solutions Have Been Deployed to the Farm"
Write-host "Review Central Administration For Accuracy"

Thursday, January 24, 2013

Extracting Solution Packages from SharePoint 2010 via PowerShell

There may come times when it becomes necessary to synchronize your SharePoint farms. In my experience, solution packages are virtual moving targets where developers apply changes willy-nilly. Thus, piecing together the contents of a solution package becomes a tedious and cumbersome task that distracts you from other time-sensitive projects.

Well, to speed up the process, you can use a shorthand approach by simply extracting the solution packages deployed to their respective content databases. How you ask? By way of Powershell. Here goes:
$solutions = [Microsoft.SharePoint.Administration.SPFarm]::Local.Solutions;

foreach ($solution in $solutions) {
        $solution.SolutionFile.SaveAs("c:\temp\packages\" + $solution.Name);
}

Friday, July 6, 2012

Parsing Managed Metadata fields in lists

Ok, here's an easy one (and short one for those of you short on patience). With the advent of the Managed Metadata Service, we've all come to know and love Groups, Term Sets and, naturally, Terms. So the question becomes, what is an effective way of parsing through a multi-value enabled Managed Metadata field to capture their text values. Easy, it's just a matter of parsing through a TaxonomyFieldValueCollection object. Here goes nothing...

First thing you have to remember is to apply a using directive to qualify use of the SharePoint Taxonomy class. I can't tell you enough how nice it is come across a post that reminds us what classes are leveraged for a given solution.

    using Microsoft.SharePoint.Taxonomy;

So let's say we have a ListItem declared as "_MyListItem," and we want to capture the text values saved in a Managed Metadata field called "MySpecialTerms" in a List object called "_MyListOfTerms." Here's how you do it:

List<string> _MyListOfTerms = new List<string>();

foreach (TaxonomyFieldValue aSingleTermValue in 
    (TaxonomyFieldValueCollection)_MyListItem["MySpecialTerms"])
{
   _MyListOfTerms.Add(aSingleTermValue.Label);
}

VoilĂ !

Happy Coding.

Wednesday, May 23, 2012

Popup Window After Event Receiver Fires

So here's a low tech approach to firing off a popup window after an event receiver fires. This approach makes the following assumptions:
  • Event occurs at list item level
  • Event Receiver cannot interrupt save process
  • Popup window loads after event receiver fires
  • Parent window loads list after save
So, what you need are the following objects:
  • Event receiver that inherits SPItemEventReceiver
  • Redirect Page
  • Popup page

Your first step is to create a new event receiver object. The event receiver will override the ItemAdded or ItemUpdated events. Bear in mind that these events are, by default, asynchronous events. Edit the elements.xml file for the event receiver to include a Synchronization node that specifies a synchronous action. Your XML should resemble the following:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListTemplateId="100">
      <Receiver>
        <Name>MyProject_EventItemAdded</Name>
        <Type>ItemAdded</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>MyProject_PopupEvent.MyProject_Event</Class>
        <SequenceNumber>10000</SequenceNumber>
        <Synchronization>Synchronous</Synchronization>
      </Receiver>
  </Receivers>
</Elements>

Next step is to include a constructor in the EventReceiver that initializes a variable to the Current HttpContext such as:

if (current == null)
                current = System.Web.HttpContext.Current;

Step 3 includes modifying the overridden ItemAdded or ItemUpdated events that perform a simple redirect call to the redirect page such as:

current.Response.Redirect({redirectUrlString});

Step 4: Your redirect page can be a simple page with inline code that captures any querystring values or anything that you want to pass on to the popup window. Otherwise, the redirect page will hold javascript code that generates the popup window such as:

 <script type='text/javascript'>
 <!-- 
     
     window.open("<%=_PopupURL %>", "PopupWindow", "location=0,status=0,scrollbars=0, width=550,height=650");

     if (window.location != window.parent.location) {
         window.frameElement.commitPopup();
     }
     else if (window.frameElement != null) {
        window.frameElement.waitDialog.close();
     }
     else {
         window.location = "<%=_SourceURL %>";
     }

// -->
</script>

Couple of things to note:
1. This javascript code accounts for whether the redirect page was called from a dialog page or a full page.
2. The _PopupURL and _SourceURL variables are derived from inline code on the redirect page
3. The redirect page either closes itself if it came from a dialog page or redirects to another page if it was called from a full page.

Happy coding.

Wednesday, January 4, 2012

Overwriting the SP 2010 'Email Link' Ribbon button

And so it goes, our user community was looking for a way to email the content from a wiki page to users outside of the network. The solution was to overwrite the "E-Mail a Link" button found on the ribbon within the Page tab. But how do we accomplish this? Well, it turns out, it's pretty easy.

While we're not technically overwriting the functionality of the button, we are hiding it and generating our own. Thus, this task became a two step process (three if you want to add a configuration page for the email form):

Step one, hide the default "E-Mail a Link" button: To perform this, we need to write a feature that calls a Custom Action. I won't bore you with the specifics of how to generate a feature project and I'll cut to the juicy details on what needs to be written in the elements.xml file.

<CustomAction
    Id="RemoveEmailPageLinkRibbonButton"
    Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.WikiPageTab.Share.EmailPageLink" />
      </CommandUIDefinitions>
    </CommandUIExtension>
  </CustomAction>


Step two, add a new Ribbon button to the Page tab: To perform this, we need to include in our Element.xml file another Custom Action. Again, I won't bore you with feature generation, so here's the code needed to add the button.

Few things to note about this code - I depended on Javascript (don't ask, I prefer it to jQuery). The clientside code here simply reads the current page and captures the content within the content area for the Wiki. Once that's established, I copy those values to variables in my modal dialog window, making them available to the child window.

<CustomAction
    Id="EmailContentButton"
    Location="CommandUI.Ribbon" 
    Sequence="5" 
    Title="Email">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.WikiPageTab.Share.Controls._children">
          <Button 
          Id="Ribbon.WikiPageTab.Share.EmailContentButton"
          Alt="E-Mail Page Content"
          Sequence="5"
          Command="EmailPageContent_Button"
          Image32by32="/_layouts/1033/images/formatmap32x32.png" Image32by32Top="-128" Image32by32Left="-448"
          Image16by16="/_layouts/1033/images/formatmap16x16.png" Image16by16Top="-16" Image16by16Left="-88"
          LabelText="E-Mail"
          TemplateAlias="o2" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
        Command="EmailPageContent_Button"
        CommandAction="javascript: 
         function GetEmailContent()
          {
            var objMainDiv = document.getElementById('ctl00_PlaceHolderMain_WikiField');
            var params = new Object();
            var options;
            params.pageTitle = document.title;
            params.parentUrl = window.location.href;
            params.pageContent = '';
             
            // if regular wiki 
            if(objMainDiv != 'undefined' && objMainDiv != null)
            {
             var objTable = objMainDiv.getElementsByTagName('table');
             
             if(objTable[0] == 'undefined' || objTable[0] == null)
             {
                if(confirm('Could Not Detect Wiki Content.\nE-Mail Page Link?'))
                    location.href='mailto: ?subject='+document.title+'&body=' + escape(window.location.href);
                return;
              }
              var objDiv = objTable[0].getElementsByTagName('td');
              if(objDiv[0] == 'undefined' || objDiv[0] == null)
             {
                if(confirm('Could Not Detect Wiki Content.\nE-Mail Page Link?'))
                    location.href='mailto: ?subject='+document.title+'&body=' + escape(window.location.href);
              }
             
             for(var i=0;i<objDiv.length;i++)
             {
                if(objDiv[i] != 'undefined' && objDiv[i] != null)
                  params.pageContent += objDiv[i].childNodes[0].innerHTML;
             }
            }
            else
            {// else check for enterprise wiki 
              objMainDiv = document.getElementById('ctl00_PlaceHolderMain_PageContent__ControlWrapper_RichHtmlField');
              
              if(objMainDiv != 'undefined' && objMainDiv != null)
              {
                 params.pageContent += objMainDiv.innerHTML;
              }
              else
              {
                 if(confirm('This Wiki Has No Content.\nE-Mail Page Link?'))
                    location.href='mailto: ?subject='+document.title+'&body=' + escape(window.location.href);

                 return;
              }
            }
            
            options = { 
                url:'{SiteUrl}/_layouts/EmailWikiContent/EmailPageContent.aspx',
                title: document.title,
                width:550,
                height:650,
                args: params};
                SP.UI.ModalDialog.showModalDialog(options);
                }
                GetEmailContent();" />
              </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>


Ok, so I hinted that there may be a step three involved. In my project, I included code to perform runtime configurations for the E-Mail form; such as enabling/disabling the subject and body fields. In any case, you have to add another Custom Action. Here goes, Step Three:

This code adds the link to my custom configuration Page to the Site Administration section of Site Settings.

<CustomAction
    Id="ModifyFeatureMailToProperty" 
    GroupId="SiteAdministration" 
    Location="Microsoft.SharePoint.SiteSettings" 
    Title="E-Mail Wiki Page Settings" 
    Rights="ManageWeb"
    Description="Adjust the Default E-Mail Settings for My Custom Wiki Page Content E-Mails">
    <UrlAction Url="_layouts/EmailWikiContent/configureCustomEmailForm.aspx" />
  </CustomAction>

Cheers!

Updating BizTalk Config with Powershell

So, I was tasked with performing a simple update to a BizTalk application. This update would read values from the BizTalk configuration and leverage said values to determine best course of action in processing messages. Why you ask. Because we wanted to create a configurable process that would eliminate the need to recompile our BizTalk solutions.

In any case, I was able to add a library to the project, from which the config values would be read. Now, the next step was in deploying the application and appending the config value to BTSNTSvc.exe.config and BTSNTSvc64.exe.config. Since I already leverage Powershell to perform my deployments, I ultimately decided to include scripts to perform the config updates. Below follows the code to add a setting within the AppSettings section, as well as a comment to differentiate the new config setting with all others:

The first step was in generating functions to perform the actual procedures for inserting appSettings nodes and including comments in the appSettings section.

function Set-ApplicationSetting ([string]$fileName, [string]$filepath, [string]$name, [string]$value)
{
    # Load the config file up in memory
    [xml]$a = get-content $fileName;

    # Find the app settings item to change
    if($a.configuration.appSettings.selectsinglenode("add[@key='" + $name + "']") -ne $null)
    {
 $a.configuration.appSettings.selectsinglenode("add[@key='" + $name + "']").value = $value
    }
    else
    {
 $newElem = $a.CreateElement("add")
 $newKeyAttr = $a.CreateAttribute("key")
 $newValAttr = $a.CreateAttribute("value")

 $newKeyAttr.innerText = $name
 $newValAttr.innerText = $value

 $newElem.Attributes.Append($newKeyAttr) > $null
 $newElem.Attributes.Append($newValAttr) > $null
 
 $a.configuration.appSettings.appendChild($newElem)
    }

    # Save Changes
    $a.Save($filepath + $filename)
}

function InsertAppSettingsComment ([string]$fileName, [string]$filepath, [string]$value)
{
    # Load the config file up in memory
    [xml]$a = get-content $fileName;

    $comment = $a.CreateComment($value)

    # Find the app settings item to change
    $a.configuration.appSettings.InsertBefore($comment, $a.configuration.appSettings.LastChild)

    # Save Changes
    $a.Save($filepath + $filename)
}


Next up was discovering the appropriate folder location of the BizTalk config file and assigning said location to a variable.
# Check for correct Program Files directory
if(Test-Path (${env:ProgramFiles(x86)} + "\Microsoft BizTalk Server 2009\BTSNTSvc.exe.config"))
{
 $BTSConfigPath = ${env:ProgramFiles(x86)} + "\Microsoft BizTalk Server 2009\"
}
else
{
 $BTSConfigPath = "D:\Program Files (x86)\Microsoft BizTalk Server 2009\"
}

# Set Program Files directory as current location
Set-Location $BTSConfigPath


Now, I want to include code to backup the current config file before applying any changes.
$currentDate = (get-date).tostring("mm_dd_yyyy-hh_mm_s") # month_day_year - hours_mins_seconds
$backup86 = ".\BTSNTSvc.exe_" + $currentDate + "_config.bak"
$backup64 = ".\BTSNTSvc64.exe_" + $currentDate + "_config.bak"

Write-Output "Backup Prior BTS Config files (86 & 64)"

Copy-Item ".\BTSNTSvc.exe.config" $backup86 -Force
Copy-Item ".\BTSNTSvc64.exe.config" $backup64 -Force


Next, I want to write the new appSettings node to the BizTalk config file.
Write-Output "Updating BizTalk Application Setting"

# Change the app setting for the path to the backups
Set-ApplicationSetting "BTSNTSvc.exe.config" $BTSConfigPath "My Node Key" "My Node Value"
Set-ApplicationSetting "BTSNTSvc64.exe.config" $BTSConfigPath "My Node Key" "My Node Value"


Finally, I want to add some comments to the appSettings section just above my new appSettings node.
# Insert Comments
InsertAppSettingsComment "BTSNTSvc.exe.config" $BTSConfigPath "My Config Setting"
InsertAppSettingsComment "BTSNTSvc64.exe.config" $BTSConfigPath "My Config Setting"


Great! My new appSettings node is now available in the BizTalk config file. But how do I remove those values if need-be? No worries, follow the below steps:

The first step, again, is to include functions that perform the actual process.

function Remove-ApplicationSetting ([string]$fileName, [string]$filepath, [string]$name, [string]$value)
{
    # Load the config file up in memory
    [xml]$a = get-content $fileName;

    # Find the app settings item to change
    if($a.configuration.appSettings.selectsinglenode("add[@key='" + $name + "']") -ne $null)
    {
 $killChild = $a.configuration.appSettings.selectsinglenode("add[@key='" + $name + "']")
 $a.configuration.appSettings.RemoveChild($killChild)
    
 # Save Changes
 $a.Save($filepath + $filename)
    }
}

function RemoveAppSettingsComment ([string]$fileName, [string]$filepath, [string]$value, [string]$siblingValue )
{
    # Load the config file up in memory
    [xml]$a = get-content $fileName;


    if($a.configuration.appSettings.selectsinglenode("add[@key='" + $siblingValue + "']") -ne $null)
    {
 $sibChild = $a.configuration.appSettings.selectsinglenode("add[@key='" + $siblingValue + "']")
 $killChild = $sibChild.PreviousSibling

 if($killChild.innerText -eq $value)
 {
  $a.configuration.appSettings.RemoveChild($killChild)
 }
    
 # Save Changes
 $a.Save($filepath + $filename)
    }
}


We'll leverage the same logic from earlier to determine the proper folder location of the BizTalk config file, so I won't revisit that here. Which leads to the next step, we'll remove the comments first
Write-Output "Removing Application Setting"
# Insert Comments
RemoveAppSettingsComment "BTSNTSvc.exe.config" $BTSConfigPath "My Config Setting" "My Node Key"
RemoveAppSettingsComment "BTSNTSvc64.exe.config" $BTSConfigPath "My Config Setting" "My Node Key"


Finally, we remove the appSettings node.
# Change the app setting for the path to the backups
Remove-ApplicationSetting "BTSNTSvc.exe.config" $BTSConfigPath "My Node Key"
Remove-ApplicationSetting "BTSNTSvc64.exe.config" $BTSConfigPath "My Node Key"


Cheers!

Monday, August 8, 2011

Workaround for the "The part 'part' of message" BizTalk annoyance

Working with BizTalk can be a frustrating experience for many developers. At least, that's the experience I've had with the tool. In any case, my latest annoyance was the dreaded "The part 'part' of message '{Message}' contained a null value at the end of the construct block."

Scouring the web for solutions turned up virtually nothing. And so it began, my journey for a workable solution to this enigma that is BizTalk (again). My first attempts were a simple exercise of attrition. It encompassed recompiling, re-referencing, de-referencing and every other ridiculously simple attempt to resolve the problem. None produced the desired outcome; the message persisted and my patience grew increasing limited.

Thus, my next series of attempted fixes resulted in reassigning the message variable. I'll spare you from the multitude of failures incurred before succumbing to the eventual fix.

The resolution consisted of the following:
1. Declare the outgoing message of type System.Xml.XmlDocument
2. Create an xPath string that will populate said outgoing message with the entire contents of the message to be leveraged.
3. In a message construct/message assignment shape, assign the outgoing message by calling an xpath solution.

Here's a sample of the solution that worked for me:
xPathString = "/*[local-name()='Employees' and namespace-uri()='http://mySpecialSchema']";
MsgOutput = xpath(MsgInput,xPathString);

Thursday, August 4, 2011

Leverage the Looping Shape in an Orchestration

So I was tasked with generating a new BizTalk project that would update an email field in a SQL database. Great I thought, a simple orchestration with straight forward mapping ought to do the trick. And there I went, pointing and clicking away until I was thrown a curve by management. I was asked to leverage a stored procedure to update the field. Ordinarily, this isn't a problem, but we leverage a canonical schema from which all people records are derived. To update the stored procedure, I needed to devise a way of updating each record contained within in the canonical, one by one. What I was able to produce was an orchestration that enacted the Looping shape.
The way our canonical is formed, it contains an instance of an employee inside an employees node. The task was to extract each employee node, one by one, and push them out to the stored procedure via a send shape. Unfortunately, my stored procedure schema could only accommodate one record at a time, hence the loop shape.
First item of business was to determine how many records my canonical contained. Well, I needed a variable to hold this value, so I created one called recordCount. Now populating the recordCount required an xpath query to return the value. Now, xpath in BizTalk, can be a little cumbersome, but possible none the less. Taking my canonical schema I created this expression in a standard expression shape:
Now, the fun part. So I created a new Loop shape and entered
recordCount > 0
The next step entailed creating the single Employee message. To do this, you need a Construct Message shape. Within the Construct Message shape, you need to apply an Expression shape and Message Assignment shape. Obviously, the Message Assignment shape will handle the mapping of data from one message to another, but the Expression does the real work here. For this exercise, I created two temporary variables of type System.Xml.XmlDocument. Within my Expression shape, I was able to populate the temp variables with one record from my grand Employees canonical schema as such:
And of course, my Message assignment shape took the single Employee message and created a new message for the send port of the Update Email stored procedure.
Looping within an orchestration can be a clunky process, so I'd recommend you leave it as a last resort. But never the less, it beats writing hundreds of lines of c# code.

Happy Coding!

Monday, July 18, 2011

How to generate a BizTalk request message

What appears to be an issue for many BizTalk developers or newbies, is how to initiate a BizTalk request message.  Most perplexing about this is that BizTalk is not an intuitive interface, which leaves many in the dark as to how or what exactly needs to be done in order to perform a simple select query against a database.  Well, I've wrestled with this issue many times before, and what I've found is that generating your request messages is best served in code.  This is the only means, I find, that permits flexibility even after promoting your application to a production environment.

At the base is the simple question of where does one start?  Well, that's simple enough, just go to your map in Visual Studio, and right-click to perform a "Test map" operation.  If you don't have a map, create a temporary map and make sure you apply the "Generate Instance" setting in the TestMap Input field.  In the Output window, you'll find a link to the sample input file leveraged to generate the test.  Open this sample and voila! You have the xml needed to help generate your request message.

With the sample xml available, you can then create a class that performs the generate request message code.  Modify the xml values such that you can modify them from outside of BizTalk (perhaps in a config file).  Below follows sample code outlining message creation process.

public static XmlDocument CreateMsg(string msgType)
{
     XmlDocument xmlDoc = new XmlDocument();
       StringBuilder strBuilder = new StringBuilder();
            string columnString = string.Empty; \\ ex "column1 as [Sales], column2 as [Pct]
            string filterString = string.Empty; \\ ex "WHERE DATEDIFF(Day, LAST_DATE_WORKED, getdate()) < 8"


            strBuilder.Append("");

            switch (msgType.ToUpper().Trim())
            {
                case "A Specific Message":
                    columnString = ConfigurationManager.AppSettings["myRunTimeColumns"];
                    filterString = ConfigurationManager.AppSettings["myRunTimeWhereClause"];

                    strBuilder.Append(string.Format("{0}",
    columnString));

                    strBuilder.Append(string.Format("{0}",
    filterString));

                    strBuilder.Append("");
                    break;
                default:
                    strBuilder.Append("FirstName, LastName, OfficeNumber, case Code when '850' then Dept else Region end as LocationCode, Email");
                    strBuilder.Append("");
                    break;
            }

            strBuilder.Append("");


            xmlDoc.LoadXml(strBuilder.ToString());

            return xmlDoc;
}

And of course, you would have to declare a message object in your orchestration and initialize it from within the orchestration as such:

MyRequestMsg = HelperNameSpace.MessageConstructorClass.CreateMsg("A Specific Message");

For this example, I populated the BizTalk service config file with new keys under the appSettings node (myRunTimeColumns and myRunTimeWhereClause) to hold the column and where clause values respectively.

Friday, June 24, 2011

OOB BizTalk FTP Adapter Alternative

We tend to get naive when working with BizTalk and its many adapters.  I, for one, admit to operating under such soundless circumstances.  My latest issue has been with the out of the box FTP Adapter Microsoft so graciously included.  My objective was to generate an application that would connect to two separate FTP servers and apply the same file to both.  It just so happens that one of the vendors in this instance, was uncooperative or unable to supply a test account. So, I decided to test my process with the more cooperative of the two vendors and configured my send ports to push both files towards that vendor.

All worked great, and I ran and reran my process dozens of times over.  I passed development, alpha, beta and UAT testing until finally pushing my application to production.  That's when the wheels fell off the wagon.  After I reconfigured my send port to send my files to both vendors, within an hour of post-deployment testing, I soon discovered that the FTP adapter was failing for one of my send ports.  Upon review of the event logs, I noticed that my uncooperative vendor was refusing connection after the initial FTP call.

Digging a little deeper, I turned on the FTP logs made available by the FTP Adapter and found that the Adapter never sends the QUIT command to sever the connection until the host instance was shut down or restarted.  This was insane, I thought.  I tried a number of solutions such as applying an "After Put" command in the FTP Adapter. Sadly these solutions did not work effectively without throwing exceptions and cluttering up BizTalk with failed orchestrations.

So, I eventually gave in and decided to leverage a dynamic port and incorporate C# into the project.  The code declared a new class that would perform folder monitoring based on a value specified from the BizTalk configuration file.  My monitor would listen to the folder and wait for a new file or file modification to occur like such:

public void initiateFolderMonitor()
{
string fullFilePath = ConfigurationManager.AppSettings["FolderPath"];

folderMonitor.Path = fullFilePath;
UploadFilePath = fullFilePath;
UploadFile = ConfigurationManager.AppSettings["FileName"];
FTPAddress = ConfigurationManager.AppSettings["FTPAddress"];
FTPUser = ConfigurationManager.AppSettings["FTPUser"];
FTPPassword = ConfigurationManager.AppSettings["FTPPassword"];
FailureCount = 0;

folderMonitor.NotifyFilter = System.IO.NotifyFilters.FileName;

folderMonitor.NotifyFilter = folderMonitor.NotifyFilter | System.IO.NotifyFilters.Attributes;

folderMonitor.Created += new FileSystemEventHandler(monitoringEventRaised);
folderMonitor.Changed += new FileSystemEventHandler(monitoringEventRaised);


 try
 {
   folderMonitor.EnableRaisingEvents = true;
 }
 catch (ArgumentException)
 {
   stopActivityMonitoring();
 }
}

public void stopActivityMonitoring()
{
 if (FailureCount == 0)
   retryFileUpload();

 if (folderMonitor.EnableRaisingEvents)
   folderMonitor.EnableRaisingEvents = false;

 folderMonitor.Dispose();
 folderMonitor = null;
}

private void monitoringEventRaised(object sender, System.IO.FileSystemEventArgs e)
{
 switch (e.ChangeType)
 {
   case WatcherChangeTypes.Changed:
   case WatcherChangeTypes.Created:
      if (e.Name == UploadFile)
      {
         FailureCount += 1;

         if (FtpUloadFile())
         {
            stopActivityMonitoring();
            removeFile();
         }
         else
         {
            retryFileUpload();
         }
      }
      break;
   default:
      break;
 }
}

private void retryFileUpload()
{
  Thread.Sleep(10000);

  if (FailureCount > 3)
  {
     stopActivityMonitoring();
     return;
  } 
  else if (FtpUloadFile())
  {
     stopActivityMonitoring();
     removeFile();
     return;
  }

  FailureCount += 1;

  retryFileUpload();
}


And naturally, I have code to perform the actual FTP upload like such:
private bool FtpUloadFile()
{
   bool ftpReturn = false;

   try
   {
     System.Net.FtpWebRequest ftpRequest = (System.Net.FtpWebRequest)System.Net.WebRequest.Create(string.Format("ftp://{0}/{1}",
       FTPAddress, 
       UploadFile));
     ftpRequest.Method = System.Net.WebRequestMethods.Ftp.UploadFile;
     ftpRequest.Credentials = new System.Net.NetworkCredential(FTPUser, FTPPassword);

     System.IO.StreamReader streamFR = new System.IO.StreamReader(string.Format(@"{0}\{1}", 
       UploadFilePath, 
       UploadFile));
     byte[] filecontents = Encoding.UTF8.GetBytes(streamFR.ReadToEnd());
     streamFR.Close();
     ftpRequest.ContentLength = filecontents.Length;

     System.IO.Stream requestStr = ftpRequest.GetRequestStream();
     requestStr.Write(filecontents, 0, filecontents.Length);
     requestStr.Close();

     System.Net.FtpWebResponse resp = (System.Net.FtpWebResponse)ftpRequest.GetResponse();

     switch (resp.StatusCode)
     {
       case System.Net.FtpStatusCode.ClosingData:
          ftpReturn = true;
          break;
       default:
          ftpReturn = false;
          break;
     }

     resp.Close();
   }
   catch (Exception) { }

   return ftpReturn;
}

So, the above code gets initiated from my orchestration before the send shape. You have to declare a variable to hold the new class. Then, within an expression shape, you set the Folder monitoring in motion like so:
//Initiate Orchestration variable
monitorObj = new my.Helpers.MonitoringClass();
monitorObj.initiateFolderMonitor();

//Initiate the dynamic Send port
DynamicSendFilePort(Microsoft.XLANGs.BaseTypes.Address) = @"file://" + ConfigurationManager.AppSettings["FolderPath"] + @"\" + ConfigurationManager.AppSettings["FileName"];


Finally, in the orchestration, you can either call the disable monitoring method, or include code in your class to timeout the monitoring piece to release those system resources.

Friday, March 25, 2011

How to clone a SharePoint 2010 Hyper-V VM

If anyone has ever used Microsoft's Hyper-V Server, you've discovered that the interface is a little difficult.  For one, you're presented with no GUI, which can be intimidating to some.  In any case, I was able to spend a little time to uncover a few steps that could help towards cloning a standalone SharePoint instance hosted on VM.  Let's begin...

Your first step involves logging into the Hyper-V base.  On the base, what we want to accomplish is to create a copy of the VHD file containing the SharePoint 2010 instance.  For some, you may log into the base and find that there are no visible windows.  This little obstacle can be overcome by calling the Task Manager window; simultaneously press the Control-Shift-Esc keys.  That should help ease the nerves a bit.

With the Task Manager open, click the File->New Task(Run) option from the file menu.  This will popup a "Create New Task" window similar to what you'd find when you recall the run command in a normal windows environment.  At this point, you want to enter the following command “c:\Windows\System32\sconfig.cmd” to bring up the Server Configuration User window.  You'll want to open the "Create New Task" window again to open an instance of the command prompt as well.

Now, you need to know where the VHD file is located that you wish to copy.  If you don't, a quick way of determining the location is by connecting to the base image from another machine with Windows Explorer and looking under the VM configuration file repository normally located under "C:\ProgramData\Microsoft\Windows\Hyper-V\Virtual Machines."  Open the available XML files until you find the one containing a reference to the file you want to copy.  With this, you will have the path to the VHD file.

On the base machine, go to the DOS prompt and navigate to the location of the VHD file.  Perform a DOS command to copy the file to wherever you wish.  Once copied, you can paste the file to the Hyper-V base image of your choice.

With a copy of the VHD file on your target base image, you'll need to log into a server that hosts Hyper-V Manager.  From Hyper-V Manager, navigate to the target base machine and begin the process of creating a new "Virtual Machine."  Assign a name to the new "Virtual Machine," and allocate an adequate amount of memory.  Configure the networking credentials for the VM before finally selecting the location of the VHD file copied over.

With those steps, you will have created a new VM instance that is an exact clone of the prior VM instance.  But we're not done just yet.  Now we want to rename the new VM instance, or suffer through duplicate name conflicts.  However, because we're also working with SharePoint, we need to modify the Alternate Access Mappings in Central Administration before anything else occurs.

Connect to the new VM instance from Hyper-V Manager.  Log in under a local administrator account and open "Central Administration."  When and if you're prompted for credentials, use the local administrator credentials.  Navigate to "Configure Alternate Access Mappings" and configure the URLs, leveraging the prior VM instance name, to point to localhost.  

One other SharePoint centric step required before officially renaming the server is to run an stsadm command.  There may be a powershell command you can run, but I haven't performed enough research to determine what that would be.  So you need to open a command prompt as administrator by explicitly selecting the "run as administrator" option.  In your command prompt, change the directory to "c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\bin" or wherever the bin folder for 14-hive is located.  Run the command "Stsadm -o renameserver -newservername {YourNewServerName} -oldservername {YourOldServerName}"  to tell SharePoint that a server name change is in effect.

Now, let's bring up Server Manager to change that pesky server name by selecting Start->Administrative Tools->Server Manager.  From the Server Summary section, select "Change System Properties."  Click the "Change" button from the "Computer Name" tab and change the server name in the available text field.  You'll be asked to restart the VM.  Allow the server to restart.

Not quite done, but we're almost there.  Log back into the VM instance and open "Central Administration" again.  Go to the "Manage the farm administrators group" under the Security section.  You'll more than likely see accounts that reflect the server name of the prior VM instance.  You'll need to edit those accounts so that their names display the appropriate server name.

Voila!  These steps should get you through cloning a VM instance with SharePoint 2010.  A few assumptions are made with this tutorial and that is that it is assumed that SQL Server resides on the same VM instance.  Also assumed is that you have knowledge of the basic DOS commands required of this tutorial.  And finally, it is assumed that you have full access to the Hyper-V environment.

Friday, February 18, 2011

Generating Flat Files With BizTalk 2009

Again, I was asked to produce a simple comma delimited file with BizTalk.  Easy, right? Well, it is once you know what you're doing, but for those of us (myself included) who rarely are asked to produce these flat files, it can be a bit challenging.  For one, we're used to XML formatting and generally writing to a database or simply producing an XML file.

So I can't stress enough how convenient it is leverage the Flat File Schema wizard.  Only catch is, you need a sample file to get the wizard rolling along. Just create a quick sample file with one row of data and the columns you need, and don't forget to add a carriage return after the last column.

What you get next is another window asking for a little clarification.  Little tricky here.  Ultimately, what you would like is a csv file that applies a carriage return after each row of data.  So select the entire row including the carriage return. Following that, make sure you are defining the record by a delimiter.  A screen will appear asking such, so populate the "child delimiter" field with a carriage return/line feed delimiter.

Moving right along. The next screen is very important. Here, you're defining the name of that root element (not that important) and the element type (important).  Select the Record element type, click next.

You'll go through the same screens for the next set of elements. Only difference here is that you won't be selecting the carriage return when asked to "Select Document Data," and your delimiter will be something else (usually a comma for comma delimited files). And of course your "Child Elements" will be defined as "Field element."  One other thing to note, ensure that your root element has the "child order" property set to "postfix."

From here your only other requirement is to define a send pipeline.  Why you ask? Because the send pipelines format your data according to the specifications defined within your schema.  But not to worry, all you have to know is that in your pipeline file, you simply need to drag a "Flat file assembler" on to the Assemble shape.  Once there, define the "Document schema" to that of what was defined by the Flat File Schema wizard.
Voila! You're ready to generate csv files....Unless of course you wanted to include column headers.  I can't stress enough how difficult it was to find resources that explained how these header schemas work.  Oh sure, define another schema with your column names and your done, right?  Not for all of us.  What I didn't understand was that even after I defined a header schema, 2 things were occurring; empty string values for column names and no carriage return.  
So I set off to find what was wrong with my definition.  What I found was that, I needed to define either the "Fixed" or "Default Value"  property for each element field.   But before I get into that, let me note that defining a header schema is as simple as taking a copy of your document schema and manipulating the values for Fixed or Default and ensuring that a PostFix value is defined for the child order of the root record element.  With that, your header values will print and your carriage return will push the first row of data below the column headers.  Now you're done. Well, almost.  Go back to your Send Pipeline and define the Header Schema property with the newly created Header Schema and now you can deploy and enjoy your BizTalk application.

Tuesday, February 15, 2011

BizTalk Orchestrations and their respective ports

It seems that every time I have a new BizTalk project land on my desk, my brain turns to mush.  My most recent project is a case in point.

So I was asked to create a simple project that would take data from two disparate systems and merge/clean the data, before finally outputting said data to a simple XML file.  "No problem!" or so I thought.  Recalling my BizTalk acronym SOME (Schemas - Orchestrations - Maps - ESB Itineraries), I was able to quickly produce a project that would perform the task required.  Everything compiled ok.  I was able to deploy successfully (or so I thought).

I only incurred one minor issue, I'd forgotten how to configure my send ports, leveraging "Specify later" binding, to produce an xml file to a directory.  "This can't be" I thought, "I've created several other send ports similar to what I need now."  Thus, panic set in once again.  So I did what any other would do in this event...I reviewed my past to uncover the secret behind properly configuring my send port.  Only problem was that the answer was not as blatantly obvious as I would have preferred. 

"Oh my god! How do you configure a simple send port?"  Well folks, the answer is rather trivial, but annoying nonetheless.  Within your orchestration, you will recall having to define a Port Type.  Some important properties to configure within this type is the "Type Modifier."  Type Modifier should be set to public so that it becomes viewable via Administration Console.  Next you should take note of the Port identifier property for the Port, not to be confused with the Port Type.  This property will prove useful when configuring your send port via Administration Console. 

So you've set your "Type Modifier" to public and you've successfully deployed your solution.  Great!  Now all you need is to configure a send port on the Administration Console.  So select the Application, and create a new send port of Transport Type "File."  Beautiful!  Now BizTalk should produce files for me to the directory specified....right?  Not quite, and here's where I panicked a bit.  At this point, you've declared a send port at the BizTalk level, and you've declared a send port at the project level.  So how do you tell BizTalk to pair the two?  Well, it just so happens that when you right-click the Application in Administration Console, and select "Configure," you are presented with a window displaying the artifacts available within the app.  You'll find both the orchestrations, and send/receive ports created.  Your final step here is to select the orchestration that produces the send port and pair it with the send port you configured via Administration Console.

Voila! 

Friday, January 14, 2011

First Annoyance Solved For Sharepoint 2010

So I was asked to begin the process of converting our Sharepoint 2007 apps to 2010 solutions.  My first thought was, "Oh Great! I forgot to study."   Once I got over the initial shock, I began tackling the assignment with clueless abandon.  I must have attempted to convert 7 to 10 different solutions with the out of the box Sharepoint conversion wizard.  Attempted, but never realized.

Needless to say, it didn't go well.  With little to no help via the web, I scrapped the idea and was momentarily defeated.  But it wouldn't be my style to simply give up.  So off I went, on an adventure to the center of the Sharepoint universe (or at least the edge of my seat).

Long story short, I created an empty sharepoint project with Visual Studio 2010, selected Sandboxed solution and added all my solution files to the project.  All well and good, my project compiled and I was able to deploy to Sharepoint...except.  I couldn't add my webpart.  I'll spare you with the troubleshooting effort and ambiguous exception messages.  To get around my issue, I discovered a few articles stating that certain solutions need to be Farmed, not Sandboxed.

Easy enough I thought, so I began looking through every VS menu item and right-clicking every conceivable object in site to convert my solution to Farm.  No luck, there was a suggestion to open the project file and hand enter the xml node, but I ran into trouble here, plus I was confident that MS had an easy method for converting these silly Sharepoint projects, I just had to dig.  Well, wouldn't you know it, I found the solution.  Just view the project properties by selecting it in Solution Explorer and setting the "Sandboxed Solution" property to False.  So there you have it.  Simple solutions that could leave you feeling silly once you find them or worse yet, feeling like an idiot when your non-technical wife points it out to you.

Wednesday, October 27, 2010

BizTalk for beginners

It's been some time since my last post, for no other reason than I've gone from SharePoint development to BizTalk related activities in recent months.  In any case, let's get to it.

For this first BizTalk related post, I just want to create a beginner's level guide to developing BizTalk projects.  So what are the terms floating around BizTalk; Orchestrations, Itineraries, Maps, Schemas, Send/Receive Ports, Messages, etc.  Now, I can go over the terms for everyone, but it's not necessary to review what's been published hundreds of times already. My focus is to help developers adjust their current mindset to become open to the idea of project compartmentalization.  I have found that it is easiest to consider every aspect of a BizTalk project as an object.  And as a rule of thumb, work from the ground up, meaning that the development effort should begin with the most basic/independent "objects" first.

From a developers standpoint, it's important to become familiar with Schemas first and foremost.  Why? For no other reason than it is how your message content will be defined for specific operations and they are the absolutely most basic aspect of the BizTalk project.  Schemas are defined in Xml format.  Generally, the top elements define the message operations, such as select or delete operations, and their children define the data processed by these operations.  So it is possible to have a field defined several times within one schema.

A big problem I first ran into during development was that I had no idea how to structure my incoming message for unit testing.  Luckily, (and I found this out after many trials) Visual Studio has the ability to generate an instance of a message based on a supporting schema (Just right-click the schema and select Generate Instance).  This comes in handy when running tests againsts maps....

So how do we transfer information from one Message/Schema to another? Maps (There are other methods, but this is just a beginner's guide).  When you create a new mapping file, you are asked for a source and destination schema.  This is where the Schemas you generated come in handy.  Define a source and destination and leverage the mapper as much as possible to map fields from one schema to the other.  There may be fields that require some massaging before transmitting to their respective destination schema fields, and this is where it becomes important to familiarize yourself with functoids.

Functoids are the tools leveraged within a map to translate data.  They come in many forms; String functoids, Mathematical functoids, Date/Time functiods, etc.  Drop these on your map and define their input/output fields, as well as any other information required of them (such as actual C# code for instance).

In review, create your schemas first, then create your Maps.  Leverage Visual Studio to generate an instance of your source message/schema so you can test your maps more precisely.  Don't forget the functoids to translate your data for the destination system.

Ok. Ports.  You have send and receive ports.  As you may have guessed, ports define what data is transmitted into or out of the BizTalk environment.  But it's not enough to discuss ports alone, because ports have several working parts; Port Types, Operations, Communication Patterns, Message Types, Message Directions, Binding.   Where do we start? Start with Port Types.

To discuss Port Types we should first discuss Communication Patterns.  Communication Patterns define the direction in which the data is to be transmitted by the port.  There are two options; One way or Request-Response.  A one way communication pattern is best defined as a pattern that does not require a formal handshake, whereas a Request-Response pattern does.  To best illustrate this, think of the task of running a select query against a database.  The handshake in this instance occurs when the database receives the select query and returns a resultset back.  Conversely, the one way pattern is illustrated as a push event.

Operations are simply actions that define what activity can occur over a port.  So let's say you have a port that connects a SQL database to BizTalk, the operations can be select, insert, and update operations.

Message Directions define the how the message content is used. It can be either as a request or a response, much like you have requests and responses over http.

Message Types are simply the operations defined within the schemas.  As mentioned earlier, schemas are defined in Xml format.  Their top level elements are generally used to define specific operations and their children define the data processed by these operations.

So Ports need a Port Type assigned to them.  Once a port type is selected for a port, the communication direction is automatically selected for it.  For one way ports, this is fine, but on Request-Response ports, you must be cautious that the comm direction is the one you intended, otherwise you may have to manually select the comm direction.  To help you decide, you must determine whether the port will send data and expect to receive data in return, or vice-versa. Bindings on the other hand can be defined either through Visual Studio or the BizTalk Administration console.  In either case, the bindings are basically the system defined connection protocol.

With all of these defined, the Ports then only allow messages that match the message type defined within their Port type. Not much left to be said about that, so on to the next item...

Messages are simple as well. Consider them the data packages defined by an operation instance within a schema.  Thus, a Message Type must be selected for each message.  Again, not much left to say about messages.

Next up are orchestrations.  With Schemas, Maps, Messages and Ports defined, the orchestration binds these objects all together.  Now within the Orchestrations, you setup your Messages, Ports, Port Types, etc. Basically, everything can be setup from within an orchestration file, but you're better off creating your Maps and Schemas first and adding references to them from within the orchestration file.  All other objects are fair game to create within the Orchestration.

Now, tips.  I've found that it is best to have two separate orchestrations.  One to act as a container for your Port Types, and the other to handle the rest.  Why, because you may have several orchestrations within a project file, so it becomes easier to maintain your port types from one source file and reference those types from the other orchestration files.  Also, I've found that my orchestrations generally leverage similar port types when contained within a project.

Well,  that's it for now.  Hopefully, I'll pick up a few more tips before Microsoft buries BizTalk, in which case, I may have to turn to AppFabric.

Friday, December 18, 2009

FullTextSqlQuery People search annoyance resolved

Ok, here's an easy one. A lot of us leverage the FullTextSqlQuery class to perform our People searches. Well, one annoyance I find is revealing the proper syntax to return the property values. There's generally no good references since People search varies from farm to farm. So what do you do? Well, you can look through all the Managed Properties in SSP to find them, or you could implement a method that could return the properties you need based on Display name. I prefer the latter since it takes the grunt work out of sifting through hundreds of records.

On to the solution. So by leveraging the UserProfileManager, you can get an instance of the PropertyCollection class and voila! A quick loop through the collection will get you the property you need. Here goes...


static string GetProfileMatchingProperty(string propertyDisplayName)
{
UserProfileManager oSPManager = new UserProfileManager(ServerContext.Default, true);

Microsoft.Office.Server.UserProfiles.PropertyCollection profileProperties =
oSPManager.Properties;

foreach (Property profProperty in profileProperties)
{
if (profProperty.DisplayName.ToLower(System.Globalization.CultureInfo.InvariantCulture) ==
propertyDisplayName.ToLower(System.Globalization.CultureInfo.InvariantCulture))
return profProperty.ManagedPropertyName;
}

throw new Exception("Couldn't find it. Sorry.");
}

Friday, December 11, 2009

Working with SPFieldChoice type

This may come as a no-brainer for some, but I thought I'd share it in any case. So, there may be times in a developers life that they need to write some functionality that fiddles around with SPList objects. Within those SPList objects, you may have a field derived from SPFieldChoice type. I'm not going to make this a note about how to derive the current value since that is ultimately a trivial task, but what I will do is talk about defaulting values programmatically (again, probably something trivial for most, but then again, maybe something you don't think about).

Ok, so let's say that you wrote a webpart that has to apply default values to an SPListItem. And within that SPListItem, say you have an SPFieldChoice type. Of course, within SharePoint, you can declare you're own default values, but say you want to buck the trend and set one of the other choices as a defaulted value.

Now you have a few decisions to make: hard code the value (bad design), always take field default value, or loop through choices. I'll show you how to get the latter 2 choices (I'm not about to champion the hardcoding movement).

To get the default value for SPFieldChoice:
  • SPListItem.Fields[Guid, Index, or Name of Column].DefaultValue
To loop through the SPFieldChoice choices:

foreach (string oneChoice in ((SPFieldChoice)SPListItem.Fields[Guid, Index, or Name of Column].Choices)
{
switch (oneChoice)
{
case value1:
{some code}
break;
case value2:
{some code}
break;
default:
{some code}
break;
}
}

Friday, December 4, 2009

MOSS Page Timeout Issues

So I was asked to write a simple webpart that would tally survey responses. Everything was moving along just fine and the code was behaving beautifully. Wouldn't you know it, once the code reached User Acceptance, issues started creeping along. I won't get into all the issues since most were minor. The one biggie was a timeout issue that appears as a nasty little default error message that reads "An unexpected error has occurred."

This was mind-boggling for me at first. How could my code fail after it passed unit testing and QA? Digging a little further, I copied masses of data over to my dev box and started debugging my code. Again, no problem while I was stepping through the code. But a funny thing happened when I switched my web.config settings on my dev machine from debug="true" to debug="false." The page threw this error at me. Finally, I was making head-way!

So I started stepping through the code once again, but nothing happened! How could this be? I thought my box was off debug mode? Turns out, when stepping through code, the box interprets those actions as having set debug="true" in web.config.

It finally dawned on me that the code was timing out. So I shut off debugger and added a new timeout setting to web.config. Presto! The code ran fine, but I wasn't satisfied. I wanted to apply my timeout setting to run only against that page. So I setup the web.config under my Layouts folder and wouldn't you know it, the issue appeared again. Now this was ridiculous, I should have the ability to set a path in MOSS to increase my timeout settings.

Turns out, I had to apply my setting in web.config at the root level. So under the home directory found in IIS (for most it would be c:\Inetpub\wwwroot), I updated web.config with the following under the configuration node and presto, my timeout setting applied only to my particular page url:

<location path="SomePath/MyPage.aspx">
<system.web>
<httpRuntime executionTimeout="3600" />
</system.web>
</location>

Friday, November 27, 2009

Downloading Attachments from a List

So I was asked to get all attachments from a List. Now, there are several articles on the web that show you how to leverage the Object Model, so I'm not going to get into that. What I was asked to do was to connect to a remote SharePoint instance and bring down all attachments found in a list.

My first inclination was to leverage the Object Model, but after spending an hour on it, I remembered, I can't connect to a remote instance via the Object Model. So I was left with Web Services. The solution wasn't bad at all either. Once you've connected to Lists service, you can call the GetAttachmentCollection method to return attachments per list item. I'm assuming that everyone knows how to grab List ids and ListItem ids via Web services, so I'll skip that tutorial.

Here's the code:

XmlNode attachmentColl = wsListObj.GetAttachmentCollection(listIDStringWithBrackets, listItemIDString);

for (int i = 0; i < attachmentColl.ChildNodes.Count; i++)
{
string[] attachmentArr = attachmentColl.ChildNodes[i].InnerXml.Split(new char[] { '/' });

System.Net.WebRequest file = System.Net.WebRequest.Create(attachmentColl.ChildNodes[i].InnerXml);

CopyFileToFolder(file.GetResponse().GetResponseStream(),
attachmentArr[attachmentArr.Length - 1]);

file = null;
}