Wednesday, October 31, 2007

I was reading the latest ComputerWorld (Oct 29) and came across one of the best quotes I've seen in a while.

I'm not a huge fan of Grady Booch, but he has had quite the impact on IT. I just can't stand UML.

At any rate, there's an interview with him in this issue and at the end, he's asked what has surprised him most in the last decade.

He responded:

"I haven't seen any revolutions.Heck, I had my first email address in 1979. There was a printed document with everyone's email address [in the world]."

Man, oh man, that'd be a sweet piece of tech memorabilia to hang on the wall.

Does that make me a geek\:\-\)

posted on Wednesday, October 31, 2007 7:43:22 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

No, I'm not converting my blog here to Craig's List or anything, but I figured I'd throw out first crack to anyone that reads here.

An Force Feedback wheel is the only way to play driving games IMO. Driving with a freakin' thumb controller just seems so....dirty.

But I'm giving up on making my Force Feedback Wheel work with Vista.

It's USB, it's a HID device. But the drivers just hate Vista. No idea why. I'm guessing that if I knew how to isolate the USB device ID, I could modify the INF files that come with the latest Thrustmaster drivers for Vista and get it functional. I just don't have the wherewithal, and I'm not a device driver writer by trade, so it's a tad more research that I want to bite off right now.

Works great in XP, though. Has paddle shifters, strong force feedback, good pedals and a thick wheel with rubber grips.

It's a Guillemot wheel, and from what I can tell online, that's actually Thrustmaster under the covers.

Wheel1a Wheel2a

If you're in the Dallas, Ft Worth area (that's Texas), let me know if your interested.

I'd let it go for cheap (say 50$ obo).

posted on Wednesday, October 31, 2007 4:38:20 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, October 25, 2007

I generally can appreciate what Joe Celko brings to the table in terms of SQL programming and theory, but a recent article of his had me scratching my head, even considering his sometimes Ivory Tower approaches.

The article is at Intelligent Enterprise here.

What caught my eye was the following paragraph. He's discussing his own classification system of keys in databases and one of the types is described thusly:

3. An "exposed physical locator" is not based on attributes in the data model and is exposed to the user. There is no reasonable way to predict it or verify it, since it usually comes from the physical state of the hardware at the time of data insertion. The system obtains a value thru some physical process in the storage hardware totally unrelated to the logical data model " for example, IDENTITY columns or other proprietary, non-relational auto-numbering devices.

Technically, these are not really keys at all, since they are attributes of the physical storage and are not even part of the logical data model. But they are handy for lazy, non-RDBMS programmers who do not want to research or think! This is the worst way to program in SQL

So, to paraphrase, he's saying that if you use IDENTITY columns in your database schemas, you're "lazy" and "do not want to research or think"

Uhuh.

So how about it, you lazy, slovenly, heathen SQL programmers out there that dare to use Identity columns?

And in case it's not obvious, I can't disagree more with Celko on this point\:\-\)

posted on Thursday, October 25, 2007 6:33:00 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [3] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Wednesday, October 24, 2007

If you're putting together an Arcade cabinet or if you just like the ambience of an old arcade parlor while hacking away at some code, you might get a kick from Andy Hofle's Arcade Ambience project.

image

Basically, Andy took recordings of playing dozens of arcade machines, remixed them, adjusting volumes, pans, etc, and ended up with several, very large, mp3 tracks of background ambience that sounds very much like stepping into an old arcade. What's even better, the tracks are so large (at 70+mb each), they effectively don't loop, so you don't get that been there, heard that feeling that's typical of ambient tracks.

Couple that with an arcade front end that can play an arbitrary mp3 looped as background sounds, and couple that with a recent audio card that supports multiple simultaneous channels, and you get all the ambience of walking into an arcade while playing any emulator, Visual Pinball table, etc.

posted on Wednesday, October 24, 2007 9:40:05 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

NASA has a nifty little "picture of the day" feature that you can add to your Google Homepage.

A picture that came up just yesterday shows the shuttle lifting off against a gray sky. Here's a snippet (from http://www.nasa.gov/multimedia/imagegallery/image_feature_940.html):

image 

But the thing that struck me about this is how much our space program is beginning to resemble the Millennium Falcon.

image 

The rusty tanks, the patchy paintwork, using a stapler for repairs. It's starting to look like the shuttles may have made the Kessel Run a few times themselves.

 image

(Detail from above)

Compare to this 1983 picture from the Wikipedia entry:

image

And this about 20 months before the first shuttle launch (again from Wikipedia):

image

Clean and sparkly

Cripes, it's 2007. Shouldn't we be flying something like this:

image

from www.starwars.com

and not this:

image

from http://www.art.net/kiyotei/blogs

posted on Wednesday, October 24, 2007 4:10:10 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, October 23, 2007

I recently had the pleasure of trying to get SQLExpress 2005 to install, on request, during my application's install, according to various parameters specified by the user during the UI portion of my install. Phew. Yeah, it's hard enough to say, much less code.

Anyway, the basic problem is that you can't nest MSI installs. And since an "MSI Install" is considered to be the portion of the install that runs during the EXECUTE sequence of the MSI logic, this means you can't just execute SQLEXPR32.EXE at some point during the EXECUTE sequence of your install.

This is normally what you'd want to do because then, that action would happen regardless of whether your installation was running silently (ie no UI) or not.

Ok, that's off the table. Hey, Microsoft! Believe it or not, nesting installs is something real world apps have to do in order to get all those nifty libraries of yours out there onto client machines! Sigh.

Fortunately, in my case (and in many others I'm guessing), not being able to run the SQL Express installer during a silent install is not a deal breaker. More than likely, if my installation is being rolled out silently, whoever's rolling it out is using some sort of automated rollout tool to do it, and it's likely they'll just automate the rollout of SQLExpress as well.

But, what about when a user is simply trying to run my installation to get the product on their machine. In this situation, you want a no fuss, no muss process that has a user up and running with a little decision-making as possible. The last thing I want a user being prompted for is the instance name to use for the SQL Server that's about to get installed. Sheesh.

Well, I can't nest MSIs, technically, but I can execute SQLEXPR32.EXE from the UI sequence of the install.

Aha! Success! Except that, as well documented by any number of posts online, it doesn't work consistently. Often, you'll get an MSI error 110 indicating that SQL can't access a file (usually sqlncli.msi) that it needs to complete the install.

            ******* CommandLine: **********
MSI (s) (18:00) [12:59:55:555]: Machine policy value 'DisableUserInstalls' is 0
MSI (s) (18:00) [12:59:55:575]: Note: 1: 1309 2: 5 3: C:\23661fc1e2705da3b45f5b05\setup\sqlncli.msi 
MSI (s) (18:00) [12:59:55:595]: MainEngineThread is returning 110
The system cannot open the device or file specified.

Trouble is, the file is there, exactly where the log says it isn't.

The problem is clearly an issue with the SQLExpress installer. I can say that pretty confidently because you can nest all of SQL's prerequisite installs just fine, including:

  • MSXML6.MSI (The XML 6.0 libraries)
  • SQLCNLI.MSI (SQL Native Client)
  • SQLServer2005_XMO.msi (SQL Server management objects installer)
  • And even VSTOR.EXE (the Visual Studio Tools for Office)

Just not the SQLEXPR32.EXE itself.

One possible solution that I found mentioned online was to extract all the SQLEXPR32.EXE files, then actually include them directly in your installation and at the end of your installation (in the UI sequence) launch the SETUP.EXE that starts the SQLExpress installation.

So I extracted them all:

SQLEXPR32.EXE /x:c:\SQLEXPR

Ouch. And I mean owwwwwwwwwchaaaaaa. 400+ files scattered over dozens of directories. The InstallShield Component Wizard choked trying to add them all as components and there was simply no way I was going to manually set up all that felgercarb in my install. Yeah, I went there\:\-\)

Then it struck me. The extraction process (that /x command line arg) isn't actually an msi install, so I should be able to nest it just fine. And if that's the case, then I could extract all the files from SQLEXPR.EXE dynamically after installing the exe, and then fire up SQL's setup.exe directly, just as I'd tried launching the SQLEXPR32.exe before (but that failed with the 110 error).

So, first, make sure you install the SQLEXPR32.EXE along with the rest of your application's files.

Then, set up a Custom Action in your MSI project, in the EXECUTE sequence, that extracts all the files from SQLEXPR32.EXE to some folder (usually inside your INSTALLDIR somewhere):

image

(note the use of /q; that will keep the extraction process quiet, including suppressing a rather bothersome "Extraction process done" message box when it finishes, Also note that this adds substantially to your app footprint so you may need to accommodate that in available space calculations).

Next, create another Custom Action to execute the SQL Express SETUP.EXE that was extracted in the previous step.

image

Set this CA to execute towards the end of the UI sequence (because this is an MSI setup and this action can't be started from the EXECUTE sequence of your installation, no getting around that one).

I set a command line of /qb to use the basic install ui for SQL Express, but you might also want to include other command line parameters, like:

  • ADDLOCAL
  • INSTANCENAME
  • SECURITYMODE
  • SAPWD
  • DISABLENETWORKPROTOCOLS

or any of the other options you can specify. More info about the command line options for SQLEXPR32 are pretty easy to find online.

And finally, to keep your installation from looking like it just hung during the extraction part above, be sure to author a row in the ACTIONTEXT table for that custom action, and give it some text, like "Extracting SQL Server files...". The text will show up over the progress bar that normally displays during an InstallShield-authored installation.

Let me know if this works for you, or if I've missed some esoteric combination in InstallShield that prevents it from working.

As always, YMMV.

posted on Tuesday, October 23, 2007 9:32:37 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [6] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Thursday, October 18, 2007

Here's a snippet from a recent Microsoft email I received when I opened a ticket with them through the MSDN support channel.

******* The following is an email for a support case from Microsoft Corp.
******* DO NOT REPLY TO THIS MESSAGE--your email will not be added to
******* the case if you do.  Instead, FORWARD your response to the
******* email address COMPMAIL@MICROSOFT.COM and place your text after
******* the keyword 'MESSAGE:'.  Also, delete all other text above
******* and below the keywords 'CASE_ID_NUM: SRnnn' and 'MESSAGE:'
******* to ensure proper delivery of your email.  Thank you.

Now, I'm pretty technical, but any support email conversation that you have to read several times over in order to even reply to is, uh, shall I say, unfriendly. 

And the wording, "DO NOT REPLY TO THIS MESSAGE...", and "Instead, FORWARD...". Wow, the antithesis of friendly.

What's worse, when I did exactly as was indicated, the email failed to go through with my attachment. I ended up, get this, having to send a reply to the very FROM address that the snippet above indicated NOT to reply to!

Now, I'm not pointing fingers at the techs themselves here; I doubt the guy that clicked the button to send this email was actually responsible for the content of this header.

But seriously, someone at Microsoft needs to take a lesson or three in email etiquette.

posted on Thursday, October 18, 2007 6:15:47 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [0] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, October 16, 2007

I've played around with some strange input devices, including the Felix (now called the Altra MicroPoint), the Kinesis Keyboard, and a mouse with a trackball for a scroll wheel among the more notables.

But the Keybowl has to be the most, well, interestingly named of the bunch so far.

What is it?, well, here

image

You basically put your hands on the two "domes" and slide them around. The left hand is like an 8 way "shift" key, and they right hand selects a specific character from a set of characters depending on the position of the left hand dome.

For someone with limited hand mobility, I could certainly see some benefits, but it does look a bit, er, strange.

They should wrap it in knit.

Sorry, I couldn't resist.

posted on Tuesday, October 16, 2007 10:01:27 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

It seems like such a simple task; check a variable for whether it contains a true value or a false value.

No problem, right? Well, if you're dealing with numbers, maybe, but when input comes from config files or databases, the truth <ahem> may not be so obviously out there.

As part of my code garage, I thought I'd post two functions I've used for ages to do just that; convert a variable, virtually any variable, to a boolean result.

They are especially useful in configuration handling, where you might want to support multiple values that might mean "true" or "false", like on/off, yes/no, etc. I've also found that they can make code clearer by specifying the "assumed" default value if the variable can't be concretely identified one way or another (is "Bob Thomas" true or false?).

Public Function IsTrue(VarToTest As Variant, Optional ByVal Default As Boolean = True) As Boolean
   '---- Resolve an input variable to a boolean
   '     but convert common "true"/"false" phrases as well
   '     Also, this allows for an easy way to indicate a
   '     "default" value in cases of an undetermined (ie blank)
   '     value
   Dim t$
   Dim s

   Select Case VarType(VarToTest)
      Case vbArray
         Err.Raise 5, "IsTrue", "Can't test an array for true"
      Case vbObject
         IsTrue = ObjPtr(VarToTest) <> 0
      Case vbString
         If Len(VarToTest) = 0 Then
            '---- is true assumes blanks are true
            IsTrue = Default
         Else
            On Error Resume Next
            '---- strip to first space
            t$ = Trim$(Left$(VarToTest, 15))
            s = InStr(t$, " ")
            If s > 0 Then t$ = Left$(t$, s - 1)
            '---- clean out any tabs
            t$ = Replace(t$, Chr$(9), vbNullString)
            '---- accept some synonyms (any other good ones?)
            If InStr(1, t$, "YES", vbTextCompare) = 1 Then
               IsTrue = True
            ElseIf InStr(1, t$, "NO", vbTextCompare) = 1 Then
               IsTrue = False
            ElseIf InStr(1, t$, "ON", vbTextCompare) = 1 Then
               IsTrue = True
            ElseIf InStr(1, t$, "OFF", vbTextCompare) = 1 Then
               IsTrue = False
            ElseIf InStr(1, t$, "TRUE", vbTextCompare) = 1 Then
               IsTrue = True
            ElseIf InStr(1, t$, "FALSE", vbTextCompare) = 1 Then
               IsTrue = False
            Else
               IsTrue = CBool(VarToTest)
               If Err Then
                  IsTrue = Val(VarToTest) <> 0
               End If
               On Error GoTo 0
            End If
         End If
      Case Else
         If IsEmpty(VarToTest) Then
            IsTrue = Default
         Else
            On Error Resume Next
            IsTrue = CBool(VarToTest)
            If Err Then
               IsTrue = Default
            End If
            On Error GoTo 0
         End If
   End Select
End Function


Public Function IsFalse(VarToTest As Variant, Optional ByVal Default As Boolean = False) As Boolean
   '---- basically, the inverse of IsTrue above
   '     mainly for convenience

   IsFalse = Not IsTrue(VarToTest, Default)
End Function
Hey, CBOOL is undoubtedly faster, but:
   If IsFalse(SettingValue) Then 
      '---- handle the negative condition here
   End If

just seems so much clearer.

The optional Default argument allow you to specify what value to return if the value to test can't be resolved satisfactorily one way or the other. Practically, this allows you to easily specify whether a blank value or a "non-boolean" value should translate to true or false. This is especially important when you're reading config options from a file or the registry where the option may not exist at all.

It allows to do something like so: 

   If IsTrue(SettingValue, False) Then 
      '---- handle positive conditions here, but if the SettingValue is blank, we default to False
   End If
Simple, but handy.
posted on Tuesday, October 16, 2007 7:48:06 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

I was chatting today when the new game Portal from the Half Life guys came up (thanks, Ralf!).

Now, what does this have to do with Visual Basic? Well, nothing. But development ideas can come from the strangest sources so it's good to keep your horizons open.

At any rate, I'd read about something similar in Wired several months ago, but apparently the concept got picked up by the Half Life team and fleshed out fully. (disclaimer: I have no idea whether I've got the backstory straight or not, that's just how I remember reading it)

Essentially, you wield a interdimensional portal generator, but just think of it as a gizmo that you fire once at a flat surface to open one side of a tunnel, and fire it at another surface to open the other end. Then you step through one end and end up at the other, complete with all the inertia you had going in. You can see the portals in this screenshot:

image 

(image from the wikipedia entry)

Pretty slick idea and it opens up a ton of puzzle possibilities in a game.

But it reminded me of a concept I had years ago, but to date haven't seen it implemented, but then, maybe I live in a bubble.

Basically, I see the game as a typical first person shooter, land based (like Quake, not like Descent), but the trick is that all the walls, and the ceiling exhibit gravity, either consistently or in varying amounts and not just a few, but all of them. Hence, you can walk not only on the "ground" but on the walls and ceiling, which effectively means there are no walls or ceilings. If you ever read Orson Scott Card's Ender's Game, the battle training sequences described there would give a pretty good picture.

Surely, it's been done before, but does anyone know by whom?

posted on Tuesday, October 16, 2007 4:45:16 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, October 12, 2007

Take one Bally pinball plunger off EBay (7.99$)

image

Hack together a plate and attach a roller microswitch to it (4.95$ at Happ, but 1.98$ at Fry's)

image 

Position things such that the switch is pressed when the plunger is at rest, but when you pull back on the plunger, the switch releases, like so:

image

(it's a vertical picture, normally the plunger sits horizontally)

Then wire the NC (normally closed) connection to an Ultimarc IPAC as just another pushbutton.

Finish up with a couple of Competition pushbuttons mounted to the side of your desk (or some other convenient place).

Now, load up Visual Pinball, grab a few table files (the Black Hole table is especially nice), crank the volume way up, and line up some quarters on the lip of your monitor, just for some ambience.

Viola!

All (ok, most) of the arcade goodness of the early eighties in a fraction of the space. This nifty thing is, wired up this way, when you pull back on the plunger, the switch is released, which, because of wiring it to the NC connection, has the effect of pressing and holding that button, until you release the plunger. This exactly models the Visual Pinball concept of pressing the Enter key and holding down on it longer to "pull farther back" on the plunger. Granted, it's not as exact as, say, a cog driven mouse wheel hack, but it seems to be pretty accurate so far and it was a heck of a lot simpler.

Next? Hook a tilt plumb bob

image

to several contacts to simulate a full-on tilt mechanism (Visual Pinball supports tilt-left and tilt-right keys, not sure about other pinball sims).

posted on Friday, October 12, 2007 4:37:48 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Sunday, October 07, 2007

If you've messed with SQL much, invariably, you get into a situation where you have to ask "What has changed in the database from the last version to this version?"

There's some good tools out there for this, to be sure.

AdeptSQL is my favorite, but RedGate SQL Compare is very good. And DBGhost is highly regarded for synchronizing db schemas (and data to a degree, I believe).

But sometimes you want (or need) to "roll your own".

So, how would you do that?

First, the criteria:

  • I need to be able to compare both schema and some "seed data" in certain tables (for instance, pre-populated lookup tables, etc)
  • I'd like to see any significant changes, but everyone's definition of significant can be different.
  • I'd like the changes to be "easy" to read. They need to pop out if possible

Next, the display.

Well, there's plenty of file comparison utilities out there, and my favorite is Araxis, so why not just use it to do the "delta-generation" heavy lifting.

I'll need two text files to compare, the "before" version and the after.

I need them grouped by schema object type (stored-procs in one section, table definitions in another, data values in another, etc).

And I need them ordered consistently so that any changes are easy to spot.

I ended up a with a stored proc that, at least right now, does most of what I need.

The basic idea is that you call DBOutput, and it then calls itself recursively to resolve and output all the various elements of the database that you might be interested in comparing. Why a recursive procedure instead of several separate procs? No earth-shattering reasons. It's just easier to deal with one proc for this sort of thing that several separate ones.

The routine itself pretty simply, and it doesn't yet have to resort to directly reading the system tables for any information. It makes heavy use of the INFORMATION_SCHEMA views to retrieve most information. It should run under MSSQL 2000, MSDE, 2005 Express and 2005, but I haven't tested it under the 2000 variants. 

Probably most notable is the use of sp_helptext to retrieve the actual full definition text of stored procs and views.

You'll have to excuse the formatting, pasting into HTML seems to have messed up my nice tabs.

ALTER PROCEDURE [dbo].[DBOutput] (
    @TableName sysname = null,
	@SPName sysname = null,
	@ViewName sysname = null
    )

AS

DECLARE @Rows as int

SET NOCOUNT ON

if	len(IsNull(@TableName, '')) = 0 and 
	Len(IsNull(@SPName,'')) = 0 and 
	Len(IsNull(@ViewName,'')) = 0 BEGIN

    -- do all tables
    declare @Table sysname
	declare @Routine sysname
	declare @View sysname
    declare @i int
    
    DECLARE cCursor CURSOR FOR
        select lower(Table_Name) from Information_Schema.Tables
        where table_type = 'base table' order by Table_Name
    
    OPEN cCursor
    FETCH NEXT FROM cCursor INTO @Table
    WHILE @@FETCH_STATUS = 0 BEGIN
        -- check for tables we don't care about
        if @Table in ('sysdiagrams'
                     )
            -- do nothing
            set @i = 0
        else if left(@Table,2) in ('ms', 'xx')
            set @i = 0
        else if left(@Table,3) in ('sys')
            set @i = 0
        else if left(@Table,6) in ('dtprop')
            set @i = 0
        else BEGIN
            exec DBOutput @Table, ''
            END
    
        FETCH NEXT FROM cCursor INTO @Table
        END

    CLOSE cCursor
    DEALLOCATE cCursor


	-- now Scan all the Stored Procs and functions
    DECLARE rCursor CURSOR FOR
        select lower(ROUTINE_NAME) from Information_Schema.Routines order by ROUTINE_NAME
    
    OPEN rCursor
    FETCH NEXT FROM rCursor INTO @Routine
    WHILE @@FETCH_STATUS = 0 BEGIN
        -- check for Stored Procs and functions we don't care about
        if @Routine in (''
                     )
            -- do nothing
            set @i = 0
        else if left(@Routine, 3) in ('sp_', 'fn_')
            set @i = 0
        else BEGIN
            exec DBOutput '', @Routine
            END
    
        FETCH NEXT FROM rCursor INTO @Routine
        END

    CLOSE rCursor
    DEALLOCATE rCursor


	-- now Scan all the Stored Procs and functions
    DECLARE vCursor CURSOR FOR
        select lower(TABLE_NAME) from Information_Schema.Views order By TABLE_NAME
    
    OPEN vCursor
    FETCH NEXT FROM vCursor INTO @View
    WHILE @@FETCH_STATUS = 0 BEGIN
        -- check for Stored Procs and functions we don't care about
        if @View in (''
                     )
            -- do nothing
            set @i = 0
        else if left(@View, 3) in ('sp_', 'fn_')
            set @i = 0
        else BEGIN
            exec DBOutput '', '', @View
            END
    
        FETCH NEXT FROM vCursor INTO @View
        END

    CLOSE vCursor
    DEALLOCATE vCursor

    RETURN
    END


-- handle a single table recursively
IF Len(@TableName) > 0 BEGIN
	DECLARE @PrimaryKeyColumn nvarchar(64)
	DECLARE @sql nvarchar(1000)
	DECLARE @Parms nvarchar(1000)
	DECLARE @Temp sysname

	-- Primary key info (we're not using yet)
	SELECT @PrimaryKeyColumn = [COLUMN_NAME] FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
	   WHERE [TABLE_NAME] = @TableName

	-- dump a list of the columns, with pertinent info
	PRINT replicate('*', 60)
	PRINT replicate('*', 60)
	PRINT 'TABLE: ' + @TableName 
	EXEC ('SELECT ORDINAL_POSITION as ColNum, left(COLUMN_NAME, 20) as ColName, left(DATA_TYPE, 20) as DataType, Left(COLUMN_DEFAULT, 20) as DefaultValue, CHARACTER_MAXIMUM_LENGTH as Len, IS_NULLABLE from INFORMATION_SCHEMA.COLUMNS WHERE Table_name = ''' + @TableName + ''' order by ColNum')

	-- dump a list of the indexes, with pertinent info
	-- also includes RI information associated with the keys (if any)
	EXEC ('SELECT	Keys.ORDINAL_POSITION as KeyNum, 
					left(Keys.CONSTRAINT_NAME,30) as IDXName, 
					left(Keys.COLUMN_NAME, 20) as ColumnName, 
					Left(RI.UNIQUE_CONSTRAINT_NAME, 20) as PrimaryKey, 
					RI.Match_Option, 
					RI.Update_Rule, 
					RI.Delete_Rule,
					CHK.Check_Clause
			FROM information_schema.Key_Column_Usage as Keys 
					left outer join information_schema.referential_constraints as ri on keys.Constraint_Name = ri.Constraint_Name 
					left outer join information_schema.check_constraints as chk on keys.Constraint_name = chk.Constraint_Name
			WHERE Table_name = ''' + @TableName + ''' 
			order by KeyNum, IDXName')

	-- check if there's any data in the table. If so, dump it all
	set @SQL = 'SELECT @Cnt = Count(*) FROM ' + @TableName
	set @parms = '@Cnt int OUTPUT'
	EXEC sp_executesql @SQL, @Parms, @Cnt = @Rows OUTPUT
	IF @Rows > 0 BEGIN
		PRINT replicate('~', 60)
		PRINT 'TABLE DATA FOR:' + @TableName
		EXEC ('SELECT * FROM ' + @TableName)
		END
	PRINT ''
	PRINT ''
	END


-- Handle all stored procs here
IF LEN(IsNull(@SPName, '')) > 0 BEGIN
	PRINT replicate('*', 60)
	PRINT replicate('*', 60)
	PRINT 'STORED PROCEDURE/FUNCTION: ' + @SPName
 
	-- pull the entire text of the proc into a variable to let us print it nicely
	EXEC sp_helptext @SPName
	PRINT ''
	PRINT ''
	END


-- Handle all Views
IF LEN(IsNull(@ViewName, '')) > 0 BEGIN
	PRINT replicate('*', 60)
	PRINT replicate('*', 60)
	PRINT 'VIEW: ' + @ViewName
 
	-- pull the entire text of the proc into a variable to let us print it nicely
	EXEC sp_helptext @ViewName
	PRINT ''
	PRINT ''
	END


SET NOCOUNT OFF

Basically, you just open up Enterprise Manager, and exec DBOutput. It's best to put the results window in TEXT mode as opposed to grid mode. Then you can simply save the output to a text file and use your favorite file compare to get a delta. All the PRINTs are there just to make things a little more legible in a typical "text editor" view.

Let me know what you think, or if there's additional bits to report that might be useful.

Also, I plan to update this script over time, and I'll post the updates here.

posted on Sunday, October 07, 2007 9:24:17 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

I was reading an article about the Chinese space program and it kinda reminded me of a few projects I've worked on.

Don't get me wrong, working on a space program could possibly be the geek's ultimate dream, and I'm all for space exploration. Mission to Mars, manned stations, space tourism, you name it.

But think about it this way:

They're working on sending an exploratory mission up sometime in 2012, and then a manned mission around 2024. This is pretty cool in and of itself.

But then, somebody else has already done it.

Those on that project would seem to be in a pretty thankless position.

  • If they succeed, after years of work, they'll get a "Congratulations, you've done something that was done almost 50 years ago, again."
  • And if they fail, they'll get "Somebody else did this 50 years ago, you've got 50 years worth of modern tech behind you and you still couldn't pull it off."

Makes me wonder how many IT projects wind up in that same boat.

It also seems that the safest way out for everyone involved is for the project to simply get canceled. Maybe that's why so many are.

And therein would be the benefits of being first, even with the inevitable casualties of diving headlong into uncharted waters, literally or metaphorically.

posted on Sunday, October 07, 2007 9:16:39 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Friday, October 05, 2007

The formatting of the code snippets I post from time to time have always bothered me.

They didn't wrap, which is good (wrapping code just annoys the hell out of me), but they trailed off the edge and just appear to get cut off.

If you selected the text, it was all there and could be copied to the clipboard, but... well.... yuck.

Anyway, I finally figured out how to change the CSS such that you get a nice little scrolling window for the snippet.

I should point to Scott Hanselman's blog as the source of the example I found, but I did tweak it a bit.

Here's an example:

Public Function AppVersion$()
   '---- Get the Current Application file's "Full" version info
   Dim p$

   If IsIDE() Then
      '---- just pull from the App values (because the version resource
      '     isn't available
      AppVersion$ = App.Major & "." & App.Minor & ".0." & App.Revision
   Else
      '---- get from the VersionInfo resource, will include the real revision and build numbers
      AppVersion$ = GetFileVersion$(App.Path & "\" & App.EXEName)
   End If
End Function

Since this is purely a CSS thing, it works backwards in all my old posts, and it appears to be (so far) cross browser compatible.

Here's the style (I've enclosed it in a STYLE tag so you can embed it directly in a html file, otherwise, just use the PRE definition).

<style type="text/css">
pre {
	border: 1px solid #e3a83d;
	border-left-width: 2px;
	background-color: #444;
	padding: 1em;
	margin: 2em;
	line-height: 1.2em;
	overflow: auto;
	width: 90%
	font-size: small;
	color: white;
	font-family: consolas, "Courier New", courier, monospace;
}
</style>

Yeah, it's probably pretty minor stuff for all the CSS genii out there, but I'm still surprises with what you can do with a little CSS. Makes me really want to get into the whole WPF thing. 

posted on Friday, October 05, 2007 4:46:43 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [1] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

And in the "Oow, I'm glad I'm not in that department" department.

The latest Woody's Office Watch reports that Excel 2007 can't format 2^16 or 2^16-1 properly, instead deciding to show it as "100,000"

image

Heh. It's not too bad. According to Microsoft, the bug only affects 12 numbers (or is it 2, I'm not sure, but the quote says 12) out of a virtually infinite number space. From Woody's; "They (Microsoft)point out that Excel can use " 9.214*10^18 different floating point numbers " but the bug only affects 12 of that incredibly large number of possibilities".

Oh man. Can I use that one with my next off-by-one mistake? (hey, I don't make 'em much anymore but still...).

posted on Friday, October 05, 2007 4:40:10 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [4] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Tuesday, October 02, 2007

Well, through an amazing set of circumstances, including spam filters gone awry, old email addresses that were no longer valid, too many years and too few vacations, somehow my domain name expired without so much as a whimper (that is, until I started getting IM from people and email through different accounts that went something like, "Hey, where's your domain at, dude!?").

Anyway, things are back up now, with all the pointers, DNS entries and hostnames back in place.

Also, I've obtained the domains vbfengshui.com and visualbasicfengshui.com, so they also point here, with all the mail and DNS forwarding goodness that is this series of tubes.

posted on Tuesday, October 02, 2007 10:53:46 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 
 Monday, October 01, 2007

No, I don't do Don Henley impersonations. But I was starting a load of laundry the other day and something struck me. Once I'd recovered and put the broom back where it was supposed to be, I realized something.

Take your standard, run of the mill washing machine. It's essentially a variable speed motor, a few belts and pulleys, a water pump, and big cog timer wheel that sort of looks like the sheet music for a player piano*.

These things will run several times a week, for years on end and only rarely have any issues. Even then, the most likely problems are things like belts wearing out, bearings getting squeaky, or the timer cogs breaking off causing cycles not to start or stop properly. Further, I'd be willing to bet that just about anyone can set one up, plug it it, connect the 2 water hoses and the drain line, and be doing laundry within 30 minutes to an hour, and just about never think about any of that "configuration and setup" again till they move. 

Compare that, then, to the lastest washing machines, with "steam" cleaning, LCD consoles, touchscreens, dozens of "operating modes", delay start timers, and even internet access.

I love gadgets, but seriously, does being internet-enabled or using an LCD touch screen get my clothes any cleaner, or prevent me from actually having to lug my laundry to the utility room? Do they actually save real people any time whatsoever over a 200$ (or cheaper) model with knobs instead of touchscreens and mechanical timers instead of microchips? Is this really progress?

I gotta stop hitting myself with brooms.

* and yes, that's an oversimplification, for all you washing machine enthusiasts out there.

posted on Monday, October 01, 2007 7:09:17 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions; 

I originally wrote about digital signatures in Office documents way back here, so check there for more information. But I just stumbled across something with Word 2007 and document signing (including signing templates) that had me scratching my head for a minute.

Word, in it's shiny new 2007 skin, now has a nifty little feature to add a signature to a document (or template) right on the...um... what they hell you do call this button?

image

Anyway, under "Prepare", you'll see this:

image

The Add a Digital Signature lets you sign the document right there. Which is great.

Except for one thing.  That signature is not the same as this one:

image

The former actually signs the document, whereas the latter signs the VBA code contained in the document.

If you don't believe me, sign a document using the Prepare menu item, then check the signature using the VBA/Tools/Digital Signature menu item. Then sigh and weep.

So, what does that matter, you ask?

Well, in terms of checking the validity of macro code in a document, from what I can tell so far, the signature on the document isn't checked, only the signature on the VBA code. From a macro/VBA standpoint, signing the document is pretty useless.

I'm still hunting for a way to automate the signing of DOC and DOT files (such as the SIGNTOOL.EXE utility for signing DLL's and EXE's). That would make the whole process much more convenient, not to mention enabling it to be built into a normal build process.

posted on Monday, October 01, 2007 6:54:10 PM (Central Standard Time, UTC-06:00)   •  # •  Comments [2] • 
Kick it •  Add to del.icio.us •  View blog reactions;