• The Old New Thing

    Why does the class name for Explorer change depending on whether you open it with /e?

    • 21 Comments

    I noted some time ago that Explorer's original name was Cabinet, and that the name lingers in the programmatic class name: Cabinet­WClass. A commenter with a rude name points out that Explorer uses the class name Explorer­WClass if you open it with the /e command line switch, adding, "This is rather strange since you can toggle the folder pane on/off in the UI either way."

    In Windows 95, the window class names for Explorer were Cabinet­WClass for plain Explorer windows and Explorer­WClass for windows opened in Explore mode with the folder tree view thingie on the left hand side. This was not strange at the time because there were two different types of Explorer windows, and there was no way to change between them. The UI to toggle the folder pane on/off did not exist.

    Internally, the two types of Explorer windows were handled by different frame window classes, and naturally the two different classes got different names. The plain Explorer window frame hosted a view window, an address bar, and a status bar, whereas the fancy Explorer window frame hosted those components plus a folder tree. It wasn't until some time later that the ability to toggle the folder pane on and off was added. To do this, the two window classes were merged into a single implementation that dynamically added in or removed the folder tree.

    Great, we can get rid of Explorer­WClass and just use Cabinet­WClass for everything.

    And then the application compatibility bug reports came in.

    Because even though it wasn't documented, application relied on the implementation detail that plain Explorer windows could be found by doing a Find­Window for Cabinet­WClass, and that fancy Explorer windows could be found by doing a Find­Window for Explorer­WClass. They would do things like launch explorer.exe /e C:\some\folder, wait a few seconds, and then do a Find­Window("Explorer­WClass", ...) and expect to find a window. (Just do a Web search for Cabinet­WClass and Explorer­WClass if you don't believe me.)

    For compatibility, therefore, Explorer windows still use the old class names from Windows 95. If you open the window with the folder pane hidden, the class name is Cabinet­WClass, and if you open it with the folder pane visible, the class name is Explorer­WClass. The two classes are functionally identical, but people who rely on undocumented behavior expect to see the same names from 1995.

  • The Old New Thing

    How come the technique for launching an unelevated process from an elevated process doesn't work?

    • 40 Comments

    A customer was following the Execute in Explorer sample to launch an unelevated process from an elevated process. (A sample which I rehashed some time ago.) The customer reported that the resulting process was still elevated.

    Upon closer inspection, the customer had disabled User Account Control (UAC).

    If UAC is disabled, then the ability for an administrative user to launch an unelevated process no longer exists.

    Since people like tables, here are some tables.

    In the classical world without UAC, administrators are administrators, and standard users are standard users. In other words, processes run by administrators are always elevated, and processes run by standard users are always non-elevated.

    UAC disabled
    User type Process type
    Elevated Non-elevated
    Administrator
    Standard

    UAC added a new option to the table: The administrator who voluntarily relinquishes administrative privilege and runs a process non-elevated.

    UAC enabled
    User type Process type
    Elevated Non-elevated
    Administrator
    Standard

    In words: In the classic non-UAC world, an administrative user can run processes elevated, and a standard user can run processes un-elevated. If UAC is enabled, then a new combination becomes available: An administrative user can run a process non-elevated.

    If you disable UAC, then you are back in the classic world, where there is no such thing as an administrative user running a non-elevated process. It's therefore no surprise that when you try to run the process unelevated, it still runs elevated.

    You can look at this issue another way: If UAC is disabled, then Explorer runs elevated. And therefore, if you ask Explorer to run a process, that process runs elevated too.

    It turns out that the customer turned off UAC because they didn't want to see any UAC prompts; they wanted their program to elevate silently, yet launch child processes unelevated. From a security-theoretical point of view, this is not an interesting configuration: If you allow silent elevation, then those child processes can just silently elevate themselves, and your attempt to run them unelevated accomplished nothing.

    If you disable UAC, then the only way to get both elevated processes and unelevated processes is to run the elevated processes as one user (an administrator) and the unelevated processes as another user (a standard user).

  • The Old New Thing

    When you think you found a problem with a function, make sure you're actually calling the function

    • 18 Comments

    On an internal mailing list, a tester asked if there were any known problems with the Find­First­File­Ex function preventing a directory from being deleted and recreated.

    Our code creates a test folder, then calls Find­First­File­Ex to look inside the test folder. When we're done, we call Find­Close, then delete the directory. When we try running the test twice, the second time fails to create the test folder; we get ERROR_ACCESS_DENIED. But if we switch to Find­First­File instead of Find­First­File­Ex, then everything works as expected.

    Here's our code, simplified.

    // Assume all functions succeed except where indicated.
    
    CreateDirectory(L"C:\\Test", NULL);
    
    // This version works:
    //
    // WIN32_FIND_DATA data;
    // HANDLE hFindFile = FindFirstFile(L"C:\\Test\\*", &data);
    
    // This version doesn't:
    //
    WIN32_FIND_DATA data;
    HANDLE hFindFile = FindFirstFileEx(L"C:\\Test\\*",
                                       FileExInfoBasic,
                                       &data,
                                       FindExSearchNameMatch,
                                       NULL,
                                       0);
    FindClose(hFindFile);
    
    RemoveDirectory(L"C:\\Test");
    
    // If we used FindFirstFile, then this CreateDirectory succeeds.
    // If we used FindFirstFileEx, then this CreateDirectory fails.
    CreateDirectory(L"C:\\Test", NULL);
    

    I suggested that they try running their test with anti-malware software disabled. Anti-malware software will frequently intrude on file operations, and it could be that the virus scanner is still checking the old C:\Test directory when you get around to creating the new one. Content indexers are another case where this can happen, but content indexers tend to wait until the machine is quiet rather than introducing on actions as they occur. (Now, well-written virus scanners and content indexers know to do things like abandon a file scan when a delete request is made, or use opportunistic locks to get out of the way when an application wants to do something with a file being scanned. But not all virus scanners and content indexers as as well-written as we might like.)

    We later heard back that they figured out the problem, and it wasn't because of a virus scanner or content indexing service.

    The problem was that their code was running inside a test harness, and that test harness had mocked the Find­First­File and Find­Close functions, but it did not mock the Find­First­File­Ex function. When the mock Find­Close function was given a handle created by the real Find­First­File­Ex function, it got confused and ended up leaking the directory handle. The Remove­Directory function succeeded, but the directory was not fully removed due to the outstanding handle, and the attempt to recreate the directory therefore failed.

    The tester also confirmed that the problem did not exist when they ran the code outside the test environment.

    When you think you found a problem with a function, make sure you're actually calling the function. In this case, the code was running under nonstandard conditions: The test harness had redirected a bunch of OS functions. As a result, when the code called Find­Close, it wasn't actually calling Find­Close but rather a mock function provided by the test harness.

    To be fair, the tester was new to the team and was likely not even aware that the test harness was mocking file I/O functions in the first place.

    If you are having trouble with a function, one thing to check is that you're actually calling the function.

  • The Old New Thing

    One way to make sure nobody reports problems with your Web site

    • 16 Comments

    I had ordered a service online, and the Web site said, "Do not close this window until you are done, because once you close the window, you won't be able to come back."

    Okay, fine. I just need to make sure I get everything I need before I close the window.

    I click a link to download some of the information I had ordered, and I get the error message:

    We're having trouble processing your request.
    If this problem persists please let us know.
    Email us at null

    Related.

  • The Old New Thing

    How can I reposition my window so it isn't covered by the touch keyboard?

    • 1 Comments

    Last week, we saw how we could rearrange our window contents to avoid having the touch keyboard cover the edit control. But what if the touch keyboard covers the entire window? No amount of rearranging will help. We'll have to move our window.

    Let's make these changes to the Do­Layout function:

    void
    DoLayout(HWND hwnd, int cx, int cy, bool isKeyboardShowing = false)
    {
      ...
      if (g_hwndChild) {
        ...
          if (IntersectRect(&rc, &rcEdit, &rcKeyboardClient)) {
            if (rcKeyboardClient.top > 50) {
              cyEdit = min(rcKeyboardClient.top, cy);
            } else if (isKeyboardShowing) {
              // need to reposition the entire window, ugh.
              int dyAdjust = 50 - rcKeyboardClient.top;
              RECT rcWindow;
              GetWindowRect(hwnd, &rcWindow);
              SetWindowPos(hwnd, nullptr,
                rcWindow.left, rcWindow.top - dyAdjust, 0, 0,
                SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
              return;
            }
          }
          ...
    }
    

    If there are at least 50 pixels of the edit control visible, then we consider that good enough. If not, and if we are responding to the keyboard showing, then we take matters into our own hands and move our window so that there are 50 pixels of the edit control visible. I didn't bother adding a check to make sure we never moved beyond the top of the work area; I'll leave that as an exercise for the reader, seeing as it's more typing that tends to distract from the point of the article.

  • The Old New Thing

    How can I force a CreateFile call to hang, in order to test something?

    • 14 Comments

    A customer was doing some testing and wanted to know if there was a way to force a call to Create­File to hang, so that they can test their program's hang detection and recovery code.

    You can ceate these scenarios with careful use of opportunistic locks (also known as oplocks). The sample program I wrote some time ago can be used, for example, to cause a Create­File call requesting write access to hang until the oplock is released.

    To cause a Create­File call requesting read access to hang until the oplock is released, use OPLOCK_LEVEL_CACHE_WRITE; this means that the oplock owner caching writes, so nobody can read from the file until the cached writes are flushed out.

  • The Old New Thing

    Keep your eye on the code page: Is this string CP_ACP or UTF-8?

    • 34 Comments

    A customer had a problem with strings and code pages.

    The customer has a password like "Müllwagen" for a particular user. Note the umlaut over the u. That character is encoded as the two bytes C3 BC according to UTF-8. When the customer passes this password to the Logon­User function in order to authenticate the user, the call fails, claiming that the password is invalid.

    If we encode the ü as the single byte FC, then the call to Logon­User succeeds.

    Therefore, if the string is in UTF-8 form, it needs to be converted, and to do this we use the Multi­Byte­To­Wide­Char function. Once converted, the logon is successful.

    The problem is that we are not sure if the password being given to the application will encode the ü as C3 BC or as FC. If it arrives as FC, and we try to convert it with the Multi­Byte­To­Wide­Char function, the ü is converted to U+FFFD.

    If I take the FC-encoded string and convert it with the Multi­Byte­To­Wide­Char function, passing CP_ACP as the first parameter, then it converts successfully (no U+FFFD), and the call to Logon­User is successful.

    For the application, the customer does not want to distinguish the two cases or implement any retry logic or anything like that. Can you help us understand the issue, what we are doing wrong, and how we can fix it?

    As the problem is stated, you are screwed.

    You have a bunch of bytes, and you don't know what encoding they are in. The byte sequence C3 BC might be a UTF-8 encoding of ü, or it could be a CP_ACP encoding of ý. You are stuck with guessing. But for something as important as passwords, you shouldn't guess. You need to know for sure, because an incorrect guess will generate audit entries, and may cause the user to become locked out of the account due to too many incorrect passwords.

    This means that you need to make sure that whoever is passing you the string also tells you what encoding it is using.

    The customer liaison replied,

    Thanks. I went back and talked to the customer, and it turns out that the password is always in UTF-8 form, so the problem is solved. We will always pass CP_UTF8 when converting the string.

  • The Old New Thing

    Taking ownership of a file doesn't give you access, yet

    • 27 Comments

    A colleague of mine accidentally ran a command from an elevated command prompt rather than a normal unelevated command prompt. By default, files created from an elevated command prompt are owned by the Administrators group, on the theory that you are doing work in the elevated command prompt in your rôle as the system administrator, so the things you are doing are done on behalf of all the administrators of the system. (If you don't like this you can use the Default owner for objects created by members of the Administrators group policy to change the default.)

    My colleague attempted to repair the damage by taking ownership of all the files back: From the root of the directory tree of files that got created with the wrong owner, he ran takeown /f . /r to take them back. (You naturally have to do this from an elevated command prompt. Since you left off the /A flag, this assigns ownership to you personally, rather than to the administrators group.)

    "I can confirm that I am the owner by using the dir /q command, but I still don't have access. What gives?"

    Having ownership of a file does not automatically grant you full access. Users always have WRITE_DAC permission on objects they own, but that's all. If you want more, you need to leverage WRITE_DAC into full access.

    Fortunately, leveraging WRITE_DAC into full access is easy, because WRITE_DAC gives you permission to change permissions, so you can just change the permission to grant yourself full access:

    cacls /e /g domain\user:F
    
  • The Old New Thing

    Bitter or acerbic? or does it make a difference?

    • 21 Comments

    One of my colleagues told me that my name came up in conversation, and somebody said that they thought I had a reputation for being bitter.

    My colleague defended me. "Nah, Raymond isn't bitter. He's, I dunno, acerbic."

    "Acerbic? What does that mean?"

    My colleague looked it up.

    "It means, um... bitter."

  • The Old New Thing

    How can I reposition my controls so they aren't covered by the touch keyboard?

    • 8 Comments

    Last week, we saw how to tell the Windows 8 touch keyboard to appear automatically in a desktop program when focus enters an edit control. But we did nothing to prevent the touch keyboard from covering the edit control.

    To find out when the touch keyboard appears and disappears, you register with the framework input pane. The framework input pane tells you the current location of the keyboard and informs you when the keyboard state changes. (Note that it does not tell you about the keyboard when it is in floating mode, on the theory that when the user floats the keyboard, the user has taken responsibility for making sure it doesn't cover anything interesting.)

    Take our program from last week and make these changes:

    #include <strsafe.h>
    
    using namespace Microsoft::WRL;
    
    // Add immediately before DoLayout
    
    ComPtr<IFrameworkInputPane> g_frameworkInputPane;
    DWORD g_cookie;
    RECT g_rcKeyboard;
    

    In a real program, of course, we wouldn't use global variables, but this is a Little Program.

    void Relayout(HWND hwnd,
      bool isKeyboardShowing = false);
    
    class Handler : public RuntimeClass<
      RuntimeClassFlags<RuntimeClassType::ClassicCom>,
      IFrameworkInputPaneHandler>
    {
    public:
      Handler(HWND hwnd) : m_hwnd(hwnd) { }
    
      STDMETHODIMP Hiding(BOOL fEnsureFocusedElementInView) override
      {
        SetRectEmpty(&g_rcKeyboard);
        Relayout(m_hwnd);
        return S_OK;
      }
    
      STDMETHODIMP Showing(RECT *prcScreenLocation,
          BOOL fEnsureFocusedElementInView) override
      {
        g_rcKeyboard = *prcScreenLocation;
        Relayout(m_hwnd, true);
        return S_OK;
      }
    private:
      HWND m_hwnd;
    };
    

    Our Handler class implements the IFramework­Input­Pane­Handler interface so it can be called when the input pane shows and hides. When this happens, we update the keyboard rectangle and ask our window to update its layout in response to the new information. We pass a special flag that indicates whether the call is in response to the keyboard showing, because we want to do extra work in that case.

    void
    DoLayout(HWND hwnd, int cx, int cy, bool isKeyboardShowing = false)
    {
      // Just for fun, put the keyboard position in the title bar.
      if (IsRectEmpty(&g_rcKeyboard)) {
        SetWindowText(hwnd, TEXT("Keyboard is not visible"));
      } else {
        TCHAR message[256];
        StringCchPrintf(message, ARRAYSIZE(message),
          TEXT("Keyboard is at (%d,%d)-(%d,%d)"),
          g_rcKeyboard.left, g_rcKeyboard.top,
          g_rcKeyboard.right, g_rcKeyboard.bottom);
        SetWindowText(hwnd, message);
      }
    
      if (g_hwndChild) {
        int cyEdit = cy;
        if (!IsRectEmpty(&g_rcKeyboard)) {
          RECT rcEdit = { 0, 0, cx - 100, cy };
          RECT rcKeyboardClient = g_rcKeyboard;
          MapWindowRect(nullptr, hwnd, &rcKeyboardClient);
          RECT rc;
          if (IntersectRect(&rc, &rcEdit, &rcKeyboardClient)) {
            cyEdit = min(rcKeyboardClient.top, cy);
          }
        }
        MoveWindow(g_hwndChild, 0, 0, cx - 100, cyEdit, TRUE);
        if (isKeyboardShowing) {
          SendMessage(g_hwndChild, EM_SCROLLCARET, 0, 0);
        }
      }
      if (g_hwndButton) {
        MoveWindow(g_hwndButton, cx - 100, 0, 100, 50, TRUE);
      }
    }
    

    First, we update the title bar to show where we think the keyboard is, just so it's easier to follow what's happening. And then the actual action: If the keyboard is visible and it overlaps the edit control, then we resize the edit control to avoid it. And if the keyboard is showing, then we scroll the edit control so that the caret is visible. We don't want to force the caret visible in the general case, because that would cause the contents to scroll at unexpected times.

    void
    OnMove(HWND hwnd, int x, int y)
    {
      Relayout(hwnd);
    }
    

    When the window moves, we want to perform relayout, because the window may have moved in such a way that the edit control is obscured by the keyboard.

    void
    Relayout(HWND hwnd, bool isKeyboardShowing);
    {
      RECT rc;
      GetClientRect(hwnd, &rc);
      DoLayout(hwnd, rc.right, rc.bottom, isKeyboardShowing);
    }
    
    This function is kind of anticlimactic. To perform relayout, we get the client rectangle and ask DoLayout to lay out the contents inside that rectangle.

    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
      ...
      EnableTouchKeyboardFocusTracking();
    
      CoCreateInstance(__uuidof(FrameworkInputPane), nullptr,
        CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&g_frameworkInputPane));
      g_frameworkInputPane->AdviseWithHWND(hwnd,
        Make<Handler>(hwnd).Get(),
        &g_cookie);
    
      return TRUE;
    }
    

    Here is how the ball gets set into motion: When we create the window, we also register our handler with the framework input pane, so that we are called back when the keyboard moves.

    void
    OnDestroy(HWND hwnd)
    {
      if (g_cookie) {
        g_frameworkInputPane->Unadvise(g_cookie);
      }
      g_frameworkInputPane = nullptr;
      PostQuitMessage(0);
    }
    

    And some bookkeeping: When the window is destroyed, we unregister from the framework input pane. We also need to release the framework input pane before COM get uninitialized. (This wouldn't have been necessary if we had put the framework input pane in a member variable, since the member variable would be destructed at window destruction. But we were lazy and used a global variable, and now we pay the price.)

    If you take this program out for a ride, you'll see that it manages to resize the edit control so that it is not covered by the touch keyboard. But there's still a problem: What if the window is near the bottom of the screen, and the user calls up the touch keyboard? The entire edit control ends up obscured! No amount of resizing will fix this. We'll look at this problem next time.

Page 2 of 453 (4,524 items) 12345»