Welcome to MSDN Blogs Sign in | Join | Help

SYSK 372: TFS – Enforcing Comments on Check-In & Violations Query

As many web sites point out, TFS uses policies to implement a requirement that developers provide comments when checking in code. The policy must be deployed to each developer (client machines).  The Team Foundation Server Power Tools include the Changeset Comments Policy, which allows you to verify that the Comments text box in the Check In dialog box is not empty (see attached…  the configuration can also be done via a batch file).  Once the policy is deployed, developers will not be able to check in a file if the Comments text box is empty.

 

To download Power Tools for VSTS 2005, go to http://msdn.microsoft.com/en-us/teamsystem/aa718351.aspx

To download Power Tools for VSTS 2008, go to http://msdn.microsoft.com/en-us/teamsystem/bb980963.aspx

 

Since check-in policies are evaluated on the client, so it’s not possible to completely prevent someone from disabling the policy, or not installing it in the first place, and, thus, checking in code without entering comments.

 

However, a manager can subscribe to an alert (Power Tools include an Alert Editor), and receive an e-mail when comments are not entered at check-in, or other policies are violated.

 

 

Alternatively, you can create a report that goes directly against SQL tables.  The query below returns all project name, change request (work item) title, source control changeset id, datetime, person ‘s name and e-mail for all checked-in files with empty comment.  The query  below filters out changes done before 1/1/2008.

 

-- Specifically, empty comments

SELECT

tn.Name,

      wi.Title,

      cs.ChangeSetId,

cs.CreationDate,   

pr.Person,

pr.Email

FROM   

    TfsVersionControl..tbl_ChangeSet cs

INNER JOIN

    TfsVersionControl..tbl_Identity i ON cs.OwnerId=i.IdentityId

INNER JOIN

    TfsWarehouse..Person pr ON (i.DisplayName COLLATE SQL_Latin1_General_CP1_CI_AS) = (pr.Domain + N'\' + pr.Alias)

INNER JOIN

      TfsWarehouse..[Work Item Changeset] w ON cs.ChangeSetId = w.Changeset

INNER JOIN

      TfsWorkItemTracking..WorkItemsAre wi ON w.[Work Item] = wi.ID

INNER JOIN

      TfsWorkItemTracking..TreeNodes tn ON w.[Team Project] = tn.ID

WHERE Comment IS NULL

      AND cs.CreationDate > '2008-01-01' 

      AND tn.ParentID = 0

      AND tn.TypeID = -42

ORDER BY

    cs.ChangeSetId DESC

 

 

 

Another query (below) returns information on policy overrides:

 

-- Policy overrides

SELECT

      tn.Name,

      wi.Title,

    p.ChangeSetId,

    cs.CreationDate,

    cs.Comment,

    p.Comment,

    pr.Person,

    pr.Email

FROM

    TfsVersionControl..tbl_PolicyOverride p

INNER JOIN

    TfsVersionControl..tbl_ChangeSet cs ON p.ChangeSetId=cs.ChangeSetId

INNER JOIN

    TfsVersionControl..tbl_Identity i ON cs.OwnerId=i.IdentityId

INNER JOIN

    TfsWarehouse..Person pr ON (i.DisplayName COLLATE SQL_Latin1_General_CP1_CI_AS) = (pr.Domain + N'\' + pr.Alias)

INNER JOIN

      TfsWarehouse..[Work Item Changeset] w ON cs.ChangeSetId = w.Changeset   

INNER JOIN

      TfsWorkItemTracking..WorkItemsAre wi ON w.[Work Item] = wi.ID

INNER JOIN

      TfsWorkItemTracking..TreeNodes tn ON w.[Team Project] = tn.ID

WHERE

      tn.ParentID = 0

      AND tn.TypeID = -42

ORDER BY

    cs.ChangeSetId DESC

 

 

 

SQL Server Connection Leak Simulation Tool

Sometimes it’s necessary to test application performance in low available resource situations…  I’ve seen tools that simulate low available memory, do network throttling, etc., but I have not come across one that simulates a lot of SQL Server connections being in use…

 

Attached is such a tool.  I’m providing both, source code and the compiled binary (see bin\Debug folder).

 

If you find any issues or have other feedback, as always, your comments are welcome.

 

Posted by irenak | 0 Comments
Attachment(s): SQLConnectionLeak.zip

SYSK 370: The Performance Cost of Extension Methods

First, for those who may not be familiar with this feature, .NET 3.5 allows developers to add methods to existing types without using inheritance or partial classes by creating static methods that can be invoked by using instance method syntax.

 

When I first heard about this feature, it reminded me of the decorator pattern in OOP (I’m not speaking of the implementation, but the concept… )

 

Since there are lots of examples for C# developers, and very few for VB.NET, here is an example of adding Serialize and Deserialize methods to any object written in VB.NET:

 

1.    Add a module file to your project and paste the following code:

 

' TODO: need to add SEH and NULL handling!

' TODO: Check for object being marked with Serializable attribute

Module Module1

    <System.Runtime.CompilerServices.Extension()> _

    Public Function Serialize(ByVal o As Object) As String

        Dim data() As Byte

        Using ms As New System.IO.MemoryStream()

            Dim f As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter

            f.Serialize(ms, o)

            data = ms.ToArray

        End Using

        Return Convert.ToBase64String(data)

    End Function

 

    <System.Runtime.CompilerServices.Extension()> _

    Public Function Deserialize(ByVal s As String) As Object

        Dim result As Object

        Dim data() As Byte = Convert.FromBase64String(s)

        Using ms As New System.IO.MemoryStream(data)

            Dim f As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter

            result = f.Deserialize(ms)

        End Using

        Return result

    End Function

End Module

 

2.    Below is an example of using this extension method (the important lines are in bold):

 

Public Class Form2

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim t As New Test

        t.i = Integer.Parse(Me.TextBox1.Text)

        t.s = Me.TextBox2.Text

 

        Me.TextBox3.Text = t.Serialize()

    End Sub

 

    Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.TextBox1.Text = New Random().Next(1, 1000).ToString()

        Me.TextBox2.Text = "Time now is " + DateTime.Now().ToLongTimeString()

    End Sub

 

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

        Dim t As Test = Me.TextBox3.Text.Deserialize()

        Me.TextBox4.Text = t.i.ToString()

        Me.TextBox5.Text = t.s

    End Sub

End Class

 

<System.Serializable()> _

Public Class Test

    Private _s As String

    Public Property s() As String

        Get

            Return _s

        End Get

        Set(ByVal value As String)

            _s = value

        End Set

    End Property

 

    Private _n As New NestedTest

    Public Property i() As Integer

        Get

            Return _n.i

        End Get

        Set(ByVal value As Integer)

            _n.i = value

        End Set

    End Property

End Class

 

<System.Serializable()> _

Public Class NestedTest

    Private _i As Integer

    Public Property i() As Integer

        Get

            Return _i

        End Get

        Set(ByVal value As Integer)

            _i = value

        End Set

    End Property

End Class

 

 

 

Now, to the promised performance impact…  I ran and timed a test that created 100,000,000 instances of an object with the extension methods above, and compared the execution time to doing the same but without the extension methods.  Just to be clear, I didn’t actually execute the extension methods…  My goal was to find out if simply adding the extension methods would significantly impact the object instantiation time by running the following code:

 

Dim t As New System.Diagnostics.Stopwatch

t.Start()

Dim o As Test

Dim i As Integer

For i = 0 To 100000000

o = New Test

o.i = 1

o = Nothing

Next

t.Stop()

System.Diagnostics.Debug.WriteLine(t.ElapsedMilliseconds().ToString())

 

To my surprise, my tests show that adding extension methods did not result in any measurable performance decrease.  On my dual proc laptop with 4 Gb RAM, the results were as follows:

 

                   With Extension Methods                 Without Extension Methods

--------------------------------------------------------------------------------------

Run 1            4071 ms                                     3905 ms

Run 2            4118 ms                                     3916 ms

Run 3            4145 ms                                     3904 ms

 

Bottom line – it appears that using extension methods does not have any measurable performance hit on object instantiation time.

 

 

Posted by irenak | 1 Comments
Filed under:

SYSK 369: What’s Taking So Much Hard Drive Space? ... or Getting ‘True’ Folder Size

If, from time to time, you delete some files & folders from your hard disk, either because it’s no longer needed or just takes too much space and needs to be moved elsewhere, you may find the following utility useful…

 

Basically, the tool below goes through all subfolders (starting with the specified location), and computes the total space taken by files in the folder, excluding subfolders… but it does it recursively, so you get the “true” folder size based on just the files in it…

As a result, it lists them in ascending or descending order (specified by a checkbox)…

 

Attached are the executable and source code…

 

Disclaimer: I created this tools as a “quick and dirty” app to help me figure out why my backups took so long…  i.e. it’s not intended to be “production quality” code, etc., etc.

Posted by irenak | 2 Comments
Filed under:

Attachment(s): FolderSize.zip

SYSK 368: SharePoint – Custom List Item Action that Starts a Workflow

 

First, I must start with the following disclaimer – this was my first SharePoint 2007 project, so, I do not claim any expertise in the subject matter…  However, I believe, this post may be of value to some readers…

 

Let’s say, you want to add a custom action to the context menu in a SharePoint list that fires your custom workflow, e.g.:

 

 

 

 

First, you need to let SharePoint know about the new action – in this case, Publish Project Plan. 

 

1.    You’ll need to create two files – elements.xml and feature.xml:

elements.xml

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

<Elements Id=”e73e411e-bac7-44bc-a55f-83a865385a6a” xmlns=”http://schemas.microsoft.com/sharepoint/”>

  <CustomAction Id=”PublishAction”

      RegistrationType=”List”

      RegistrationId=”101”

      Location=”EditControlBlock”

      Sequence=”5000”

      Title=”Publish Project Plan”>

    <UrlAction Url=”~site/_layouts/StartWorkflow.aspx?ListId={ListId}&amp;ItemId={ItemId}&amp;WFTemplateID=068591c6-be5a-4b36-8a7c-6fe2c1ae434f” />

  </CustomAction>

</Elements>

 

 

 

feature.xml

<?xml version=”1.0” encoding=”utf-8”?>.

 

<Feature xmlns=”http://schemas.microsoft.com/sharepoint/”   

    Id=”ee4f8e3d-7956-46f7-bd38-9888cce2f0ab”   

    Scope=”Web” 

    Title=”Publish” 

    Version=”1.0.0.0”   

    Description=”Publish Project Plan Custom Action”>  

  <ElementManifests> 

    <ElementManifest Location=”elements.xml” /> 

  </ElementManifests> 

</Feature> 

 

 

By doing so, you’re telling SharePoint that there is a custom action called PublishAction associated with EditControlBlock (i.e. context sensitive menu that is displayed for a list item)…  You tell it what to do when clicked (UrlAction), what to display (Title), the menu item index (sequence), etc.

 

2.    Put those files in a directory, e.g. PublishAction in “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES” folder.

 

3.    The way you “register” and activate this new “feature” with SharePoint 2007 is by invoking the following commands:

 

"C:\Program Files\common files\microsoft shared\web server extensions\12\bin\stsadm.exe" -o installfeature -filename PublishAction\feature.xml –force

 

"C:\Program Files\common files\microsoft shared\web server extensions\12\bin\stsadm.exe" -o activatefeature -name PublishAction -url http://yourserver/vdir/

 

 

 

As you can see, the UrlAction tells SharePoint to kick off StartWorkflow.aspx file and pass it some parameters…    

 

4.    My file looks as follows:

 

StartWorkflow.aspx

<%@ Page Language="VB" Inherits="System.Web.UI.Page" EnableViewState="false"%>

<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Register Tagprefix="SPSWC" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

 

<HTML dir="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,multipages_direction_dir_value%>' EncodeMethod='HtmlEncode'/>">

       <HEAD>

              <title>

                     <SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,gear_pagetitle%>" EncodeMethod='HtmlEncode'/>

              </title>

              <link rel="stylesheet" type="text/css" href="/_layouts/<%=System.Threading.Thread.CurrentThread.CurrentUICulture.LCID%>/styles/core.css" />

       </HEAD>

       <BODY>

       <form runat="server" id="Form1">

      

  <TABLE class="ms-main" CELLPADDING=0 CELLSPACING=0 BORDER=0 WIDTH="100%" HEIGHT="100%">

   <!-- Global navigation -->

       <tr><td>

          <table CELLPADDING=0 CELLSPACING=0 BORDER=0 WIDTH="100%">

              <tr>

               <td colspan=4 class="ms-globalbreadcrumb" align="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,multipages_direction_right_align_value%>' EncodeMethod='HtmlEncode'/>">

               </td>

              </tr>

          </table>

        </td></tr>

       <TR height="100%">

        <TD>

         <TABLE height="100%" width="100%" cellspacing="0" cellpadding="0">

          <tr>

                 <td class="ms-titleareaframe" id="TitleAreaImageCell" valign="middle" nowrap></td>

                 <td class="ms-titleareaframe" id="TitleAreaFrameClass">

                     <table cellpadding=0 height=100% width=100% cellspacing=0>

                      <tr><td class="ms-areaseparatorleft"><IMG SRC="/_layouts/images/blank.gif" width=1 height=1 alt=""></td></tr>

                     </table>

                 </td>

                 <td valign=top  id="onetidPageTitleAreaFrame" class='ms-areaseparator' nowrap>

                     <table id="onetidPageTitleAreaTable" cellpadding=0 cellspacing=0 border="0">

                      <tr>

                       <td valign="top" class="ms-titlearea">

                       &nbsp;

                       </td>

                      </tr>

                      <tr>

                       <td ID=onetidPageTitle class="ms-pagetitle">

                       <!-- Page Title -->

                           <SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,gear_pagetitle%>" EncodeMethod='HtmlEncode'/>

                       </td>

                      </tr>

                     </table>

                 </td>

                 <td><div class='ms-areaseparatorright'><IMG SRC="/_layouts/images/blank.gif" width=8 height=100% alt=""></div></td>

          </tr>

          <TR>

              <TD class="ms-leftareacell" valign=top height=100% id="LeftNavigationAreaCell" >

               <table class=ms-nav width=100% height=100% cellpadding=0 cellspacing=0>

                <tr>

                 <td>

                     <TABLE height="100%" class=ms-navframe CELLPADDING=0 CELLSPACING=0 border="0" >

                      <tr valign="top">

                       <td width="4px"><IMG SRC="/_layouts/images/blank.gif" width=4 height=1 alt=""></td>

                       <td valign="top" width="100%">

                       &nbsp;

                       </td>

                      </tr>

                      <tr><td colspan=2><IMG SRC="/_layouts/images/blank.gif" width=138 height=1 alt=""></td></tr>

                     </TABLE>

                 </td>

                 <td></td>

                </tr>

               </table>

              </TD>

          <td>

              <div class='ms-areaseparatorleft'><IMG SRC="/_layouts/images/blank.gif" width=8 height=100% alt=""></div>

          </td>

          <!-- Contents -->

          <!-- Layout_Page_Description -->

          <td class='ms-formareaframe' width=100% valign="top">

              <TABLE width=100% border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet">

               <tr>

                <td>

                     <table ID="Table1" cellpadding=0 cellspacing=0 width="100%" height="100%">

                           <tr>

                                  <td width="100%" height="100%" align="center" valign="middle">

                                         <IMG SRC="/_layouts/images/blank.gif" width=590 height=1 alt="">

                                         <table cellpadding=0 cellspacing=0  width="100%">

                                                <tr>

                                                       <td style="padding-top:0px;padding-left: 20px;padding-right:20px;" >

                                                        <img alt="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,gear_tooltip%>' EncodeMethod='HtmlEncode'/>" src="/_layouts/images/gears_an.gif" >

                                                       </td>

                                                       <td width=100%><span class="ms-sectionheader">

                                                           <!-- LEADING HTML -->

                                                           <SharePoint:EncodedLiteral runat="server" id="MessageDesc" text="<%$Resources:wss,htmltrredir_pleasewait%>" EncodeMethod='HtmlEncode'/>                                                      

                                                       </span><span class='ms-descriptiontext'>

                                                       <!-- TRAILING HTML -->                                                    

                                                       </span></td></tr>

                                                <TR><TD  height=1 colspan=2><IMG SRC="/_layouts/images/blank.gif" width=1 height=8 alt=""></TD></TR>

                                                <TR><TD class=ms-sectionline height=1 colspan=2><IMG SRC="/_layouts/images/blank.gif" width=1 height=1 alt=""></TD></TR>

                                         </table>

                                  </td>

                           </tr>

                     </table>

                </td>

               </tr>

              </table>

              <table>

               <TR>

                <TD ID=onetidXPadding height="20px"><IMG SRC="/_layouts/images/blank.gif" width=1 height=20 alt=""></TD>

               </TR>

              </TABLE>

          </td>

         <td class="ms-rightareacell">

          <div class='ms-areaseparatorright'><IMG SRC="/_layouts/images/blank.gif" width=8 height=100% alt=""></div>

         </td>

        </TR>

       </TABLE>

   </TD>

  </TR>

 

 </TABLE>

 

<asp:Label id="ErrorLabel" runat="server" EnableViewState="False" class="ms-error" Text="">

<%

 

' TODO: put all strings into resource file and localize them

 

If Page.IsPostBack Then                  

        Try            

            Using web As Microsoft.SharePoint.SPWeb = Microsoft.SharePoint.SPContext.Current.Web                           

                Using site As Microsoft.SharePoint.SPSite = web.Site

                    Dim list As Microsoft.SharePoint.SPList = web.Lists.Item(New Guid(Request("ListId")))

                    Dim listItem As Microsoft.SharePoint.SPListItem = list.GetItemById(Request("ItemId"))

             

                    If list.WorkflowAssociations.Count > 0 Then

                        Dim wfAssociation As Microsoft.SharePoint.Workflow.SPWorkflowAssociation = list.WorkflowAssociations.GetAssociationByBaseID(New Guid(Request("WFTemplateID")))

                        If wfAssociation IsNot Nothing Then

                            If wfAssociation.Enabled = True Then

                                Dim wfRunning As Boolean = False

                                If listItem.Workflows.Count > 0 Then

                                    For Each wf As Microsoft.SharePoint.Workflow.SPWorkflow In listItem.Workflows

                                        If wf.ParentAssociation.BaseTemplate.Id = wfAssociation.BaseTemplate.Id Then

                                            If wf.InternalState = Microsoft.SharePoint.Workflow.SPWorkflowState.Running Then

                                                wfRunning = True

                                                Response.Write("An instance of this workflow is already running")

                                            End If

 

                                            Exit For

                                        End If

                                    Next

                                End If

                                  

                                If wfRunning = False Then

                                    web.AllowUnsafeUpdates = True

                           

                                    site.WorkflowManager.StartWorkflow(listItem, wfAssociation, "")

                                    site.WorkflowManager.Dispose()

                           

                                    Response.Redirect(list.DefaultViewUrl)

                                End If

                            Else

                                Response.Write("Workflow association is not enabled")

                            End If

                        Else

                            Response.Write("No workflow with this id found in the list")

                        End If

                    Else

                        Response.Write("No workflow is associated with this list")

                    End If

                End Using

            End Using

        Catch ex as Exception

            Response.Write(ex.ToString())

           

            If ex.InnerException IsNot Nothing Then

                Response.Write("<br/></br/>")

                Response.Write(ex.InnerException.ToString())

            End If

        Finally

            

        End Try

                    

    Else   

       ClientScript.RegisterStartupScript(Me.GetType(), "onload", "Form1.submit();", True)

    End If

      

 %>

</asp:Label>

                                                      

 </form>

</body>

 

</html>

 

 

 

Important things to point out (that, unfortunately, I have not seen mentioned in some of the post I’ve come across):

·         You workflow must be invoked from an HTTP POST (for security reasons), thus, the ugly (IMHO) workaround

·         You need to call Dispose on any disposable SharePoint objects, e.g. site, web, workflow manager…  Otherwise, after a few workflow invocations, you’ll start getting errors and will not be able to start another instance of your workflow…

 

 

5.    StartWorkflow.aspx file should be placed into C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS folder.  

 

 

The next step is to create and “register” your workflow…

 

6.    Create a WF workflow project and add your workflow logic… Make sure to add Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated shape as the first thing that happens in your workflow.  Otherwise, SharePoint will not be successfully invoke it.  To get to the SharePoint properties, e.g. the list item that triggered the workflow, etc., use Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties member variable…  For example, the skeleton (custom logic removed) of my workflow (written in VB.NET since my customer is a VB shop) looked like this:

 

 

Option Explicit On

Option Strict On

 

Imports System

Imports System.Security.Permissions

Imports System.Runtime.InteropServices

Imports Microsoft.SharePoint.Workflow

Imports Microsoft.SharePoint

Imports System.Data.SqlClient

 

Public Class PublishProjectPlan

    Inherits SequentialWorkflowActivity

 

    Private onWorkflowActivated1 As Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated

 

#Region "Member variables"

    Public workflowProps As Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties _

            = New Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties()

    Public workflowID As Guid = workflowProps.WorkflowId

 

    Private logToHistoryListActivity1 As Microsoft.SharePoint.WorkflowActions.LogToHistoryListActivity

#End Region

 

#Region "Ctor"

    Public Sub New()

        InitializeComponent()

    End Sub

#End Region

 

#Region "InitializeComponent"

    Private Sub InitializeComponent()

        Me.CanModifyActivities = True

        Dim activitybind2 As System.Workflow.ComponentModel.ActivityBind = New System.Workflow.ComponentModel.ActivityBind

        Dim correlationtoken1 As System.Workflow.Runtime.CorrelationToken = New System.Workflow.Runtime.CorrelationToken

        Dim activitybind1 As System.Workflow.ComponentModel.ActivityBind = New System.Workflow.ComponentModel.ActivityBind

        Me.logToHistoryListActivity1 = New Microsoft.SharePoint.WorkflowActions.LogToHistoryListActivity

        Me.onWorkflowActivated1 = New Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated

       

        'GetPlanData

        '

        Me.GetPlanData.Name = "GetPlanData"

        AddHandler Me.GetPlanData.ExecuteCode, AddressOf Me.GetPlanData_ExecuteCode

        '

        'logToHistoryListActivity1

        '

        Me.logToHistoryListActivity1.Description = "Project Plan Publishing Workflow Started"

        Me.logToHistoryListActivity1.Duration = System.TimeSpan.Parse("-10675199.02:48:05.4775808")

        Me.logToHistoryListActivity1.EventId = Microsoft.SharePoint.Workflow.SPWorkflowHistoryEventType.None

        Me.logToHistoryListActivity1.HistoryDescription = "Project Plan Publishing Workflow Started"

        Me.logToHistoryListActivity1.HistoryOutcome = ""

        Me.logToHistoryListActivity1.Name = "logToHistoryListActivity1"

        Me.logToHistoryListActivity1.OtherData = ""

        Me.logToHistoryListActivity1.UserId = -1

        activitybind2.Name = "PublishProjectPlan"

        activitybind2.Path = "workflowID"

        '

        'onWorkflowActivated1

        '

        correlationtoken1.Name = "workflowToken"

        correlationtoken1.OwnerActivityName = "PublishProjectPlan"

        Me.onWorkflowActivated1.CorrelationToken = correlationtoken1

        Me.onWorkflowActivated1.EventName = "OnWorkflowActivated"

        Me.onWorkflowActivated1.Name = "onWorkflowActivated1"

        activitybind1.Name = "PublishProjectPlan"

        activitybind1.Path = "workflowProps"

        AddHandler Me.onWorkflowActivated1.Invoked, AddressOf Me.onWorkflowActivated1_Invoked

        Me.onWorkflowActivated1.SetBinding(Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated.WorkflowIdProperty, CType(activitybind2, System.Workflow.ComponentModel.ActivityBind))

        Me.onWorkflowActivated1.SetBinding(Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated.WorkflowPropertiesProperty, CType(activitybind1, System.Workflow.ComponentModel.ActivityBind))

       

        Me.Name = "PublishProjectPlan"

        Me.CanModifyActivities = False

 

    End Sub

#End Region

 

    Protected Overrides Function Execute(ByVal executionContext As System.Workflow.ComponentModel.ActivityExecutionContext) As System.Workflow.ComponentModel.ActivityExecutionStatus

        Return MyBase.Execute(executionContext)

    End Function

 

    Protected Overrides Function HandleFault(ByVal executionContext As System.Workflow.ComponentModel.ActivityExecutionContext, ByVal exception As System.Exception) As System.Workflow.ComponentModel.ActivityExecutionStatus

        Try

            If workflowProps IsNot Nothing And workflowProps.Workflow IsNot Nothing Then

                workflowProps.Workflow.CreateHistoryEvent(SPWorkflowHistoryEventType.WorkflowError, _

                0, workflowProps.OriginatorUser, "Failed", _

                String.Format("Failed to publish project plan due to the following error:  {0}{1}", vbCrLf, exception.Message), _

                String.Format("Activity name: {0}\{1}Exception: {2}", executionContext.Activity.Name, vbCrLf, exception.ToString()))

            End If

 

            ' In either case, log to windows event log

            LogEvent(exception.ToString(), EventLogEntryType.Error)

 

        Catch ex As Exception

            ' TODO: safe-log to windows event log and/or database

        End Try

 

        ' TODO: cancel workflow?

        'SPWorkflowManager.CancelWorkflow(workflowProps.Workflow)

 

        Return MyBase.HandleFault(executionContext, exception)

    End Function

 

 

    Private Sub LogEvent(ByVal message As String, ByVal eventType As EventLogEntryType)

        Try

            If EventLog.SourceExists("SharePoint Workflow") = False Then

                EventLog.CreateEventSource("SharePoint Workflow", "Application")

            End If

            EventLog.WriteEntry("SharePoint Workflow", message, eventType)

        Catch

            EventLog.WriteEntry("Application", message, eventType)

            ' TODO: Add handling

        End Try

 

    End Sub

 

    Private Sub onWorkflowActivated1_Invoked(ByVal sender As System.Object, ByVal e As System.Workflow.Activities.ExternalDataEventArgs)

        'LogEvent("CIA Workflow Started", EventLogEntryType.Information)

    End Sub

End Class

 

 

 

 

The important things to point out are:

·         Any configuration data from app.config should be moved to web.config in your vdir folder.

·         To avoid trust issues, register your assembly in GAC

·         Make sure to recycle IIS worker process for your SharePoint site

 

 

7.    Now we need to register this new workflow as a SharePoint “feature”.  To do so, you’ll need the following two files place into PublishProjectPlanWorkflow (or choose your name) subfolder in the  c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES folder:

 

workflow.xml

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

<Elements Id="1bd66ed4-b9e0-4dc5-820e-eb7de884e902" xmlns="http://schemas.microsoft.com/sharepoint/">

  <Workflow

          Name="PublishProjectPlan"

          Description="CIA Project Plan Publishing Workflow"

          Id="068591c6-be5a-4b36-8a7c-6fe2c1ae434f"

          CodeBesideClass="YourNamespace.YourClass"

          CodeBesideAssembly="YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9c9f47ae4ce21556"

          StatusUrl="_layouts/WrkStat.aspx">

    <Categories/>

    <MetaData>

    </MetaData>

  </Workflow>

</Elements>

 

 

feature.xml

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

<Feature Id="ee542b6e-7053-47e0-86e6-1f344034072e"

      Title="Public CIA Project Plan"

      Description="CIA Project Plan Publishing Workflow"

      Version="12.0.0.0"

      Scope="Site"

      ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"

      ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver"

      xmlns="http://schemas.microsoft.com/sharepoint/">

  <ElementManifests>   

    <ElementManifest Location="workflow.xml" />

  </ElementManifests>

  <Properties>

    <Property Key="GloballyAvailable" Value="true" />

 

    <!-- Value for RegisterForms key indicates the path to the forms relative to feature file location -->

    <!-- if you don't have forms, use *.xsn -->

    <Property Key="RegisterForms" Value="Forms\*.xsn" />

  </Properties>

</Feature> 

 

 

 

 

8.    To register and activate this workflow with SharePoint, run the following commands:

 

"C:\Program Files\common files\microsoft shared\web server extensions\12\bin\stsadm.exe" -o installfeature -filename PublishProjectPlanWorkflow\feature.xml –force

 

"C:\Program Files\common files\microsoft shared\web server extensions\12\bin\stsadm.exe" -o activatefeature -name PublishProjectPlanWorkflow -url http://yoursite

 

 

 

9.    Finally, you’ll need to “associate” your SharePoint list with this workflow:

·         Navigate to http://yoursite/vdir/yourlist/Forms/AllItems.aspx

·         Click on Settings -> Document Library Settings

·         Under Permissions and Management, click  on Workflow Settings

·         Choose PublishProjectPlan workflow template and type in a unique name, e.g. Publish Project Plan

·         Keep all other options as defaults and click OK

 

 

 

 

That’s it…  Yes, it’s a lot of steps, but, once you do it, it’ll be more “natural” J

 

 

Note: for development purposes only, if you want to delete workflow instances, run the following command in the SharePoint SQL database:

 

 delete from dbo.Workflow

 

 

 

 

Posted by irenak | 1 Comments

SYSK 367: Outlook Add-In to Archive E-Mails

Do you wish there was an easier way to save individual messages to your hard disk in Outlook 2007?  Moreover, do you want this utility to automatically modify the file name making it unique?  For example, you may want to search for all messages from/to anybody at Contoso.com and archive them in \data\2009\Customers\Contoso\EMails folder as .msg files.  The tool should make sure that messages with the same title do not override each other…

 

I wrote an Outlook add-in to do just that, and have been using it for a few months now…     It allows archiving a single or multiple e-mail items from any mail folder (e.g. Inbox, Sent Items, Deleted Items, etc.) or from the advanced search window.

 

The archiving functionality is available via a context menu for any e-mail item:

 

Also, you get the new ribbon group in the mail item inspector (this functionality is not available for new items, only for sent/received ones):

 

 

When you click ‘Archive’ menu items, a Save-As dialog will allow you to change the folder, and the message will be saved with the date the message was sent on and the sender’s name pre-pended to the message title, thus assuring that messages with the same title do not collide.   If multiple mail items are selected, the user will also be prompted whether to save all of them to the same folder. 

 

If you don’t like the SentOn and SenderName as the fields used to decorate the file name, you can change it by going to Tools -> Options  menu item and clicking on the Archive tab.  Just use drag-and-drop action to change the order in the Selected Fields list or to add/remove other fields.

 

The file format is configured using custom property page:

 

The configuration options are stored in user isolated storage file.

 

 

 

 

If you like this add-in, see attachments for the source code and the binaries…

 

Enjoy!

 

 

 

SYSK 366: Tool to Automatically Set Internet Explorer Proxy

As a consultant, I frequently work at my customer sites, and at my home office.  Needless to say, remembering the proxy configurations, and changing them every time I plug in is a chore I’d rather delegate to software running in the background and automatically detecting network connectivity changes, and setting the IE proxy based on pre-configured settings…  This blog post is a set of code snapshots from the tool I wrote to do just that…

 

Since IE settings are stored in CurrentUser registry hive, the path of least resistance was to create a UI-less Windows user process (not a service) and run it at start up by putting it in the Startup folder.

 

My configuration file (app.config) looks like this:

 

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

<configuration>

  <configSections>

    <section name="IEProxySettings" type="AutoProxy.ProxySettingsSection, AutoProxy"  />

  </configSections>

  <IEProxySettings>

    <proxyConfig>

      <proxy dnsSuffix="[YOUR_DNS_HERE]" defaultGateway="[YOUR_DEFAULT_IP_HERE]" address="[YOUR_IE_PROXY]" port="[YOUR_PORT]" bypassLocal="[true/false]"  />

    </proxyConfig>

  </IEProxySettings>

</configuration>

 

To get the dnsSuffix and defaultGateway, run the command prompt (Start -> Run -> cmd) and execute the ipconfig command.

 

You can have as many <proxy> settings as needed.

 

If the dnsSuffix/defaultGateway is matched for one of the networks with status = OperationalStatus.Up, then the  IE proxy settings are changed accordingly.  If there is no match, then the ‘Use a proxy server for your LAN’ setting in IE browser is unchecked. 

 

To read the settings, I use the following ConfigSettings.cs file:

 

using System;

using System.Configuration;

 

namespace AutoProxy

{

    public class ProxySettingsSection : ConfigurationSection

    {

        public static ProxyElement[] Proxies

        {

            get

            {

                ProxyElement[] result = null;

 

                ProxySettingsSection data = System.Configuration.ConfigurationManager.GetSection("IEProxySettings") as ProxySettingsSection;

 

                if (data == null)

                    throw new ApplicationException("Configuration file is missing 'IEProxySettings' section", null);

 

 

                result = new ProxyElement[data.ProxyConfig.Count];

                data.ProxyConfig.CopyTo(result, 0);

 

                return result;

            }

        }

 

        [ConfigurationProperty("proxyConfig", Options = ConfigurationPropertyOptions.IsRequired, IsDefaultCollection = true, IsKey = false)]

        public ProxyConfigCollection ProxyConfig

        {

            get

            {

                return ((ProxyConfigCollection)base["proxyConfig"]);

            }

        }

    }

 

    [ConfigurationCollection(typeof(ProxyElement), AddItemName = "proxy", CollectionType = ConfigurationElementCollectionType.BasicMap)]

    public sealed class ProxyConfigCollection : ConfigurationElementCollection

    {

        public ProxyConfigCollection()

            : base(StringComparer.OrdinalIgnoreCase)

        {

 

        }

 

        protected override Object GetElementKey(ConfigurationElement element)

        {

            return ((ProxyElement)element).DNSSuffix;

        }

 

        protected override ConfigurationElement CreateNewElement()

        {

            return new ProxyElement();

        }

 

 

        public new ProxyElement this[string dnxSuffix]

        {

            get

            {

                // Force the get by key, not index

                object key = dnxSuffix;

 

                return (ProxyElement)base.BaseGet(key);

            }

        }

 

        public ProxyElement this[int index]

        {

            get { return (ProxyElement)base.BaseGet(index); }

        }

    }

 

    #region ProxyElement

    public class ProxyElement : ConfigurationElement

    {

        public ProxyElement()

        {

        }

 

        public ProxyElement(string dnsSuffix, string defaultGateway, string address, string port)

        {

            DNSSuffix = dnsSuffix;

            DefaultGateway = defaultGateway;

            Address = address;

            Port = port;

        }

 

        [ConfigurationProperty("dnsSuffix", IsKey = true, IsRequired = true, DefaultValue = "")]

        public string DNSSuffix

        {

            get { return (string)base["dnsSuffix"]; }

            set { base["dnsSuffix"] = value; }

        }

 

        [ConfigurationProperty("defaultGateway", IsRequired = true, DefaultValue = "")]

        public string DefaultGateway

        {

            get { return (string)base["defaultGateway"]; }

            set { base["defaultGateway"] = value; }

        }

 

        [ConfigurationProperty("address", DefaultValue = "")]

        public string Address

        {

            get { return (string)base["address"]; }

            set { base["address"] = value; }

        }

 

        [ConfigurationProperty("port", DefaultValue = "")]

        public string Port

        {

            get { return (string)base["port"]; }

            set { base["port"] = value; }

        }

 

        [ConfigurationProperty("bypassLocal", DefaultValue = true)]

        public bool BypassLocal

        {

            get { return (bool)base["bypassLocal"]; }

            set { base["bypassLocal"] = value; }

        }

       

    }

    #endregion

}

 

 

 

I went the “lazy-route”, and simply created a Windows exe application, set the Form1 to start in the minimized state and indicated that it should not appear in Windows taskbar by setting ShowInTaskBar property to false.

 

The Form1 has no added controls, and the entire code behind is listed below:

 

using System;

using System.Windows.Forms;

using System.Diagnostics;

using System.Net.NetworkInformation;

 

namespace AutoProxy

{

    public partial class Form1 : Form

    {

        ProxyElement[] _proxies = null;

        EventLog _log = null;

 

        public Form1()

        {

            InitializeComponent();

 

            try

            {

                _log = new EventLog();

                _log.Source = "AutoProxy";

 

                _proxies = ProxySettingsSection.Proxies;

 

                // Set it now...

                NetworkChange_NetworkAddressChanged(null, null);

            }

            catch (Exception ex)

            {               

                _log.WriteEntry("SmartIEProxy failed reading configuration file.  " + ex.Message, EventLogEntryType.Error);

 

                // Abort the service startup

                throw;

            }

 

            try

            {

                // Do we have access to the configuration file?

                // System.Configuration.ConfigurationSettings.

                // No exception handling, so the service stops in case of an exception

                NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(NetworkChange_NetworkAddressChanged);

            }

            catch (Exception ex)

            {

                _log.WriteEntry("SmartIEProxy failed to subscribe to NetworkAddressChanged event.  " + ex.Message, EventLogEntryType.Error);

 

                // Abort the service startup

                throw;

            }

 

        }

 

        void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)

        {

            try

            {

                IEProxy.UnsetProxy();

                _log.WriteEntry(string.Format("IE proxy un-set by AutoProxy.exe"), EventLogEntryType.Information);

 

                NetworkInterface[] networks = NetworkInterface.GetAllNetworkInterfaces();

 

                foreach (NetworkInterface n in networks)

                {

                    if (n.OperationalStatus == OperationalStatus.Up)

                    {

                        IPInterfaceProperties props = n.GetIPProperties();

 

                        string newProxy = null, exceptions = null;

 

                        foreach (ProxyElement proxy in _proxies)

                        {

                            if (string.Compare(props.DnsSuffix, proxy.DNSSuffix, true) == 0 &&

                                ((props.GatewayAddresses.Count > 0) && props.GatewayAddresses[0].Address.ToString() == proxy.DefaultGateway))

                            {

                                newProxy = proxy.Address + ":" + proxy.Port;

                                exceptions = proxy.BypassLocal ? "<local>" : "";

 

                                break;

                            }

                            

                        }

 

                        if (string.IsNullOrEmpty(newProxy) == false)

                        {

                            IEProxy.SetProxy(newProxy, exceptions);

                            _log.WriteEntry(string.Format("Proxy changed to {0} by AutoProxy.exe", newProxy), EventLogEntryType.Information);

                            break;

                        }

 

                    }

                }

            }

            catch (Exception ex)

            {

                _log.WriteEntry(ex.ToString(), EventLogEntryType.Error);

            }

        }

    }

}

 

 

The last piece of the puzzle is the code that actually sets the IE’s proxy settings.  Create IEProxy.cs class and add the code below:

 

using System.Runtime.InteropServices;

using System;

using System.ComponentModel;

namespace AutoProxy

{

    // Source:  http://www.codeproject.com/KB/IP/Change_IE7_Proxy_Setting.aspx

    internal class IEProxy

    {

        public static bool UnsetProxy()

        {

            return SetProxy(null, null);

        }

        public static bool SetProxy(string strProxy)

        {

            return SetProxy(strProxy, null);

        }

 

        public static bool SetProxy(string strProxy, string exceptions)

        {

            InternetPerConnOptionList list = new InternetPerConnOptionList();

 

            int optionCount = string.IsNullOrEmpty(strProxy) ? 1 : (string.IsNullOrEmpty(exceptions) ? 2 : 3);

            InternetConnectionOption[] options = new InternetConnectionOption[optionCount];

            // USE a proxy server ...

            options[0].m_Option = PerConnOption.INTERNET_PER_CONN_FLAGS;

            options[0].m_Value.m_Int = (int)((optionCount < 2) ? PerConnFlags.PROXY_TYPE_DIRECT : (PerConnFlags.PROXY_TYPE_DIRECT | PerConnFlags.PROXY_TYPE_PROXY));

            // use THIS proxy server

            if (optionCount > 1)

            {

                options[1].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_SERVER;

                options[1].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(strProxy);

                // except for these addresses ...

                if (optionCount > 2)

                {

                    options[2].m_Option = PerConnOption.INTERNET_PER_CONN_PROXY_BYPASS;

                    options[2].m_Value.m_StringPtr = Marshal.StringToHGlobalAuto(exceptions);

                }

            }

 

            // default stuff

            list.dwSize = Marshal.SizeOf(list);

            list.szConnection = IntPtr.Zero;

            list.dwOptionCount = options.Length;

            list.dwOptionError = 0;

 

 

            int optSize = Marshal.SizeOf(typeof(InternetConnectionOption));

            // make a pointer out of all that ...

            IntPtr optionsPtr = Marshal.AllocCoTaskMem(optSize * options.Length);

            // copy the array over into that spot in memory ...

            for (int i = 0; i < options.Length; ++i)

            {

                IntPtr opt = new IntPtr(optionsPtr.ToInt32() + (i * optSize));

                Marshal.StructureToPtr(options[i], opt, false);

            }

 

            list.options = optionsPtr;

 

            // and then make a pointer out of the whole list

            IntPtr ipcoListPtr = Marshal.AllocCoTaskMem((Int32)list.dwSize);

            Marshal.StructureToPtr(list, ipcoListPtr, false);

 

            // and finally, call the API method!

            int returnvalue = NativeMethods.InternetSetOption(IntPtr.Zero,

               InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,

               ipcoListPtr, list.dwSize) ? -1 : 0;

            if (returnvalue == 0)

            {  // get the error codes, they might be helpful

                returnvalue = Marshal.GetLastWin32Error();

            }

            // FREE the data ASAP

            Marshal.FreeCoTaskMem(optionsPtr);

            Marshal.FreeCoTaskMem(ipcoListPtr);

            if (returnvalue > 0)

            {  // throw the error codes, they might be helpful

                throw new Win32Exception(Marshal.GetLastWin32Error());

            }

 

            return (returnvalue < 0);

        }

   

 

        #region WinInet structures

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

        public struct InternetPerConnOptionList

        {

            public int dwSize;               // size of the INTERNET_PER_CONN_OPTION_LIST struct

            public IntPtr szConnection;         // connection name to set/query options

            public int dwOptionCount;      // number of options to set/query

            public int dwOptionError;        // on error, which option failed

            //[MarshalAs(UnmanagedType.)]

            public IntPtr options;

        };

 

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]

        public struct InternetConnectionOption

        {

            static readonly int Size;

            public PerConnOption m_Option;

            public InternetConnectionOptionValue m_Value;

            static InternetConnectionOption()

            {

                InternetConnectionOption.Size = Marshal.SizeOf(typeof(InternetConnectionOption));

            }

 

            // Nested Types

            [StructLayout(LayoutKind.Explicit)]

            public struct InternetConnectionOptionValue

            {

                // Fields

                [FieldOffset(0)]

                public System.Runtime.InteropServices.ComTypes.FILETIME m_FileTime;

                [FieldOffset(0)]

                public int m_Int;

                [FieldOffset(0)]

                public IntPtr m_StringPtr;

            }

        }

        #endregion

 

        #region WinInet enums

        //

        // options manifests for Internet{Query|Set}Option

        //

        public enum InternetOption : uint

        {

            INTERNET_OPTION_PER_CONNECTION_OPTION = 75

        }

 

        //

        // Options used in INTERNET_PER_CONN_OPTON struct

        //

        public enum PerConnOption

        {

            INTERNET_PER_CONN_FLAGS = 1, // Sets or retrieves the connection type. The Value member will contain one or more of the values from PerConnFlags

            INTERNET_PER_CONN_PROXY_SERVER = 2, // Sets or retrieves a string containing the proxy servers. 

            INTERNET_PER_CONN_PROXY_BYPASS = 3, // Sets or retrieves a string containing the URLs that do not use the proxy server. 

            INTERNET_PER_CONN_AUTOCONFIG_URL = 4//, // Sets or retrieves a string containing the URL to the automatic configuration script. 

 

        }

 

        //

        // PER_CONN_FLAGS

        //

        [Flags]

        public enum PerConnFlags

        {

            PROXY_TYPE_DIRECT = 0x00000001,  // direct to net

            PROXY_TYPE_PROXY = 0x00000002,  // via named proxy

            PROXY_TYPE_AUTO_PROXY_URL = 0x00000004,  // autoproxy URL

            PROXY_TYPE_AUTO_DETECT = 0x00000008   // use autoproxy detection

        }

        #endregion

 

        internal static class NativeMethods

        {

            [DllImport("WinInet.dll", SetLastError = true, CharSet = CharSet.Auto)]

            [return: MarshalAs(UnmanagedType.Bool)]

            public static extern bool InternetSetOption(IntPtr hInternet, InternetOption dwOption, IntPtr lpBuffer, int dwBufferLength);

        }

 

    }

}

 

 

That’s all there is to it…  Now, compile, and add a shortcut to the Startup folder for a specific user or all users, e.g. C:\Users\UserNameHere\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup.

 

Enjoy.

 

P.S. The binaries are posted as an attachment.

 

 

Posted by irenak | 2 Comments
Filed under:

Attachment(s): AutoProxy.zip

SYSK 365: How to get your unit tests (test project in Visual Studio 2008, a.k.a. MSTest) run multithreaded

If you want to test code that requires an MTA threading model (e.g. the code being tested uses WaitHandle.WaitAll), you need to change the .testrunconfig file manually and change the threading model from STA to MTA.

 

There are other posts that say for VS 2005, you add the following line to the config file:

<apartmentState type="System.Threading.ApartmentState">

<value__ type="System.Int32">1</value__>

</apartmentState>

 

Unfortunately, same posts say that in VS 2008 you use

<ExecutionThread apartmentState="1" />

 

In my experience, it doesn’t work!  Instead, you should use

          <TestRunConfiguration ...>

            ...

<ExecutionThread apartmentState="MTA" />

</TestRunConfiguration>

 

 

To check the threading model used, add the following line to your test(s):

 

[TestMethod]

public void YourTest()

{

System.Diagnostics.Debug.WriteLine(System.Threading.Thread.CurrentThread.GetApartmentState().ToString());

 

// TODO: Add test logic here

}

 

 

 

 

Posted by irenak | 2 Comments

SYSK 364: AJAX Extensions 1.0 Source Code and Debug Symbols

Did you know that AJAX Extensions source code and debugging symbols are available to everybody at http://www.microsoft.com/downloads/details.aspx?FamilyID=ef2c1acc-051a-4fe6-ad72-f3bed8623b43&DisplayLang=en?

 

 

Posted by irenak | 0 Comments
Filed under: ,

SYSK 363: ‘Software is not made of bricks’ by Ragan Wald

I recently came across this gem http://weblog.raganwald.com/2007/08/bricks.html -- strongly recommended!

 

Here are the section titles to whet your appetite:

 

·         Software is not made of bricks

·         Assumption: it’s all about moving bricks

·         Software is more complicated than bricks

·         Determine the baseline competence required for a project and don’t violate it

·         Software development is difficult to parallelize

·         How to make the team twice as productive without parallelizing everything

·         Software is transfinite

·         How to measure progress on software development projects with estimated work remaining

·         How to measure progress on software development projects with customer satisfaction

·         Building software without treating it like a pile of bricks

I recently came across this gem http://weblog.raganwald.com/2007/08/bricks.html -- strongly recommended!

 

Here are the section titles to whet your appetite:

 

·         Software is not made of bricks

·         Assumption: it’s all about moving bricks

·         Software is more complicated than bricks

·         Determine the baseline competence required for a project and don’t violate it

·         Software development is difficult to parallelize

·         How to make the team twice as productive without parallelizing everything

·         Software is transfinite

·         How to measure progress on software development projects with estimated work remaining

·         How to measure progress on software development projects with customer satisfaction

·         Building software without treating it like a pile of bricks

 

 

Posted by irenak | 2 Comments

SYSK 362: The cost of try/catch

Since I’m still coming across a lot of conflicting recommendations on the “best practices for structure exception handling” (including those coming from different folks from Microsoft), I decided to write a blog post on the topic. 

 

So, what are the benefits and costs with SEH?

 

Let me first address the cost of adding try/catch/finally…

 

John Lee (a peer MCS consultant) did some profiling on his HP Compaq nc8430 Laptop (Dual Core CPU 2.16GHz, 4GB RAM) and, using that hardware, the approximate overhead of adding try/catch per function when exception is not thrown is 0.00001235 ms.  In comparison, getting a connection from a connection pool takes 0.0409662 ms => over 3,000 times slower.  If an exception is thrown from a function nested 10 levels deep with try/catch and rethrow at every level, then the execution time goes up to 0.02010861 ms.

 

Using the numbers above, in my opinion, the cost of try/catch (especially given that, in most cases, the code runs successfully without incurring the cost of throwing an exception) in the context of creating business applications (I’m not talking about critical, real-time or near real time apps which most likely are not written in .NET anyway), is so negligible that the benefits (see next paragraph) far outweigh the cost.

 

As to the benefits…  In my projects, in addition to the standard call stack info, I add the actual data passed to each function in the faulting call stack to the exception message eventually logged to a database.  In most cases, this gives me sufficient data to recreate the problem and do root cause analysis in a fraction of the time it would take without such details. 

 

In summary, if a method can either handle the exception (e.g. correct the problem and/or retry) or has other value-add (e.g. log passed in parameter values in the catch block), then the value of sprinkling the try/catch in every single method (not just at the top-most level) in business level applications is significantly greater, in my opinion, then the cost of the try/catch statements.

 

 

Special thanks to John Lee for doing the profiling!

 

 

Posted by irenak | 4 Comments

Why I have not been writing any new posts...

As much as I love writing the SYSK posts, the reality is that it takes a lot of time...  For the last couple of months, I've been working 14+ hour days consistently...  To make the matters worse, I've learned that this type of activity doesn't even result in a "pat on the back" as, given my role and responsibilities, I'm expected to do this kind of work (and other type of knowledge transfer, mentoring, etc.) internally within Microsoft... 

Bottom line -- the bloging I do here comes out of my personal time, and, with 70+ hour work weeks, there is not much personal time left...

This does not mean that I will not be posting any more...  I just need to get to a more sane work schedule...  So, hang on...  keep checking...  I will be back!

 

Posted by irenak | 8 Comments

SYSK 361: On the importance of signing assemblies from the versioning perspective

There are many reasons for signing your assemblies (security being at the top of my list), but in this post I’d like to address the importance of using a key to sign your assemblies from the versioning point of view.

 

Now, let’s assume for the sake of this discussion, that we’re talking about private assemblies (no GAC).  So, let’s consider a simple factory pattern: client calls a factory class to get an object, the factory uses the context and configuration information to determine the right implementation, creates an instance of that object and returns it to the caller. 

 

For simplicity sake, let’s say you deploy the different implementations of a class in versioned folders, but keep the assembly name, e.g.  

…\bin\Client.exe

…\bin\ClassLibrary\v1.0.0.0\ClassLibrary.dll

…\bin\ClassLibrary\v2.0.0.0\ClassLibrary.dll

 

Once the factory class resolves which class implementation it needs to instantiate, it can use different mechanisms to actually create the object, e.g.:

Assembly asm = Assembly.LoadFrom(@"ClassLibrary\v1.0.0.0\ClassLibrary.dll");

ClassLibraryInterfaces.IClass1 class1 = asm.CreateInstance("ClassLibrary.Class1") as ClassLibraryInterfaces.IClass1;

 

or

 

ClassLibraryInterfaces.IClass1 class1 = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(@"ClassLibrary, Version=1.0.0.0, publicKeyToken=1db56723ca128f1b, culture=neutral", "ClassLibrary.Class1") as ClassLibraryInterfaces.IClass1;

 

or

 

Assembly asm = Assembly.Load(@"ClassLibrary, Version=1.0.0.0, publicKeyToken=1db56723ca128f1b, culture=neutral");

ClassLibraryInterfaces.IClass1 class1 = asm.CreateInstance("ClassLibrary.Class1") as ClassLibraryInterfaces.IClass1;

 

Of course, you would need to give a hint to the runtime about where to find your assembly either via the probing element or codebase:

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

<configuration>

  <runtime>

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

      <probing privatePath="ClassLibrary\v1.0.0.0;ClassLibrary\v2.0.0.0"/>

         </assemblyBinding>

  </runtime>

</configuration>

or

 

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

<configuration>

  <runtime>

    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

     <dependentAssembly>

        <assemblyIdentity name="ClassLibrary" publicKeyToken="1db56723ca128f1b" culture="neutral" />

        <codeBase version="2.0.0.0" href="ClassLibrary/v2.0.0.0/ClassLibrary.dll" />

        <codeBase version="1.0.0.0" href="ClassLibrary/v1.0.0.0/ClassLibrary.dll" />

      </dependentAssembly>

         </assemblyBinding>

  </runtime>

</configuration>

 

However, if you don’t sign your assembly with a key (i.e. publicKeyToken=”null”), no matter how you implement the class instantiation in the factory class implementation, on the second request, the version portion will be ignored and you’ll get back the instance of the object you created the first time around.  For example, if the first request is for the version 1.0.0.0 and the second is for 2.0.0.0, in both cases, you’ll get back the 1.0.0.0 implementation.

Moreover, if you use probing, you’ll always get the first file name match regardless of the version.

The solution is simple – use a key to sign your assembly.

Also, beware that if you’re using probing you must use Assembly.LoadFrom to avoid type mismatch type of exceptions in cases where the first match is not of the right version.  Or, simply use codebase hints instead (my personal preference).

 

Posted by irenak | 0 Comments

:) When was the color khaki first used for military uniforms?

It was first used by the British in India in 1845, when soldiers turned their highly visible white uniforms khaki by soaking them in mud, coffee, and curry and better blend in with the landscape.

 

Source:  The 365 amazing trivia facts calendar 2007.

Posted by irenak | 5 Comments

SYSK 360: What’s taking up the space in my database? Also, a word about SQL reports…

So, you’ve updated usage stats on your database by running

 

EXEC sp_spaceused @updateusage = N'TRUE'

 

But, you still can’t figure out why it’s reporting much more used space then the data that appears in your tables…

Try running the following query to get number of rows and detailed space usage information for each table and each index.

 

SELECT object_name(object_id) as ObjName,

      index_id as IndexID,

      sum(in_row_data_page_count) as InRowDataPages,

      sum(lob_used_page_count) as LargeObjectPages,

      sum(row_overflow_used_page_count) as RowOverflowPages,

      sum(row_count) as Rows

FROM sys.dm_db_partition_stats

GROUP BY object_id,index_id

GO

Also, there are a number of commonly used reports that are available in SQL Server Management Studio.  Just right-mouse click on the database, and choose Reports -> Standard Reports…

While I am on the SQL reports topic, you can also get a number of useful reports for the entire server,  e.g. All Blocking Transactions, Top Queries by Average CPU time, Top Queries by Average IO, Schema Changes History, Memory Consumption and many more!  Simply, right-mouse click on the server name and choose Reports ->Standard Reports…

Finally, one more query that can come in handy:

SELECT * FROM fn_trace_gettable ('C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\LOG\log.trc', default) GO

 

Special thanks to Curtis Krumel and Saleem Hakani for the queries above!

Posted by irenak | 3 Comments
Filed under:
More Posts Next page »
 
Page view tracker