• The Old New Thing

    If You Are The One: The crazy Chinese dating show


    My wife watches 非誠勿擾, a Chinese dating game show. The title literally translates as "If not sincere, then do not bother", but that doesn't really convey what the title means. It follows in the Chinese tradition of the cryptic four-character phrase. The less cryptic version would be "If you are not sincere, then don't waste my time." The English equivalent would be something like "Serious inquiries only."

    Here are the rules of the show as I understand them:

    • The bachelor contestant could be handsome, or he could be a nerd.
    • The bachelor faces a panel of 24 women, most of whom are conventionally attractive Chinese women, although the producers toss in a few wildcards like a foreign student or a someone who is a little chubby or homely.
    • The bachelor secretly reveals to the host (and the home audience) the woman he has preselected based only on her appearance, known as the "heartbeat girl".
    • Each woman starts out with her podium light lit green.
    • If a woman decides that she is not interested in dating the bachelor, she turns off her light (which actually just turns it red).
    • If all the women turn off their lights, then the game is over, and the bachelor goes home.
    • In the first video, the bachelor introduces himself.
    • The host invites the bachelor to do a brief talent demonstration, be it dancing, singing, playing a musical instrument, whatever.
    • In the second video, the bachelor's romantic history is dramatized.
    • In the third video, the bachelor's friends and relatives describe him.
    • Throughout, the host and two additional consultants banter with the bachelor and the women on the panel. This is where most of the bizarre statements come out.
    • A woman who is particularly attracted to the bachelor can activate the "burst light", thereby guaranteeing a position in the final round. (I've never seen it activated more than once. I don't know if that's a rule, or that's just how it works out.)
    • After all the videos and banter are complete, if there are more than two women with their lights still green, the bachelor eliminates all but two by turning off their lights.
    • All of the women with their lights still turned on, plus the one who activated the burst light (if any), plus the heartbeat girl advance to the final round.
    • The bachelor selects a question from a menu, and prerecorded video responses from each finalist are played.
    • The bachelor asks a question of his own choosing, and each finalist gives her answer.
    • The bachelor selects the woman he wants to date. In the special case where the bachelor selects the heartbeat girl, but she had turned off her light earlier in the round, then she has the opportunity to reject the bachelor.
    • The women who did not win a date return for the next round, or the next show if this was the final round of the day.

    Anyway, my wife enjoys watching the show. Me less so, seeing as I don't understand Mandarin beyond being able to count to one hundred and maybe a dozen other vocabulary words.

    And then I learned that the show has been subtitled in English by Australian broadcaster SBS Two, and the show has taken the country by storm. Unfortunately for me, I'm not in Australia, so in order to understand the show, I have to ask my wife to translate what's going on. (Which she does, if I ask.)

    If you don't understand Mandarin and you aren't married to a translator, you can follow the Commemorative Fashion Shoes Tumblr¹ which is devoted to screencaps of the more bizarre things said on the show. The Tumblr if you are the gif does the same thing, but does not appear to be active any more.

    Bonus chatter: One should be careful not to assume that what happens on television dating shows is how dating actually works in the country of origin. After all, you wouldn't want people to base their knowledge of dating in the United States on what happens on The Bachelor or Studs. (Yes, that's Bill Nye.)

    Bonus bonus chatter: An undercover story: If you are the one. A woman describes the experience of being on the show. Covers, among other things, the careful arrangement of the 24 women on stage.

    ¹ Apparently, the winning couple win a cruise and fashion shoes, hence the strange name for the Tumblr.

  • The Old New Thing

    Why is the StartRunNOHOMEPATH policy so very specific about what it does?


    The Start­Run­No­HOME­PATH policy affects whether the user's HOME path is the current directory when you run a program from the Run dialog. But that's all it does. DWalker asks, "I wonder why the behavior is different between Start and Start.Run."

    The Start.Run dialog is not related to the Start menu. It's just some dialog box that happens to be directly invokable from Start. This dialog is also used by Task Manager when you say File.New Task, and you can also invoke it programmatically.

    The search box on the Start menu is a search box, not a run box. Its job is to use what you typed as the basis for a search and present you a list of matches, and to execute the top match if you press Enter. That it runs Notepad if you type N-O-T-E-P-A-D is a consequence of the search rather than any attempt to run the command line "notepad".

    As for why the policy is so narrowly-defined, a lot of that falls back to the customer who requested the policy in the first place. Many policies are the result of direct requests from IT departments who want to control some very precise aspect of the system because it affects their network or otherwise creates a burden on their computing infrastructure. You can usually spot these types of policies because of their extremely precise formulation.

    morlanweb wonders if there are equivalent policies for other launch points. Given that the Start­Run­No­HOME­PATH policy was created in response to a specific customer request, it stands to reason that corresponding policies for other launch points would exist only if a customer requested them. And so far, no corporate IT department has asked for them.

    As a rule, IT departments do not notify us when they no longer need a policy, so these random strange-looking policies can linger for a long time without any real clients. Not that it would help, because once the policy is published, any other company's IT department could start using it without telling us.

    These special one-off policies are tested primarily by the customers who requsted them. If the policy implementation solves their problem, then that's that. This helps to shift the support burden of the policy from the product team to the individual who requested the policy. If a service pack or a future version of Windows causes the policy to behave strangely, we rely partly on the customer letting us know. In a sense, the loser become responsible for testing his check box.

  • The Old New Thing

    The ritual of choosing your next office


    The joke at Microsoft is "Don't like your office? Don't worry. You'll be moved to a different one soon." It's not actually that bad, at least not in my experience, but the joke still stands.

    When a team moves to a new building, there is the question of who gets which office. And this is one of the few things at Microsoft that is done purely by seniority: The best offices go to people who have been at Microsoft the longest.¹

    Different teams manage things differently. The most free-for-all method is simply to give everybody a time slot, and at the appointed time, you come in, look at the available offices, and pick yours. Subject to constraints like, "All the people who work on the X component should be in this area of the building."

    More commonly, the manager of the team that is moving sits down and decides where everybody will get moved to, taking seniority into account, so that more senior people tend to get better offices.

    I heard of one manager who augmented the standard pattern: After everybody was given their office assignments, she said, "Okay, anybody can swap offices by mutual agreement. You can make side deals if you want. If you want my office, make me an offer."

    I don't know whether anybody tried to swap for her office, or what they offered to sweeten the deal.

    ¹ Of course, senior executives will pull rank and claim the best offices for themselves, using excuses like "I need an office large enough to have a meeting table," or "My doctor says that I have to have a nice view of Mount Rainier."

  • The Old New Thing

    How can I get notified when the cursor changes?


    Today's Little Program tracks changs to the cursor. You might want to do this as part of instrumentation, in order to see how often the user is staring at an hourglass, for example. (It's easier to make the Move cursor appear on demand, so I'll use that instead.)

    The magic words here are OBJID_CURSOR and Get­Cursor­Info.

    #include <windows.h>
    #include <stdio.h>
    void log()
      CURSORINFO ci = { sizeof(ci) };
      printf("showing = %d, suppressed = %d, pos = (%d, %d), handle = %p\n",
        !!(ci.flags & CURSOR_SHOWING),
        !!(ci.flags & CURSOR_SUPPRESSED),

    The log function prints information about the current cursor. For now, we just dump it to the screen, but obviously you could do something fancier with it. The CURSOR_SHOWING flag tells you whether the cursor show count is nonnegative, which is what classically controls whether the cursor is visible on the screen. The CURSOR_SUPPRESSED flag tells tells you that nominally visible cursor is not visible to the user because the user touched the screen with a finger or pen.

    void CALLBACK WinEventProc(
      DWORD event,
      HWND hwnd,
      LONG idObject,
      LONG idChild,
      DWORD idEventThread,
      DWORD time)
      if (hwnd == nullptr &&
          idObject == OBJID_CURSOR &&
          idChild == CHILDID_SELF) {
        switch (event) {
        case EVENT_OBJECT_HIDE:
          printf("cursor hidden\n");
        case EVENT_OBJECT_SHOW:
          printf("cursor shown\n");
          printf("cursor changed\n");

    Our event hook procedure checks if we're being notified about the cursor. If so, then we print some information about the event we received, and then log the cursor details.

    int __cdecl main(int, char**)
      printf("Move cursor = %p\n", LoadCursor(nullptr, IDC_SIZEALL));
      HWINEVENTHOOK hook = SetWinEventHook(
      MessageBox(nullptr, TEXT("Press Ok when bored"),
                 TEXT("Title"), MB_OK);
      return 0;

    Our main program prints the handle of the Move cursor, just to demonstrate that the handle will match the output. Next, it installs the event hook on its own process and thread. (If you want to monitor the entire process, then pass 0 for the thread ID. If you wanted to monitor all processes on the desktop, then pass 0 for both the process ID and thread ID.) Next, we display a message box to give you a way to exit the program, and to fire up a message pump. After you are bored, we remove the hook and exit.

    Now, I chose the Move cursor because it is pretty much the only cursor you can get to from a message box: Press Alt+Space, then hit M for Move. Bingo, a Move cursor. And you can see the program spit out the new cursor handle, and it should match the value printed at the start of the program.

  • The Old New Thing

    Exploring the Supercute World of Hello Kitty


    There's still time to get to the Hello Kitty Supercute Opening Party at the Experience Music Project, kicking off a four-month exhibition of Everything Hello Kitty. (Here are pictures from the Los Angeles leg of the tour.)

    Curiously, one of the events on the agenda for the opening night gala is a "Q&A with Hello Kitty" at 8:00pm, which is going to be interesting, seeing as Hello Kitty does not have a mouth.

    One of the exhibit's sponsors is UNIQLO, who I should remind you, has a very strange clock.

    Bonus chatter: EVA Air now has a thrice-weekly flight between Taipei and Houston, so you can now fly Hello Kitty Air from the United States.

  • The Old New Thing

    Diagnosing high CPU by studying profiling results, example


    A customer asked for assistance determining why their program demonstrated sporadic high CPU. It occurred only at a client location, so they couldn't set up a full debugging environment. They were able to convince the client to trace one of the spikes in a profiler. During this capture, high CPU was recorded for around 20% of the running time. Here is the drill-down of where most of that time was going.

    Stack% Weight
     |- ntdll.dll!RtlUserThreadStart19.36
     |    kernel32.dll!BaseThreadInitThunk19.36
     |    contoso.exe!__wmainCRTStartup19.36
     |    contoso.exe!wWinMain19.36
     | ⋮
     |    fabrikam.dll!Widget::~Widget19.36
     |    fabrikam.dll!WidgetProxy::Disconnect19.36
     |    fabrikam.dll!WidgetProxy::DisconnectAll19.36
     |    fabrikam.dll!WidgetProxy::TransactCommand19.36
     |    fabrikam.dll!WidgetProxy::WaitForResponse19.36
     |    |- user32.dll!MsgWaitForMultipleObjectsEx18.63
     |    |    |- kernel32.dll!WaitForMultipleObjectsExImplementation17.37
     |    |    |    |- KernelBase.dll!WaitForMultipleObjectsEx16.55
     |    |    |    |    |- ntdll.dll!ZwWaitForMultipleObjects8.69
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceCopyEnd4.76
     |    |    |    |    |    |    |- ntoskrnl.exe!NtWaitForMultipleObjects4.71
     |    |    |    |    |    |    |    |- ntoskrnl.exe!ObpWaitForMultipleObjects4.02
     |    |    |    |    |    |    |    |    |- ntoskrnl.exe!ObpWaitForMultipleObjects4.02
     |    |    |    |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |    |    |    |    |    |- ntoskrnl.exe!KiCheckForKernelApcDelivery<0.01
     |    |    |    |    |    |    |    |    |- ntoskrnl.exe!KiDpcInterrupt<0.01
     |    |    |    |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatch<0.01
     |    |    |    |    |    |    |    |- ntoskrnl.exe!NtWaitForMultipleObjects0.49
     |    |    |    |    |    |    |    |- ntoskrnl.exe!memcpy0.20
     |    |    |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |    |    |    |    |- ntoskrnl.exe!KiDpcInterrupt<0.01
     |    |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceCopyEnd0.05
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemCall641.84
     |    |    |    |    |    |- ntdll.dll!ZwWaitForMultipleObjects1.20
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceExit0.56
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceRepeat0.16
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceGdiTebAccess0.13
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceStart0.03
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceCopyStart0.01
     |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |    |- KernelBase.dll!BaseSetLastNTError6.57
     |    |    |    |    |    |- ntdll.dll!RtlNtStatusToDosError6.33
     |    |    |    |    |    |    |- ntdll.dll!RtlNtStatusToDosErrorNoTeb6.05
     |    |    |    |    |    |    |    |- ntdll.dll!RtlNtStatusToDosErrorNoTeb6.04
     |    |    |    |    |    |    |    |- ntoskrnl.exe!KiDpcInterrupt<0.01
     |    |    |    |    |    |    |    |- ntoskrnl.exe!KiApcInterrupt<0.01
     |    |    |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |    |    |    |- ntdll.dll!RtlNtStatusToDosError0.29
     |    |    |    |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |    |    |- KernelBase.dll!BaseSetLastNTError0.19
     |    |    |    |    |    |- ntdll.dll!RtlSetLastWin32Error0.05
     |    |    |    |    |- ntdll.dll!memcpy0.15
     |    |    |    |    |- KernelBase.dll!memcpy0.01
     |    |    |    |    |- ntoskrnl.exe!KiApcInterrupt<0.01
     |    |    |    |    |- ntoskrnl.exe!KiDpcInterrupt<0.01
     |    |    |    |- kernel32.dll!WaitForMultipleObjectsExImplementation0.60
     |    |    |    |- ntdll.dll!memcpy0.16
     |    |    |    |- kernel32.dll!WaitForMultipleObjectsEx0.04
     |    |    |    |- kernel32.dll!memcpy0.02
     |    |    |    |- ntoskrnl.exe!KiInterruptDispatchNoLock<0.01
     |    |    |    |- ntoskrnl.exe!KiApcInterrupt<0.01
     |    |    |- ntoskrnl.exe!KiDpcInterrupt<0.01
     |    |    |- ntoskrnl.exe!KiApcInterrupt<0.01
     |    |- KernelBase.dll!GetTickCount0.24
     |    |- KernelBase.dll!GetLastError0.20
     |    |- fabrikam.dll!GetLastError0.09
     |    |- kernel32.dll!GetLastError0.02

    From this chart, you can see that all of the time is consumed in Widget­Proxy::Wait­For­Response, and most of that time is in Msg­Wait­For­Multiple­Objects­Ex. There is a lot of detail inside that function, so let's hide everything that contributes less than one percent.

    Stack% Weight
     |    fabrikam.dll!WidgetProxy::WaitForResponse19.36
     |    |- user32.dll!MsgWaitForMultipleObjectsEx18.63
     |    |    |- kernel32.dll!WaitForMultipleObjectsExImplementation17.37
     |    |    |    |- KernelBase.dll!WaitForMultipleObjectsEx16.55
     |    |    |    |    |- ntdll.dll!ZwWaitForMultipleObjects8.69
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemServiceCopyEnd4.76
     |    |    |    |    |    |    |- ntoskrnl.exe!NtWaitForMultipleObjects4.71
     |    |    |    |    |    |- ntoskrnl.exe!KiSystemCall641.84
     |    |    |    |    |- KernelBase.dll!BaseSetLastNTError6.57

    If we look only at the time spent in Wait­For­Multiple­Objects­Ex, 8.69%/16.55% = around half of the time is spent in Zw­Wait­For­Multiple­Objects (the kernel part of the function which does the waiting), and 6.57%/16.55% = around 40% of the time is the time setting the last error code.

    That's odd. Why is this function spending nearly half of its time setting the last error code?

    My theory: Because the call is failing with an error!

    That explains the high CPU. The call to Wait­For­Multiple­Objects­Ex is failing, which means that instead of waiting, it returns immediately with an error code. The Widget­Proxy::Wait­For­Response function doesn't quite know what to do in that case, so it shrugs its shoulders and tries waiting again. Eventually, whatever it's waiting for actually happens, and the call returns, but instead of waiting at low CPU, it accidentally created a CPU spin loop.

  • The Old New Thing

    Why is January 1 being reported as the last week of the previous year?


    A customer (via the customer liaison) reported a problem that occurred when their program was run in Germany.

    Ah, those pesky Germans. Always causing trouble.

    In Germany, we find that the week numbers are wrong. For example, for the date January 1, 2005, the Get­Locale­Info function reports FIRST­DAY­OF­WEEK as 53 instead of the expected value of 1. The attached program demonstrates the issue.

    The customer wants to know if there is anything we can do so we get the correct result. We saw the Set­Locale­Info function but weren't sure if it can be used to get the English calendar on a German system, or what unintended consequences there may be.

    using System;
    using System.Globalization;
    class Program
      static void RunScenario()
        DateTimeFormatInfo dfi = DateTimeFormatInfo.CurrentInfo;
        Calendar cal = dfi.Calendar;
        DateTime date = new DateTime(2005, 1, 1);
        int weekOfYear = cal.GetWeekOfYear(date, dfi.CalendarWeekRule,
        Console.WriteLine("{0:d} is week number {1}", date, weekOfYear);
      static void Main()
        CurrentThread.CurrentCulture = new CultureInfo("da-DK");

    One thing I noticed was that it wasn't the Germans causing trouble at all. It's the Danes!

        ... new CultureInfo("da-DK");

    So your frustration is misdirected. You should be upset at those pesky Danes.

    But the first thing I noticed is that the question makes no sense. The FIRST­DAY­OF­WEEK is an integer in the range 0 through 6, where 0 means Monday, and 6 means Sunday. It never returns 53. What's more, the Get­Locale­Info function is never called by this program.

    So let's ignore the stated question and try to infer the question from the code. The code prints the week number for January 1, 2005, according to US-English and Danish rules. For US-English, it returns week 1, and for Denmark, it returns week 53.

    This is hardly surprising, because if you play around in the Immediate window, or just add some more Write­Line statements, you'll see that the US-English and Danish locales have different calendar preferences:

    Property US-English Danish
    First­Day­Of­Week Sunday Monday
    Calendar­Week­Rule First­Day First­Four­Day­Week

    How did I know to look at those properties? Because those are the properties that the program itself passes to the Get­Week­Of­Year function!

    Denmark starts the week on Monday. It follows the rule that Week 1 of the year is the first week with at least four days from that year. And January 1, 2005 was a Saturday. This means that the week containing January 1, 2005 has only two days from 2005. That's not enough, so the the first week of the year begins on January 3, 2005. January 1, 2005 is considered to be part of the last week of 2004, which is week number 53.

    Therefore, the value of 53 is correct. If you want Denmark to follow week-numbering rules consistent with the United States, you will need to take it up with the Folketing, and possibly the European Parliament.

    Now, if you decide that you want to show week numbers according to the United States convention even to people who did nothing wrong aside from happening to live in a country with the wrong week numbering rules, you can just pass your custom week numbering rules to the Get­Week­Of­Year function.

        int weekOfYear = cal.GetWeekOfYear(date,

    Don't change the system locale; that is trying to apply a global solution to a local problem.

    Mind you, your local solution is going to make everybody in Denmark say, "How come your program gets all the week numbers wrong?"

    Bonus reading: ISO 8601 Week of Year Format in Microsoft .NET.

  • The Old New Thing

    Oh, that's probably why I'm in the Quake credits


    Back in 2012, I couldn't remember why I was in the Quake credits. But then the comment from Kristaps pinpointed the most likely reason: The Sys_Page­In function.

    void Sys_PageIn (void *ptr, int size)
        byte    *x;
        int     j, m, n;
    // touch all the memory to make sure it's there. The 16-page skip is to
    // keep Win 95 from thinking we're trying to page ourselves in (we are
    // doing that, of course, but there's no reason we shouldn't)
        x = (byte *)ptr;
        for (n=0 ; n<4 ; n++)
            for (m=0 ; m<(size - 16 * 0x1000) ; m += 4)
                sys_checksum += *(int *)&x[m];
                sys_checksum += *(int *)&x[m + 16 * 0x1000];

    What this code does is access the memory block specified by the ptr and size parameters in an unusual pattern: It reads byte zero, then the byte at an offset of 16 pages, then byte one, then a byte at an offset of 16 pages plus one, and so on, alternating between a byte and its counterpart 16 pages ahead.

    This specific access pattern in Windows 95 defeated the "sequential memory scan" detection algorithm.

    Recall that computers in the Windows 95 era had 4MB of RAM. Suppose you were working in a document for a long time. Finally, you're done, and you close the window or minimize it. Boom, now your desktop is visible and the wallpaper bitmap needs to be paged in. If your screen is 1024 × 768 at 16 bits per pixel, that comes out to 1.5MB of memory. Paging in 1.5MB of memory means for the bitmap means kicking out 1.5MB of memory being used for other stuff, and that's a lot of memory for a machine that has only 4MB to work with (especially since a lot of that 4MB belongs to stuff that isn't eligible for being paged out). The phenomenon we saw was that repainting your desktop would flush out most of your memory.

    And then the next thing you do is probably launch a new application, which will cover the wallpaper, so the wallpaper memory isn't going to be needed any more. So we basically purged all the memory in your system in order to handle a huge block of memory that got accessed only once.

    The trick that Windows 95 used was to watch your pattern of page faults, and if it saw that you were doing sequential memory access, it started marking the memory 16 pages behind the current access as not recently accessed. In the case of a straight sequential scan, this means that the entire buffer cycles through a 64KB window of memory, regardless of the buffer size. With this trick, a 4MB buffer ends up consuming only 64KB of memory, as opposed to using all the memory in your system.

    The Sys_Page­In function specifically defeates the sequential-scan detector by intentionally going back 16 pages and accessing the page again. This causes it to be marked recently used, counteracting the not recently used that the sequential-scan detector had done. Result: The memory pages are all marked recently used and are no longer prime candidates for being paged out.

  • The Old New Thing

    It's called "proofreading", give it a shot why don't you, episode 2


    A Reuters article from last week includes the sentence

    That annual regulatory filing also introduced a so-called "anti-pledging" provision that prevented further loans to company directors, executives and employees using stock as collaterol.


    Now, sure, this is an article about a pharmaceutical company, so maybe "collaterol" looks like an expensive new drug.

    But still. Four authors, a contributor, two editors, and two updates later, and still nobody has fixed it.

  • The Old New Thing

    I cloned a project, but the new project still groups with the old project on the taskbar


    Alternate title from a non-programmer point of view: When I copy an executable to another name, sometimes it groups separately, and sometimes it groups with the original.

    A customer had a product, let's call it Contoso Designer. They decided to try an experimental new version of Contoso Designer. So they cloned the Contoso Designer project, and then started changing the parts of the program that were related to the experiment.

    When they did this, they noticed that Contoso Designer Experimental and Contoso Designer both grouped together in the taskbar. This isn't what they wanted, because the original and the experimental versions were not replacements for each other; they are separate programs that merely happen to have started out from the same code base.

    So they ran another experiment.

    They created a scratch project, put a scratch program in it, compiled it, and ran it. They then copied the scratch.exe file to scratch2.exe and ran that one, and it did not group with scratch.exe in the taskbar. Then they cloned the scratch project, recompiled the scratch program, and ran the clone. All three copies were separate in the taskbar.

    So what was haunted about their project that caused the clone to have a secret psychic connection to the original, and group together with it? And more important, how do they get it to stop?

    The answer is in the Application User Model ID. As we saw some time ago, the Application User Model ID is how the taskbar identifies applications. If two processes have the same Application User Model ID, then they are treated as the same application, even if the physical executable is different. In other words, the program and its clone were following the instructions in that article and saying, "I want these programs to group together."

    Given that nudge, the customer wrote back, "Thanks. Updating the call to Set­Current­Process­Explicit­App­User­Model­ID did the trick."

    I hope they remembered to update their Start menu shortcut, too.

Page 2 of 465 (4,641 items) 12345»