Welcome to RenEvo Sign in | Join | Help

CAB – Part 5 Simple Module

In the previous articles on CAB, we have gone over some of the basics of getting our shell application ready to load up some modules.

In this article, we will actually create a simplistic Module to display a “SmartPart” in our primary workspace.

The first thing we need to do, is to add a new “Class Library” project to our current solution. Name the new project “RenEvo.Blogs.Cab.SimpleModule” and hit ok. Rename the “Class1.vb” to “SimpleModuleInit.vb”.  This class will be our entry point into the actual module.

At this point, we are going to make a few modifications to our current projects, as the build directories are kind of all over the place, and since we have a defined structure we need to adhere to (example: ./Modules/ for any modules loaded into the Shell).

In the RenEvo.Blogs.Cab project, set the Build output Path in the Compile tab of the project properties to “..\bin\Debug\” for Debug configuration, and “..\bin\Release\” for the Release configuration.  Do the same thing to the RenEvo.Blogs.Cab.Interfaces project.

For the new RenEvo.Blogs.Cab.SimpleModule, set the output paths to “..\bin\Debug\Modules\” and “..\bin\Release\Modules\” respectively.

Build the entire solution, and verify that there is a bin directory in the Solution folder that contains all of the projects output assemblies in the per-configuration folder.

Back to our Simple Module project, we need to add some key references. Add the following references, and be sure to set the “Copy Local” property to False for each reference.

  • Microsoft.Practices.CompositeUI
  • Microsoft.Practices.CompositeUI.WinForms
  • Microsoft.Practices.ObjectBuilder
  • System.Windows.Forms
  • System.Drawing

And then finally add a project reference to the RenEvo.Blogs.Cab.Interfaces, also settings the “Copy Local” property to false.

image

Next, create a few new folders inside of the Simple Module project.

  • Services
  • SmartParts
  • WorkItems

The first thing we need to do is to setup the SimpleModuleInit class so that the shell will load it up. The first thing we will do is to import a few namespaces, then add an assembly attribute “Module” which will be the attribute that the Shell will look for when loading modules via reflection. Next we will inherit ModuleInit in our class.  At this point, your code will look like this:

   1:  Imports Microsoft.Practices.CompositeUI
   2:  Imports Microsoft.Practices.CompositeUI.Services
   3:   
   4:  <Assembly: [Module]("Simple Module")> 
   5:   
   6:  Public Class SimpleModuleInit
   7:      Inherits ModuleInit
   8:   
   9:   
  10:  End Class

We will have to setup our Dependency Injection property in order to integrate into the Shell Application. If you are not familiar with Dependency Injection, I suggest you read up on it before continuing.  Essentially for this implementation of Injection, we will use Property Attributes to notify the base framework that we have a service dependency, and then the property declared type will provide the service type we are accessing, in this case “WorkItem” type.

   1:  #Region " Root Work Item Dependency Injection "
   2:   
   3:      Private m_WorkItem As WorkItem = Nothing
   4:      <ServiceDependency()> _
   5:      Public Property RootWorkItem() As WorkItem
   6:          Get
   7:              Return m_WorkItem
   8:          End Get
   9:          Set(ByVal value As WorkItem)
  10:              m_WorkItem = value
  11:          End Set
  12:      End Property
  13:   
  14:  #End Region

This is basically setting this property by doing the following line of code (not used in our code, but deep behind the scenes).

   1:  SimpleModuleInit.RootWorkItem = Services.Get(Of WorkItem)()

By using Dependency Injection, we don’t need to know what is going on behind the scenes, and in some later examples of implementing our own services, it is quite useful to just have what we need automatically populated, rather than trying to find it manually.

Moving along, the next step we will need to do, is to override the ModuleInit.Load() sub in our SimpleModuleInit. Within this Method, lets just simple do the MessageBox.Show(“SimpleModule Loaded”) to verify that our module is being loaded.

Be sure that the RenEvo.Blogs.Cab project is set as the Startup Project, and run the application.  While the Splash Screen is displayed, you should see a message box stating that your SimpleModule Loaded.

Ok, moving along quickly here, lets remove that message box line. Now in the SmartParts folder, add a new UserControl named "SimpleSmartPart.vb” and in the WorkItems folder, add a new Class named “SimpleWorkItem.vb”

Code for SimpleSmartPart.vb

   1:  Imports Microsoft.Practices.CompositeUI.SmartParts
   2:   
   3:  <SmartPart()> _
   4:  Public Class SimpleSmartPart
   5:   
   6:  End Class

What we are doing in the class above is just tagging this user control as a SmartPart, this allows the WorkSpace objects to communicate with them properly, which we will go over in a later article.

For the sake of being able to see the smart part easier, drop a label control in the design view on the SimpleSmartPart, name it “uxSmartPartName” and set its Text property to “Simple Smart Part Loaded”. Where you put the label is irrelevant, mine looks like this:

image 

Now, the code for the SimpleWorkItem.vb is as follows:

   1:  Imports Microsoft.Practices.CompositeUI
   2:  Imports Microsoft.Practices.CompositeUI.Commands
   3:  Imports RenEvo.Blogs.Cab.Interfaces.Common
   4:   
   5:  Imports System.Windows.Forms
   6:   
   7:  Public Class SimpleWorkItem
   8:      Inherits WorkItem
   9:   
  10:      Protected Overrides Sub OnRunStarted()
  11:          MyBase.OnRunStarted()
  12:   
  13:          Me.RootWorkItem.Workspaces(Constants.WorkSpaces.PrimaryWorkSpace).Show(New SimpleSmartPart)
  14:   
  15:      End Sub
  16:   
  17:  End Class

The code above inherits from WorkItem and overrides the OnRunStarted method, we then Show a new SimpleSmartPart in our PrimaryWorkSpace.

Finally, to get the work item to actually “run” we need to modify the SimpleModuleInit.vb by adding a workitem to the RootWorkItem property, and calling the “Run” method.  The code below goes in the Load() method in SimpleModuleInit.vb

   1:  Me.RootWorkItem.WorkItems.AddNew(Of SimpleWorkItem).Run()

In the above code we are using the “AddNew” method with a generic typing of SimpleWorkitem and then calling the method “Run” on the newly created WorkItem.

Save everything, and run the application.  When the Shell interface displays after the splash screen, you will see the label with the text “Simple Smart Part Loaded” in our interface.

image

And there you have it, we have loaded up a module into the Shell, and displayed our First Smart Part.

In the next article, we will be adding a Controller to the SmartPart for the MVC (Model View Controller) design pattern, and implementing a few commands within that Controller.

Download the Solution

Posted by Dante | 0 Comments
Filed under:

SQL Server 2008 Express Now Available

SQL Server Express 2008, which released on Monday, is now available from Microsoft.

New Features:

  • Support for LINQ, Entity Data Model and ADO.NET Entity Framework make it easy to create next generation data-enabled applications
  • New Date and Time data types with time zone support and .NET compatibility provides full control over temporal data
  • New T-SQL IntelliSense support in SQL Server Management Studio makes it easy to write accurate T-SQL code
  • Access a vast community of other SQL Server enthusiasts from beginners to experts via the SQL Server Express forum

Overview:

  • Tight integration with Visual Studio 2008 with SP1
  • SQL Server 2008 Express provides high-end database features
  • Support for new data types and features like spatial data, HierarchyID, and FileStream makes it easier to model complex data
  • Support for MERGE, GROUPING SETS, sparse columns and table-valued parameters makes it easier to write T-SQL code
  • New Import/Export wizard makes it easy to migrate data
Posted by Dante | 0 Comments

CAB - Part 4 Adding a WorkSpace

In the previous articles on CAB we have gone over creating the basics with a CAB, right now our application really has no "Composite" to it, just "Application Block".  In this article we will go over creating our first WorkSpace for modules to dock into, which will lead us to our next article on creating a basic module with a single workspace.

The first thing we need to do, in order to work with the CAB WinForms controls, is to create a new Tab on our Forms Toolbox, and load up the Microsoft.Practices.CompositeUI.WinForms.dll controls to it.  If you need help on this, you can see many articles on the web on how to add items to your toolbox, like this one.

You should now have 7 new controls, the one we are going to concentrate on for this article is the DeckWorkspace.  Simply drag it onto our form, set the Dock to Fill, name it uxPrimaryWorkspace, then clear the Text property.

Your form in designer should look like this:

image

The size of the form is un-important, later we will go about saving the forms settings, etc... but for now, lets leave it as is.

Now that we have our form setup, we need to add some ways to access this workspace, as well as register the WorkSpace with the CAB framework.  This is usually done via id strings, but since we want our users to properly access this workspace, we will want to create constants for access.  Instead of the modules accessing and referencing the main executable, we will add another project with the name ".Interfaces" that will contain all the constants for extension sites, workspaces, as well as internal services.

Add a Class Library project to the solution named "RenEvo.Blogs.Cab.Interfaces" with the default directory specified.  Next delete the Class1.vb that is automaticaly created.

Create a new folder called Common, then a new class file inside of that folder called WorkSpaces.  In the class file you need to implement a namespace, as well as a few constants inside of the class.  The namespace is only for better organization, and separation of objects.

   1:  Namespace Common.Constants
   2:   
   3:      ''' <summary>
   4:      ''' Class to store all of the workspace names
   5:      ''' </summary>
   6:      Public Class WorkSpaces
   7:          ''' <summary>
   8:          ''' The Primary Workspace for the CAB application
   9:          ''' </summary>
  10:          Public Const PrimaryWorkSpace As String = "PrimaryWorkSpace"
  11:   
  12:      End Class
  13:   
  14:  End Namespace

 

For now our class really doesn't cover a lot of constants, but in a larger application this could get quite huge.  Plus we no longer will have casing or mis-spelling issues with the other developers.

Now add a reference to the new project to the main Shell project, setting the copy to True.

Next, back in our form, we need to access the Constructor, and after the InitializeComponent, we want to reset the name of the uxPrimaryWorkspace in runtime to the name of the string in the preceding class.

   1:      Public Sub New()
   2:   
   3:          ' This call is required by the Windows Form Designer.
   4:          InitializeComponent()
   5:   
   6:          ' Add any initialization after the InitializeComponent() call.
   7:          Me.uxPrimaryWorkspace.Name = Interfaces.Common.Constants.WorkSpaces.PrimaryWorkSpace
   8:   
   9:      End Sub

 

This is a really easy implementation, probably the easiest in the entire application.  To test it, we can do the following in our Startup.vb class.

In the ShellShown event handlers, add the following lines of code to the bottom of the method.

   1:      Protected Sub ShellShown(ByVal sender As Object, ByVal e As EventArgs)
   2:          'remove the handler
   3:          RemoveHandler Shell.Shown, AddressOf ShellShown
   4:   
   5:          'set the cursor back to normal
   6:          m_Splash.Cursor = Cursors.Default
   7:   
   8:          'hide, dispose, and kill the splash 
   9:          m_Splash.Hide()
  10:          m_Splash.Dispose()
  11:          m_Splash = Nothing
  12:   
  13:          'test to see if we have a workspace
  14:          MessageBox.Show("Workspace Initialized: " & Boolean.Parse(Me.RootWorkItem.Workspaces(Interfaces.Common.Constants.WorkSpaces.PrimaryWorkSpace) IsNot Nothing))
  15:      End Sub

When ran, the application will display "Workspace Initialized: True" in a message box just after the splash screen closes.

Now remove that line of code, as we don't want that message box to continue displaying.

As stated before, in the next article I will be covering the creation of a very simplistic module that will load into this newly created workspace.

Download the Solution

Posted by Dante | 0 Comments
Filed under:

CAB - Part 3 Splash Screen

In the previous articles for CAB we have gone over creating our base form, as well as implementing a new way to load our modules (once we create some), in this article we are going to go over another simple implementation (that is specifically not covered in the documentation) to make our application much more user friendly.

Splash Screens.  As you know, in Visual Studio 2005 and 2008 you have the ability to enable the Application Framework, and simply select a Splash Screen from the project properties, in a CAB shell, you must run through the Sub Main() instead of simply using a Form for startup.  We will add one real fast that displays while the CAB is loading its assemblies and modules.

First, add a new form to our project (under Forms->Dialogs in our directory structure), select the Splash Screen form template, and name it CabSplashScreen.  For now we aren't going to modify the Splash Screen, just use the default template.

Just close the Splash Screen, and forget about it for now.

In our Startup class, we need to create a new private field for the splash screen, add creation to it in the sub new (non-shared instance), override the AfterShellCreated, as well as create an event handler for ShellShown.

Below is the code to do just that:

   1:      Private m_Splash As CabSplashScreen = Nothing
   2:   
   3:      Public Sub New()
   4:          'create the splash
   5:          m_Splash = New CabSplashScreen
   6:   
   7:          'show and update the splash
   8:          m_Splash.Show()
   9:          m_Splash.Update()
  10:   
  11:          'set the cursor to the hourglass
  12:          m_Splash.Cursor = Cursors.WaitCursor
  13:   
  14:          'let splash screen events process
  15:          Application.DoEvents()
  16:      End Sub
  17:   
  18:      Protected Overrides Sub AfterShellCreated()
  19:          MyBase.AfterShellCreated()
  20:   
  21:          'add an event handler for the ShellForm.Show event (this is when we will kill our splash)
  22:          AddHandler Shell.Shown, AddressOf ShellShown
  23:   
  24:      End Sub
  25:   
  26:      Protected Sub ShellShown(ByVal sender As Object, ByVal e As EventArgs)
  27:          'remove the handler
  28:          RemoveHandler Shell.Shown, AddressOf ShellShown
  29:   
  30:          'set the cursor back to normal
  31:          m_Splash.Cursor = Cursors.Default
  32:   
  33:          'hide, dispose, and kill the splash 
  34:          m_Splash.Hide()
  35:          m_Splash.Dispose()
  36:          m_Splash = Nothing
  37:      End Sub

The code is pretty straight forward, and I will leave you to the Comments to figure out what it is doing (I am sure that if you are venturing into creating a CAB shell then this is nothing new to you)

In our next article, we will go over creating our first WorkSpace.

Download the Solution

Posted by Dante | 0 Comments
Filed under:

CAB - Part 2 Loading Modules from a directory

In the first part of the CAB articles, I explained a bit about getting the CAB working in a generic shell, and simply initiating the framework, in this article I will explain a little known ability with the CAB to dynamically load all modules in a directory, instead of the "ProductCatalog.xml" that is used in the CAB samples.  This is something that I do personally in all 3 of our CAB shells, and have found it to be much easier to deploy new modules to customers.

In our Startup class, we simply need to override one of the base WorkItem methods for adding services "AddServices", remove teh IModuleEnumerator that is currently loaded (that productcatalog.xml loader) and replace it with a new ReflectionModuleEnumerator instead.

   1:      Protected Overrides Sub AddServices()
   2:          'setup base services
   3:          MyBase.AddServices()
   4:   
   5:          'remove the FileModule for ProductCatalog.xml
   6:          MyBase.RootWorkItem.Services.Remove(Of IModuleEnumerator)()
   7:   
   8:          'create a new reflection enumerator
   9:          Dim reflectionEnumerator As New ReflectionModuleEnumerator()
  10:   
  11:          'make sure our path exists for modules
  12:          Directory.CreateDirectory(Path.GetDirectoryName(Application.ExecutablePath) & "\Modules\")
  13:   
  14:          'set the working path to ./Modules/
  15:          reflectionEnumerator.BasePath = Path.GetDirectoryName(Application.ExecutablePath) & "\Modules\"
  16:   
  17:          'add it back to the services
  18:          MyBase.RootWorkItem.Services.Add(GetType(IModuleEnumerator), reflectionEnumerator)
  19:   
  20:      End Sub

In the next article, I will go over how to add a Splash Screen to the main form, since we have not enabled the "Application Framework".

Download Solution

Posted by Dante | 0 Comments
Filed under:

Combo Box Enumerations with Titles

Have you ever just wanted to populate a combo box with values from an enumeration, but hated the fact that it dealt with the name of the item in the enumeration, instead of some snazzy string? Take the following Enum as an example:

   1:  Public Enum TitledValues
   2:      FirstName
   3:      LastName
   4:      Address1
   5:      Address2
   6:      City
   7:      State
   8:      ZipCode
   9:      PhoneNumber
  10:      EmailAddress
  11:  End Enum

For some of those properties displaying "City" would be fine, but who wants to show a user "FirstName" ?

I have figured out a little technique using a custom type converter and attributes to be able at code time set the titles from the Enum instead of having to do huge select cases when handling the drop down events.

The first thing to do is to create a new attribute called "EnumTitleAttribute", this attribute is pretty straight forward, it simply contains a Title property.

   1:  <AttributeUsage(AttributeTargets.Field)> _
   2:  Public Class EnumTitleAttribute
   3:      Inherits Attribute
   4:   
   5:      Private m_Title As String = String.Empty
   6:      Public Property Title() As String
   7:          Get
   8:              Return m_Title
   9:          End Get
  10:          Set(ByVal value As String)
  11:              m_Title = value
  12:          End Set
  13:      End Property
  14:   
  15:  End Class

The AttributeUsage attribute is restricting usage of this attribute to fields, which is what enum values are stored as in the object.

The next step was to create a type converter to work with these new custom attributes, since we want this to be a global type of enum, we are going to implement it as a generic.

   1:  Public Class EnumTitleTypeConverter(Of T)
   2:      Inherits TypeConverter
   3:   
   4:      Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
   5:          If value.GetType Is GetType(T) AndAlso destinationType Is GetType(String) Then
   6:              If value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True).Length > 0 Then
   7:                  Return DirectCast(value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True)(0), EnumTitleAttribute).Title
   8:              Else
   9:                  Return value.ToString
  10:              End If
  11:          Else
  12:              Return MyBase.ConvertTo(context, culture, value, destinationType)
  13:          End If
  14:      End Function
  15:  End Class

The only functionality we really care about in this class is the "ConvertTo" method.  We want to check to see if the value being converted is the type of our generic (of T) and that the destination type is string.

The basics of this method are check the input parameters for the proper conversion types, if they match, see if the field in the type has the EnumTitleAttribute assigned to it, and if so, we are going to get the first one only and return the Title property, otherwise, we are simply going to return the default ToString on the value object.  If none of these match, we will simply let the standard type converter try to deal with it, where it generally just returns the ToString of the object.

Now, to apply the title's to an enumeration, we simply add the attributes to the enum fields and set the TypeConverter.

   1:  <TypeConverter(GetType(EnumTitleTypeConverter(Of TitledValues)))> _
   2:  Public Enum TitledValues
   3:      <EnumTitle(Title:="First Name")> _
   4:      FirstName
   5:      <EnumTitle(Title:="Last Name")> _
   6:      LastName
   7:      <EnumTitle(Title:="Address 1")> _
   8:      Address1
   9:      <EnumTitle(Title:="Address 2")> _
  10:      Address2
  11:      City
  12:      State
  13:      <EnumTitle(Title:="Zip Code")> _
  14:      ZipCode
  15:      <EnumTitle(Title:="Phone Number")> _
  16:      PhoneNumber
  17:      <EnumTitle(Title:="Email Address")> _
  18:      EmailAddress
  19:  End Enum

So the first blaring question is, how do you use it?  Create a form (or use an existing one), add a combo box (for this example, name it uxFieldNames) and set the DropDownStyle to DropDownList, then in the form load, do the following code.

   1:  Me.uxFieldNames.DataSource = [Enum].GetValues(GetType(TitledValues))

And then in the SelectedIndexChanged event for the ComboBox, you can find out the selected item with the following code

   1:          If Me.uxFieldNames.SelectedIndex > -1 Then
   2:              Select Case DirectCast(uxFieldNames.SelectedItem, TitledValues)
   3:                  Case TitledValues.FirstName
   4:                      'do something with the first name
   5:                  Case TitledValues.LastName
   6:                      'do something with the last name
   7:              End Select
   8:          End If

Without hardly any additional effort, especially from your UI coding, you end up with this result:

image

instead of this:

image

Good luck, and happy coding!

 

Full Source Code:

   1:  Imports System.ComponentModel
   2:  Imports System.Globalization
   3:   
   4:  <TypeConverter(GetType(EnumTitleTypeConverter(Of TitledValues)))> _
   5:  Public Enum TitledValues
   6:      <EnumTitle(Title:="First Name")> _
   7:      FirstName
   8:      <EnumTitle(Title:="Last Name")> _
   9:      LastName
  10:      <EnumTitle(Title:="Address 1")> _
  11:      Address1
  12:      <EnumTitle(Title:="Address 2")> _
  13:      Address2
  14:      City
  15:      State
  16:      <EnumTitle(Title:="Zip Code")> _
  17:      ZipCode
  18:      <EnumTitle(Title:="Phone Number")> _
  19:      PhoneNumber
  20:      <EnumTitle(Title:="Email Address")> _
  21:      EmailAddress
  22:  End Enum
  23:   
  24:  <AttributeUsage(AttributeTargets.Field)> _
  25:  Public Class EnumTitleAttribute
  26:      Inherits Attribute
  27:   
  28:      Private m_Title As String = String.Empty
  29:      Public Property Title() As String
  30:          Get
  31:              Return m_Title
  32:          End Get
  33:          Set(ByVal value As String)
  34:              m_Title = value
  35:          End Set
  36:      End Property
  37:   
  38:  End Class
  39:   
  40:  Public Class EnumTitleTypeConverter(Of T)
  41:      Inherits TypeConverter
  42:   
  43:      Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, ByVal culture As CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
  44:          If value.GetType Is GetType(T) AndAlso destinationType Is GetType(String) Then
  45:              If value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True).Length > 0 Then
  46:                  Return DirectCast(value.GetType.GetField(value.ToString).GetCustomAttributes(GetType(EnumTitleAttribute), True)(0), EnumTitleAttribute).Title
  47:              Else
  48:                  Return value.ToString
  49:              End If
  50:          Else
  51:              Return MyBase.ConvertTo(context, culture, value, destinationType)
  52:          End If
  53:      End Function
  54:  End Class

 

 

*Edit: Added C# Code below

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using System.ComponentModel;
   5:  using System.Globalization;
   6:   
   7:  [TypeConverter(typeof(EnumTitleTypeConverter<TitledValues>))]
   8:  enum TitledValues
   9:  {
  10:      [EnumTitle(Title="First Name")]
  11:      FirstName,
  12:      [EnumTitle(Title = "Last Name")]
  13:      LastName,
  14:      [EnumTitle(Title = "Address 1")]
  15:      Address1,
  16:      [EnumTitle(Title = "Address 2")]
  17:      Address2,
  18:      City,
  19:      State,
  20:      [EnumTitle(Title = "Zip Code")]
  21:      ZipCode,
  22:      [EnumTitle(Title = "Phone Number")]
  23:      PhoneNumber,
  24:      [EnumTitle(Title = "Email Address")]
  25:      EmailAddress
  26:  }
  27:   
  28:  [AttributeUsage(AttributeTargets.Field)]
  29:  public class EnumTitleAttribute : Attribute
  30:  {
  31:      private string m_Title = string.Empty;
  32:      public string Title
  33:      {
  34:          get { return m_Title; }
  35:          set { m_Title = value; }
  36:      }
  37:  }
  38:   
  39:  public class EnumTitleTypeConverter<T> : TypeConverter
  40:  {
  41:      public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
  42:      {
  43:          if ((value.GetType() == typeof(T)) && (destinationType == typeof(String)))
  44:          {
  45:              if (value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(EnumTitleAttribute), true).Length > 0)
  46:              {
  47:                  return (value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(EnumTitleAttribute),true)[0] as EnumTitleAttribute).Title;
  48:              }
  49:              else
  50:              {
  51:                  return value.ToString();
  52:              }
  53:          } 
  54:          else 
  55:          {
  56:              return base.ConvertTo(context, culture, value, destinationType);
  57:          }
  58:      }
  59:  }
Posted by Dante | 0 Comments

Correction for Vertigo Software post about debugging

Recently I read the article by Chris Idzerda over at Vertigo software that described how to Detect Visual Studio Debugging.  While the method he used was a bit of a "hacky workaround", below is a more sound way to detect it using built in .Net libraries, rather than a string lookup on the running executable.

VB.Net Version

   1:          If System.Diagnostics.Debugger.IsAttached Then
   2:              ' do code here for debugging with visual studio
   3:   
   4:          End If
 

C# Version

   1:              if (System.Diagnostics.Debugger.IsAttached)
   2:              {
   3:                  // do code here for debugging with visual studio
   4:              }

I hope this helps clear up any confusion.

Posted by Dante | 0 Comments

SWGEmu - .Net Stuffz

So a while back some of you may know that I was writing a pure .Net emulator.  That went "ok" and I had made zone in both pre-cu and nge based clients.  But after a while, the time constraints just couldn't be dealt with, and the project was, how do I say, huge?

One of the big issues that I had when writing the emulator in .Net was the encryption, decryption, and the crc calculations.  Originally I had just used a c++ api to pull it off.  This bothered me literally for some time as I just wanted a pure .Net solution.

So today, I spent a bit of time looking at the great documentation on the encryption/decryption algorigthm that the SWGEmu team has put up (if you recall, I used to be a part of that team for a very short lived time).

Below is the test app that I created and played around with to get these things working in pure (you got it) VB.Net.  I know the C# port would have been WAY easier using unsafe or even almost just duplicating the code, but I wanted a direct port that was spot on.

 

   1:  Public Class Program
   2:   
   3:      Public Shared Sub Main(ByVal args() As String)
   4:          Dim rawArray() As Byte = New Byte() {0, 9, 0, 1, 0, 2, &HAB, &H43, &HE3, &HD5, 0, &HFF, 0, &H11, &H45, &H32, &H76, &H43, &HD4, &HF1, 0, &HAB, &HCD}
   5:          Dim encryptedArray() As Byte
   6:          Dim decryptedArray() As Byte
   7:          Dim crcSeed As Integer = &H1D4E3287
   8:   
   9:          Console.WriteLine("Raw Bytes:")
  10:          For Each bt As Byte In rawArray
  11:              Console.Write("{0:X2} ", bt)
  12:          Next
  13:          Console.WriteLine()
  14:          Console.WriteLine()
  15:   
  16:   
  17:          encryptedArray = Encrypt(Strip(rawArray), crcSeed)
  18:          Console.WriteLine("Encrypted Bytes:")
  19:          For Each bt As Byte In encryptedArray
  20:              Console.Write("{0:X2} ", bt)
  21:          Next
  22:          Console.WriteLine()
  23:          Console.WriteLine()
  24:   
  25:          decryptedArray = Decrypt(encryptedArray, crcSeed)
  26:          Console.WriteLine("Decrypted Bytes:")
  27:          For Each bt As Byte In decryptedArray
  28:              Console.Write("{0:X2} ", bt)
  29:          Next
  30:          Console.WriteLine()
  31:          Console.WriteLine()
  32:   
  33:          Console.WriteLine("CRC Test")
  34:          Dim crc As Integer = CRC32(New Byte() {&H0, &H5, &HAA, &HBB, &HCC, &HDD, &H0, &H6, &H0}, 9, crcSeed)
  35:          Console.WriteLine("My CRC32:  {0:X4}", crc)
  36:          crc = API.GenerateCrc(New Byte() {&H0, &H5, &HAA, &HBB, &HCC, &HDD, &H0, &H6, &H0}, crcSeed)
  37:          Console.WriteLine("C++ CRC32: {0:X4}", crc)
  38:   
  39:          Console.ReadLine()
  40:      End Sub
  41:   
  42:      Private Shared Function Strip(ByVal inData() As Byte) As Byte()
  43:          Dim retVal As New List(Of Byte)
  44:          retVal.AddRange(inData)
  45:   
  46:          'strip channel and op code
  47:          If retVal(0) = 0 Then
  48:              retVal.RemoveAt(0)
  49:              retVal.RemoveAt(0)
  50:          End If
  51:   
  52:          'strip crc
  53:          retVal.RemoveAt(retVal.Count - 1)
  54:          retVal.RemoveAt(retVal.Count - 1)
  55:   
  56:          Return retVal.ToArray
  57:      End Function
  58:   
  59:      Public Shared Function Encrypt(ByVal inData() As Byte, ByVal seed As Integer) As Byte()
  60:          Dim count As Integer = Math.Floor(Decimal.Parse(inData.Length) / 4D)
  61:          Dim remainder As Integer = inData.Length - (count * 4)
  62:          Dim seedBytes() As Byte = BitConverter.GetBytes(seed)
  63:   
  64:          Array.Reverse(seedBytes)
  65:   
  66:          For i As Integer = 0 To count - 1
  67:              For b As Integer = 0 To 3
  68:                  inData((i * 4) + b) = inData((i * 4) + b) Xor seedBytes(b)
  69:              Next
  70:              For b As Integer = 0 To 3
  71:                  seedBytes(b) = inData((i * 4) + b)
  72:              Next
  73:          Next
  74:   
  75:          For i As Integer = 0 To remainder - 1
  76:              inData((count * 4) + i) = inData((count * 4) + i) Xor seedBytes(0)
  77:          Next
  78:   
  79:          Return inData
  80:      End Function
  81:   
  82:      Public Shared Function Decrypt(ByVal inData() As Byte, ByVal seed As Integer) As Byte()
  83:          Dim count As Integer = Math.Floor(Decimal.Parse(inData.Length) / 4D)
  84:          Dim remainder As Integer = inData.Length - (count * 4)
  85:          Dim seedBytes() As Byte = BitConverter.GetBytes(seed)
  86:   
  87:          Array.Reverse(seedBytes)
  88:   
  89:          For i As Integer = 0 To count - 1
  90:              Dim newSeed(3) As Byte
  91:   
  92:              For b As Integer = 0 To 3
  93:                  newSeed(b) = inData((i * 4) + b)
  94:              Next
  95:              For b As Integer = 0 To 3
  96:                  inData((i * 4) + b) = inData((i * 4) + b) Xor seedBytes(b)
  97:              Next
  98:              For b As Integer = 0 To 3
  99:                  seedBytes(b) = newSeed(b)
 100:              Next
 101:          Next
 102:   
 103:          For i As Integer = 0 To remainder - 1
 104:              inData((count * 4) + i) = inData((count * 4) + i) Xor seedBytes(0)
 105:          Next
 106:   
 107:          Return inData
 108:      End Function
 109:   
 110:      Public Shared Function CRC32(ByVal inData() As Byte, ByVal length As Integer, ByVal nCrcSeed As Integer) As Integer
 111:          'unsigned int nCrc = g_nCrcTable[(~nCrcSeed) & 0xFF];
 112:          Dim nCrc As UInt32 = m_CRCTable(Not nCrcSeed And &HFF)
 113:   
 114:          'nCrc ^= 0x00FFFFFF;
 115:          nCrc = nCrc Xor &HFFFFFF
 116:   
 117:          'unsigned int nIndex = (nCrcSeed >> 8) ^ nCrc;
 118:          Dim nIndex As UInt32 = (nCrcSeed >> 8) Xor nCrc
 119:   
 120:          'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 121:          nCrc = (nCrc >> 8) And &HFFFFFF
 122:   
 123:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 124:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 125:   
 126:          'nIndex = (nCrcSeed >> 16) ^ nCrc;
 127:          nIndex = (nCrcSeed >> 16) Xor nCrc
 128:   
 129:          'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 130:          nCrc = (nCrc >> 8) And &HFFFFFF
 131:   
 132:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 133:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 134:   
 135:          'nIndex = (nCrcSeed >> 24) ^ nCrc;
 136:          nIndex = (nCrcSeed >> 24) Xor nCrc
 137:   
 138:          'nCrc = (nCrc >> 8) &0x00FFFFFF;
 139:          nCrc = (nCrc >> 8) And &HFFFFFF
 140:   
 141:          'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 142:          nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 143:   
 144:          'for( short i = 0; i < nLength; i++ ) {
 145:          For i As Integer = 0 To length - 1
 146:              'nIndex = (pData[ i]) ^ nCrc;
 147:              nIndex = inData(i) Xor nCrc
 148:              'nCrc = (nCrc >> 8) & 0x00FFFFFF;
 149:              nCrc = (nCrc >> 8) And &HFFFFFF
 150:              'nCrc ^= g_nCrcTable[nIndex & 0xFF];
 151:              nCrc = nCrc Xor m_CRCTable(nIndex And &HFF)
 152:   
 153:          Next
 154:          '}
 155:   
 156:          'return ~nCrc;
 157:          Return Not nCrc
 158:      End Function
 159:   
 160:      '   void CRC_(unsigned char *data, int size, unsigned long &crc){
 161:      '        if (!size) return;
 162:      '        for (int i = 0; i < size; i++)
 163:      '            crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ data[ i]];
 164:      '   }
 165:   
 166:      Private Shared m_CRCTable() As UInt32 = New UInt32() { _
 167:                                                      &H0UL, &H77073096UL, &HEE0E612CUL, &H990951BAUL, &H76DC419UL, &H706AF48FUL, _
 168:                                                      &HE963A535UL, &H9E6495A3UL, &HEDB8832UL, &H79DCB8A4UL, &HE0D5E91EUL, &H97D2D988UL, _
 169:                                                      &H9B64C2BUL, &H7EB17CBDUL, &HE7B82D07UL, &H90BF1D91UL, &H1DB71064UL, &H6AB020F2UL, _
 170:                                                      &HF3B97148UL, &H84BE41DEUL, &H1ADAD47DUL, &H6DDDE4EBUL, &HF4D4B551UL, &H83D385C7UL, _
 171:                                                      &H136C9856UL, &H646BA8C0UL, &HFD62F97AUL, &H8A65C9ECUL, &H14015C4FUL, &H63066CD9UL, _
 172:                                                      &HFA0F3D63UL, &H8D080DF5UL, &H3B6E20C8UL, &H4C69105EUL, &HD56041E4UL, &HA2677172UL, _
 173:                                                      &H3C03E4D1UL, &H4B04D447UL, &HD20D85FDUL, &HA50AB56BUL, &H35B5A8FAUL, &H42B2986CUL, _
 174:                                                      &HDBBBC9D6UL, &HACBCF940UL, &H32D86CE3UL, &H45DF5C75UL, &HDCD60DCFUL, &HABD13D59UL, _
 175:                                                      &H26D930ACUL, &H51DE003AUL, &HC8D75180UL, &HBFD06116UL, &H21B4F4B5UL, &H56B3C423UL, _
 176:                                                      &HCFBA9599UL, &HB8BDA50FUL, &H2802B89EUL, &H5F058808UL, &HC60CD9B2UL, &HB10BE924UL, _
 177:                                                      &H2F6F7C87UL, &H58684C11UL, &HC1611DABUL, &HB6662D3DUL, &H76DC4190UL, &H1DB7106UL, _
 178:                                                      &H98D220BCUL, &HEFD5102AUL, &H71B18589UL, &H6B6B51FUL, &H9FBFE4A5UL, &HE8B8D433UL, _
 179:                                                      &H7807C9A2UL, &HF00F934UL, &H9609A88EUL, &HE10E9818UL, &H7F6A0DBBUL, &H86D3D2DUL, _
 180:                                                      &H91646C97UL, &HE6635C01UL, &H6B6B51F4UL, &H1C6C6162UL, &H856530D8UL, &HF262004EUL, _
 181:                                                      &H6C0695EDUL, &H1B01A57BUL, &H8208F4C1UL, &HF50FC457UL, &H65B0D9C6UL, &H12B7E950UL, _
 182:                                                      &H8BBEB8EAUL, &HFCB9887CUL, &H62DD1DDFUL, &H15DA2D49UL, &H8CD37CF3UL, &HFBD44C65UL, _
 183:                                                      &H4DB26158UL, &H3AB551CEUL, &HA3BC0074UL, &HD4BB30E2UL, &H4ADFA541UL, &H3DD895D7UL, _
 184:                                                      &HA4D1C46DUL, &HD3D6F4FBUL, &H4369E96AUL, &H346ED9FCUL, &HAD678846UL, &HDA60B8D0UL, _
 185:                                                      &H44042D73UL, &H33031DE5UL, &HAA0A4C5FUL, &HDD0D7CC9UL, &H5005713CUL, &H270241AAUL, _
 186:                                                      &HBE0B1010UL, &HC90C2086UL, &H5768B525UL, &H206F85B3UL, &HB966D409UL, &HCE61E49FUL, _
 187:                                                      &H5EDEF90EUL, &H29D9C998UL, &HB0D09822UL, &HC7D7A8B4UL, &H59B33D17UL, &H2EB40D81UL, _
 188:                                                      &HB7BD5C3BUL, &HC0BA6CADUL, &HEDB88320UL, &H9ABFB3B6UL, &H3B6E20CUL, &H74B1D29AUL, _
 189:                                                      &HEAD54739UL, &H9DD277AFUL, &H4DB2615UL, &H73DC1683UL, &HE3630B12UL, &H94643B84UL, _
 190:                                                      &HD6D6A3EUL, &H7A6A5AA8UL, &HE40ECF0BUL, &H9309FF9DUL, &HA00AE27UL, &H7D079EB1UL, _
 191:                                                      &HF00F9344UL, &H8708A3D2UL, &H1E01F268UL, &H6906C2FEUL, &HF762575DUL, &H806567CBUL, _
 192:                                                      &H196C3671UL, &H6E6B06E7UL, &HFED41B76UL, &H89D32BE0UL, &H10DA7A5AUL, &H67DD4ACCUL, _
 193:                                                      &HF9B9DF6FUL, &H8EBEEFF9UL, &H17B7BE43UL, &H60B08ED5UL, &HD6D6A3E8UL, &HA1D1937EUL, _
 194:                                                      &H38D8C2C4UL, &H4FDFF252UL, &HD1BB67F1UL, &HA6BC5767UL, &H3FB506DDUL, &H48B2364BUL, _
 195:                                                      &HD80D2BDAUL, &HAF0A1B4CUL, &H36034AF6UL, &H41047A60UL, &HDF60EFC3UL, &HA867DF55UL, _
 196:                                                      &H316E8EEFUL, &H4669BE79UL, &HCB61B38CUL, &HBC66831AUL, &H256FD2A0UL, &H5268E236UL, _
 197:                                                      &HCC0C7795UL, &HBB0B4703UL, &H220216B9UL, &H5505262FUL, &HC5BA3BBEUL, &HB2BD0B28UL, _
 198:                                                      &H2BB45A92UL, &H5CB36A04UL, &HC2D7FFA7UL, &HB5D0CF31UL, &H2CD99E8BUL, &H5BDEAE1DUL, _
 199:                                                      &H9B64C2B0UL, &HEC63F226UL, &H756AA39CUL, &H26D930AUL, &H9C0906A9UL, &HEB0E363FUL, _
 200:                                                      &H72076785UL, &H5005713UL, &H95BF4A82UL, &HE2B87A14UL, &H7BB12BAEUL, &HCB61B38UL, _
 201:                                                      &H92D28E9BUL, &HE5D5BE0DUL, &H7CDCEFB7UL, &HBDBDF21UL, &H86D3D2D4UL, &HF1D4E242UL, _
 202:                                                      &H68DDB3F8UL, &H1FDA836EUL, &H81BE16CDUL, &HF6B9265BUL, &H6FB077E1UL, &H18B74777UL, _
 203:                                                      &H88085AE6UL, &HFF0F6A70UL, &H66063BCAUL, &H11010B5CUL, &H8F659EFFUL, &HF862AE69UL, _
 204:                                                      &H616BFFD3UL, &H166CCF45UL, &HA00AE278UL, &HD70DD2EEUL, &H4E048354UL, &H3903B3C2UL, _
 205:                                                      &HA7672661UL, &HD06016F7UL, &H4969474DUL, &H3E6E77DBUL, &HAED16A4AUL, &HD9D65ADCUL, _
 206:                                                      &H40DF0B66UL, &H37D83BF0UL, &HA9BCAE53UL, &HDEBB9EC5UL, &H47B2CF7FUL, &H30B5FFE9UL, _
 207:                                                      &HBDBDF21CUL, &HCABAC28AUL, &H53B39330UL, &H24B4A3A6UL, &HBAD03605UL, &HCDD70693UL, _
 208:                                                      &H54DE5729UL, &H23D967BFUL, &HB3667A2EUL, &HC4614AB8UL, &H5D681B02UL, &H2A6F2B94UL, _
 209:                                                      &HB40BBE37UL, &HC30C8EA1UL, &H5A05DF1BUL, &H2D02EF8DUL _
 210:                                                  }
 211:   
 212:  End Class

 

There it is, in all the unfriendly nastyness.  The API class I am using is calling the previous c++ implementation that I had before, just to verify that they match.

Just thought I would gloat a bit, it was a tedious process, and a lot of learning about .Net binary actions that I wasn't aware of, sometimes you just look at the code, but don't really understand it, I finally have a really good understanding of how this all works.

*EDIT For Credit
SWGEmu.com
CRC Explained
Encryption Explained

Posted by Dante | 1 Comments

Renamed Web Blog - .Net Articles

I just renamed this web blog from vb.net articles to .net articles.  I have decided to start blogging all .net now, instead of just vb.net.

More to come later!

 

*Edit: I have just realised that in the RSS the images are borked, this will be fixed in the next aggregate, which should occur on next startup, if not, I may have to force a re-aggregation.

Posted by Dante | 0 Comments

Loading Config Files from non-Default Locations

I have spent a good deal of my time trying to figure out how to load a *.config file that is compatable with the System.Configuration.Configuration object. Over the years I have done several "fake" .config files, which where nothing more then glorified structured xml files that I manually load.  I have also even created fake 0 byte files so I could load the .config using the OpenExeConfiguration.

Until recently, this has been a viable solution, and since these files where mostly hidden by ClickOnce installations and storing them in the Application Data file structure. Now though, I got a hair up my butt to do it correctly.

Enter the System.Configuration.ExeConfigurationFileMap class.  This lets you specify an exe config file name, and allows you to load it up at runtime without having a valid executable to load off of.  So now, I can save logged in specific users along with windows users configuration files. 

For example, my goal was to do the following:
Allow the user to setup connections and save them to the Application Data\Product\ folder under their user account.
After authenticating with the server, I want to store per-logged in user settings.

Just short of using datasets and/or serialized objects, I was using folders to seperate between the different logged in users.

Now, to the solution:

   1:  Imports System.Configuration
   2:   
   3:  Public Class LocalConfiguration
   4:   
   5:      Public Shared Function GetConfig(ByVal Path As String) As Configuration
   6:          Dim retVal As Configuration = Nothing
   7:   
   8:          Dim configFileMap As New ExeConfigurationFileMap()
   9:          configFileMap.ExeConfigFilename = Path
  10:   
  11:          retVal = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None)
  12:   
  13:          Return retVal
  14:      End Function
  15:   
  16:  End Class

That is it, now you can simply load a valid application config file and get a System.Configuration.Configuration object back.  All it takes is the following line of code:

   1:          Dim config As Configuration = LocalConfiguration.GetConfig("./data/user.config")

The beuty is, the file doesn't even have to exist. When you save the config, it will automatically create it if it doesn't exist.

The next step, which turned out to be a bit tricky, was actually saving stuff, like appSettings, to the file, specifically if they didn't exist to begin with.

   1:      Private Sub LoadSettings()
   2:          Dim config As Configuration = LocalConfiguration.GetConfig("./data/user.config")
   3:          Dim newSection As AppSettingsSection = Nothing
   4:   
   5:          If config.Sections("MySettings") Is Nothing Then
   6:              newSection = New AppSettingsSection()
   7:              newSection.SectionInformation.AllowExeDefinition = ConfigurationAllowExeDefinition.MachineToLocalUser
   8:              config.Sections.Add("MySettings", newSection)
   9:          Else
  10:              newSection = config.Sections("MySettings")
  11:          End If
  12:   
  13:          If newSection.Settings("Text") Is Nothing Then
  14:              newSection.Settings.Add("Text", Me.Text)
  15:          End If
  16:   
  17:          Me.Text = newSection.Settings("Text").Value
  18:   
  19:          config.Save(ConfigurationSaveMode.Minimal)
  20:   
  21:      End Sub

On line 2 I call the method from the first example to get the configuration object, next I define an AppSettingsSection which is a key/value pair collection that is used in the <appSettings> in normal .config files.  Depeding on whether the section exists or not in the current file I either set the newSection to the existing reference, or create a new one, set the SectionInformation to Allow Machine to Local User, then work with the keys as normal.

Finally, I save the config file, just incase it didn't exist before I read it into the application.

Once saved, the user.config looks like this:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <configuration>
   3:      <configSections>
   4:          <section name="MySettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" allowExeDefinition="MachineToLocalUser" />
   5:      </configSections>
   6:      <MySettings>
   7:          <add key="Text" value="My Application" />
   8:      </MySettings>
   9:  </configuration>

As you can see, it created a new config section named "MySettings" using the System.Configuration.AppSettingsSection.

Pretty simple once you get the hang of it, and I could very easily see a CAB based, or plugin based application really taking advantage of loading different config sections per module/plugin, and reading other settings from other modules/plugins.

In the next discovery, I will be trying to figure out how to merge multiple configurations into a single config file to ease the reading of multi-configs.

Posted by Dante | 0 Comments

Update to the Build Automation

Well, after a few months working on this off and on, I thought I would share the current status of our operations.

First and foremost, we got Cruise Control up and running, this made a huge step forward with the process, and it is open source, so that allowed me to create a custom UI for it to run.  This resulted in us having a custom application that runs very similarly to the Cruise Control Tray Application (CCTray), except I implemented grouping by categories, displaying the categories, as well as auto-discovery of projects, rather then having to set them up.

Additionally, because we don't need everyone in our department building every piece of software all the time, I created a pseudo permission system that is bound into an xml file, like the example below.

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<security>
    <add user="tanderson" viewConfig="true" projects="*" />
    <add user="jsmith" viewConfig="false" projects="RecipeEditor_VB6;Rocket;QReports;" />
</security>

As you can see, it is a very simplistic permissions system, the user is the currently logged in computer user, viewConfig is a small permission to see if they can view the servers ccnet.config, and the projects are a semi-colon separated list of the names of the projects they can see status of, or for ease of people like me, an asterisk for all projects.

Below is a screen shot of the current version of the software.

image

I have made quite a bit of headway with the system in getting more and more projects completed and into the daily build.  The "DailyBuild" is the only project that is built nightly, and it builds everyone of the other projects.

The actual process of creating all of this mayhem was a few steps.

Creating an Environment

The first thing I had to do, was get an environment going that was as clean as possible to do the builds, this is a remote connection only off the domain standalone box that only myself and the VP of software development have access to.  The computer is loaded with Server 2003 Standard Edition, SQL 2000, SQL 2005, Visual Basic 6, VS 2003, VS 2005, and RedGate toolkit.

Starting the batches

When someone told me I would do a crap load of batch files as a quick solution to get a process done, I would have laughed at them, but in this case, it seemed to be the quickest route to the finish line. In total for what you see above, I have roughly 38 batch files.

The first ones that I built, where simplistic, setup the compiling environments, and then get the latest code from source control. I created two separate batch files for this to ease the calling of them from other batch files.

SetupEnvironment.cmd

@rem Setup all variables here
@SET SSCMLOGIN=user:password
@SET SSCMSERVER=server:4900
@SET CBSSoftware="C:\Build\CBS Software"
@SET CBSLegacy="C:\Build\CBS Legacy"
@SET CBSProto="C:\Build\CBS Prototype"

@SET VB6EXE="C:\Program Files\Microsoft Visual Studio\VB98\vb6.exe"
@SET VBCEXE="C:\windows\microsoft.net\framework\v2.0.50727\vbc.exe"
@SET VS2003="c:\program files\Microsoft Visual Studio .NET 2003\common7\ide\devenv.exe"
@SET VS2005="C:\Program files\microsoft visual studio 8\common7\ide\devenv.exe"
@SET VS2008="C:\Program Files\microsoft visual studio 9.0\common7\ide\devenv.exe"

@rem Build utilities here
@call Utilities/BuildUtilities

@EXIT /B %ERRORLEVEL%

I added a utilities folder to store a lot of extra files that I didn't want in the main batch folder, such as:

  • Icons
  • Sign Keys
  • Winrar.exe
  • XCopy exclusion lists for different types of software
  • Cruise Control Log Files

I also added a BuildUtilities batch file that allows me to compile and use stand alone class files in .Net, such as one that I use very frequently, the List.exe.

Utilities/BuildUtilities.cmd

@rem add any utility compiles here
@echo Building Utilities

@rem Building List
@"%VBCEXE%" /target:exe /nologo "C:\Build\Utilities\list.vb"

@EXIT /B %ERRORLEVEL%

List.exe is a very simple executable that basically outputs a file from disc to the console.

The next batch file that I built was to pull all of the code for our products from source control.

GetSource.cmd

@rem Retrieve all source
@echo Retrieving latest source code
@mkdir %CBSSoftware%
@mkdir %CBSLegacy%
@mkdir %CBSProto%

@sscm workdir ""%CBSSoftware%"" "CBS Software" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSSoftware%"" -p"CBS Software" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@sscm workdir ""%CBSLegacy%"" "CBS Legacy" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSLegacy%"" -p"CBS Legacy" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@sscm workdir ""%CBSProto%"" "CBS Prototype" -y"%SSCMLOGIN%" -z"%SSCMSERVER%"
@sscm get *.* -d""%CBSProto%"" -p"CBS Prototype" -r -e -q -wreplace -y"%SSCMLOGIN%" -z"%SSCMSERVER%"

@EXIT /B %ERRORLEVEL%

We use Surround SCM from Seapine software, so your command lines may differ quite a bit.

Creating a compile project

To do anything, I needed to be able to compile the projects, this was done mostly via command line to the proper visual studio, I wanted to do the direct calls to the compilers, but there was entirely way too much legwork t