Thursday, December 13, 2007

HDD and Thumb Drive Performance Comparison

So, since I tend to be a speed freak of sorts, I wanted to see just how well, or not, my storage devices perform. In my Dell Precision M90 laptop, I've got a Seagate - Momentus 100GB drive, then a Western Digital Passport 250GB USB external drive, and finally a PNY Attaché 16GB USB thumb drive.

The following are the screen shots from HD Tune that indicate how each performed:









All in all, I'm fairly pleased. My primary desire is to simply have capacity. But when you start storing GB sized database files and virtual machines, better performance can mean a lot!

Tuesday, October 23, 2007

Automatic Updating of AssemblyFileVersion in C# Projects

So my good buddy and co-worker Stacy Draper and I put together a solution today to handle versioning of .NET assemblies used for SharePoint web part development. Maybe this has been done before, maybe even better than this, but a quick Google search didn't turn up squat for us, or at least anything we liked, so we created our own solution.

The general problem revolves around the fact that the AssemblyVersion attribute in .NET assemblies is used by other assemblies when referencing strong named/signed assemblies stored in the GAC. Now you don't have to put your SharePoint assemblies in the GAC, but in most all cases, it simply makes the most sense. If you change the AssemblyVersion of an already installed and used over and over and over again web part, you're in a world of hurt if you arbitrarily change the AssemblyVersion to something new. At that point, you're faced with having to update 10's to 100's to 1000's of web part pages that reference the original web part. That's not to say there aren't times when it makes sense to do so, but for day to day development and deployment scenarios where it really isn't a new version of the web part, but itis an updated version of the code, you still want to be able to have a very noticeable way to see what version is currently installed on your portal.

So that takes us to what Stacy and I whipped up today. I created a script that updates your AssemblyInfo.cs file, setting the AssemblyFileVersion attribute to a time-stamped version number, based off of the AssemblyVersion attribute's value, of when the assembly was built. Stacy created a script that updates the feature.xml file that is part of the SharePoint feature with the new version number of the AssemblyFileVersion to be displayed within the description of the feature. This is so you can also see that version info from within SharPoint, in addition to viewing the file properties. Both of these scripts are then configured to run in your pre-build events for your C# project. What follows is my pre-build commands and the content of my VBS script. I place my VBS file in a folder named UTIL inside my C# project. See Stacy's blog for his stuff.


CD "$(ProjectDir)UTIL"
cscript /nologo UpdateAssemblyFileVersion.vbs "$(ProjectDir)Properties\AssemblyInfo.cs"

'------------------------------------------------------------------------------
'--- UpdateAssemblyFileVersion.vbs
'------------------------------------------------------------------------------
'--- Author - Jim Sally
'--- Date - 2007.10.23
'------------------------------------------------------------------------------
'--- This updates the AssemblyFileVersion attribute in the AssemblyInfo.cs file
'--- of a given C# project. The AssemblyFileVersion is updated to contain the
'--- first two digits of the AssemblyVersion, with YMMDD.HHmm as the last two
'--- digits.
'------------------------------------------------------------------------------
Option Explicit

Dim fileHeader
fileHeader = "UpdateAssemblyFileVersion.vbs :: "

EnsureCommandLineArgumentExists
EnsureAssemblyInfoFileExists WScript.Arguments(0)
UpdateAssemblyFileVersion WScript.Arguments(0)

Private Sub EnsureCommandLineArgumentExists()
If WScript.Arguments.Count <> 1 Then
WScript.Echo fileHeader & "You must supply the path to the AssemblyInfo.cs file as the only argument on the command line"
WScript.Quit 1
End If
End Sub

Private Sub EnsureAssemblyInfoFileExists(AssemblyInfoFileName)
Dim fso

Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(AssemblyInfoFileName) Then
WScript.Echo fileHeader & "AssemblyInfo.cs does not exist at the path provided -> " & AssemblyInfoFileName
WScript.Quit 1
End If

Set fso = Nothing
End Sub

Private Sub UpdateAssemblyFileVersion(AssemblyInfoFileName)
Dim assemblyInfo, newAssemblyInfo

assemblyInfo = GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
newAssemblyInfo = UpdateAssemblyInfoText(assemblyInfo)

If newAssemblyInfo <> assemblyInfo Then
SaveContentsOfAssemblyInfoFile AssemblyInfoFileName, newAssemblyInfo
End If
End Sub

Private Function GetContentsOfAssemblyInfoFile(AssemblyInfoFileName)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.OpenTextFile(AssemblyInfoFileName)
GetContentsOfAssemblyInfoFile = file.ReadAll()
file.Close

Set file = Nothing
Set fso = Nothing
End Function

Private Sub SaveContentsOfAssemblyInfoFile(AssemblyInfoFileName, infoText)
Dim fso, file

Set fso = CreateObject("Scripting.FileSystemObject")
Set file = fso.CreateTextFile(AssemblyInfoFileName, true)
file.Write(infoText)
file.Close

Set file = Nothing
Set fso = Nothing
End Sub

Private Function UpdateAssemblyInfoText(AssemblyInfoText)
Dim posAV, posAFV, posEolAV, posEolAFV, aNAV
Dim originalAssemblyVersionText, originalAssemblyFileVersionText, newAssemblyFileVersionText

posAV = InStr(AssemblyInfoText, "AssemblyVersion")
posAFV = InStr(AssemblyInfoText, "AssemblyFileVersion")

If Not IsNull(posAV) And Not IsNull(posAFV) And posAV > 0 And posAFV > 0 Then
posEolAV = InStr(posAV, AssemblyInfoText, "]")
posEolAFV = InStr(posAFV, AssemblyInfoText, "]")

originalAssemblyVersionText = Mid(AssemblyInfoText, posAV, posEolAV - posAV)
originalAssemblyFileVersionText = Mid(AssemblyInfoText, posAFV, posEolAFV - posAFV)

aNAV = Split(Split(originalAssemblyVersionText, """")(1), ".")
newAssemblyFileVersionText = "AssemblyFileVersion(""" & GetNewFileVersionNumber(aNAV) & """)"

If originalAssemblyFileVersionText <> newAssemblyFileVersionText Then
UpdateAssemblyInfoText = Replace(AssemblyInfoText, originalAssemblyFileVersionText, newAssemblyFileVersionText)

WScript.Echo fileHeader & "Updated attribute from '" & originalAssemblyFileVersionText & "' to '" & newAssemblyFileVersionText & "'"
WScript.Echo
Else
WScript.Echo fileHeader & "AssemblyFileVersion attribute already '" & newAssemblyFileVersionText & "'"
WScript.Echo
WScript.Quit
End If
Else
WScript.Echo fileHeader & "AssemblyVersion or AssemblyFileVersion attribute not found"
WScript.Echo
WScript.Quit 1
End If
End Function

Private Function GetNewFileVersionNumber(versionArray)
GetNewFileVersionNumber = versionArray(0) & "." & versionArray(1) & "." & GetVersionYMMDD() & "." & GetVersionHHmm()
End Function

Private Function GetVersionYMMDD()
Dim y, m, d

y = right(year(now()),1)
If y = 0 Then
y = 10
ElseIf y > 6 Then
m = (y - 6) * 12
y = 6
End If

m = Right("00" & (m + Month(now)), 2)
d = Right("00" & Day(now), 2)

GetVersionYMMDD = y & m & d
End Function

Private Function GetVersionHHmm()
GetVersionHHmm = Right("00" & Hour(Now()), 2) & Right("00" & Minute(Now()), 2)
End Function

Friday, October 5, 2007

Missing MOSS 2007 Functionality

So having been using MOSS 2007 for the past 3 months now, I figured I'd put my own stake in the ground as to some things that would be quite handy to be built into the product. Yes, there are probably ways to do some custom webparts to handle some of these, but IMHO, these are pieces of functionality that should be part of the base product. Hopefully they make it into the next version...
  1. List View Security
    • This follows some of the same logic behind the use of a database VIEW. An administrator should be able to create a SharePoint list view with a row-level filter and then secure the list of views to the appropriate folks (to the point of hiding views a user doesn't have access to use).
    • This would allow various business scenarios to be simply handled via view security. For instance
      • hiding items based on the state/status code of an item to augment workflows
      • storing heterogeneous content types, but securing which are visible to whom
  2. List Item Column Security
    • This is very similar to the view security above, but at a column-level, rather than row-level (vertical security versus horizontal security). In fact, this could work very similarly to how views currently work, but could be called item views, instead of list views.
    • Yes, you could write custom ASPX pages to handle this, but the point of this functionality is to give a non-developer the ability to create this kind of functionality.
  3. Custom Attributes On Lists
    • This is more or less a property bag (key/value pair bucket) that can be populated via the GUI by an end user, or via the API.
    • This would be primarily useful to a developer who wants to track/manipulate additional details about a list, but enable end-users to make changes to those values when necessary.

Thursday, August 30, 2007

Programmatically Accessing Excel Data via .NET on 64-bit servers

So it turns out that using ADO.NET to access MS Excel worksheet data in an ASP.NET application running on the 64-bit version of Windows Server 2003 does not quite work as expected. This also goes for any 64-bit .NET code. In short, the Microsoft.Jet.OLEDB.4.0 provider is a 32-bit driver that does not work when directly used by 64-bit code. The following post from a Microsoft employee describes the issue:

Connect to Excel. Using x64 (64-bit) platform. Compiled as x64

"Since there are no 64-bit OleDb drivers for anything other than SQL Server, therefore, we cannot write 64-bit apps which interface with databases directly.

What you need to do is split your application into a 32 bit part and a 64 bit part, use COM interop to cross the 64/32 bit boundary. For instance, drop the code (just a simple class library compiled as 32 bit) that retrieves the Excel data into a COM+ (System.EnterpriseServices) as a "server type" application, and call those server methods from your 64 bit Windows service. This is exactly why System.EnterpriseServices are made for."

To restate the above in a list of to do items:
  • Refactor the Excel access code into a class by itself in a class library by itself
  • Make sure that this new class inherits from System.EnterpriseServices.ServicedComponent so that it can be hosted within COM+ (Component Services)
  • Make sure the new ServicedComponent's class library is compiled in 32-bit mode
  • Alter the calling code (ASP.NET web page, control, web part, etc.) to call the new ServicedComponent for the Excel data retrieval routines
  • Install the new ServicedComponent into Component Services

Using Log4Net in 4 Simple Steps

First off, yes, this is a complete rip-off from this blog post, but with value added in that this represents my personal preference when using log4net. This is a simple four step process of adding logging to an application. I prefer log4net over the MS logging application block, primarily as log4net has zero dependencies and NHibernate already references it. IMHO, if you're doing ANY kind of logging, reference log4net into your project and use it instead of Trace.WriteLine() or Debug.WriteLine().

Step 1. Add the following in the AssemblyInfo.cs

[assembly: log4net.Config.XmlConfiguratorAttribute(
ConfigFile = "log4net.xml", Watch = true)]
Step 2. Create the log4net.xml file and add the following to the new file

With respect to the location of this file and SharePoint, this file can exist in the root of the wss root, next to the relevant web.config file for the given site. In general, this should sit next to the app.config or web.config file for the given application.

<log4net>
<appender name="GeneralLog" type="log4net.Appender.RollingFileAppender">
<file value="${TEMP}\\Logs\\AppName_${COMPUTERNAME} " />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<staticLogFileName value="false" />
<datePattern value=".yyyyMMdd.'log'" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="5MB" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d [%t] %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="GeneralLog" />
<appender-ref ref="DebugAppender" />
</root>
<!-- Print only messages of level ERROR or above in the package NHibernate -->
<logger name="NHibernate" additivity="true">
<level value="ERROR" />
</logger>
</log4net>

Step 3. Add the following at the top of your class
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

Step 4. Add the following line in the code where you want logging to be done

log.Info("Program Started " + DateTime.Now.ToString ());
References

Apache log4net Home Page - http://logging.apache.org/log4net/

Saturday, July 28, 2007

Potential solutions to the prior mentioned hack about KB928365 and ASP.NET Request Header modification

As "Anonymous" asked about my referenced solutions to the hack to resolve issues with MS KB928365, I figured instead of simply writing a follow-up comment, I would simply create a follow-up post. That said, I can imagine at least two primary solutions to this, each depending on your exact situation.

First off, with regards to my situation, I can create a provider pattern solution for looking up the enterprise provided AUTHID, such that if I want to fake it, my code isn't directly coupled to a value out of the header, but simply an interface that provides an AUTHID. I would then create two separate implementations of the interface. The first implementation would be the DEV simulation implementation, which would read the AUTHID out of the XML configuration file that we are currently using to inject into the request headers (for use in DEV). The second interface implementation would be one that reads the value off of the request headers (for use in PROD). You then configure which one is in use via your web.config, or via your Dependency Injection (DI) or Inversion of Control (IoC) framework (i.e., StructureMap, Windsor, Spring.NET).

Second, if you don't have that level of control of the situation, you are probably stuck with having to create a custom reverse proxy server to sit in front of your application that injects the AUTHID before making the request directly to your application.

Overall, the first solution is an order of magnitude more desirable. It is very much the "proper" object oriented approach, as it creates a loosely coupled solution that can be easily reconfigured, and more importantly, tested. And I would also highly recommend against even considering deploying the second solution in a production environment. It should only be used for simulating things for development purposes.

Friday, July 13, 2007

Blogs vs. Articles

I love this post! It's posts like this one that I swear and Jeremy Miller are my long lost brothers. Week after week, those two get down in writing what I've been thinking about for quite some time, but until recently, didn't even bother to have a blog to write in myself.

Overall, Nielsen is simply incorrect, much as O'Brien points out. Roughly 10 years ago, as a new college grad, I spent some time in the publishing world as a Technical Editor on a number of articles and two books. I did it, because I thought wow, there could be my name in print. But just doing those few jobs, not even being the author, I quickly came to realize all of the same things Larry points out in his post, so I stopped pursuing the publishing world any further.

Since then, second to a few choice books most folks are aware of, blogs have become the primary source of learning on my end. The amount of posts that I queue up daily in Google Reader as Starred Posts and then read while commuting to work, in elevators, at lunch, in waiting rooms, in conference rooms, etc on my WM5 Smartphone makes keeping up with this stuff seamless. Toting around magazines, print-outs, and/or books just isn't practical these days.

MS KB928365, ASP.NET Request.Headers.Add() Hack No Longer Works

So a project that I am currently a part of is using an ASP.NET 2.0 HttpModule to add some additional values to the incoming HTTP request's headers in the DEV environment (i.e., our local disconnected laptops) to simulate what an enterprise single-sign-on solution is performing in the production environment. It has worked like a charm. That is until I installed the new security update for the .NET Framework 2.0 release this past Wednesday, July 10, MS KB928365.

Apparently this "hack"
has been disabled with the release of this security update.

When attempting to call Headers.Add(), with or without the above hack in place, you will now receive a PlatformNotSupported exception.

All in all, this post is by no means a rant against the security update, but simply an attempt to add a quick answer to the "Google answer machine" for those searching. I am also already aware of a number of other potentially better solutions than the one currently in place for simulating those user accounts, but for now, simply uninstalling the above referenced security update gets the team back working again.

Tuesday, June 12, 2007

Using Multiple iPods on a Single Windows Computer

So I got a new laptop a week ago, and in the process was determined to figure out a better way to sync both my girlfriend Anne's 4GB iPod Nano and my 60GB iPod Photo using the single laptop.

With the last laptop, I created an additional user account to be used just for managing the iTunes library for my iPod. Anytime I needed to sync my iPod, I logged off my "primary" user account and then back in with a "secondary" user account used just for managing my iTunes library. I was convinced that there had to be a better way, but simply hadn't spent the time trying to find it. With the new laptop in hand, I was determined to find out what the answer was.

After Google'n for an answer, I found a number of solutions similar to the following ones:

http://lifehacker.com/software/itunes/multiple-libraries-in-itunes-7-202302.php

http://www.allthingsmarked.com/2006/09/13/howto-manage-multiple-libraries-in-itunes-7/

http://www.mcelhearn.com/article.php?story=20050124101441405

But none of them struck me as what I wanted the solution to be.

I then remembered the RUNAS functionality built into Windows XP, which in short, allows you to execute any given program under a different security context (i.e., using a different user account than the one you are currently logged in with). I then gathered that I should be able to run an instance of iTunes using RUNAS via a secondary user account.

So I created the secondary user account that I would use for managing my iTunes library, logged in with the secondary user account, and launched iTunes to ensure everything was working as expected. I then imported my iTunes library from the old laptop, etc. I then logged back in with my primary user account for the laptop and used RUNAS to launch iTunes with the secondary user account.

In short, it worked perfectly! The only hick-up of sorts was trying to figure out how to get the "Run as..." option to show up on the context menu for the iTunes icon on my Windows Desktop. The trick is to hold down the shift key while right-clicking the icon.

It was bound to eventually happen...

...that I would start a blog.

Anyway, I've spent better than the past 5 years now reading other people's blogs, and steering clear of starting my own. I still don't think I have the time to offer a lot of content, but hopefully what I do post will be more useful than this obligatory intro. The only real reason I decided to start this was to give myself somewhere to post write-ups on random topics that I couldn't find an answer to via Google. Hopefully posting those write-ups here will save someone else some time some day.

In the mean time, you can keep yourself busy reading my Google Reader Shared Posts.