August 2004 - Posts

Source Control Pet Peeves

Here are a couple of my pet peeves in source control.

Let’s say the sales and marketing department has decided the company is about to build version 3.0 of an amazing product: FooBar. Sometime thereafter, someone checks in a file named FooBar3.cs into source control.

I see two problems here. First, source control software, also known as version control software, will manage versions all by itself. Instead of putting version numbers in the file name, leave version numbers to the tool. When it comes time to associate a file version with a specific product release, I use the tagging / labeling functionality of the tool, and avoid potentially confusing conversations like:

Is FooBar3.cs checked in to build FooBar 4.0?

Does FooBar 5.0 still have FooBar3.cs?

This reminds me of an RFC on machine naming conventions (which I can’t find now). It suggests not giving the computer a name like UP or DOWN, because technical support will get calls like:

Is DOWN up?

Is UP down?

The second problem is letting a product name into the source code. Product names belong to marketing and sales. Product names are subject to change with the phases of the moon. Today the product is “FooBar 3.0”, tomorrow the product is renamed “Fabio 2005”.

Code names - really cool code names - like “Death Star” and “Predator”, belong to engineering. No one can make you change a code name. Well, unless you are Apple. In 1993 somebody at Apple decided to use “Carl Sagan” as a code name. Sagan’s attorney sent a letter to Apple asking them to stop. Someone at Apple then decided to change the codename to BHA – short for ‘butt-head astronomer’. Sagan then filed suit for emotional distress.

My advice then is this: don’t use version numbers, product names, company names, or the names of humor-impaired scientists when naming your project artifacts. Still, you might want to check with your lawyer first.

posted by scott with 3 Comments

When Authorization Is Not A Technical Problem

Designing an authentication and authorization scheme for a non-trivial web application is, in most cases, non-trivial. You need to minimize risks and always err on a safe side, while giving customers usable software. .NET provides useful mechanisms to implement a security design, including impersonation, delegation, role-based authorization, and a choice of authentication options. The technical part is sometimes the easy part.

Designing an authentication and authorization scheme for software in the healthcare market means sitting down with people and understanding their interpretations about the sticky pit of legal and regulatory goo they live in. Everyone has a different opinion about the U.S. Health Insurance Portability and Accountability Act of 1996 (HIPPA). One of the objectives of HIPPA was to guarantee the security and privacy of health information. The effectiveness of HIPPA on the privacy of a health record is debatable, but is has certainly been a boon to consultants and companies offering to bring a healthcare organization into compliance. Some places want software to audit every user move, others just want to disable USB ports so nobody walks out of the place with patient records on a jump drive.

I’m all for privacy, but I do find it frustrating from a professional angle when trying to sort out what people want and what the ‘right thing’ is. It’s also sadly amusing to watch to what happens when legislation and regulatory agencies collide.

For example, the Occupational Safety and Health Administration (OSHA) requires employers to keep a list of injury and illness reports with employee names, and to make the list available to employers, former employers, and employee representatives (like the AFL-CIO, a labor organization covering many industries). HIPPA leans towards hiding names, OSHA wants names on the logs. So what do you do? One solution is to ask for a clarification from the Director of the Directorate of Evaluation and Analysis at OSHA (via hippablog). Titles like these make me wonder who the libertarian candidate is this year…

posted by scott with 1 Comments

CreateProcessWithLogonW

Don Kiely recently pointed to an article on CodeProject with C# code to run a process in a new security context. The code PInvokes CreateProcessWithLogonW, but doesn’t work under ASP.NET.

CreateProcessWithLogonW is one of those tricky APIs that doesn’t pick up and move well from one environment to the next. Unfortunately, there is no way with .NET 1.1 to start a new process under alternate credentials without PInvoke. A spawned process always inherits the token of the creator process, so even if a thread is impersonating when it calls Process.Start, the new process always has the same identity of the current process. The good news is that Microsoft makes it easy in .NET 2.0.

Always approach launching a process on the server with caution. Launching an interactive process or a process under different credentials from a service should usually be avoided. In addition to overcoming all the privilege checks, you also have to deal with windowstations and desktops – not an issue in WinForms programming.

One little note about the code: the call to CreateProcessWithLogonW will fill the ProcessInformation structure with two IntPtr members representing Windows HANDLE types - these should be properly cleaned up with CloseHandle.

posted by scott with 2 Comments

The Dreaded “Aspnet_wp.exe Could Not Be Started” Error

This morning started on a bad foot. We officially designated one of our dev machines as “pooched”.

Inside the application log was the dreaded ‘Aspnet_wp.exe Could Not Be Started’ message. I cringe at the sight of this message because it could be the result of any number of security or permission related mis-configurations. One way to start troubleshooting the problem is by enabling auditing and getting some tools from sysinternals.com. See my post Security Whodunit and read Anil’s comment.

Going through all the KB articles (linked below) and trouble-shooting steps did not solve the problem. After four hours, the computer was one runtime error away from being placed into a charcoal picnic grill outside the building and set on fire. That’s when we realized a second machine was complaining about certificate errors and not being able to connect to SQL Server.

Both of these machines had something in common. Some of our client’s have moved to using VPN solutions over SSL, and both machines were setup to use this. The advantages to this type of VPN are the ability to tunnel over port 443 from just about anywhere, and (in theory) there is no need for client software deployment. In fact, everywhere I look the VPN over SSL vendors insist there is no need to deploy software on the client and make messy configuration changes. This white paper even claims the technology gives you “clientless access”. Isn't that an oxymoron?

You typically start a VPN over SSL session by browsing to a secure website outside the destination’s firewall and entering some credentials. The browser then asks if you want to install some signed ActiveX software onto your machine. I’m guessing since this is “clientless access” it doesn’t count as a software deployment. Nevertheless, after going into Add / Remove programs and instructing the software to uninstall from the clientless machine, ASP.NET worked perfectly again – even though the uninstall barfs on an access violation just before it completes.

The VPN software we were using was from Juniper networks. According to the signature, Neoteris wrote the ActiveX control. Neoteris, by the way, was bought by NetScreen, which in turn was bought by Juniper. You know when you are running an installation program and see signs of three different companies it’s going to be trouble. If this is a low cost deployment, then I want the Cisco IPSEC client back. Oh, I forgot, it's not really a deployment - it's clientless.

There are many other reasons you might see this error. It could be that the account for the ASP.NET worker process is disabled, missing, locked out, or you have the wrong password setup in machine.config. You might be trying to run ASP.NET on a domain controller. Alternatively, the ASP.NET process account might not have the permissions to access files that it needs, and woe to anyone who names their machine “SYSTEM”. These scenarios and fixes are described in the following KB articles.

FIX: ASP.NET Does Not Work with the Default ASPNET Account on a Domain Controller

PRB: "Aspnet_wp.exe Could Not Be Started" Error Message When You View an ASP.NET Page

You receive a "W3wp.exe could not be started" error message in the application event log when you view an ASP.NET page

FIX: Cannot Browse to ASP.NET Pages If Computer Name Contains Certain Words

posted by scott with 0 Comments

A Friend In Need

Multiple choice question:

A friend in need is a ____.

A) Friend with a non-booting laptop.

B) Friend with a non-booting desktop.

C) A friend indeed.

D) All of the above.

I’m sitting here with not one, but two pieces of hardware from two different people I know. Everyone in this field is familiar with playing tech support occasionally, it’s my turn now and they've come in bunches.

At least I’m getting a few good meals for my efforts. I’ve already had a down payment made in the form of a sausage, shrimp, and red pepper jambalaya. It’s a shame I can’t help this person out much, it appears the hard drive has destroyed more files than an Enron paper shredder.

The problem I’m really having is with a Toshiba Satellite notebook. I did manage to get this machine running. The screen is large and crisp and clear. It’s fast. It’s sleek. It makes my aging Thinkpad look so bad.

I keep finding myself on the Dell homepage clicking “Customize It”. I almost get to the checkout when I think: “No, what I really want is a Tablet PC”, and I’m off browsing for a Tablet. Then I wonder if I will really make good use of the Tablet. How often will I use the Pen? What if I need to do some extended development work on the machine? It’s just so cool, but is it worth it?

Now I can find no middle ground. It either has to be the super-portable, battery friendly Tablet PC, or the desktop replacement monster laptop that will give my legs third degree burns if I use it on the couch.

Pen versus Pentium 4

Sexy versus Sledgehammer

Space versus Time

This is the last time I fix a computer for someone who has nicer hardware than I do.

posted by scott with 9 Comments

A DBA's Dream - SQL 2005 DDL Triggers

I’ve never put a trigger into production. I’m not saying this is good or bad, it just hasn’t happened …. yet.

With all these new features in SQL 2005 it’s easy to overlook the new capability to use DDL triggers. I think they will become a DBA’s friend long before CREATE ASSEMBLY and stored procedures in managed code ever will.

DDL triggers fire when data definition language events happen. For instance, you can block DROP TABLE and ALTER TABLE statements in a database with the following trigger.

CREATE TRIGGER AuditTableDDL
ON DATABASE 
FOR DROP_TABLE, ALTER_TABLE 
AS 
   PRINT 'No DROP or ALTER for you!' 
   PRINT CONVERT (nvarchar (1000), EVENTDATA())
   ROLLBACK;

In SQL 2000, the only way to prevent an accidental table drop was by using CREATE VIEW to touch a table and add the WITH SCHEMABINDING clause. DDL triggers are more explicit about this, and give you auditing capability easily with EVENTDATA().

The EVENTDATA() function is what really makes DDL interesting. If you need to audit DDL activity, the XML return value will contain all of the pertinent information for you. For example, if I have table Foo, and try DROP TABLE Foo with the previous trigger in place, I’ll get the following response (with some formatting applied):

No DROP or ALTER for you!
 
<EVENT_INSTANCE>
  <EventType>DROP_TABLE</EventType>
  <PostTime>2004-08-22T22:54:37.377</PostTime>
  <SPID>55</SPID>
  <ServerName>SQL2005B2</ServerName>
  <LoginName>SQL2005B2\bitmask</LoginName>
  <UserName>SQL2005B2\bitmask</UserName>
  <DatabaseName>AdventureWorks</DatabaseName>
  <SchemaName>dbo</SchemaName>
  <ObjectName>Foo</ObjectName>
  <ObjectType>TABLE</ObjectType>
  <TSQLCommand>
    <SetOptions ANSI_NULLS="ON" ANSI_NULL_DEFAULT="ON" 
                ANSI_PADDING="ON" QUOTED_IDENTIFIER="ON"
                ENCRYPTED="FALSE"
    />
    <CommandText>DROP TABLE Foo</CommandText>
  </TSQLCommand>
</EVENT_INSTANCE>
 
Msg 3609, Level 16, State 2, Line 1
Transaction ended in trigger. Batch has been aborted.

SQL 2005 also has predefined event groups to make writing DDL triggers easier. In the following trigger, DDL_TABLE_EVENTS will catch CREATE TABLE, DROP TABLE, and ALTER TABLE:

CREATE TRIGGER AuditTableDDL
ON DATABASE 
FOR DDL_TABLE_EVENTS
AS   
   PRINT CONVERT (nvarchar (1000), EVENTDATA())
   ROLLBACK;

Likewise DDL_INDEX_EVENTS will fire on CREATE, ALTER, or DROP INDEX. These groups roll up into larger groups. DDL_TABLE_VIEW_EVENTS will fire on all table, view, index, or statistics DDL.

The above triggers operate at database scope, and only react to events in the current database. You can also apply triggers at a server scope to fire on CREATE, DROP, ALTER LOGIN, for instance.

And of course you could write the trigger in C# or VB.NET, but let’s not get ahead of ourselves just yet…

posted by scott with 2 Comments

The Misunderstood Mutex

Someone should delete this article in purgatory at codeproject.com. The article is full of mis-information, but look at the view count. 

The article attempts to restrict a Windows application to a single running instance. The article tries to do this using the Process class from the System.Diagnostics namespace. The code invokes Process.GetProcessesByName(processName) to see if there are any existing processes running with the same name, and exits if another is found. If you do some searching, you’ll find other code snippets using the same technique.

There are at least three problems with this approach:

1) It doesn’t account for race conditions. Two instances of the application could launch at nearly the same time, see each other, and both shut down.

2) It doesn’t work in terminal services, at least not if I want the application to run an instance in each login session.

3) It doesn’t account for the possibility that someone else might have a process with the same name.

These problems might be considered ‘edge conditions’, except there is an easy, foolproof way to check for a running instance of an application. A named mutex allows multiple processes to use the same mutex object for interprocess synchronization. The author asserts a mutex is not safe on a multiprocessor machine. If this were true it would be the end of civilization as we know it.

The name of the mutex will preferably be a unique identifier to offset the chances of another application using the same name. One could chose to use the full name of the executing assembly, or a GUID. If the application can acquire the named mutex with WaitOne, then it is the first instance running. If the application calls WaitOne with a timeout value and WaitOne returns false, another instance of the application is running and this one needs to exit.

When using this approach in .NET there is one ‘gotcha’. The following code has a small problem:

[STAThread]
static void Main() 
{
   Mutex mutex = new Mutex(false, appGuid);
   if(!mutex.WaitOne(0, false))
   {
      MessageBox.Show("Instance already running");
      return;
   }
   Application.Run(new Form1());
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

The problem is easy to reproduce if you run the following code in a release build:

[STAThread]
static void Main() 
{
   Mutex mutex = new Mutex(false, appGuid);
   if(!mutex.WaitOne(0, false))
   {
      MessageBox.Show("Instance already running");
      return;
   }
   GC.Collect();                
   Application.Run(new Form1());
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

Since the mutex goes unused when the Form starts running, the compiler and garbage collector are free to conspire together to collect the mutex out of existence. After the first garbage collector run, one might be able to launch multiple instances of the application again. The following code will keep the mutex alive. (The call to GC.Collect is still here just for testing).

[STAThread]
static void Main() 
{
   Mutex mutex = new Mutex(false, appGuid);
   if(!mutex.WaitOne(0, false))
   {
      MessageBox.Show("Instance already running");
      return;
   }
         
   GC.Collect();                
   Application.Run(new Form1());
   GC.KeepAlive(mutex);
}
private static string appGuid = "c0a76b5a-12ab-45c5-b9d9-d693faa6e7b9";

There is still an imperfection in the code. Mutex derives from WaitHandle, and WaitHandle implements IDisposable. Here is one more example that keeps the mutex alive and properly disposes the mutex when finished.

[STAThread]
static void Main() 
{
   using(Mutex mutex = new Mutex(false, appGuid))
   {
      if(!mutex.WaitOne(0, false))
      {
         MessageBox.Show("Instance already running");
         return;
      }
   
      GC.Collect();                
      Application.Run(new Form1());
   }
}

With the above code I can run the application from the console, and also log into the machine with terminal services and run the application in a different session. Terminal services provides a unique namespace for each client session (so does fast user switching on Windows XP). When I create a named mutex, the mutex lives inside the namespace for the session I am running in. Like .NET namespaces, terminal services uses namespaces to prevent naming collisions.

If I want to have only one instance of the application running across all sessions on the machine, I can put the named mutex into the global namespace with the prefix “Global\”.

[STAThread]
static void Main() 
{
   using(Mutex mutex = new Mutex(false, @"Global\" + appGuid))
   {
      if(!mutex.WaitOne(0, false))
      {
         MessageBox.Show("Instance already running");
         return;
      }
   
      GC.Collect();                
      Application.Run(new Form1());
   }
}

After all this you may find you need to adjust permissions on the mutex in order to access the mutex from another process running with different credentials than the first. This requires some PInvoke work and deserves a post unto itself.

posted by scott with 14 Comments

Talking Plug-ins and CFOs (Small Company Life)

A lazy developer is a good developer. One developer I worked with had a passion for automating everything. By the time you asked Dan to do something twice, he would have a SQL script ready, or an ASP page, or a VB program that screen scraped data from web servers in 15 different time zones and sent a report to the printer with instructions for automatic collation and stapling.

This was all particularly impressive considering Dan had moved into the pointy haired legions of ‘management’.

Back in the day before Outlook had a desktop alert, Dan decided to write his own alert as an Outlook plug-in. Every time a new message arrived, the plug-in used the MS text to speech engine to read the subject line aloud.

When Dan's machine said "Error in production", Dan knew something important was up. When Dan's machine said "Halloween Dress Up Day”, Dan knew he could continue working on something important and leave the email alone.

This all worked pretty well until the childish developers in the company realized the power of speech. Dan would be having a staff meeting with 4 other people in the office when his computer would blurt out:

“Beer. Beer. Beer. Beer. Beer. Beer. Beer. Beer. Beer. Beer. Beer.“

Anyway, when we were an up and coming company we had to plan for our destiny. It was very important, the VCs would tell us, to be prepared for the massive incoming rush of future customers. The absolute worst scenario for an Internet company is not to have the infrastructure required to meet customer demand. IBM would ridicule any company caught unprepared in a television commercial.

Like many companies of the late 90s, we promptly leased enough networking gear to wire the planet over twice.  

In 2001 cash became an issue. Someone decided that if we could just get out of all the leases and replace high end Cisco and Sun hardware with stuff from WalMart, then we would buy ourselves enough time for the massive incoming rush of customers to arrive.

Dan’s job was to crunch all the numbers and present a plan to the CFO. Dan knew the first answer is never acceptable in these scenarios so he came up with a complete financial model in the form of a spreadsheet. If they didn’t like the first answer then they could just change a few numbers around and the entire spreadsheet recalculated.

About 6 months later Dan told me what happened when he turned the spreadsheet over to the CFO. The CFO was excited and called Dan into his office.

CFO: Look, I can change a number here, and the number down here changes!

Dan: Yes, I thought I’d put this in a spreadsheet to try a few different things and see what works out best.

CFO: Yes, but look! I can change a number here, and the number down here changes!

Dan: Well, yeah, it’s a spreadsheet…..

CFO: But this is fantastic! I can change a number here, and the number down here changes!

I heard this story as I was packing the books at my desk into cardboard boxes. At this point nothing surprised me. In fact, being as how we were only days away from closing the door for good, maybe it was all starting to make sense.

Note: this story is not my fondest memory of the CFO…..

posted by scott with 1 Comments

The Blog Delivery Extension

In order for SQL Server Reporting Services to deliver a report to the destination of your choosing, you only need to create an assembly with class types implementing 3 simple interfaces: IExtension, IDeliveryExtension, and ISubscriptionBaseUIUserControl.

Then the fun really begins. I have some tips to share for anyone else who tries. Refer to the last post for the source code.

Building The Delivery Extension

My delivery extension needs to plug into both the user interface of the Report Manager (for the user to set delivery parameters), and into the ReportServerService (where all the heavy lifting and rendering takes place). After every build I had to get the assembly into the bin directory of both the Report Manager (the web application), and the ReportServerService. All this takes is a little batch file (like the following) and a command prompt with admin privileges.

net stop reportserver
 
xcopy /y (BuildPath) (SSRSHome)\ReportServer\bin
xcopy /y (BuildPath)  (SSRSHome)\ReportManager\bin"
 
net start reportserver

There is no need to shutdown the Report Manager web application. ASP.NET shadow copies the extension assembly and leaves the original unlocked in the bin directory. The runtime will recognize when the assembly has changed and loads the new version immediately.

Configuring The Delivery Extension

The next step was to have SSRS accept my new delivery extension with loving arms. There were 4 total configuration files to modify. The RSWebApplication.config and RSReportServer.config files are easy to figure out. Just provide SSRS with the type name and the assembly name at the appropriate location in the XML:

<Extensions>
  <Delivery>
    <Extension Name="Report Server Blog" Type="OdeToCode.BlogDeliveryExtension.BlogDeliveryProvider, BlogDeliveryExtension">
      <MaxRetries>3</MaxRetries>
      <SecondsBeforeRetry>900</SecondsBeforeRetry>                         
    </Extension>
  </Delivery>
</Extensions>

The rssrvpolicy.config and rsmgrpolicy.config policy files are a different story. These files manage the security policies of SSRS. After nearly blacking out from reading the documentation on code groups for the 15th time and not having any success, I found Bryan Keller’s post with a hint on where to place extension code groups (right after the CodeGen membership group).

<CodeGroup 
  class="UnionCodeGroup"
  version="1"
  PermissionSetName="FullTrust"
  Name="Report Server Blog"
  Description="Code group for OdeToCode Blog Extension">
  <IMembershipCondition 
    class="UrlMembershipCondition"
    version="1"
    Url="C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportManager\Bin\BlogDeliveryExtension.dll"
  />
</CodeGroup>                                              

Typos, or incorrect element placement can lead to a wide variety of interesting exception messages and log file entries. Any exception with “security”, “permission”, or “cannot load type” in the description is a possible user malfunction in editing the code groups.

Debug The Extension

To debug the UI behavior I’d attach to the ASP.NET worker process. To debug the actual delivery, I’d attach to the ReportServerService, or use System.Diagnostics.Debugger.Launch() to bring up a debugger as soon as it hit the line of code.

To execute the delivery I first needed to setup a subscription to a report. I setup a subscription to run a report every Monday morning at 8 am. I’ve got the debugger ready and just need to wait a few days now for the breakpoint to hit.

Just kidding.

There is a SQL Agent job for each schedule in SSRS. By executing the Agent job, you can trigger the subscription to fire off a delivery. Finding the right job can be problematic if you have several report subscriptions set, but scanning the results of the following query might help.

SELECT     
 
  RS.ScheduleID, 
  C.Name, 
  U.UserName, 
  S.Description
 
FROM 
 
  ReportSchedule RS
 
  INNER JOIN Subscriptions S
    ON RS.SubscriptionID = S.SubscriptionID 
 
  INNER JOIN Users U
    ON S.OwnerID = U.UserID 
 
  INNER JOIN [Catalog] C
    ON RS.ReportID = C.ItemID
 
ScheduleIDNameUserNameDescription
B20C9057-EE51-41E2-B3B5-7450AC73FFCBCustomers ReportREPORTING\bitmaskPost report to http://ibm600xp/dottext/scott/services/simpleblogservice.asmx

That concludes the tips for now. Happy 100th post to me.

posted by scott with 2 Comments

Deliver Reports To A Blog

SQL Server Reporting Services ships with two delivery extensions: one to deliver reports through email, and one to deliver reports to a shared network drive. A third extension to deliver reports to a printer exists in the SSRS samples directory.

One day I was setting up an email subscription and a thought occurred. Delivering reports to a blog instead of to a company email alias would be ideal. Instead of sitting in a slew of inboxes, a new delivery extension could post these reports with the blog’s web service API and intranet users could easily comment on and link to the report. Anyone needing the report subscribes to the blog with an aggregator and knows when a new report is ready.

I learned quite a bit and have quite a number of tips to share about the experience, but I’ll have to save those for future posts. Implementing, deploying, testing, and debugging a reporting service extension involves a little more work than I initially suspected. It looks easy in theory (just implement these 3 simple interfaces!).

In the meantime, you can look at the source if you dare. It has no warranty, no guarantees, contains liberal amounts of TODO comments, and comes with no installation instructions (yet).

BlogDeliveryExtension.zip (C#)

On the plus side, it does work on the simple reports I've tested so far. I have not tried reports with images, I suspect these are going to pose a problem. I just finished adding DPAPI calls to keep the blog user's password encrypted in the ReportServerDB, and the next step is to look at the fancier reports.

Feedback and criticism welcomed.

If you find reporting to be a boring subject, then I'm sure you won't download the code, and won't offer any feedback, because you've already stopped reading this.

posted by scott with 4 Comments

Visual Studio 2005 Notes

 It’s experimentation day.

I was looking at some web forms with Trace enabled in ASP.NET 2.0, looked at the ViewState size, and remembered reading about some view state enhancements in 2.0. So I did a little experiment.

In ASP.NET 1.1 I rendered ‘SELECT * FROM pubs..employee’ in a DataGrid control using all the default settings (auto-generated columns, ViewState enabled). The resulting page used 26,241 bytes.

In ASP.NET 2.0 I rendered ‘SELECT * FROM pub..employee’ in a DataGrid control, again using all the defaults. The resulting page used 16,089 bytes. A difference of 10,152 bytes, which is quite a bit if you insist on enabling ViewState on a DataGrid. The same experiment using the new GridView control, with sorting enabled, ran 17,996 bytes.

For anyone who has not seen the GridView in action, Dino Espisito has an article: Move Over DataGrid, There's a New Grid in Town!


I noticed in the trace there is a new PreInit event in the page life cycle. This makes me wonder if we might need a PrePreInit event in ASP.NET 3.0? Perhaps a BeforePreInit?  

It turns out PreInit is the event to grab in order to dynamically change the personalization, theme, or master page settings (it was also time to get in touch with my kindler, gentler, VB side, but don’t tell anyone):

Private Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreInit

    MasterPageFile = "~/FunkyDoodleLayout.master"

End Sub

Setting the MasterPageFile property after the PreInit event only throws an exception. Paul Wilson has a good introduction to the Master Page feature: Standardize Your Site Fast With Master Pages.


Somehow, in the middle of this, I began surfing and read about Boston’s Great Molasses Flood of 1919. I’ve never heard of this and first thought it was an urban myth, but it’s not. A 30 foot high wall of molasses moving at 35 miles per hour.  


The code expansion in C# is quite good. I type the ‘using’ keyword, hit the TAB key, and the IDE inserts a code block for me:

using (resource)
{
 
}

The word resource appears in yellow and is selected, so I just need to type in the expression. If you expand a for statement, then the initializer is highlighted. As soon as you change the variable name in the initializer, the IDE changes the variable name in the expression and iterator to match, saving quite a few keystrokes.

What is interesting is the C# code snippets center around simple block statements (lock, using, for, do). The VB snippets seem to include entire algorithms. You choose to “Insert Snippet…”, then chose “Processing Drives, Folders, and Files”, then “Parse Column Data In a Text File”, and the IDE spits out the following code, where the file name and delimiters array are replaceable by tabbing through the snippet and typing (this is hard to describe, you just have to try it):

' This example parses a file with this structure.
' Line1Column1, Line1Column2, Line1Column3
' Line2Column1, Line2Column2, Line2Column3
' Line3Column1, Line3Column2, Line3Column3
' Line4Column1, Line4Column2, Line4Column3

Dim parser As TextFieldParser
parser = My.Computer.FileSystem.OpenTextFieldParser("C:\TextFile.txt")
parser.Delimiters = New String() {","}
Dim fields() As String
While Not parser.EndOfData
    Try
        ' ReadFields reads one line of data from the file.
        ' Array 'fields' contains one string element for each column.
        fields = parser.ReadFields
    Catch ex As MalformedLineException
        MsgBox("Error on line: " & ex.LineNumber)
        Throw ex
    End Try
End While
parser.Close()

As I’ve said before, someday I’m going to come home and find my cat has written a Tetris clone by sleeping on my keyboard. In VB of course.

posted by scott with 0 Comments

I Can't Take The Heat

The air conditioner is broken at work and I am now sitting in the world’s only sauna that requires business casual dress. Like a fine Pinot Grigio, I must be properly chilled before I can reach my full potential. It’s too hot here to debug software, so I’m going to blog.

I work in a government technology “incubator” building, meaning there are a dozen startup companies around here and we share a common copier room, common break room, and common conference rooms. The key word here is ‘government’. I have complete faith the county maintenance people feel my discomfort and will rush to fix the air conditioning problems just in time for the first major snowfall of the year.

In other news, I’ve been under a lot of peer pressure lately. Test driven development is a great way to write quality code, but sometimes good old fashioned peer pressure works just as well. Why just recently I opened the latest build notes to look at the list of file diffs. Imagine my surprise when I see:

File Check-in Date Version Check-in By Comment
VisitSearch.ascx.cs 8/8/2004 11:42:36 AM 2 Plall Fix Scott’s sloppy code.

Ouch. Now I have another excuse not to write code. Not only is this room so hot I feel I should be wearing sunscreen lotion, but if I slip up in these conditions my work is subject to public humiliation. Time to find cooler air….
posted by scott with 2 Comments

Reporting Services Security

For anyone facing authentication or authorization errors in a Reporting Services environment, or just looking for some introductory material, I've posted two new articles to OdeToCode:

Introduction To Role-Based Security In SQL Server Reporting Services

Authentication, Role-based Security, and Reporting Services Web Services

If you give them a read, please let me know if you think they are easy to understand, and please let me know if they contain any errors.

posted by scott with 2 Comments

The Telephone Is Ringing (Small Company Life)

Working for a startup means we forego some ‘luxuries’ of business life, like having a receptionist in the office to answer phones. We all have to answer the phone. I purposefully keep my phone at a location out of arm’s reach, giving everyone else who keeps a phone close by the first chance at answering.

There are a few reasons I try to avoid the phone.

First, I get tired of talking to the cold-calling photocopier sales people. There is nothing worse than being neck deep in a database and having one of these overly cheerful people call and tell me how I need a photocopier with a re-circulating automatic document feeder. Yes, of course, our office could use a re-circulating automatic document feeder, but what the office really needs is a new laser printer with a warm up time of less than 15-minutes. Unless they are giving away a free photocopier, talking about it over the phone is a waste of time.

To understand another reason why I avoid the phone, I have to give you some background. The company founder, who works part-time in the office, is a practicing physician - a gastroenterologist to be exact. I also have to tell you his name, because you might find this amusing. His name is, and I swear I am not making this up, Dr. Butt.

On occasion, the doctor will call a patient from the office phones to discuss their condition, or their colonoscopy results. Some patients, like the ones who are stark raving mad hypochondriacs, will see the phone number on their caller ID and write the number down. They think if they call this number, they will talk to a doctor or someone with medical training. When they do call, they get people like me, who have no medical training and feel lightheaded when I see a needle.

Telling some people that you are not qualified to give them medical advice does little to slow them down. Some people don’t seem to want advice, actually. What they want is just to be able to describe their problems to another human in graphic detail. This includes the finer facts of their fecal incontinence, or their irritable bowels, or the blood in their stool.

Quite frankly, I’m not paid enough to listen to this sort of talk on the phone. I’ll ask the person to please hold, and forward the call to our sales person. Sales people can talk about anything, even before right before lunch.

posted by scott with 3 Comments

Rush

I had to miss this month’s CMAP user group meeting – but for good reason. Rush was playing at the Nissan Pavilion in VA. I scored tickets next to the soundboard area. The person I sat the closest to had 3 Alienware laptops setup, but only one of which I could see clearly. It was running Windows XP. I saw a splash screen for software by the name of TouchMixer. Google tells me the software is made by Derivative Inc. This fellow controlled all of the video effects on the big screen.

I’ve seen some good performances the first 7 months of this year:

The next 4 months look pretty bleak.

posted by scott with 0 Comments