Windows TSF: ActivateProfile returns S_OK, but GetActiveLanguageProfile returns S_FALSE and the Text Service DLL is not loaded into a new Notepad process

soso 20 Reputation points
2026-08-29T01:34:41.3033333+00:00

I am developing a Windows input assistance application using a native C++ Text Service based on the Windows Text Services Framework (TSF).

I am currently encountering behavior related to Text Service activation after a Windows restart that I do not fully understand. I would appreciate guidance regarding the expected TSF behavior and the recommended implementation.

Application purpose

The application detects user-defined abbreviations and expands them into registered text.

The basic flow is:

User types an abbreviation
↓
TSF Text Service detects the input
↓
Registered candidates are retrieved
↓
The user confirms a candidate
↓
The text is replaced using a TSF EditSession / SetText

The input detection, candidate handling, and text replacement logic have already been implemented and tested separately.

The current issue is specifically related to activating the Text Service after a Windows restart and getting the Text Service DLL loaded into a newly started Notepad process.


Registration state

I have verified the following registrations:

  • COM registration
  • TSF text service registration
  • language profile registration
  • TSF category registration
  • DLL path
  • DLL SHA-256

The registration state can also be verified from a fresh process.


Observed behavior

After registering the Text Service, I enable and activate the language profile.

The simplified sequence is:

Register Text Service
↓
EnableLanguageProfile(...)
↓
ActivateProfile(..., TF_IPPMF_FORSESSION)
↓
Start a fresh process
↓
GetActiveLanguageProfile(...)
↓
Launch a new Notepad process

The API calls return:

EnableLanguageProfile(...) = S_OK

ActivateProfile(..., TF_IPPMF_FORSESSION) = S_OK

However, when I check the state from a fresh process afterward:

GetActiveLanguageProfile(...) = S_FALSE
active = false

When a new Notepad process is launched:

  • the Text Service DLL is not mapped into the Notepad process
  • the Text Service does not appear to be activated

Behavior after Windows restart

This issue is reproducible in the current Windows session after a Windows restart.

The registration APIs succeed, and the COM registration, TSF service registration, language profile registration, category registration, and DLL path appear to be correct.

However, even though:

ActivateProfile = S_OK

a fresh process reports:

GetActiveLanguageProfile = S_FALSE

and a newly launched Notepad process does not load the Text Service DLL.


What I have already checked

To isolate the issue, I performed a limited set of checks:

  • COM registration
  • TSF service registration
  • language profile registration
  • category registration
  • DLL path
  • DLL hash
  • EnableLanguageProfile
  • ActivateProfile
  • GetActiveLanguageProfile from a fresh process
  • DLL mapping in a newly launched Notepad process
  • non-elevated process
  • elevated process
  • minimal comparison of activation flags

I have not found an obvious registration error.

Both:

EnableLanguageProfile = S_OK
ActivateProfile = S_OK

are returned successfully, but the profile is still not reported as active from a fresh process.


Questions

I would greatly appreciate guidance from anyone familiar with TSF regarding the following questions.

1.

When ITfInputProcessorProfileMgr::ActivateProfile is called with TF_IPPMF_FORSESSION and returns S_OK, what is the exact scope of that activation?

Does it apply to the entire Windows logon session, or is it limited to the calling process, thread, TSF thread manager, or another scope?

2.

Is it expected TSF behavior for:

ActivateProfile(..., TF_IPPMF_FORSESSION)

to return S_OK, while a subsequent call to GetActiveLanguageProfile from another fresh process returns S_FALSE?

3.

Is calling GetActiveLanguageProfile from a separate process an appropriate way to determine whether the Text Service is active and available to newly started applications?

If not, what is the recommended method for verifying this state?

4.

After registering a TSF Text Service, what is the recommended activation sequence to ensure that newly started TSF-enabled applications, such as Notepad, load the Text Service correctly after a Windows restart?

5.

If ActivateProfile returns S_OK but a newly launched Notepad process does not load the Text Service DLL, which additional TSF states, APIs, registrations, or profile settings should be checked?

6.

For a Text Service intended to support input across Windows applications, should activation normally be performed for each application/process?

Or is the expected design to enable/activate the profile once for the Windows session and allow subsequently launched TSF-enabled applications to use it?


Expected behavior

Ultimately, I would like to achieve the following stable behavior:

Windows session starts
↓
Text Service becomes available
↓
User launches a TSF-enabled application
↓
Text Service DLL is loaded
↓
The Text Service receives the appropriate TSF activation
↓
Input assistance becomes available

I would prefer to use the TSF registration and activation mechanism as intended by Microsoft rather than relying on permanent changes to Windows or IME settings as a workaround.

If my understanding of TSF activation or my method of verifying the active state is incorrect, I would greatly appreciate any clarification.

Thank you very much for your help.I am developing a Windows input assistance application using a native C++ Text Service based on the Windows Text Services Framework (TSF).

I am currently encountering behavior related to Text Service activation after a Windows restart that I do not fully understand. I would appreciate guidance regarding the expected TSF behavior and the recommended implementation.

Application purpose

The application detects user-defined abbreviations and expands them into registered text.

The basic flow is:

User types an abbreviation
↓
TSF Text Service detects the input
↓
Registered candidates are retrieved
↓
The user confirms a candidate
↓
The text is replaced using a TSF EditSession / SetText

The input detection, candidate handling, and text replacement logic have already been implemented and tested separately.

The current issue is specifically related to activating the Text Service after a Windows restart and getting the Text Service DLL loaded into a newly started Notepad process.


Registration state

I have verified the following registrations:

  • COM registration
  • TSF text service registration
  • language profile registration
  • TSF category registration
  • DLL path
  • DLL SHA-256

The registration state can also be verified from a fresh process.


Observed behavior

After registering the Text Service, I enable and activate the language profile.

The simplified sequence is:

Register Text Service
↓
EnableLanguageProfile(...)
↓
ActivateProfile(..., TF_IPPMF_FORSESSION)
↓
Start a fresh process
↓
GetActiveLanguageProfile(...)
↓
Launch a new Notepad process

The API calls return:

EnableLanguageProfile(...) = S_OK

ActivateProfile(..., TF_IPPMF_FORSESSION) = S_OK

However, when I check the state from a fresh process afterward:

GetActiveLanguageProfile(...) = S_FALSE
active = false

When a new Notepad process is launched:

  • the Text Service DLL is not mapped into the Notepad process
  • the Text Service does not appear to be activated

Behavior after Windows restart

This issue is reproducible in the current Windows session after a Windows restart.

The registration APIs succeed, and the COM registration, TSF service registration, language profile registration, category registration, and DLL path appear to be correct.

However, even though:

ActivateProfile = S_OK

a fresh process reports:

GetActiveLanguageProfile = S_FALSE

and a newly launched Notepad process does not load the Text Service DLL.


What I have already checked

To isolate the issue, I performed a limited set of checks:

  • COM registration
  • TSF service registration
  • language profile registration
  • category registration
  • DLL path
  • DLL hash
  • EnableLanguageProfile
  • ActivateProfile
  • GetActiveLanguageProfile from a fresh process
  • DLL mapping in a newly launched Notepad process
  • non-elevated process
  • elevated process
  • minimal comparison of activation flags

I have not found an obvious registration error.

Both:

EnableLanguageProfile = S_OK
ActivateProfile = S_OK

are returned successfully, but the profile is still not reported as active from a fresh process.


Questions

I would greatly appreciate guidance from anyone familiar with TSF regarding the following questions.

1.

When ITfInputProcessorProfileMgr::ActivateProfile is called with TF_IPPMF_FORSESSION and returns S_OK, what is the exact scope of that activation?

Does it apply to the entire Windows logon session, or is it limited to the calling process, thread, TSF thread manager, or another scope?

2.

Is it expected TSF behavior for:

ActivateProfile(..., TF_IPPMF_FORSESSION)

to return S_OK, while a subsequent call to GetActiveLanguageProfile from another fresh process returns S_FALSE?

3.

Is calling GetActiveLanguageProfile from a separate process an appropriate way to determine whether the Text Service is active and available to newly started applications?

If not, what is the recommended method for verifying this state?

4.

After registering a TSF Text Service, what is the recommended activation sequence to ensure that newly started TSF-enabled applications, such as Notepad, load the Text Service correctly after a Windows restart?

5.

If ActivateProfile returns S_OK but a newly launched Notepad process does not load the Text Service DLL, which additional TSF states, APIs, registrations, or profile settings should be checked?

6.

For a Text Service intended to support input across Windows applications, should activation normally be performed for each application/process?

Or is the expected design to enable/activate the profile once for the Windows session and allow subsequently launched TSF-enabled applications to use it?


Expected behavior

Ultimately, I would like to achieve the following stable behavior:

Windows session starts
↓
Text Service becomes available
↓
User launches a TSF-enabled application
↓
Text Service DLL is loaded
↓
The Text Service receives the appropriate TSF activation
↓
Input assistance becomes available

I would prefer to use the TSF registration and activation mechanism as intended by Microsoft rather than relying on permanent changes to Windows or IME settings as a workaround.

If my understanding of TSF activation or my method of verifying the active state is incorrect, I would greatly appreciate any clarification.

Thank you very much for your help.

Windows development | Windows API - Win32
0 comments No comments

Answer accepted by question author
Taki Ly (WICLOUD CORPORATION) 4,440 Reputation points Microsoft External Staff Moderator
2026-08-31T10:18:08.25+00:00

Hello @soso ,

Thank you for the very detailed write-up. I built a small test text service to compare the behavior against what you are seeing, and the results point to a fairly clear direction, so let me summarize it plainly and address each of your questions.

When a profile is enabled and activated correctly, the activation does apply beyond the calling process. In my test, a fresh separate process read the profile back as active (GetActiveLanguageProfile returned S_OK), and every newly launched TSF app, including Notepad, loaded the text service DLL on its own. Because you are seeing the opposite, the most likely explanation is that your activation is not actually taking effect for the interactive session, even though the call returns S_OK. That directly answers your first two questions: per the documentation, TF_IPPMF_FORSESSION activates the profile for all threads in the current desktop (while TF_IPPMF_FORPROCESS is the per-process scope), so the scope is not a single process or thread, and an S_OK activation followed by S_FALSE from another fresh process is not the expected outcome.

On your third question, S_FALSE is not an error, it simply means the text service is not the currently active one for that process (and pguidProfile comes back as GUID_NULL), so a fresh process reading S_FALSE is a reliable sign that the activation did not persist. Reading it from a separate process is a valid check, but a more precise one is ITfInputProcessorProfileMgr::GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD), which returns the active profile record so you can compare its clsid, langid, and dwFlags and confirm your CLSID is the active keyboard profile with the enabled and active flags set.

For your fourth question, the recommended sequence is to make sure the service is fully registered first, then enable, then activate once, then verify. Concretely: register the COM server (InprocServer32), register the text service and language profile with ITfInputProcessorProfiles::Register and AddLanguageProfile, and register the keyboard category with ITfCategoryMgr::RegisterCategory(GUID_TFCAT_TIP_KEYBOARD); then call EnableLanguageProfile(clsid, langid, guidProfile, TRUE); then ITfInputProcessorProfileMgr::ActivateProfile(TF_PROFILETYPE_INPUTPROCESSOR, langid, clsid, guidProfile, NULL, TF_IPPMF_FORSESSION); and finally verify with GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD). You do not need to re-activate per application. Once it is the active keyboard profile, newly launched TSF apps load the DLL on their own.

For your fifth question, if ActivateProfile returns S_OK but a new Notepad does not load the DLL, it is worth checking both the runtime state and the registration. On the state side, ctfmon.exe is what propagates the active profile to other processes, so if your activation runs at logon before ctfmon is fully up, which fits your "after Windows restart" scenario, the change may not broadcast, and it is worth confirming ctfmon.exe is running when you activate. It also matters that your activator runs on the same desktop and at the same integrity level as the target apps, because TF_IPPMF_FORSESSION is scoped to the current desktop, so if the activator is elevated while Notepad runs as the normal user, the change may not reach it. A very short-lived activator that exits immediately may also not commit the session state reliably, so doing the activation on an STA thread that pumps messages is the safer pattern. On the registration side, please confirm the keyboard category (GUID_TFCAT_TIP_KEYBOARD) is registered for your CLSID, that the profile is actually enabled, and consider calling ITfInputProcessorProfiles::SetDefaultLanguageProfile so your service is the default profile for that language, which helps newly launched apps pick it up consistently after a restart.

On your sixth question, the intended design is to enable and activate the profile once per session, not once per application. After that, TSF loads your service into each newly launched TSF-enabled app automatically. Per-process activation (TF_IPPMF_FORPROCESS) is only for the narrow case where you deliberately want the change limited to a single process.

If it helps to narrow it down, could you reproduce the issue and, in a fresh process, capture what GetActiveProfile(GUID_TFCAT_TIP_KEYBOARD) returns (the clsid, langid, and dwFlags), and confirm whether ctfmon.exe is running and whether your activator and the target apps run on the same desktop and integrity level. That should make it clear whether the activation is reaching the interactive session or being isolated from it.

References

Hope this helps. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

Thank you.

Was this answer helpful?

1 person found this answer helpful.
0 comments No comments

2 additional answers

Sort by: Newest
  1. soso 20 Reputation points
    2026-08-31T13:05:33.9166667+00:00

    Hello Taki Lee,

    Thank you very much for the detailed response. Your explanation was extremely helpful.

    After receiving your guidance, we continued the investigation and were able to reproduce a successful activation of the same text service that had previously appeared to fail.

    We found that the original failure was not caused by a fundamentally broken Text Service DLL or by a basic failure of ActivateProfile. Instead, several conditions in our previous verification procedure had been mixed together.

    In the successful test, we used the following conditions:

    ・The test was performed from the interactive user session.

    ・EnableLanguageProfile(..., TRUE) returned S_OK.

    ・ActivateProfile(..., TF_IPPMF_FORSESSION) returned S_OK.

    ・A fresh observer process in the same interactive session reported the target profile as active.

    ・We launched the packaged Windows Notepad explicitly through its AUMID

     (Microsoft.WindowsNotepad_8wekyb3d8bbwe!App).

    ・We confirmed that the Text Service DLL was actually mapped into the new Notepad process.

    ・Our Text Service log confirmed that ITfTextInputProcessor::Activate was reached successfully in that same Notepad PID.

    Therefore, we can now confirm that TF_IPPMF_FORSESSION activation can propagate to a newly launched TSF application in our environment, consistent with your explanation.

    We also identified an important issue in our previous registration verification procedure.

    Immediately after AddLanguageProfile returned S_OK, a fresh process did not initially enumerate the Language Profile. Our old test treated this single observation as a registration failure and immediately rolled the registration back.

    In the new test, we separated the registration mutation from the observation and waited for the state to converge. The registration state eventually became visible to fresh observer processes, and we required two consecutive successful observations before continuing.

    In this particular test, convergence took approximately 90 seconds.

    We observed a similar phenomenon in the opposite direction during cleanup:

    ・RemoveLanguageProfile returned S_OK.

    ・A fresh observer immediately afterward still saw the Language Profile.

    ・After some time, the profile was no longer enumerated.

    We therefore now distinguish between:

    1.the success of the registration/removal API call, and

    2.when the resulting state becomes observable from another fresh process.

    We have not yet determined whether this delay is caused by TSF caching, ctfmon.exe, notification propagation, per-process state, or another internal mechanism.

    There is one additional architectural detail about our Text Service that may be relevant. Our service is intended to coexist with Microsoft IME rather than replace it as the keyboard input profile. Therefore, our current implementation does not register itself under GUID_TFCAT_TIP_KEYBOARD; it uses an auxiliary TIP category. Despite this, the Text Service successfully loaded into the packaged Notepad process and its Activate method was called.

    For this reason, we have not yet adopted SetDefaultLanguageProfile, since changing the default keyboard language profile could conflict with our coexistence design.

    Your comments about session/desktop context, elevation, ctfmon.exe, and distinguishing S_FALSE from an API error were especially useful and helped us correct our verification procedure.

    If possible, we would appreciate clarification on one remaining point:

    Is it expected that, after AddLanguageProfile or RemoveLanguageProfile returns S_OK, a newly created process may temporarily observe the previous Language Profile enumeration state?

    If so, is there a recommended notification mechanism, API, or synchronization method for determining when the Language Profile registration state has become visible system-wide, rather than relying on bounded polling?

    Thank you again for your help. Your answer allowed us to significantly narrow down the problem and establish a reproducible successful activation procedure.

    For context, this investigation is being conducted collaboratively by a human developer using ChatGPT for technical analysis and Codex for code inspection and test support.

    Was this answer helpful?


  2. Taki Ly (WICLOUD CORPORATION) 4,440 Reputation points Microsoft External Staff Moderator
    2026-08-31T02:53:16.8166667+00:00

    Hi @soso ,

    I'm working on this issue and will try to get back to you soon. Thank you for your patience.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.