April 26, 2008

Firefox & IE Tab: Rendering the Intranet in IE

I really like Firefox but still find many web sites that just work better in Internet Explorer.  IE Tab is a great extension that allows you to use the Internet Explorer rendering engine within a Firefox tab.  One of it's most useful features is the ability to create rules that define when IE Tab should be used automagically.  These rules evaluate the requested web address and can be simple string matches or can be more complex regular expression pattern matches.

Here is a screen shot of the interface for configuring the web addresses that will be rendered using Internet Explorer.

IE Tab Sites Filter

The * in some of the addresses specifies a wild card and is a very simple way to define a pattern.  More powerful patterns can be defined using regular expressions.  An example of a regular expression pattern in the above screen shot is, "/^https?:\/\/[A-Z,a-z,0-9,\/]*$/", this pattern will match all intranet web addresses and render them using IE.  If you work for a company that uses Active Directory and has a sizable intranet IE is often an easier browser to use because it will not force you to login for every different intranet web server that resources are requested from (it will automagically pass your Windows credentials).

Explanation of intranet regular expression site filter:

  • The // surrounding the pattern let IE Tab know that the rule is a regular expression.
  • "^" calls out the beginning of the web address
  • "http" must be the beginning of the web address
  • "s?" means that "s" is an optional character in the web address following the "http". ? is states that the character immediately preceding is optional and may or may not exist
  • ":\/\/" means that "://" must come after the http(s) portion of the pattern.  The backslashes are used because the forward slashes have to be escaped as they are special characters in the Regular Expression syntax
  • "[A-Z,a-z,0-9,\/]" means that any character that falls in the ranges defined within the brackets is a valid pattern match.  In this case we are restricting valid characters to alphanumeric values and forward slash
  • "*" states that the previous pattern can be matched for many characters.  Without the star following the "[A-Z,a-z,0-9,\/]" the pattern would only match a single character following the "://"
  • Finally the $ calls out the expected end of the web address

**UPDATE**
Came up with a better version that works for more cases.  This one will work for any address that doesn't include a top level domain before the first slash or question mark.

/^https?:\/\/[^\.\/\?\n]*((\?|\/).*)?$/

March 8, 2007

Remote Service Deployment with VS.NET PowerShell Build Events

This post is a follow up to "Visual Studio Build Events using PowerShell". I wanted to post a more compelling example of what could be done easily with the Pre/Post Build events in Visual Studio .NET using PowerShell.

When developing Windows Services that are deployed on a remote machine within your development environment it can be very annoying to have to stop and start the service each time you build. The following script when executed within a post build event (read the original article) on a Windows Service project will take the following steps:

  1. Stop the service on the remote computer
  2. Wait for the service to stop and remove the files in the remote service root directory (to ensure a fresh deployment each time)
  3. Copy all the files from the Visual Studio project's Target Directory to the remote service root directory
  4. Start the service on the remote computer

DeployConfig.xml Fragment

<project targetFileName="Service.exe">

   <buildConfig name="Debug">

      <user name="default">

         <buildEvent name="Post">

            <![CDATA[write-Output "Script Started"

write-Output ""

write-Output "Stopping IMM DAM Service on REMOTEMACHINENAME"

write-Output ""

$remoteService = (Get-WmiObject -computer REMOTEMACHINENAME Win32_Service -Filter "Name='Service Title'")

$hideOutput = ($remoteService.StopService() | out-string)

 

write-host -no "Waiting for 'Service Title' to stop"

while((Test-Path '\\REMOTEMACHINENAME\c$\SERVICEDIR\*.*') -eq $True) {

   remove-item -path "\\REMOTEMACHINENAME\c$\SERVICEDIR\*.*" -force -erroraction silentlycontinue

   write-host -no "."

}

write-Output ""

write-Output ""

 

copy-item -path "{targetDir}*.*" -destination "\\REMOTEMACHINENAME\c$\SERVICEDIR" -force

write-Output "Files Copied"

write-Output ""

 

write-Output "Starting 'Service Title' on REMOTEMACHINENAME"

write-Output ""

$hideOutput = ($remoteService.StartService() | out-string)

write-Output "Script Complete"]]>

         </buildEvent>

      </user>

   </buildConfig>

</project>

March 6, 2007

Visual Studio Build Events using PowerShell

See the bug?

UPDATE #4: Just found another nice looking editor PowerShellIDE.

UPDATE #3: PowerShell team responded and their guidance to avoid issues with CMD messing up the arguments passed to the PowerShell script is to use single quotes around the PowerShell arguments.  I updated the build event example below.

UPDATE #2: Fixed a minor bug in the build DeploymentScript.ps1.  It was starting to parse the "Additional Args" at an index of 3 when it should have starting at 4. 

UPDATE #1: Forgot to link to PowerShell Analyzer.  A nice tool for working with PowerShell, have not played with it a ton yet, but it looks promising.

Working in a team with multiple developers and different environments often introduces the need for multiple deployment configurations.

There are quite a few interesting tools out there for advanced deployment configuration tasks:

In my case I just need something simple that each developers on my team (currently only 4-5 regular contributors) can use and customize.  In the past I have used Windows Scripting Host scripts written in JScript to perform my post build events but with the release of PowerShell I was interested in finding a task that would afford me the excuse of learning a new technology (you can never know too many).

My solution at the moment is a PowerShell script that loads information from an XML file based on Project, Build Configuration, User Name, and Build Event Type (Pre or Post). The XML file is essentially a list of PowerShell scripts depending on the passed in variables it loads the proper PowerShell script and executes it.

<tangent degree="Minor">

For me PowerShell has a steep learning curve and it's taking me a little while to wrap my head around.  It took me about a day to figure out how to write the script for this post just because of the lack of relevant examples online and my lack of familiarity with the command syntax.

I'm really excited about how it can be extended with the use of custom CmdLets; a CmdLet pack that supported common Visual Studio Team Foundation activities like TF.exe would add a lot of value of the type of scenario that I am playing with here.  I am also interested in finding CmdLets or information on how to perform remote command execution (executing CMD or PowerShell commands on remote machines). Anyone know of examples or projects that cover these scenarios?

</tangent>

Onto the examples (I know what you are here for)...

Pre/Post Build Event in Visual Studio .NET
A simple example of the actually CMD line that is executed as part of Visual Studio's configured post-build event. Use single quotes for the arguments that should be passed to the PowerShell script.  That will avoid CMD causing any problems by the way it handles double quoted string. Note that for some reason the quotes that come immediately after a Visual Studio variable that ends with a \ have to have an additional \ placed in front of them.  I have communicated with the PowerShell team about this and believe that they are aware of the behavior.

The first 5 arguments are required and are used to lookup the script that should be executed.  Additional argument are optional and can be configured on a per project basis, they are loaded into a Hashtable using a (key)=(value) format and are used to replace variables in the executed script.

powershell.exe "$(SolutionDir)DeployScript.ps1" 'Post' '$(SolutionDir)' '$(TargetFileName)' '$(ConfigurationName)' 'targetDir=$(TargetDir)' 'projectDir=$(ProjectDir)'

DeployConfig.xsd
The documented schema that can be used to validate DeployConfig.xml.  Word wrap makes it a little messy.. sorry about that.

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

   <xs:element name="configuration">

      <xs:annotation>

         <xs:documentation>The root element of the DeployConfig XML document used by the DeployScript.ps1 PowerShell script.</xs:documentation>

      </xs:annotation>

      <xs:complexType>

         <xs:sequence>

            <xs:element name="project" maxOccurs="unbounded">

               <xs:annotation>

                  <xs:documentation>Each project that is being configured with a build event will have it's own project node</xs:documentation>

               </xs:annotation>

               <xs:complexType>

                  <xs:sequence>

                     <xs:element name="buildConfig" maxOccurs="unbounded">

                        <xs:annotation>

                           <xs:documentation>Each build configuratoin (ie. Debug, Release) should have it's own buildConfig node.</xs:documentation>

                        </xs:annotation>

                        <xs:complexType>

                           <xs:sequence>

                              <xs:element name="user" maxOccurs="unbounded">

                                 <xs:annotation>

                                    <xs:documentation>Each user can be configured to execute a different PowerShell script by creating a new user node that specifies their Active Directory username (eg. domain\user).  If no node is found for the current user DeployScript.ps1 will search for a user node with the name attribute set to 'default'</xs:documentation>

                                 </xs:annotation>

                                 <xs:complexType>

                                    <xs:sequence>

                                       <xs:element name="buildEvent" maxOccurs="2">

                                          <xs:annotation>

                                             <xs:documentation>Each user can have two build events specified for each project and build configuration.  One can be executed on pre-build and one on post-build.  The text for this node is the actual script that will be executed on build.  It is recommended that the commands within this node be wrapped with CDATA.</xs:documentation>

                                          </xs:annotation>

                                          <xs:complexType>

                                             <xs:simpleContent>

                                                <xs:extension base="xs:string">

                                                   <xs:attribute name="name" use="required">

                                                      <xs:annotation>

                                                         <xs:documentation>Possible values are 'Pre' and 'Post' depending on when the event should be executed.</xs:documentation>

                                                      </xs:annotation>

                                                      <xs:simpleType>

                                                         <xs:restriction base="xs:string">

                                                            <xs:enumeration value="Pre"/>

                                                            <xs:enumeration value="Post"/>

                                                         </xs:restriction>

                                                      </xs:simpleType>

                                                   </xs:attribute>

                                                </xs:extension>

                                             </xs:simpleContent>

                                          </xs:complexType>

                                       </xs:element>

                                    </xs:sequence>

                                    <xs:attribute name="name" type="xs:string" use="required">

                                       <xs:annotation>

                                          <xs:documentation>The name of the user that this build script applies to.  Use 'default' to specify a default script that should be run if the current username is not found.</xs:documentation>

                                       </xs:annotation>

                                    </xs:attribute>

                                 </xs:complexType>

                              </xs:element>

                           </xs:sequence>

                           <xs:attribute name="name" type="xs:string" use="required">

                              <xs:annotation>

                                 <xs:documentation>The Build Configuration of the executing project build.  Visual Studio Build Event Variable: $(ConfigurationName)</xs:documentation>

                              </xs:annotation>

                           </xs:attribute>

                        </xs:complexType>

                     </xs:element>

                  </xs:sequence>

                  <xs:attribute name="targetFileName" type="xs:string" use="required">

                     <xs:annotation>

                        <xs:documentation>The Target File Name of the project that the build event is being configured for. Visual Studio Build Event Variable: $(TargetFileName)</xs:documentation>

                     </xs:annotation>

                  </xs:attribute>

               </xs:complexType>

            </xs:element>

         </xs:sequence>

      </xs:complexType>

   </xs:element>

</xs:schema>

DeployConfig.xml
This is a simple example of a post build event for a sample project using the Visual Studio Post Build Event example above.  Note that the optional arguments in the Post Build Event are used to populate the actual command using string.Replace.  Keys from the optional arguments Hashtable are searched for (encapsulated in {}) and replaced with the value from the Hashtable.

<?xml version="1.0" encoding="utf-8"?>

<configuration>

   <project targetFileName="Namespace.Sample.dll">

      <buildConfig name="Debug">

         <user name="domain\username">

            <buildEvent name="Post"><![CDATA[remove-item -path "\\server\c$\Inetpub\wwwroot\bin\*.pdb", "\\server\c$\Inetpub\wwwroot\bin\*.dll", "\\server\c$\Inetpub\wwwroot\*.svc", "\\server\c$\Inetpub\wwwroot\*.config" -force

   copy-item -path "{targetDir}*.dll","{targetDir}*.pdb" -destination "\\server\c$\Inetpub\wwwroot\bin" -force

   copy-item -path "{projectDir}*.config","{projectDir}*.svc" -destination "\\meservices01\c$\Inetpub\wwwroot" -force]]></buildEvent>

         </user>

      </buildConfig>

   </project>

</configuration>

DeployScript.ps1
The PowerShell script that is executed during the Pre/Post build event.  I invite anyone with good ideas to modify this script and repost it to their blog with a link back to this post. Join the conversation and help me make this script better!

trap {

   $errorTxt = $_.Exception.Message

   write-output "$errorTxt"

   break

}

 

$buildEvent = $args[0]

$solutionDir = $args[1]

$targetFile = $args[2]

$configName = $args[3]

 

write-output ""

write-output "*******************************************"

write-output "Required Args:"

write-output ""

write-output "Build Event:         $buildEvent"

write-output "Configuration Name:      $configName"

write-output "Target File Name:      $targetFile"

write-output "Solution Directory:      $solutionDir"

write-output "*******************************************"

write-output "Additional Args:"

write-output ""

 

$argsHash = @{}

for($x=4; $x -lt $args.Length; $x++) {

   $curArg = $args[$x]

   $curArgSplit = $args[$x].split("=")

   $argsHash.add($curArgSplit[0], $curArgSplit[1]);

   write-output "$curArg"

}

 

write-output "*******************************************"

write-output ""

write-output "Deployment Started"

 

$configPath = (join-path $solutionDir "DeployConfig.xml")

[xml]$configDoc = get-content $configPath

write-output "Configuration Loaded from $configPath"

 

$projNode = $configDoc.configuration.project | ?{$_.targetFileName -eq $targetFile}

if($projNode -ne $null) {

   write-output "Project Node Found: $targetFile"

}

else {

   throw "No Project node could be found: $targetFile";

}

 

$configNode = $projNode.buildConfig | ?{$_.name -eq $configName}

 

if($configNode -ne $null) {

   write-output "Build Config Node found: $configName"

}

else {

   throw "No Build Config node could be found: $configName";

}

 

#$compName = (get-wmiobject win32_computersystem).Name

$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

$userNode = $configNode.user | ?{$_.name -eq $currentUser}

 

if($userNode -eq $null) {

   write-output "User Node not found for $currentUser"

   $userNode = $configNode.user | ?{$_.name -eq 'default'}

 

   if($userNode -ne $null) {

      write-output "User Node found: default"

   }

   else {

      throw "No User Node (named or default) could be found: $currentUser"

   }

}

else {

   write-output "User Node found: $currentUser"

}

 

$buildEventNode = $userNode.buildEvent | ?{$_.name -eq $buildEvent}

 

if($buildEventNode -eq $null) {

   throw "No build event could be found for the current build event: $buildEvent"

}

else {

   write-output "Build Event Node found: $buildEvent"

}

 

$deployCmd = $buildEventNode.get_InnerText()

 

foreach($arg in $argsHash.Keys) {

   $deployCmd = $deployCmd.Replace("{$arg}", $argsHash[$arg]);

}

 

write-output "Executing Command:"

invoke-expression $deployCmd

 

write-output "Deployment Ended"

February 22, 2007

SmartPart and AjaxBasePart get Together

That was fast... Jan Tielens has implemented the AjaxBasePart in the SmartPart web part to add AJAX functionality.

From Jan Tielens' Bloggings:

I've created a version of the SmartPart that makes use of the Eric's technique, let me introduce you the SmartPart with AJAX! So basically said: you can create a Web User Control (ASCX) with the Visual Studio Designer, that uses the ASP.NET AJAX extensions and run that user control as a SharePoint web part.

That's awsome!  Be sure not to miss the screencast that Jan has put together, it does a pretty good job of showing some of the interesting things you can do with AJAX inside of a web part.

The SmartPart is an excellent tool, especially for casual developers that would like to develop SharePoint web parts but don't want to give up the designer experience that you get with normal ASP.NET development.

February 19, 2007

AjaxBasePart: Easy ASP.NET 2.0 AJAX Extensions 1.0 and Office SharePoint Server 2007

I have noticed quite a few posts/projects out there that focus on using ASP.NET AJAX within custom MOSS web parts.  Mike Ammerlaan has a great post on some of the background around AJAX and MOSS

This post describes what my team and I have been using for the past couple of months. It is a simple base class called AjaxBasePart.

Background:
I developed a .NET class in early November that allowed my team to take advantage of the MS AJAX Extensions from custom MOSS web parts.  There were a number of problems that I did not resolve on my own though.

For the next month or so I worked on and off with the AJAX Extensions and MOSS product teams and with our powers combined we were able to come up with the AjaxBasePart class that custom MOSS web parts can derive from and fully enables all AJAX Extensions functionality within MOSS.

Very Simple Setup:
There is no need to modify master pages after the AJAX Extensions have been installed and the web.config is setup your web parts will manage the registration of a shared ScriptManager or in the case that one exists use that. If I'm missing anything please notify me in the comments and I will update.

  1. Download and install ASP.NET 2.0 AJAX Extensions 1.0
  2. Configure your MOSS web.config to support ASP.NET AJAX 1.0
    Mike's post covers most of the configuration you need to support AjaxBasePart within your own MOSS environment. I've listed below a couple of points that you should be aware of when reading his article if you are using the AjaxBasePart
    • You can skip the "Adding a ScriptManager into a SharePoint MasterPage" section. No modification of the .master page is necessary with AjaxBasePart
    • Leave the EnsureUpdatePanelFixups method out of your custom web parts, that is handled by the AjaxBasePart.
  3. Add the AjaxBasePart class (at the end of this post) to your solution and derive all custom web parts from CapDes.SharePoint.AjaxBasePart instead of Microsoft.SharePoint.WebPartPages.WebPart.

Support:
If you are looking for support I will do my best to assist via the comments on this post.  This is in no way supported by Microsoft.

Credits:
The following Microsoft employees assisted me in the development of the AjaxBasePart.  Thanks guys!

Bonus:
You will also notice a public RegisterError method on the AjaxBasePart.  I find it to be a clean way to show users web part related error messages in a common way.

To use all you need to do is; from your custom web part call the RegisterError message and pass the string that you would like displayed to the user.

base.RegisterError("Some error message.");

Code:

using System;

using System.Xml.Serialization;

using System.Web.UI.WebControls;

using System.Web.UI;

using System.Drawing;

using System.Web;

using Microsoft.SharePoint.Utilities;

using Microsoft.SharePoint.WebPartPages;

 

namespace CapDes.SharePoint

{

   /// <summary>

   /// AjaxBasePart allows Microsoft ASP.NET AJAX Extensions to work within Microsoft Office SharePoint Server 2007 webparts.

   /// </summary>

   [XmlRoot(Namespace = "CapDes.SharePoint.AjaxBasePart")]

   [CLSCompliant(false)]

   public abstract class AjaxBasePart : WebPart

   {

        private string _ValidationGroupId;

        private ValidationSummary _ErrorContainer;

        private ScriptManager _AjaxManager;

 

        /// <summary>

        /// Exposes the Page's script manager. The value is not set until after OnInit

        /// </summary>

        [WebPartStorage(Storage.None)]

        public ScriptManager AjaxManager

        {

            get { return _AjaxManager; }

            set { _AjaxManager = value; }

        }

 

        /// <summary>

        /// Oninit fires before page load. Modifications to the page that are necessary to support Ajax are done here.

        /// </summary>

        protected override void OnInit(EventArgs e)

        {

            base.OnInit(e);

 

            //get the existing ScriptManager if it exists on the page

            _AjaxManager = ScriptManager.GetCurrent(this.Page);

 

            if (_AjaxManager == null)

            {

               //create new ScriptManager and EnablePartialRendering

               _AjaxManager = new ScriptManager();

               _AjaxManager.EnablePartialRendering = true;

 

               // Fix problem with postbacks and form actions (DevDiv 55525)

               Page.ClientScript.RegisterStartupScript(typeof(AjaxBasePart), this.ID, "_spOriginalFormAction = document.forms[0].action;", true);

 

               //tag:"form" att:"onsubmit" val:"return _spFormOnSubmitWrapper()" blocks async postbacks after the first one

               //not calling "_spFormOnSubmitWrapper()" breaks all postbacks

               //returning true all the time, somewhat defeats the purpose of the _spFormOnSubmitWrapper() which is to block repetitive postbacks, but it allows MS AJAX Extensions to work properly

               //its a hack that hopefully has minimal effect

               if (this.Page.Form != null)

               {

                    string formOnSubmitAtt = this.Page.Form.Attributes["onsubmit"];

                    if (!string.IsNullOrEmpty(formOnSubmitAtt) && formOnSubmitAtt == "return _spFormOnSubmitWrapper();")

                    {

                        this.Page.Form.Attributes["onsubmit"] = "_spFormOnSubmitWrapper();";

                    }

 

                    //add the ScriptManager as the first control in the Page.Form

                    //I don't think this actually matters, but I did it to be consistent with how you are supposed to place the ScriptManager when used declaritevly

                    this.Page.Form.Controls.AddAt(0, _AjaxManager);

               }

            }

        }

 

        /// <summary>

        /// Needs to be called to ensure that the ValidationSummary control is registered on the page.  Any child web parts will need to have base.CreateChildControls() at the top of their own CreateChildControls override.

        /// </summary>

        protected override void CreateChildControls()

        {

            base.CreateChildControls();

 

            if (!this.Controls.Contains(_ErrorContainer))

            {

               _ValidationGroupId = Guid.NewGuid().ToString();

 

               _ErrorContainer = new ValidationSummary();

               _ErrorContainer.ID = "_ErrorContainer";

               _ErrorContainer.ValidationGroup = _ValidationGroupId;

               _ErrorContainer.BorderStyle = BorderStyle.Solid;

               _ErrorContainer.BorderWidth = Unit.Pixel(3);

               _ErrorContainer.BorderColor = Color.Red;

               this.Controls.Add(_ErrorContainer);

            }

        }

 

        /// <summary>

        /// Used to provide a common way to display errors to the user of the current web part.

        /// </summary>

        /// <param name="message">Description of the error that occured.</param>

        public void RegisterError(string message)

        {

            if (this.Controls.Contains(_ErrorContainer))

            {

               //this way of generating a unique control id is used in some of the OOB web parts

               int uniqueCounter;

               if (HttpContext.Current.Items["GetUniqueControlId"] != null)

               {

                    uniqueCounter = (int)HttpContext.Current.Items["GetUniqueControlId"];

               }

               else

               {

                    uniqueCounter = 0;

               }

               uniqueCounter++;

               HttpContext.Current.Items["GetUniqueControlId"] = uniqueCounter;

 

               //create a custom validator to register the current error message with the ValidationSummary control

               CustomValidator cv = new CustomValidator();

               cv.ID = string.Concat("_Error_", uniqueCounter);

               cv.ValidationGroup = _ValidationGroupId;

               cv.Display = ValidatorDisplay.None;

               cv.IsValid = false;

               cv.ErrorMessage = message;

 

               this.Controls.Add(cv);

            }

            else

            {

               //if RegisterError is called before the CreateChildControls override in AjaxBasePart then transfer the user to an error page using the SPUtility

               SPUtility.TransferToErrorPage("The CreateChildControls function of the AjaxBasePart has not been called.  You probably need to add \"base.CreateChildControls()\" to the top of your CreateChildControls override.");

            }

        }

   }

}

Example: Call Private Method w/ Reflection

For some reason I was having issues with this today and wasn't able to find a very simple example. This is good mostly for Unit Testing private methods.

using System;

using System.Reflection;

using System.Collections.Generic;

 

public class MyClass

{

   public static void Main()

   {

        try

        {

            Console.WriteLine("TestReflect started.");

            TestReflect test = new TestReflect();

            Console.WriteLine("TestReflect ended.");

        }

        catch (Exception ex)

        {

            Console.WriteLine(ex.ToString());

        }

 

        ConsoleKeyInfo cki;

        do

        {

            Console.WriteLine("Press the 'Q' key to exit.");

            cki = Console.ReadKey(true);

        } while (cki.Key != ConsoleKey.Q);

   }

}

 

public class TestReflect

{

   public TestReflect()

   {

        this.GetType().GetMethod("PublicMethod").Invoke(this, null);

        this.GetType().GetMethod("PrivateMethod", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(this, null);

   }

 

   public void PublicMethod()

   {

        Console.WriteLine("PublicMethod called");

   }

 

   private void PrivateMethod()

   {

        Console.WriteLine("FTW!one1");

   }

}


Technorati tags: , , , ,

February 11, 2007

James blunt

He didn't win any Grammys and my wife is super mad :(