Small code with powerful results, the occasional opinion … and beer. 

02 Mar 2009
I’ve got the Power! … but not over power properties?!

I have been encountering a really annoying behavior in Windows XP Media Center on my laptop, and the solution is really elusive.  It is arbitrarily changing my power settings for hibernation and standby.

The problem occurs intermittently, when I change my point of access between RDC and the laptop’s keyboard.  I normally set the power properties a particular way for outlet power: hibernate = never, and standby = never.  I also only have one power profile defined.

If I power up the laptop (awakens from hibernation), and login at the laptop itself, the power properties are correct.  If I power up the laptop, but make my initial login via RDC, there’s a better than 50% (but not 100%) chance that my standby property will mutate to 20 minutes.  It is frustrating to issue an RDC connection request, watch it get stuck at “connecting”, and discover that the standby light is flashing on the laptop.

I’ve scanned for viruses, and find none.  I also can’t find anything on Google referencing the problem.  Is this a bug, by design, or some weird interaction with advanced power management on the motherboard?  If anyone has any explanation or reference to the problem/solution, I’d like to hear from you.

21 Feb 2009
Scraping… in VBScript

So this is a tidbit I discovered while cleaning out some files.  It is a VBScript class to do basic scraping, and a test script to validate the methods in the class–and scrape a web page to an XML document.  The WSF file is the execution file, which links the two VBS source files together.  To run it, unpack it a folder, and at a command-line with that folder as the current directory, enter…

1
cscript  ScrapeClass_TEST.wsf

The class, as is, can’t do anything beyond capturing the response stream from a simple request, and the extraction is hard-coded for the page it targets.  Yet it does demonstrate what is possible with VBScript and ActiveX objects.

It’s older code, but enjoy.  You can download the code in the Download page (ScrapeClass_vbs.zip).

10 Feb 2009
The Clipboard Munger Project

I’ve release the initial version of Clipboard Munger.  Follow the links on the Projects page for details.

03 Feb 2009
Hibernating or switching network connections… Part 2

In my previous post about RDC, I didn’t fully like the batch file I wrote which required me to specify a parameter for swapping connections, versus hibernating.  In fact, my favorite batch file is one where the action can be specified by the operator, or on the command line directly as a parameter.

So I rewrote the COMMSWAP.bat process to do just that.   If you launch it with no parameters, it displays a list of options which the operator can select with a letter and pressing ENTER.  If it launched with a parameter, it will directly execute the action (useful for schedule or other event launches).

The functionality hasn’t changed.  You can also see an example of how SET /P variable=”prompt” is used to prompt the user and record the answer.  Enjoy.

(Commswap.bat)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@echo off
 
REM CommSwap.bat -- release network resources (IP addresses), optionally put the laptop
REM into hibernation or shutdown. Reads the command line argument, or prompts user if no argument supplied.
REM
REM CommSwap.bat s   -- (swap) release network resources, wait 5 seconds, then renew resources.
REM CommSwap.bat h   -- (hibernate) release network resources, then hibernate.
REM
 
set xAction=%1
if not "%1" == "" goto CHECKPARM
 
:ASK
echo.
echo Network resource release, with optional renew.  Use this process when
echo switching between wireless and wired connections, or to gracefully
echo prepare the computer to hibernate before changing location.
echo.
echo Select from the following:
echo.
echo S -- (Swap) Release all adapters, wait 5 seconds, the renew all adapters.
echo H -- (Hibernate) Release all adapters, then initiate hibernation.
echo X -- Exit: don't do anything.
echo.
set /P xAction="Enter your choice: "
 
:CHECKPARM
if /I "%xAction%" == "h" goto OK
if /I "%xAction%" == "s" goto OK
if /I "%xAction%" == "x" exit
 
echo.
echo Command "%xAction%" not understood.. please try again.
goto ASK
 
:OK
 
ipconfig /release *
 
if /I "%xAction%" == "h" start rundll32 Powrprof.dll,SetSuspendState
if /I "%xAction%" == "h" exit
 
echo Waiting 5 seconds for you to disconnect a network cable or
echo turn off the wireless connection.
echo.
echo WScript.Sleep (5000) > c:\$$tmp$$.vbs
cscript //nologo //b c:\$$tmp$$.vbs
del c:\$$tmp$$.vbs
 
ipconfig /renew *
 
exit

20 Jan 2009
When an empty string is really an empty string…

Sometimes clarity of thinking during late night programming is elusive…

1
(footerMessage.Trim().Length == 0 ? string.Empty : footerMessage + "\r\n\r\n")

There are two bloopers in this one line of code:

  • a string with nothing but spaces really is an empty string (for practical reasons).
  • it would be nice to put two line breaks on the empty string as well as the non-empty string.

So file this one under redundant, i.e. A practical dictionary entry for Redundant would say “See Redundant”

Note: if your not familiar with this construct, it is a carry over from the C-language.  It simplifies an if statement, when the goal is to return one of two values to a variable, depending on the result of a boolean test.  These quick tests can be quite elaborate, but are most effective for generating max, min, floor (etc, etc) functions where the evaluation is short.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
/* usual evaluation */
 
int Productivity_Percent = 90;
 
float AnnualPay = 35000;
 
if (Productivity_Percent > 85) Annual_Pay = 50000;
 
/* ... or ... */
 
int Productivity_Percent = 90;
 
float AnnualPay = Productivity_Percent > 85 ? 50000 : 35000;

The immediate eval is not always efficient.  Usually the code must evaluate both possible answers, before evaluating which one to return–which can waste time on unneeded work. And in the case above, the if statement is actually slightly more efficient than the immediate eval.

It can be very useful for an inline lookup when initializing a value, instead of using a case statement.  E.g.

1
2
3
4
public void Initialize (string Method)
{
   int PortValue = (Method == "Full Power", 37, (Method == "Half Power", 45, 7));
}

02 Dec 2008
Einstein’s Challenge… are you in the 2% category

Einstein’s Challenge is something that I had heard sometime in my life, but never checked it out…until one of our developers emailed it to the development staff this week.  I was intrigued, since I love puzzles and deductive logic is my favorite brain exercise.

If you want the puzzle itself, do three things:

  • Be honest with yourself and don’t Google the answer ahead of time.  This was good advice from the developer who sent it, and I’m glad I followed it rather than looking for a quick way out.
  • Don’t worry about whether it’s from Einstein or not.  I’m sure someone slapped his name on the puzzle at some point to draw public interest.
  • Go to this page to read the puzzle, and don’t click the link to go to the solution (yet)…Einstein’s Challenge (GreyLabyrinth.com)

I solved it without any help, so I guess that puts me in the 2% category–whatever that means.  I have also had various IQ tests on cereal boxes rate me at 144 or above, but I guess the results of those tests are far more reliable if you take the test before you reach your 40’s.

I really liked this puzzle.  I was not only surprised at how much I learned about my own approach to solving the puzzle, I have also enjoyed watching other people try to solve it.  Not only are there different approaches taken by different people, but watching people’s body language when they are perplexed is really amusing.  If you put my wife’s scratch paper next to mine, you will see two entirely different sets of diagrams and notes.  I still don’t understand her line of thinking, but she did solve it.  I guess the result justified the means.

Read on only if you’ve tried the puzzle and solved it–or given up.  I’ll share with you how my mind approached the problem: I’ll leave the judgment of efficient or inefficient to you.

Reading and Absorbing

As I read through the description of the problem, the FACTS OF THE PUZZLE were describing five entities, with five unique items–one item from each of five groups.  That was easy.

And as I read the facts which had to be true, I began finding myself fighting prejudice and stereotypes in my thought.  This was somewhat expected, because I have an associative mind: part of that from training in Military Intelligence, most of it from watching Robin Williams too much.

The real trick to solving a problem is rational thinking, and a lot of questions instead of assumptions.  So I would read about the Swede and the Norwegian and think their houses must be log cabins with mud roofs, or that it was weird that the Dane drank the tea instead of the Britt.  So I had to quickly admit to myself that part of solving the puzzle was to avoid preconception as a means of self-deception.

Towards the end of the facts, I noticed a pattern of 5 in all the “who owns what” descriptions, except for pets which had only four.  So the question of “Who has the fish”(which sounds like something out of Monty Python–associative mind again) didn’t throw me off.  It was obvious that it would reveal itself by the process of elimination.

So I took what I grasped from the concept, and drew five boxes, left to right (I figured that was important since the Norwegian lived in the left house–not the top house).  In each of the houses, I drew five lines to represent the properties of the house.  Line 1 for Nationality, line 2 for house color, line 3 for liquid, line 4 for pet/livestock, and line 5 for tobacco product.

BTW: I consider a horse livestock, not a pet.  A pet can stay in the house with you, and if you have a horse that really is your pet, I won’t be visiting you anytime soon.

The Solving Process

I found it quite easy to determine the color of the houses from facts 9, 15, 4, 5 and 7 (the latter mentions the fifth color of Yellow).  After I had done this, I didn’t like the jumping around I was doing in the list of facts, and I found myself wanting to reorder/regroup the facts, thinking that might keep the solution more logical.  But I realized there was really nothing to gain by that, so I just took a pen and crossed off those I was sure was right and kept the order as it appeared.  I remembered an old adage at this point: focus on the accomplishment, not the activity.

After establishing which house was which color, the Yellow house (7) led to Dunhills (7), which led to horses  (11).  No problem there.  At this point, the obviously easy fits come to an end.  Looking at the other facts, it’s clear there are multiple fits that are possible in all cases.  This is probably the point in the exercise where it becomes difficult for the 98% who don’t solve it.

And I notice a peculiar thing: two of the clues show an association to a common item: Blend and Water are in neighboring houses, and Blend and Cats are also in neighboring houses.  But I also smell something fishy, which could be a good thing: maybe it helps to find the house with the fish.

As I visualize the description in my head and draw out the relations as circles with connecting lines, I see two possibilities: either Blend has water on one side and cats on the other side, or both water and cats are actually in the same house.  So my first reaction is to read the text again:  does it exclude the possibility that the two (water and birds) belong in the same house.  No, the text does not exclude it.

I see the potential for being caught in a maze because of an ambiguity here.  In a maze, you have three pieces of information: there is an entrance, there is an exit, and there is a unobstructed path between the two.  But there are, of course, a number of obstructed paths that you can take which are are wrong.  A maze has to be solved by trial and error: a wrong turn in the maze leads to a dead end, and requires a certain amount of backtracking before trying another route.  The way to make this easier is to mark the trails you have tried, so there is some history of past failures to refer to when deciding on another path to try.

I realize that some trial and error will now come into play at this step, so I copy what I have (i.e. what I am sure of) to a separate paper, and treat it as my test area.  I also now use a pencil.  These are my breadcrumbs.

I decide to go with a hunch that cats and water are in the same house is probably right, since it is not obvious. That’s just from experience with past logic problems: they try to trick you like that.  Now my important decision: do I test for the negative (separate houses which I think is wrong), or the positive (same house which I think is right).

I decide to test for the negative: I assume different houses, and check a few possible combinations of tobacco, drinks, people, etc, in the leftover spots to verify that I get consistent dead ends every time.  Each time I did this (luckily it was only twice), that was what happened.

So I go back to the combination I think is right: that water and cats belong in the same house.  I initially put them in house 5, and that hits a dead end.  The only other place they logically can go is in house 1, and then all of the other clues just fall into place–at least for me.

Hindsight

I’m not sure the step of checking for the negative was the best choice, but it didn’t matter with the pencil: I was quickly correcting errors, and the “what if” approach also helped me to see patterns and pairings in the remaining clues.  So I felt it was worth the extra time.

The real challenge I had in solving this puzzle was putting a stop to the creative thinking, and just use objective thinking. and pure deductive logic: i.e. stick to the facts.  Drawing things out on paper was the best way to enforce that discipline.

——————

Later on my wife showed the puzzle to my sons. My 18 year old solved it this evening in 20 minutes.  It is now late in the evening and my 15 year old son, after 30 minutes on the problem, has just blurted out that he is convinced the Norwegian lives in either a yellow or a white house.   I may have to impose a cut-off time tonight, but I admire his tenacity.

10 Oct 2008
Opinion: Tomato or To-mah-toe..it’s a great firmware for your router.

I’ve used home/home office based routers since Comcast installed my first cable internet connection.  One of my favorites was the BEFR11S4 from Linksys.  It was a very nice, simple design which had nice features like syslog.  Linksys even provided a utility to capture the syslog packets, which provided not only network management actions, but the connections being made both inbound and outbound on the network.  But alas, the Linksys routers began to lock up arbitrarily under heavy traffic load.. and I began to look elsewhere.

Currently, I use a Buffalo WHR-HP-G54 which is very nice and reliable. I’d encountered a good alternate firmware, DD-WRT, for which its users highly recommended this Buffalo router as a good solid router.  I was able to leverage Virtual LAN’s and other features with this firmware, which I had only seen in commercial-grade routers.  But DD-WRT didn’t have the connection information provided by the Linksys router, which I liked so much–and really found useful.  Since I have kids and data to protect (in that order), I needed to monitor the connection. I also wanted to keep the fine-tuning and security offered by DD-WRT.

So, enter Tomato… another alternate to your router’s firmware.  What impressed me with this firmware is that not only did it easily load using the DD-WRT’s administration page, it copied all of my settings from the configuration.  I was able to have the router fully configured and running in less than 10 minutes.  Impressive.

The management page is also very simple and easy to navigate.  I currently am working on a CSharp service to capture the syslog packets related to connections, and persist them in a SQL Server 2005 Express database.  I’ll post that code once I have it finished.

One other nice feature of Tomato: graphical representations of bandwith loading.

The site for tomato is here: http://www.polarcloud.com/tomato/

30 Sep 2008
Anti-Collision Work Queue Retrieval: an approach using GUIDs

I recently wrote a high-volume, fast paced application which supports many threads reaching into a work queue (SQL table) and retrieving a row representing a piece of work.  The table uses an identity column as its primary key.

In other work experiences, I have seen SQL locking hints used to prevent two threads from grabbing the same piece of work.  Mostly, the locking hints are READPAST, and some form of ROWLOCK, PAGELOCK or TABLELOCK.  In high volume environments, the locking hints have periodically failed, and occasionally the same piece of work goes to different threads.

I decided to try a different approach which isolates the allocation away from SQL internal locking  mechanisms, although the locking mechanisms can be used.  In my table, the work is marked as assigned, and later is marked as either failed/reassign or successful, or can timeout and become available for reassignment.  In other words, the row survives the work completion: it is not deleted upon retrieval.

The technique uses a column in the work queue table called Process_Marker, which is a nullable uniqueidentifier.  When a piece of work is injected, this column is left null.  When the worker calls the stored procedure to retrieve a piece of available work, the stored procedure creates a GUID using NEWID() in a static variable, and attempts to update the Process_Marker column in an available row (i.e. Process Marker is null) with that GUID.  As its final action, the stored procedure selects all the rows with that particular GUID, and updates any necessary admin columns in those rows.  In the UPDATE statement, the READPAST locking hint is used.

The identity column is used by the workers to report/update results, in case the workers are capable of requesting multiple work pieces in a single call to the stored procedure. The worker just needs to set the Last_Attempt_End_Date to the current system time, to prevent the work from being reissued/retried.

I haven’t done full load testing on it yet, but the results look promising.

Here is the code to create the work table (Job_Queue):

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
 
CREATE TABLE [dbo].[Job_Queue] (
[ID] [BIGINT] IDENTITY(1,1) NOT NULL,
[Priority] [tinyint] NOT NULL CONSTRAINT [DF_Job_Queue_Priority]  DEFAULT ((255)),
[Available_Date] [datetime] NOT NULL CONSTRAINT [DF_Job_Queue_Available_Date]  DEFAULT (getdate()),
[Timeout_Date] [datetime] NULL,
[Attempts] [INT] NOT NULL CONSTRAINT [DF_Job_Queue_Attempts]  DEFAULT ((0)),
[Processing_Mark] [uniqueidentifier] NULL,
[Processing_Server] [VARCHAR](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[Last_Attempt_Start_Date] [datetime] NULL,
[Last_Attempt_End_Date] [datetime] NULL,
[Payload] [VARCHAR](100) NOT NULL CONSTRAINT [DF_Job_Queue_Payload] DEFAULT ('a piece of work in the queue')
CONSTRAINT [PK_Job_Queue] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
 
GO
 
SET ANSI_PADDING OFF
GO

And here is the stored procedure for retrieving one or more jobs, with anti-collision logic (usp_API_Get_Job):

SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
ALTER PROCEDURE [dbo].[usp_API_Get_Job]
 
	@sProcessingServer VARCHAR(50),
	@nWorkCount INT = 1
 
AS
 
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;
 
	DECLARE @xGUID UNIQUEIDENTIFIER
 
	SET @xGUID = NEWID()
 
	UPDATE jq SET
		Processing_Mark = @xGUID,
		Processing_Server = @sProcessingServer,
		Attempts = Attempts + 1,
		Last_Attempt_Start_Date = GETDATE(),
		Timeout_Date = DATEADD (mi, 15, GETDATE())
	FROM Job_Queue AS jq WITH (UPDLOCK)
	INNER JOIN (
		SELECT TOP (@nWorkCount) ID
		FROM Job_Queue WITH (READPAST)
		WHERE
		(Processing_Mark IS NULL AND Attempts = 0 AND Available_Date <= GETDATE())
		OR (
			ISNULL(Last_Attempt_End_Date, '1/1/2000') < Last_Attempt_Start_Date
			AND ISNULL (Timeout_Date, Available_Date) <= GETDATE()
			AND Attempts < 3
		)
		ORDER BY Priority ASC, Available_Date ASC
	) AS jqs
	ON jq.ID = jqs.ID
	WHERE
	(jq.Processing_Mark IS NULL AND jq.Attempts = 0 AND jq.Available_Date <= GETDATE())
	OR (
		ISNULL(jq.Last_Attempt_End_Date, '1/1/2000') < jq.Last_Attempt_Start_Date
		AND ISNULL (jq.Timeout_Date, jq.Available_Date) <= GETDATE()
		AND jq.Attempts < 3
	)
 
	SELECT ID, Payload
	FROM Job_Queue
	WHERE Processing_Mark = @xGUID
END

So I’ll see if this behaves well under some heavy loads..

25 Sep 2008
Triggers in SQL Server: how often are they called

Our dba and I had a short discussion on triggers, because I could not locate an answer to this question:

When inserting, updating or deleting more than one row in a table, is the trigger fired iteratively for each affected row, or once with all the records in the inserted and deleted tables?

To me it seemed logical that multiple rows could appear in these tables, yet I had heard from any number of people that the calls were iterative. The answer was important, since I was writing triggers to populate an audit table, and I needed to account for single rows or multiple rows in the logic.

So with the differing opinions, and not being able to get a definitive written answer, I proceeded to write a test in SQL Server 2005 Express to test the behavior. The test was easy:

First, I created a simple table called primary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[PRIMARY](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Name_Value] [VARCHAR](500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[Updated] [datetime] NOT NULL CONSTRAINT [DF_Primary_Updated] DEFAULT (getdate()),
CONSTRAINT [PK_Primary] PRIMARY KEY CLUSTERED
(
[ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
 
GO
SET ANSI_PADDING OFF

Next, I created Primary_AUDIT, which wrapped extra administrative columns around the original table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Primary_AUDIT](
  [Primary_AUDIT_ID] [BIGINT] IDENTITY(1,1) NOT NULL,
  [ID] [INT] NOT NULL, 
  [Name_Value] [VARCHAR](500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [Updated] [datetime] NOT NULL CONSTRAINT [DF_Primary_AUDIT_Updated]  DEFAULT (getdate()),
  [AUDIT_TYPE] [VARCHAR](25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [AUDIT_DATE] [datetime] NOT NULL,
  [AUDIT_GUID] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Primary_AUDIT] PRIMARY KEY CLUSTERED
(
  [Primary_AUDIT_ID] ASC
) ON [PRIMARY]
) ON [PRIMARY]
 
GO
SET ANSI_PADDING OFF

The AUDIT_GUID column is important for this test. Here is the trigger I wrote for inserts (note that it uses FOR INSERT, but AFTER INSERT also has the same behavior).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
 
CREATE TRIGGER [dbo].[tr_Primary_i]
ON  [dbo].[PRIMARY]
FOR INSERT
 
AS
 
BEGIN
SET NOCOUNT ON;
 
DECLARE @dNow DATETIME
SET @dNow = GETDATE()
 
DECLARE @xGUID UNIQUEIDENTIFIER
SET @xGUID = NEWID()
 
INSERT INTO Primary_AUDIT (
  ID,
  Name_Value,
  Updated,
  AUDIT_TYPE,
  AUDIT_DATE,
  AUDIT_GUID)
  SELECT
  ID,
  Name_Value,
  Updated,
  'INSERTED',
  @dNow,
  @xGUID
FROM Inserted
 
END

Note that both the AUDIT_DATE and AUDIT_GUID are populated with local variables, set before the insert statement, to ensure that every row is updated with the same datetime in AUDIT_DATE, and the same uniqueidentifier in AUDIT_GUID.

So here is the acid test. I load a memory table with 5 rows, and the inject the rows in table Primary using a select statement. This inserts 5 rows.

1
2
3
4
5
6
7
8
9
10
DECLARE @p TABLE (Name_Value VARCHAR(500))
 
INSERT INTO @p (Name_Value) VALUES ('Test0')
INSERT INTO @p (Name_Value) VALUES ('Test1')
INSERT INTO @p (Name_Value) VALUES ('Test2')
INSERT INTO @p (Name_Value) VALUES ('Test3')
INSERT INTO @p (Name_Value) VALUES ('Test4')
 
INSERT INTO [PRIMARY] (Name_Value)
SELECT Name_Value FROM @p

When executed, this code will show five iterations of “1 row(s) affected”, then a single iteration of “5 row(s) affected”. That last line is the first red flag that inserted has more than one row. To verify what was inserted:

1
2
3
SELECT * FROM [PRIMARY]
 
SELECT * FROM [Primary_AUDIT]

Notice that in the AUDIT table, all the unique identifiers are the same. So the inserted table does have more than one row.

Keep this behavior in mind when writing triggers, especially when a trigger needs to write multiple lines for a single row (e.g. OLD and NEW rows for an update) and the information needs some kind of tag to associate the records.

22 Sep 2008
RDC and Hibernation on Windows XP Pro / Media Center

I encountered some strange behavior in Windows XP and Media Center versions, where hibernation or shutdown would hang after the system had serviced RDC connections.  It took a while to solve it.

RDC into a computer that hibernates?! Before jumping to conclusions, it is just a technique I use to access a mobile laptop from my desktop computer, while it is connected at the office.  By RDC’ing into the laptop, it just makes it easier to run both from the same keyboard, mouse and display.

So here’s the scenario:

  • The laptop is brought out of hibernation, I login, and enable the network connection it is going to use.
  • I then go to another computer, and make an RDC connection to the laptop.
  • When complete, I either log off the laptop, or simply disconnect the RDC connection.
  • I then hit the power button to start hibernation, or request a shutdown.
  • Shutdown will start, but hangs during “Saving your settings”.  Hibernation will just not start.  Note: If I login to the laptop on its keyboard before starting hibernation or shutdown, it makes no difference.

The problem seems to occur somewhere in the network layer.  Although the RDC connection was gracefully closed, either a network connection or the RDC service itself is not in a clean state.  Initiating the hibernation or shutdown at this point places the computer in a brain-dead state.  I have had to do a hard reset or power down to recover in each case.

Resolution:

After closing the RDC connection and logging directly onto the laptop itself, performing either of these actions will put the computer into a clean state:

  • Disable the network connection, or
  • Open a console window, and execute ipconfig /release [connection_name] to release the IP address assigned

Just unplugging the cable (or, for wireless, disabling the radio) does not resolve the problem.  By releasing the ip address or disabling the connection in the network connections control panel applet (which releases the ip address as part of the disable), evidently some event is fired which clears out any orphaned dependency at the network layer.

After this action, the shutdown or hibernation sequences will not hang the computer.

<ubergeeking>

So, having solved this, I now use a single batch file through RDC to prepare the laptop to hibernate, and to actually launch the hibernatation process.  It makes getting out of the office a few seconds faster…

😉

1
2
3
4
5
6
7
8
9
10
11
12
13
@echo off
 
REM GoHome.bat -- put the laptop into hibernation after releasing network resources.
 
echo This will release all network adapters and hibernate the computer.
echo Press any key to continue, or Ctrl+C or Ctrl+Break to abort.
echo.
pause > nul
 
ipconfig /release *
 
start rundll32 Powrprof.dll,SetSuspendState
exit

</ubergeeking>