November 17th, 2025
likeheart3 reactions

How can I detect that Windows is running in S-Mode, redux

Igor Levicki asked for a plain C version of the sample code to detect whether Windows is running in S-Mode. I didn’t write one for two reasons. First, I didn’t realize that so many people still tried to use COM from plain C. And second, I didn’t realize that the people who try to use COM from plain C are not sufficiently familiar with how COM works at the ABI level to perform the mechanical conversion themselves.

  • p->Method(args) becomes p->lpVtbl->Method(p, args).
  • Copying a C++ smart COM pointer consists of copying the raw pointer and performing an AddRef if the raw pointer is non-null.
  • Destroying a C++ smart COM pointer consists of performing a Release if the raw pointer is non-null.
  • Before overwriting a C++ smart COM pointer, remember the old pointer value, and if it is non-null, Release it after you AddRef the new non-null pointer value.

The wrinkle added by the Windows Runtime is that C doesn’t support namespaces, so the Windows Runtime type names are decorated by their namespaces.

And since you’re not using WRL, then you don’t get the WRL helpers for creating HSTRINGs, so you have to call the low-level HSTRING functions yourself.

#include <Windows.System.Profile.h>

HRESULT ShouldSuggestCompanion(BOOL* suggestCompanion)
{
    HSTRING_HEADER header;
    HSTRING className;
    HRESULT hr;

    hr = WindowsCreateStringReference(RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy,
                ARRAYSIZE(RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy) - 1,
                &header, &className);
    if (SUCCEEDED(hr))
    {
        __x_ABI_CWindows_CSystem_CProfile_CIWindowsIntegrityPolicyStatics* statics;
        hr = RoGetActivationFactory(className, &IID___x_ABI_CWindows_CSystem_CProfile_CIWindowsIntegrityPolicyStatics, (void**)&statics);
        if (SUCCEEDED(hr))
        {
            boolean isEnabled;
            hr = statics->lpVtbl->get_IsEnabled(statics, &isEnabled);
            if (SUCCEEDED(hr))
            {
                if (isEnabled)
                {
                    // System is in S-Mode
                    boolean canDisable;
                    hr = statics->lpVtbl->get_CanDisable(statics, &canDisable);
                    if (SUCCEEDED(hr))
                    {
                        // System is in S-Mode but can be taken out of S-Mode
                        *suggestCompanion = TRUE;
                    }
                    else
                    {
                        // System is locked into S-Mode
                        *suggestCompanion = FALSE;
                    }
                }
                else
                {
                    // System is not in S-Mode
                    *suggestCompanion = TRUE;
                }
            }
            statics->lpVtbl->Release(statics);
        }
    }

    return hr;
}

There is a micro-optimization here: We don’t need to call Windows­Delete­String(hstring) at the end because the string we created is a string reference, and those are not reference-counted. (All of the memory is preallocated; there is nothing to clean up.) That said, it doesn’t hurt to call Windows­Delete­String on a string reference; it’s just a nop.

It wasn’t that exciting. It was merely annoying. So that’s another reason I didn’t bother including a plain C sample.

Baltasar García offered a simplification to the original code:

bool s_mode = WindowsIntegrityPolicy.IsEnabled;
bool unlockable_s_mode = WindowsIntegrityPolicy.CanDisable;
bool suggestCompanion = !s_mode || (s_mode && unlockable_s_mode);

and Csaba Varga simplified it further:

bool suggestCompanion = !s_mode || unlockable_s_mode;

I agree that these are valid simplifications, but I spelled it out the long way to make the multi-step logic more explicit, and to allow you to insert other logic into the blocks that right now merely contain an explanatory comment and a Boolean assignment.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

21 comments

Discussion is closed. Login to edit/delete existing comments.

Sort by :
  • skSdnW

    @Danielix No I did not, I’m taking about __x_ABI_CWindows_CSystem_CProfile_CIWindowsIntegrityPolicyStatics and this is a struct, IID is a GUID.

  • Baltasar García

    > Baltasar García offered a simplification to the original code:

    Holy cow! I was mentioned by Raymond Chen!!

  • skSdnW

    The remarks don’t make any sense because this is a SDK issue, not a Windows issue for the most part. Just make your own lib file that imports by ordinal and it will work out of the box on 98 and 2000+ if you care about something that old.

  • Dmitry

    To make things ”worse”, Raymond, some people still use COM from plain asm. And looking at the code for C with its mostly weak typing that always gets stronger at the most inconvenient moments, I feel happy there’s no need to type this nonsensically long type name in asm code 🙂

  • Yexuan Xiao

    Perhaps someday in the future, someone or I might need to translate ancient C code into C++ and will come across this article. I recently translated some C# code that uses COM into C++ and found it quite straightforward.

  • skSdnW

    WinRT makes it available to the other languages/UWP. Win32 developers just have to suck it up and suffer. Would have been a nice fit for the IsOS function in shlwapi…

    • Igor Levicki

      You can “suck up” as much bloat in your C++ projects you want dude, nobody is stopping you. Some of us try to minimize dependencies — especially Microsoft’s new API dependencies seeing how those things come and go compared to old Win32 API.

    • LB · Edited

      C++/WinRT works just fine in plain Win32 projects too, nothing is stopping you from mixing the best of both worlds.

      • Igor Levicki

        It does but it introduces a dependency on WinRT. Not everyone wants that.

    • 許恩嘉 · Edited

      In the Remarks of IsOS function (shlwapi.h):
      In Windows versions earlier than Windows Vista, IsOS was not exported by name or declared in a public header file. To use it in those cases, you must use GetProcAddress and request ordinal 437 from Shlwapi.dll to obtain a function pointer. Under Windows Vista, IsOS is included in Shlwapi.h and this is not necessary.

      This reminds me the history of the GetRandomRgn function and the story about BOZOSLIVEHERE and TABTHETEXTOUTFORWIMPS.
      Of course, the story of the IsOS function may be different.

      Read more
  • Igor Levicki

    @Raymond,

    I guess I owe you an apology for not phrasing my question better.

    Yes, it is nice to see a C version, but what I actually meant was “C(++) without Windows Runtime / COM” — in other words I was hoping for native Win32 API or registry key or EFI variable or whatever which can sidestep all the COM nonsense for such a trivial question about OS configuration state. The amount of boilerplate to read a single config bool is staggering and whoever did not think to expose this via a single API call should be ashamed.

    • LB

      COM is part of the native Win32 API, so I’m not sure what you mean by this. You’re basically trying to materialize an API you like better by asking someone at Microsoft if such an API exists.

      • Igor Levicki

        Yes, COM is part of native Win32 API and to use it you don’t need to add WinRT dependencies to your project — and that was exactly my point.

        I am shocked that there’s no simpler way to read this property.

    • Joshua Hudson

      I'm pretty sure Raymond Chen is thinking "no difficulty in using COM because S mode can only run Windows Store apps which are COM." But awhile back we had this support case where our WinForms product was running on somebody's machine and couldn't use any features that launched processes. On investigation, Windows was running in S mode somehow. We debated adding a check for this but could not because it had only a COM API that was completely alien to us. Without an interop DLL we could not possibly call the API and check, and I'm pretty sure Microsoft isn't...

      Read more
      • Christopher Lee

        Microsoft offers a Windows Defender Application Control (WDAC) policy that simulates S mode intended for testing purposes. Administrators can create similar policies that also allow additional specific applications to run, so perhaps one was authored and customized to allow your product. The WindowsIntegrityPolicy class won’t detect this situation.

    • 許恩嘉

      Assuming this can be achieved through a single API call, I believe it would be GetSystemMetrics(SM_SMODE). After all, there are already SM CONVERTIBLESLATEMODE, SM DIGITIZER, SM MEDIACENTER, SM_STARTER, SM_TABLETPC, etc. there.

      • Jan Ringoš

        It used to be possible to call GetProductInfo and then compare the product type to known S mode types, like PRODUCT_CLOUD. But that apparently no longer works.

    • Yexuan Xiao · Edited

      I don't think using COM is a problem (I use C++/WinRT), but I don't understand when it's necessary to initialize COM and when to uninitialize it. Currently, the behavior of CRT/Win32 functions is complex, and there's no clear guide telling people when and how to do these things (this is somewhat similar to what can be done during DLL initialization).

      For an operating system like Windows, nothing is trivial; even a boolean value may require extensive checks to compute. The code to obtain it using C++/WinRT is straightforward (compared to using Win32 to read the registry), so that is not the...

      Read more
    • skSdnW

      What is with the ugly __x_ABI prefix? Double underscore makes it a compiler reserved name and could in theory clash with something in Clang/GCC. Why wasn’t a better prefix chosen? WINRTABI? WINABINS?

      • Igor Levicki

        That’s explained in the article (C doesn’t support namespaces part).

      • Danielix Klimax

        That is not entire prefix. You skipped “IID_” part.