March, 2012

  • The Old New Thing

    Why do program files go into the Program Files directory?

    • 74 Comments

    Some of Microsoft's software certification programs (such as the Windows Logo) require that applications set their default installation location to the Program Files directory. What is the reason for this?

    One technical reason is that this ensures that the directory receives an appropriate default security descriptor. But the Program Files directory was introduced in Windows 95, which didn't have security descriptors, so that can't be the entire reason.

    Rewind the clock to Windows 3.1. Microsoft didn't provide guidance on where applications should install by default. As a result, they went everywhere. Some installed into the root of your C: drive. Some installed to a C:\LitWare directory. Some installed into the Windows directory. It was total chaos.

    Program Files was introduced in an attempt to bring order to chaos. Think of it as painting lines in a parking garage.

    Bonus chatter: I recall an application compatibility investigation from the Windows 95 days. After you installed a particular program, it refused to run. This was clearly a serious problem, even more so when you realized that the program in question was a very popular commercial program. Eventually the source of the problem was identified: When you installed the program, you must accept the default installation location. If you tried to install the program somewhere else, it refused to run. The problem was not caused by Windows 95; you had the same problem if you installed the program on Windows 3.1 to a non-default directory.

  • The Old New Thing

    If you have multiple versions of Windows installed, why do they all try to adjust the clock?

    • 48 Comments

    Commenter Martin notes that if you have multiple copies of Windows installed on your machine, then each one will try to adjust the clock when you enter or exit daylight saving time. "I cannot believe that this feature is a bug. Please could you comment this?"

    This falls into a category of issue that I like to call "So what did you expect?" (This was the catch phrase of the old Call-A.P.P.L.E. magazine.)

    If you have multiple operating systems installed on your machine, each one thinks that it has control of your computer. It's not like there's some standard cross-operating system mechanism for negotiating control of hardware resources. If you install CP/M and MINIX on your machine, each one is unaware of the presence of the other. CP/M doesn't know how to mount MINIX file systems and update a configuration file to say "Hey, I updated the time, you don't need to."

    And not that you would expect it to, either.

    It's like signing up for two housekeeping services and telling both of them to water the plants every Monday. And lo and behold, every Monday, the plants get double-watered. There's no standard protocol for multiple housekeeping services to coordinate their activities; each housekeeping service assumes it's responsible for cleaning your house.

    Bonus reading: Why does Windows keep your BIOS clock on local time?

  • The Old New Thing

    Memory allocation functions can give you more memory than you ask for, and you are welcome to use the freebies too, but watch out for the free lunch

    • 44 Comments

    Memory allocation functions like Heap­Alloc, Global­Alloc, Local­Alloc, and Co­Task­Mem­Alloc all have the property that they can return more memory than you requested. For example, if you ask for 13 bytes, you may very well get a pointer to 16 bytes. The corresponding Xxx­Size functions return the actual size of the memory block, and you are welcome to use all the memory in the block up to the actual size (even the bytes beyond the ones you requested). But watch out for the free lunch.

    Consider the following code:

    BYTE *GetSomeZeroBytes(SIZE_T size)
    {
     BYTE *bytes = (BYTE*)HeapAlloc(GetProcessHeap(), 0, size);
     if (bytes) ZeroMemory(bytes, size);
     return bytes;
    }
    

    So far so good. We allocate some memory, and then fill it with zeroes. That gives us our zero-initialized memory.

    Or does it?

    BYTE *bytes = GetSomeZeroBytes(13);
    SIZE_T actualSize = HeapSize(GetProcessHeap(), 0, bytes);
    for (SIZE_T i = 0; i < actualSize; i++) {
     assert(bytes[i] == 0); // assertion fires!?
    }
    

    When you ask the heap manager for 13 bytes, it's probably going to round that up to 16, and when you call Heap­Size, it may very well say, "Hey, I gave you three extra bytes. Don't need to thank me."

    The problem comes when you try to reallocate the memory:

    BYTE *ReallocAndZero(BYTE *bytes, SIZE_T newSize)
    {
     return (BYTE*)HeapReAlloc(bytes, GetProcessHeap(),
                               HEAP_ZERO_MEMORY, newSize);
    }
    

    Here, you said, "Dear heap manager, please make this memory block bigger, and zero out the new bytes. Kthxbai." And, assuming the heap manager was successful, you will indeed have a larger memory block, and the new bytes will have been zeroed out.

    But the memory manager won't zero out the three bonus bytes it gave you when you called Heap­Alloc, because those bytes aren't new. In fact, the heap manager assumes that you knew about those three extra bytes and were actively using them, and it would be rude to zero out those bytes behind your back.

    Those bytes you didn't know about since you didn't check.

    You might think the problem is that you mixed zero-allocation modes. You allocated the memory as "Go ahead and give me garbage, I'll zero it out myself", and then you reallocated it as "Can you zero it out for me?" The problem is that you and the heap manager disagree on how big it is. While you assume that the size of it is "the exact number of bytes I asked for", the heap manager assumes that the size of it is "the exact number of bytes I gave you." Those bytes in the middle fall through the cracks.

    Therefore, you might try to fix it by changing your function like this:

    BYTE *ReallocAndZero(BYTE *bytes, SIZE_T newSize)
    {
     SIZE_T oldSize = HeapSize(GetProcessHeap(), bytes);
     BYTE *newBytes = (BYTE*)HeapReAlloc(bytes, GetProcessHeap(),
                                         0, size);
     if (newBytes && newSize > oldSize) {
      ZeroMemory(newBytes + oldSize, newSize - oldSize);
     }
     return newBytes;
    }
    

    But this doesn't work, because of the reason we gave above: Your call to Heap­Size will return the actual block size, not the requested size. You will therefore forget to zero out those three bytes you didn't know about.

    The real problem is in the Get­Some­Zero­Bytes function. It decided to manually zero out the bytes it received, but it zeroed out only the bytes that were requested, not the actual bytes received.

    One solution is to make sure to zero out everything, so that if it is reallocated, the extra bytes gained in the reallocation will also be zero.

    BYTE *GetSomeZeroBytes(SIZE_T size)
    {
     BYTE *bytes = (BYTE*)HeapAlloc(GetProcessHeap(), 0, size);
     if (bytes) ZeroMemory(bytes,
                           HeapSize(GetProcessHeap(), bytes));
     return bytes;
    }
    

    Another solution is to take advantage of the memory manager's HEAP_ZERO_MEMORY flag, which tells the memory manager to zero out the entire block of memory when it is allocated:

    BYTE *GetSomeZeroBytes(SIZE_T size)
    {
     return (BYTE*)HeapAlloc(GetProcessHeap(),
                             HEAP_ZERO_MEMORY, size);
    }
    

    … and to use the same flag when reallocating:

    BYTE *ReallocAndZero(BYTE *bytes, SIZE_T newSize)
    {
     return (BYTE*)HeapReAlloc(bytes, GetProcessHeap(),
                               HEAP_ZERO_MEMORY, size);
    }
    

    Most of the heap functions let you specify that you want the heap manager to zero out the memory for you, and that includes the bonus bytes. For example, you can use GMEM_ZERO­INIT with the Global­Alloc family of functions, and LMEM_ZERO­INIT with the Local­Alloc family of functions. The annoying one is Co­Task­Mem­Alloc, since it does not provide a flag for zero-allocation. You have to zero out the memory yourself, and you have to do it right. (The inspiration for today's article was a bug caused by not zeroing out the memory correctly.)

    There are other implications of these bonus bytes. For example, if you use Create­Stream­On­HGlobal to create a stream on an existing HGLOBAL, the function uses Global­Size to determine the size of the stream it should create. And that value includes the bonus bytes, even though you may not have realized that they were there. Result: You create a stream of 13 bytes, but somebody who tries to read from it will get 16 bytes. You need to make sure that the code which reads from the stream won't get upset by those extra bytes. (For example, if you passed it to a function that concatenates streams, you just inserted three bytes of garbage between the streams.) You also need to be careful that those extra bytes don't leak any sensitive information if you, say, put the memory block on the clipboard for everyone to see.

    Bonus chatter: It appears that at some point, the kernel folks decided that these "bonus bytes" were more hassle than they were worth, and now they spend extra effort remembering not only the actual size of the memory block but also the requested size. When you ask, "How big is this memory block?" they lie and return the requested size rather than the actual size. In other words, the free bonus bytes are no longer exposed to applications by the kernel heap functions. Note, however, that this behavior is not contractual; future versions of Windows may start handing out free bonus bytes again. Note also that not all heap managers have done the extra work to remember the requested size, and they will continue to hand out bonus bytes. Therefore, you must continue to code defensively and assume that bonus bytes may exist (even if they usually don't). (And note that heap debugging tools may intentionally generate "bonus bytes" to help flush out bugs.)

    Double extra bonus chatter: Note that this gotcha is not specific to Windows.

    // resize a block of memory originally allocated by calloc
    // and zero out the new bytes
    void *crealloc(void *bytes, size_t new_size)
    {
     size_t old_size = malloc_size(bytes);
     void *new_bytes = realloc(bytes, new_size);
     if (new_bytes && new_size > old_size) {
      memset((char*)new_bytes + old_size, 0, new_size - old_size);
     }
     return new_bytes;
    }
    

    Virtually all heap libraries have bonus bytes.

  • The Old New Thing

    Why doesn't the Maximize button maximize across all monitors?

    • 42 Comments

    Cheong wonders why there isn't a way for the Maximize button to maximize a window across multiple monitors. (Troll asks a similar question: Why doesn't Windows support spanned mode for multiple monitors?)

    We tried it that way at first. And we quickly discovered why it was a bad idea.

    Having multiple monitors behave as a single giant display surface creates a bunch of problems, because there's this annoying line down the center that breaks up everything it touches. Everything on one side of the line is on one monitor and everything on the other side of the line is on the other monitor. (And add additional annoying lines if you have more than two monitors.) If that line intersects text, then you have letters chopped in half, and then you have to mentally glue the two pieces back together in order to read them.

    What's even worse, the broken-up-text problem shows up more often than you might expect, because a lot of programs like to do things like center their dialog boxes. The result is that nearly every dialog box that you see is perfectly bisected by the annoying line. The dialog boxes consistently appear in the worst possible location.

    (Accessibility note: The text in this table cell is identical to the text in the previous cell.)

    Having multiple monitors behave as a single giant display surface creates a bunch of problems, because there's this annoying line down the center that breaks up everything it touches. Everything on one side of the line is on one monitor and everything on the other side of the line is on the other monitor. (And add additional annoying lines if you have more than two monitors.) If that line intersects text, then you have letters chopped in half, and then you have to mentally glue the two pieces back together in order to read them.

    What's even worse, the broken-up-text problem shows up more often than you might expect, because a lot of programs like to do things like center their dialog boxes. The result is that nearly every dialog box that you see is perfectly bisected by the annoying line. The dialog boxes consistently appear in the worst possible location.

    It gets worse if the two monitors do not have exactly the same dot pitch, because it means that no matter how you position your monitors, you will never get all the lines of text to line up perfectly. If you carefully align the monitors so that, say, the baselines of the first lines of text match up, you'll find that the bottom lines don't. Not only do your eyes have to navigate the horizontal gap between the monitors, they also have to navigate the vertical gap created by the pixel density mismatch. Which is even harder because the vertical gap varies from line to line.

    It's like that trick where you put a pencil in a glass of water and observe it from the side. Now imagine a glass filled with pencils, and each pencil refracts differently. And now imagine each pencil is a line of text you're trying to read.

    (Accessibility note: The text in this table cell is identical to the text in the previous cell.)

    It gets worse if the two monitors do not have exactly the same dot pitch, because it means that no matter how you position your monitors, you will never get all the lines of text to line up perfectly. If you carefully align the monitors so that, say, the baselines of the first lines of text match up, you'll find that the bottom lines don't. Not only do your eyes have to navigate the horizontal gap between the monitors, they also have to navigate the vertical gap created by the pixel density mismatch. Which is even harder because the vertical gap varies from line to line.

    It's like that trick where you put a pencil in a glass of water and observe it from the side. Now imagine a glass filled with pencils, and each pencil refracts differently. And now imagine each pencil is a line of text you're trying to read.

    Wait, I'm not finished yet. Things get still worse if your two monitors are not the same size. In that case, the virtual screen is larger than the visible region. For example, my monitor arrangement has a landscape monitor on the left and a portrait monitor on the right, with the bottoms of the monitors aligned.

       Virtual monitor   
       
    Monitor 2
       
    Virtual
    Monitor
       
    Monitor 1

    If a window were maximized across the virtual screen, the contents of the upper left corner would not be visible at all!

    Now, there may be specific cases where it would be meaningful to maximize a window across the virtual monitor, and if a program wants to do that, it can certainly implement that on its own. But even for a picture-viewing application, maximizing across the virtual monitor may not be a great idea: Pictures often have people in the center, and then you end up with somebody's head cut in half.

  • The Old New Thing

    To some people, time zones are just a fancy way of sounding important

    • 42 Comments

    As I noted some time ago, there is a standard series of announcements that are sent out when a server is undergoing planned (or unplanned) maintenance. And since these are official announcements, the authors want to sound official.

    One way of sounding official is to give the times during which the outage will take place is a very formal manner. "The servers will be unavailable on Saturday, March 17, 2012 from 1:00 AM to 9:00 AM Pacific Standard Time."

    Did you notice something funny about that announcement?

    On March 17, 2012, most of the United States will not be on Standard Time. They will be on Daylight Time. (The switchover takes place this weekend.)¹

    I sent mail to the "If you have questions, please contact X" address to confirm that they are indeed taking the server down from 1am to 9am Pacific Standard Time (i.e., from 2am to 10am Pacific Daylight Time), pointing out that on March 17th, most of the United States won't be using Standard Time. (I was planning on coming to work, but if the servers won't be back up until 10am, I can sleep in.)

    The response I got back was "The machines will be unavailable from 1am to 9am local time."

    So in fact when they wrote Pacific Standard Time, they didn't mean Pacific Standard Time. They really meant Pacific Time, but we'll stick the word Standard in there because it makes us sound all official-like. In other words, "We're using words not for what they mean but for how they sound." I'm surprised they didn't use military time, just to sound that much more awesome.

    Bonus chatter: Not to be outdone, another announcement said that a particular server would be available from time X to time Y PDT, even though the United States was on standard time. So now I'm not sure what the logic is. Maybe they just pick a time zone randomly.

    Tip to people who write these announcements: Just say "Pacific Time" or "Redmond local time".

    Nitpicker's corner

    ¹ Other parts of the world may change on a different day from the United States.

  • The Old New Thing

    Why does Explorer ignore seconds when sorting by Date Modified?

    • 39 Comments

    A customer reported that Explorer appears to be ignoring the seconds when sorting by Date Modified. The customer was kind enough to include detailed steps to reproduce the problem.

    Start with a folder with several files, sorted by Date Modified.

    Name Date modified Type
    TPS 06-05-2012 Report 6/12/2012 7:00 AM Contoso Document
    TPS 05-29-2012 Report 6/5/2012 11:30 AM Contoso Document
    TPS 05-22-2012 Report 5/29/2012 10:17 AM Contoso Document
    TPS 05-15-2012 Report 5/22/2012 2:35 PM Contoso Document
    TPS 05-09-2012 Report 5/15/2012 11:26 AM Contoso Document
    TPS 05-02-2012 Report 5/9/2012 10:31 AM Contoso Document

    Right-click on the newest file, select Copy.

    Right-click on the blank column on the right, select Paste. This will create a file with the same name, but with "- Copy" appended.

    Press F5 to refresh the view and note the sort order. The copy appears at the top of the list.

    Name Date modified Type
    TPS 06-05-2012 Report - Copy 6/12/2012 7:00 AM Contoso Document
    TPS 06-05-2012 Report 6/12/2012 7:00 AM Contoso Document
    TPS 05-29-2012 Report 6/5/2012 11:30 AM Contoso Document
    TPS 05-22-2012 Report 5/29/2012 10:17 AM Contoso Document
    TPS 05-15-2012 Report 5/22/2012 2:35 PM Contoso Document
    TPS 05-09-2012 Report 5/15/2012 11:26 AM Contoso Document
    TPS 05-02-2012 Report 5/9/2012 10:31 AM Contoso Document

    Highlight the newly-created file, hit F2, and give the document a different name, and also remove the "- Copy" suffix. Hit Enter to accept the operation.

    Press F5 to refresh the view again. Notice that the file that you just renamed, which is the newest file in the folder (it having just been created seconds ago) appears second in the list.

    Name Date modified Type
    TPS 06-05-2012 Report 6/12/2012 7:00 AM Contoso Document
    TPS 06-12-2012 Report 6/12/2012 7:00 AM Contoso Document
    TPS 05-29-2012 Report 6/5/2012 11:30 AM Contoso Document
    TPS 05-22-2012 Report 5/29/2012 10:17 AM Contoso Document
    TPS 05-15-2012 Report 5/22/2012 2:35 PM Contoso Document
    TPS 05-09-2012 Report 5/15/2012 11:26 AM Contoso Document
    TPS 05-02-2012 Report 5/9/2012 10:31 AM Contoso Document

    It appears that Explorer is ignoring the seconds in the Date Modified column and sorting only by the hour and minute.

    It's an interesting theory the customer came up with, but the customer was fooled by the fact that he ran the experiment shortly after modifying the TPS 06-05-2012 Report document, so that the real behavior was masked.

    When you copy a file, the system preserves the date-modified timestamp. The Date modified column is not ignoring the seconds; in fact, it's comparing them quite carefully, but since the timestamps of the original and the copy are the same, the timestamps compare equal. And when the items compare equal according to the sort criteria, Explorer falls back to sorting by name, and the fallback sort is always ascending.

    The confusion would have been cleared up if the Date modified column used the long time format instead of the short time format, but that only pushes the problem to files whose timestamps are fractional seconds apart. You have two files which show up as "6/12/2012 7:00:12 AM" and don't realize that one of them is "6/12/2012 7:00:12.02 AM" and the other is "6/12/2012 7:00:12.89 AM". My guess is that geeks won't be satisfied until Explorer shows the full 64-bit FILETIME so you can see the difference down to the 100-nanosecond interval.

    (If you want to see the seconds, you can look on the file's property sheet.)

  • The Old New Thing

    The most exciting part of my morning is catching my bus, specifically, making the transfer

    • 36 Comments

    Note: Transit nerd content. You have been warned.

    I still rely on One Bus Away to tell me when my bus is coming. Recent changes in bus service means that there is no longer a direct bus from my neighborhood to my work. My basic options are as follows:

    • Walk 3 minutes to Stop A (my neighborhood stop), catch Bus 1 (comes every 30 minutes), ride for 11 minutes, get off at Stop X, then walk 15 minutes to work.
    • Walk 7 minutes to Stop B, where I can
      • catch Bus 2 (comes every 10 minutes),
        • ride 7 minutes, get off at Stop X, then walk 15 minutes to work.
        • ride 13 minutes, get off at Stop Y, then walk 8 minutes to work.
      • catch Bus 3 (comes every 5 minutes), ride 9 minutes, get off at Stop Z, then walk 12 minutes to work.
    Start walk: 3 minutes Stop A Bus 1 11 minutes Stop X walk: 15 minutes Finish
    walk: 7 minutes Stop B Bus 2 7 minutes
    13 minutes Stop Y walk: 8 minutes
    Bus 3 9 minutes Stop Z walk: 12 minutes
    prep: 2 minutes ride bicycle: 25 minutes park: 2 minutes

    If you sit and work out the math, the total travel time for all the options is about the same, around 29 minutes. Which is about the same time it takes me to ride my bicycle, so it basically doesn't matter which route I take, especially since traffic lights randomize the travel time by a few minutes each way. But the paradox of choice means that I still try to optimize something that is basically irrelevant. (I'll spare you the calculations that went into choosing which bike route to use!)

    Anyway, my morning commute-decision algorithm is:

    • Do I want to ride my bicycle? If so, then ride. (This is the most common branch.)
    • Else, is Bus 1 coming soon? If so, then go to Stop A.
    • Else, walk to Stop B and take whichever bus comes next (usually Bus 3).

    The excitement is the Stop X extension.

    I recently discovered that there's another route, Bus 4, which runs parallel to buses 1 and 2 for a stretch (for stops V, W, and X), and which then veers away and drops me off in front of my building. If I'm on Bus 1 or Bus 2, I can check on the status of Bus 4, and if it's only a few minutes behind the bus that I'm on, then some new options become available.

    The high-risk option is to transfer at Stop V. This is a high-risk move because if I don't time it right, I end up having to wait for the next Bus 2 to resume my commute.

    The next safer option is to transfer at Stop W, which is only twenty minutes of walking from my office. (Update: Bus 2 does not stop at W.)

    The safest option is to transfer at Stop X, since the only downside is that I do the normal amount of walking anyway. But this has a higher risk of missing the connection because I have to cross the street to get from one bus stop to the other, and it's a busy street, so I may have to wait a long time before I get the Walk signal.

    When one of these higher-risk moves comes into play, I will use Realtime Transit, which plots the locations of the buses on a map, so I can decide whether I feel lucky today, punk.

    Last Friday was my first opportunity to try out the Stop X extension, and it was a nail-biter, because the bus locations in the Realtime Transit application were inconsistent. Sometimes, Bus 4 would show up a bit too close for comfort (it might end up passing my bus because my bus stops more often), and then sometimes it would show up miles and miles away.

    Stop V was too risky. If the nearby bus was just a mirage, then I got off a perfectly good bus and stranded myself. As we neared Stop W, I looked out the back window of the bus and didn't see a Bus 4 in the distance, so I decided to go for the safe approach and get off at Stop X.

    As I waited for the traffic light to change, I saw Bus 4 go zooming past.

    One of the days, I will actually succeed at making the Stop X extension.

  • The Old New Thing

    Converting to Unicode usually involves, you know, some sort of conversion

    • 36 Comments

    A colleague was investigating a problem with a third party application and found an unusual window class name: L"整瑳整瑳". He remarked, "This looks quite odd and could be some problem with the application."

    The string is nonsense in Chinese, but I immediately recognized what was up.

    Here's a hint: Rewrite the string as

    L"\x6574" L"\x7473" L"\x6574" L"\x7473"

    Still don't see it? How about looking at the byte sequence, remembering that Windows uses UTF-16LE.

    0x74 0x65 0x73 0x74 0x74 0x65 0x73 0x74

    Okay, maybe you don't have your ASCII table memorized.

    0x74 0x65 0x73 0x74 0x74 0x65 0x73 0x74
    t e s t t e s t

    That's right, the application took the ASCII string "testtest" and just treated it as a Unicode string without actually converting it to Unicode. When the compiler complained "Cannot convert char * to wchar_t *" they just stuck a cast to make the compiler shut up.

    // Code in italics is wrong
    WNDCLASSW wc;
    wc.lpszClassName = (LPWSTR)"testtest";
    

    They were lucky that the compiler happened to put two null bytes at the end of the "testtest" string.

    Bonus psychic powers: Actually, I have a theory as to how this happened that doesn't involve maliciousness. (This is generally a good mindset to maintain, since most of the time, when people cause a problem, it's not willful; it's accidental.) Consider a library with the following interface header file:

    // mylib.h
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    BOOL RegisterWindowClass(LPCTSTR pszClassName);
    
    #ifdef __cplusplus
    }; // extern "C"
    #endif
    

    Somebody uses this header file like this:

    #include <mylib.h>
    
    BOOL Initialize()
    {
        return RegisterWindowClass(TEXT("testtest"));
    }
    

    So far so good.

    Meanwhile, the library implementation goes like this:

    #define UNICODE
    #define _UNICODE
    
    #include <mylib.h>
    
    LRESULT CALLBACK StandardWndProc(HWND, UINT, WPARAM, LPARAM);
    
    BOOL RegisterWindowClass(LPCTSTR pszClassName)
    {
        WNDCLASS wc = { 0, StandardWndProc, 0, 0, g_hInstance,
                        LoadIcon(IDI_APPLICATION),
                        LoadCursor(IDC_ARROW),
                        (HBRUSH)(COLOR_WINDOW + 1),
                        NULL, pszClassName);
        return RegisterClass(&wc);
    }
    

    The two files both compile successfully, and they even link together. Unfortunately, one of them was compiled with Unicode disabled, and the other was compiled with Unicode enabled. Since the header file uses LPCTSTR, the actual declaration of RegisterWindowClass changes depending on whether the code that includes the header file is compiled as Unicode or ANSI.

    Result: If one file is compiled as ANSI and the other is compiled as Unicode, then one will pass an ANSI string, which the other will receive and treat as Unicode.

    This is why functions in Windows which are dependent on whether the caller is compiled as ANSI or Unicode are really two functions, one with the A suffix (for ANSI) and another with the W suffix (for Wnicode?), and the generic name is really a macro that forwards to one or the other. It prevents TCHARs from sneaking past the compiler and ending up being interpreted differently by the two sides.

  • The Old New Thing

    Amusing message on a whiteboard in the hallway

    • 28 Comments

    It is common to see whiteboards mounted in hallways. Sometimes they have official purposes; other times they are just placed for the convenience of hallway conversations or impromptu meetings.

    One of the hallways near my office has a very large whiteboard, completely blank, save for one note clearly written in the corner.

    DO NOT ERASE















  • The Old New Thing

    In 1993, Microsoft stole my colleague's car

    • 28 Comments

    I remember walking between buildings at Microsoft back in the 1990's and seeing a moss-covered, rusted-out jalopy in one of the parking spaces. It clearly hadn't moved in ages. The person I was with said, "Oh, yeah, Microsoft owns that car. They stole it from Bob." (Bob is my generic name for a Microsoft employee.)

    The Inaugural Day Storm of 1993 left felled trees and other wind damage in its wake on the Microsoft Redmond campus. One of my colleagues was out of town when the storm hit, and he returned to stories of fallen trees, wind damage, and howling winds.

    Bob also returned to find that his car had been stolen out of the parking lot outside his building. (It was at the time a common practice to use Microsoft's parking lots as personal vehicle storage.)

    Bob filed a stolen-car report with his insurance company and received his payment. As far as Bob was concerned, that was the end of that.

    But that's not the whole story.

    Right after the storm, the Facilities department set about cleaning up all the trees and branches and leaves that were strewn across the parking lots. They waited until after work hours so the parking lot would be empty, but that didn't work in one particular lot, because Bob's car was still there. To permit the cleanup to proceed, they towed the car to the far corner of the parking lot to get it out of the way.

    Thus, when Bob returned from his trip, he found that his car was gone. It was actually not that far away, but this particular parking lot was nestled in a densely-wooded area, with the lot divided into sub-lots, each separated by a small stand of trees, so if you didn't know where to look, you could easily have missed the car.

    This left the car sitting abandoned in a Microsoft parking lot. Technically, the car was owned by Bob's insurance company, and technically Microsoft stole it.

    Pre-emptive snarky comment: "Wouldn't be the first time Microsoft stole something."

Page 1 of 3 (24 items) 123