Related
In Windows 8 and Windows 10 before Anniversary update it was possible to show touch keyboard by starting
C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe
It no longer works in Windows 10 Anniversary update; the TabTip.exe process is running, but the keyboard is not shown.
Is there a way to show it programmatically?
UPDATE
I found a workaround - fake mouse click on touch keyboard icon in system tray. Here is code in Delphi
// Find tray icon window
function FindTrayButtonWindow: THandle;
var
ShellTrayWnd: THandle;
TrayNotifyWnd: THandle;
begin
Result := 0;
ShellTrayWnd := FindWindow('Shell_TrayWnd', nil);
if ShellTrayWnd > 0 then
begin
TrayNotifyWnd := FindWindowEx(ShellTrayWnd, 0, 'TrayNotifyWnd', nil);
if TrayNotifyWnd > 0 then
begin
Result := FindWindowEx(TrayNotifyWnd, 0, 'TIPBand', nil);
end;
end;
end;
// Post mouse click messages to it
TrayButtonWindow := FindTrayButtonWindow;
if TrayButtonWindow > 0 then
begin
PostMessage(TrayButtonWindow, WM_LBUTTONDOWN, MK_LBUTTON, $00010001);
PostMessage(TrayButtonWindow, WM_LBUTTONUP, 0, $00010001);
end;
UPDATE 2
Another thing I found is that setting this registry key restores old functionality when starting TabTip.exe shows touch keyboard
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\TabletTip\1.7\EnableDesktopModeAutoInvoke=1
OK, I reverse engineered what explorer does when the user presses that button in the system tray.
Basically it creates an instance of an undocumented interface ITipInvocation and calls its Toggle(HWND) method, passing desktop window as an argument. As the name suggests, the method either shows or hides the keyboard depending on its current state.
Please note that explorer creates an instance of ITipInvocation on every button click. So I believe the instance should not be cached. I also noticed that explorer never calls Release() on the obtained instance. I'm not too familiar with COM, but this looks like a bug.
I tested this in Windows 8.1, Windows 10 & Windows 10 Anniversary Edition and it works perfectly. Here's a minimal example in C that obviously lacks some error checks.
#include <initguid.h>
#include <Objbase.h>
#pragma hdrstop
// 4ce576fa-83dc-4F88-951c-9d0782b4e376
DEFINE_GUID(CLSID_UIHostNoLaunch,
0x4CE576FA, 0x83DC, 0x4f88, 0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76);
// 37c994e7_432b_4834_a2f7_dce1f13b834b
DEFINE_GUID(IID_ITipInvocation,
0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b);
struct ITipInvocation : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Toggle(HWND wnd) = 0;
};
int WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr;
hr = CoInitialize(0);
ITipInvocation* tip;
hr = CoCreateInstance(CLSID_UIHostNoLaunch, 0, CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**)&tip);
tip->Toggle(GetDesktopWindow());
tip->Release();
return 0;
}
Here's the C# version as well:
class Program
{
static void Main(string[] args)
{
var uiHostNoLaunch = new UIHostNoLaunch();
var tipInvocation = (ITipInvocation)uiHostNoLaunch;
tipInvocation.Toggle(GetDesktopWindow());
Marshal.ReleaseComObject(uiHostNoLaunch);
}
[ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
class UIHostNoLaunch
{
}
[ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ITipInvocation
{
void Toggle(IntPtr hwnd);
}
[DllImport("user32.dll", SetLastError = false)]
static extern IntPtr GetDesktopWindow();
}
Update: per #EugeneK comments, I believe that tabtip.exe is the COM server for the COM component in question, so if your code gets REGDB_E_CLASSNOTREG, it should probably run tabtip.exe and try again.
I had the same problem too. It took me much time and headache, but Thanks to Alexei and Torvin I finally got it working on Win 10 1709. Visibility check was the difficulty. Maybe The OSKlib Nuget could be updated. Let me sum up the complete sulotion (For sure my code has some unnecessary lines now):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using Osklib.Interop;
using System.Runtime.InteropServices;
using System.Threading;
namespace OSK
{
public static class OnScreenKeyboard
{
static OnScreenKeyboard()
{
var version = Environment.OSVersion.Version;
switch (version.Major)
{
case 6:
switch (version.Minor)
{
case 2:
// Windows 10 (ok)
break;
}
break;
default:
break;
}
}
private static void StartTabTip()
{
var p = Process.Start(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
int handle = 0;
while ((handle = NativeMethods.FindWindow("IPTIP_Main_Window", "")) <= 0)
{
Thread.Sleep(100);
}
}
public static void ToggleVisibility()
{
var type = Type.GetTypeFromCLSID(Guid.Parse("4ce576fa-83dc-4F88-951c-9d0782b4e376"));
var instance = (ITipInvocation)Activator.CreateInstance(type);
instance.Toggle(NativeMethods.GetDesktopWindow());
Marshal.ReleaseComObject(instance);
}
public static void Show()
{
int handle = NativeMethods.FindWindow("IPTIP_Main_Window", "");
if (handle <= 0) // nothing found
{
StartTabTip();
Thread.Sleep(100);
}
// on some devices starting TabTip don't show keyboard, on some does ¯\_(ツ)_/¯
if (!IsOpen())
{
ToggleVisibility();
}
}
public static void Hide()
{
if (IsOpen())
{
ToggleVisibility();
}
}
public static bool Close()
{
// find it
int handle = NativeMethods.FindWindow("IPTIP_Main_Window", "");
bool active = handle > 0;
if (active)
{
// don't check style - just close
NativeMethods.SendMessage(handle, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_CLOSE, 0);
}
return active;
}
public static bool IsOpen()
{
return GetIsOpen1709() ?? GetIsOpenLegacy();
}
[DllImport("user32.dll", SetLastError = false)]
private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string className, string title = null);
[DllImport("user32.dll", SetLastError = false)]
private static extern uint GetWindowLong(IntPtr wnd, int index);
private static bool? GetIsOpen1709()
{
// if there is a top-level window - the keyboard is closed
var wnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass1709, WindowCaption1709);
if (wnd != IntPtr.Zero)
return false;
var parent = IntPtr.Zero;
for (;;)
{
parent = FindWindowEx(IntPtr.Zero, parent, WindowParentClass1709);
if (parent == IntPtr.Zero)
return null; // no more windows, keyboard state is unknown
// if it's a child of a WindowParentClass1709 window - the keyboard is open
wnd = FindWindowEx(parent, IntPtr.Zero, WindowClass1709, WindowCaption1709);
if (wnd != IntPtr.Zero)
return true;
}
}
private static bool GetIsOpenLegacy()
{
var wnd = FindWindowEx(IntPtr.Zero, IntPtr.Zero, WindowClass);
if (wnd == IntPtr.Zero)
return false;
var style = GetWindowStyle(wnd);
return style.HasFlag(WindowStyle.Visible)
&& !style.HasFlag(WindowStyle.Disabled);
}
private const string WindowClass = "IPTip_Main_Window";
private const string WindowParentClass1709 = "ApplicationFrameWindow";
private const string WindowClass1709 = "Windows.UI.Core.CoreWindow";
private const string WindowCaption1709 = "Microsoft Text Input Application";
private enum WindowStyle : uint
{
Disabled = 0x08000000,
Visible = 0x10000000,
}
private static WindowStyle GetWindowStyle(IntPtr wnd)
{
return (WindowStyle)GetWindowLong(wnd, -16);
}
}
[ComImport]
[Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface ITipInvocation
{
void Toggle(IntPtr hwnd);
}
internal static class NativeMethods
{
[DllImport("user32.dll", EntryPoint = "FindWindow")]
internal static extern int FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
internal static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow", SetLastError = false)]
internal static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
internal static extern int GetWindowLong(int hWnd, int nIndex);
internal const int GWL_STYLE = -16;
internal const int GWL_EXSTYLE = -20;
internal const int WM_SYSCOMMAND = 0x0112;
internal const int SC_CLOSE = 0xF060;
internal const int WS_DISABLED = 0x08000000;
internal const int WS_VISIBLE = 0x10000000;
}
}
The only solution I've found to work is by sending PostMessage as you've mentioned in answer 1. Here's the C# version of it in case someone needs it.
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(string sClassName, string sAppName);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
var trayWnd = FindWindow("Shell_TrayWnd", null);
var nullIntPtr = new IntPtr(0);
if (trayWnd != nullIntPtr)
{
var trayNotifyWnd = FindWindowEx(trayWnd, nullIntPtr, "TrayNotifyWnd", null);
if (trayNotifyWnd != nullIntPtr)
{
var tIPBandWnd = FindWindowEx(trayNotifyWnd, nullIntPtr, "TIPBand", null);
if (tIPBandWnd != nullIntPtr)
{
PostMessage(tIPBandWnd, (UInt32)WMessages.WM_LBUTTONDOWN, 1, 65537);
PostMessage(tIPBandWnd, (UInt32)WMessages.WM_LBUTTONUP, 1, 65537);
}
}
}
public enum WMessages : int
{
WM_LBUTTONDOWN = 0x201,
WM_LBUTTONUP = 0x202,
WM_KEYDOWN = 0x100,
WM_KEYUP = 0x101,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14,
}
I detect 4 situations when trying to open Touch Keyboard on Windows 10 Anniversary Update
Keyboard is Visible - when "IPTIP_Main_Window" is present, NOT disabled and IS visible
Keyboard is not visible - when "IPTIP_Main_Window" is present but disabled
Keyboard is not visible - when "IPTIP_Main_Window" is present but NOT disabled and NOT visible
Keyboard is not visible - when "IPTIP_Main_Window" is NOT present
1 - nothing to do
2+3 - activating via COM
4 - most interesting scenario. In some devices starting TabTip process opens touch keyboard, on some - not. So we must start TabTip process, wait for appearing window "IPTIP_Main_Window", check it for visibility and activate it via COM if nessesary.
I make small library for my project, you can use it - osklib
The following code will always work since it uses latest MS Api
I put it into a dll (Needed for a Delphi project) but it is a plain C
Also useful for obtaining the keyboard size and adjusting application layout
//*******************************************************************
//
// RETURNS KEYBOARD RECTANGLE OR EMPTY ONE IF KEYBOARD IS NOT VISIBLE
//
//*******************************************************************
RECT __stdcall GetKeyboardRect()
{
IFrameworkInputPane *inputPane = NULL;
RECT prcInputPaneScreenLocation = { 0,0,0,0 };
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_FrameworkInputPane, NULL, CLSCTX_INPROC_SERVER, IID_IFrameworkInputPane, (LPVOID*)&inputPane);
if (SUCCEEDED(hr))
{
hr=inputPane->Location(&prcInputPaneScreenLocation);
if (!SUCCEEDED(hr))
{
}
inputPane->Release();
}
}
CoUninitialize();
return prcInputPaneScreenLocation;
}
There is still some mystery about how the touch keyboard is set visible by Windows 10 Anniversary Update. I'm actually having the exact same issue and here are the lastest infos i've found :
Windows 10 1607 works in two modes : Desktop and Tablet. While in Desktop mode, TabTip.exe can be called but won't show. While in Tablet mode, everything works fine : TabTip.exe shows itself when called. So a 100% working workaround is to set your computer in Tablet Mode but who wants his desktop/laptop to work in tablet mode ? Not me anyway !
You can use the "EnableDesktopModeAutoInvoke" Key (HKCU, DWORD set to 1) and on some computers running 1607 it worked great while in Desktop Mode. But for some unknown reasons, it is not working on my HP touchpad.
Please note that this registry value is the "Show touch keyboard on desktop mode if there is no attached keyboard" option in Windows parameters > touch
You can use Torvin's code to show TabTip.exe (as mentioned TabTip.exe should be running when you do the COM stuff), it is working fine on some computers running 1607 (including my HP touchpad ! yay !) But it will do nothing on some others comps with the same windows Build.
So far tested on 4 different computers and i'm unable to get something working fine on all...
The problem seems to be with setting of Windows OS. I have faced same issue with the app I was developing. With Windows 8 and 10 (before update) code that called keyboard worked fine, but after update failed to work. After reading this article, I did following:
Pressed Win+I to open the Settings app
Clicked on Devices > Typing
Turned "Automatically show the touch keyboard in windowed apps when there’s no keyboard attached to your device" ON.
Right after that keyboard starting showing up in Windows 10 also.
Implementing the IValueProvider/ITextProvider in your control is a correct way to achieve this, as described here: https://stackoverflow.com/a/43886052/1184950
I tried multiple things that did not work. But I discovered that I can use the Shortcut Keys Windows/Ctrl/O to open the On Screen Key Board.
Also there is a Nuget package: Input Simulator by Michael Noonan.
If you install the InputSimulator NuGet package in your Winforms project - then add code like this to an event, like a button:
private void button1_Click(object sender, EventArgs e)
{
var simu = new InputSimulator();
simu.Keyboard.ModifiedKeyStroke(new[] { VirtualKeyCode.LWIN, VirtualKeyCode.CONTROL }, VirtualKeyCode.VK_O);
}
You will also need to add these using statements:
using WindowsInput;
using WindowsInput.Native;
Run your app and the button will display the keyboard and also hit it again and it will remove it.
I am on Windows 10 and vs 2019.
Set oShell = CreateObject("WScript.Shell")
oShell.AppActivate "Program Manager"
oShell.Run "tabtip.exe", 0, true
oShell.SendKeys "%{TAB}"
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\TabletTip\1.7]
"EnableDesktopModeAutoInvoke"=dword:00000001
Following code works well on 21h1 version tablet and notebook,thanks for #torvin method
#include <dwmapi.h>
#include <windows.h>
#pragma comment(lib,"dwmapi.lib")
void toggleKeyboardShowState()
{
if(FindWindowEx(nullptr,nullptr,L"IPTip_Main_Window",nullptr)!=nullptr)
{
ITipInvocation *tip_invocation_;
if(CoCreateInstance(CLSID_UIHostNoLaunch,nullptr,CLSCTX_INPROC_HANDLER|CLSCTX_LOCAL_SERVER,IID_ITipInvocation,(void **)&tip_invocation_)==S_OK)
tip_invocation_->Toggle(GetDesktopWindow());
return;
}
PVOID OldValue=nullptr;
BOOL bRet=Wow64DisableWow64FsRedirection(&OldValue);
ShellExecute(nullptr,L"open",L"C:\\Program Files\\Common Files\\microsoft shared\\ink\\TabTip.exe",nullptr,nullptr,SW_SHOWNORMAL);
if(bRet)
Wow64RevertWow64FsRedirection(OldValue);
}
bool keyboardIsShow()
{
int cloaked=0;
return DwmGetWindowAttribute(FindWindowExW(FindWindowExW(nullptr,nullptr,L"ApplicationFrameWindow",nullptr),nullptr,L"Windows.UI.Core.CoreWindow",L"Microsoft Text Input Application"),DWMWA_CLOAKED,&cloaked,DWM_CLOAKED_INHERITED)==S_OK?0==cloaked:false;
}
It will start and show taptip and show it default if there it isn't in background when call toggleKeyboardShowState(),otherwise change taptip show state.
start tabtip process or hide->show
if(!keyboardIsShow())
toggleKeyboardShowState();
show->hide
if(keyboardIsShow())
toggleKeyboardShowState();
The keyboard show state can be switched even without if judgment.
Use this method:
Create osk.bat file and save it under your program folder ie. C:\My Software\osk.bat
Type in this osk.bat the following cmd:
"C:\Program Files\Common Files\Microsoft Shared\Ink\Tabtip.exe"
Use Windows Script to run this bat file
oWSH = CREATEOBJECT("wscript.shell")
oWSH.Run("osk.bat", 0, .T.)
In Win10 Ver 1803, DesktopMode, there is no reliable way to
toggle the "Touch Keyboard" on|off [ ITipInvocation.Toggle() ];
nor can you reliably discover if it's "up" ( on screen )
[ IFrameworkInputPane.Location() ]; both routines fail randomly.
Instead, ensure that "TabTIP.EXE" and "....InputApp.EXE"
only run when the keyboard is "up" ( on screen ).
To toggle the keyboard on and off ( from X.CPP in Jeff-Relf.Me/X.ZIP ):
if ( WM == WM_HOTKEY && C == 'K' ) {
// A mouse button takes me here. Jeff-Relf.Me/g600.PNG
if ( KillProc = 1, Running( L"TabTIP.EXE" ), KillProc = 1, Running(
L"WindowsInternal.ComposableShell.Experiences.TextInput.InputApp.EXE"
) )
// The keyboard was _On_ ( i.e. its processes were running ),
// so it was "turned _Off_" (killed); and we're done.
goto Done ;
// The keyboard was _Off_ ( i.e. no running processes ).
// Turn it _On_:
Launch( L"%CommonProgramFiles%/microsoft shared/ink/TabTIP.EXE" );
Sleep(99);
static const GUID CLSID_UIHostNoLaunch = { 0x4CE576FA, 0x83DC,
0x4f88, 0x95, 0x1C, 0x9D, 0x07, 0x82, 0xB4, 0xE3, 0x76 };
static const GUID IID_ITipInvocation = { 0x37c994e7, 0x432b,
0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b };
static struct ITipInvocation : IUnknown { virtual HRESULT
STDMETHODCALLTYPE Toggle( HWND wnd ) = 0 ; } * Tog ;
Tog = 0, CoCreateInstance( CLSID_UIHostNoLaunch, 0, CLSCTX_INPROC_HANDLER
| CLSCTX_LOCAL_SERVER, IID_ITipInvocation, (void**) & Tog );
// Firefox and Chrome need this:
Tog ? Tog->Toggle( GetDesktopWindow() ), Tog->Release() : 0 ; }
- - - - - - - - - - - - -
// To get the process list, and kill stuff:
#include <tlhelp32.H>
int KillProc ;
int Running( wchar * EXE ) { int Found ; HANDLE PIDs, aProc ;
PROCESSENTRY32 aPID = { sizeof aPID };
PIDs = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
Process32First( PIDs, &aPID );
while ( Found = !strCmpI( aPID.szExeFile, EXE ),
KillProc && Found && (
aProc = OpenProcess( PROCESS_TERMINATE, 0, aPID.th32ProcessID ),
aProc ? TerminateProcess( aProc, 9 ), CloseHandle( aProc ) : 0 ),
!Found && Process32Next( PIDs, &aPID ) );
KillProc = 0, CloseHandle( PIDs ); return Found ; }
Launch( wchar * Cmd ) { wchar _Cmd[333]; static PROCESS_INFORMATION Stat ;
static STARTUPINFO SU = { sizeof SU };
SetEnvironmentVariable( L"__compat_layer", L"RunAsInvoker" );
ExpandEnvironmentStrings( Cmd, _Cmd, 333 ), Cmd = _Cmd ;
if ( CreateProcess( 0, Cmd, 0,0,1,0,0,0, &SU , &Stat ) )
CloseHandle( Stat.hProcess ), CloseHandle( Stat.hThread ); }
// CoInitialize(0);
I have a project that I developed about seven years ago in Win95, and works in Win7. It is developed in Visual Studio 2005. This application looks for the "You have new email" tray icon that appears in the tray (in various forms) by most email applications. I use it to blink an LED on a serial port, so I can glance in the room to see if I have email, rather than going to the computer, moving the mouse to wake the screen, and looking at the tray or the email program itself. It's a time-saver and aggravation-reducer.
It works by getting the system tray handle, and then using this handle, iterates all the buttons in the tray, and comparing the button text for a specific string. Here is the part that is having a problem in Windows 10:
IntPtr hWndTray = GetSystemTrayHandle();
listBoxIcons.Items.Add(string.Format("Tray handle=0x{0:X}", (int)hWndTray));
UInt32 count = User32.SendMessage(hWndTray, TB.BUTTONCOUNT, 0, 0);
listBoxIcons.Items.Add(string.Format("Tray button count={0:D}", count));
The call to GetSystemTrayHandle() works fine, I get a non-null value. The call to SendMessage(hWndTray, TB.BUTTONCOUNT, ...) returns zero, even though in the test case I'm using, there are nine buttons in the tray.
Did the concept of "tray icons", or the API calls to get them, change in Windows 10?
Here are the API calls I am using:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
From User32.cs:
internal class TB
{
public const uint GETBUTTON = WM.USER + 23 ;
public const uint BUTTONCOUNT = WM.USER + 24 ;
public const uint CUSTOMIZE = WM.USER + 27 ;
public const uint GETBUTTONTEXTA = WM.USER + 45 ;
public const uint GETBUTTONTEXTW = WM.USER + 75 ;
}
Here is the GetSystemTrayHandle() method:
private IntPtr GetSystemTrayHandle()
{
IntPtr hWndTray = FindWindow("Shell_TrayWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "SysPager", null);
if (hWndTray != IntPtr.Zero)
{
hWndTray = FindWindowEx(hWndTray, IntPtr.Zero, "ToolbarWindow32", null);
return hWndTray;
}
}
}
return IntPtr.Zero;
}
The "SendMessage" call has existed since about the inception of Windows, so if it no longer works, TB.BUTTONCOUNT may have been redefined or superceded in Windows 10. I cannot find any information on this.
Edit: Developed on Win98, not Win95.
Remy's message spurred further research into notification icons. The answer was found using information at http://www.ghacks.net/2015/03/11/manage-and-display-system-tray-icons-in-windows-10/. Once notifications were enabled, for example "email" and "volume," this program, as written, can now see them.
I run another program from Windows Service
private Process RunApp(string _appLocation)
{
//Create Process
Process _ret = new Process();
_ret.StartInfo.FileName = _appLocation;
//Run as Administrator.
_ret.StartInfo.Verb = "runas";
//Window = normal
_ret.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
_ret.StartInfo.CreateNoWindow = false;
_ret.Start();
return _ret;
}
I see it in Task Manager. it is working but I can't see window.
(If I can not see this then I can change Parent and see it)I try this:
Create new MidiChildWindow project and:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private void GetListeners()
{
Process[] Memory = Process.GetProcesses();
foreach (Process _prc in Memory)
{
if (_prc.ProcessName == "MyRunnedApplication")
{
SetParent(_prc.MainWindowHandle, this.Handle);
ShowWindow(_prc.MainWindowHandle, 1);
}
}
}
but its not work.
Runned my application is child the Windows service and I can not change it.
How I see it?
(Sory my bad english)
According to MSDN:
In Windows 8, spell checking is built-in to edit controls.
Well, I have the option enabled in my settings (both highlight and auto-correct) and I'm not seeing this in Notepad.exe or my own legacy Win32 app.
What do I need to do to enable (hopefully it's that simple) this for my application? I did try to follow the info in the article and read a lot of the reference, but it really isn't clear and seems geared towards creating custom providers/solutions, but I'd be happy with any "built-in" behaviors.
It is built-in only for Rich Edit controls, EM_SETLANGOPTIONS, IMF_SPELLCHECKING option. You need to use the later version of Rich Edit, the one in MsftEdit.dll instead of the more common v2.0 version that you get by default.
I tried it out in a Winforms control, worked well. Do note that it doesn't make spell-checking inter-active, nothing resembling a dialog that lets you pick from a set of suggested alternatives. Anything that can be auto-corrected, like "teh" to "the" and "spelll" to "spell" is immediately applied, words that don't have an auto-correction are underlined in red. Ctrl+Z turns an auto-corrected word back into its original.
You shouldn't have to much trouble working from this C# code to your otherwise unspecified language. There's some boilerplate Winforms plumbing present, key take-aways is to use LoadLibrary to get the v5 version of the control initialized so you can use the RichEdit50W window class name. And use SendMessage() to turn the option on:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class RichTextBoxEx : RichTextBox {
protected override CreateParams CreateParams {
get {
if (moduleHandle == IntPtr.Zero) {
moduleHandle = LoadLibrary("msftedit.dll");
if (moduleHandle == IntPtr.Zero) throw new Win32Exception("Could not load Msftedit.dll");
}
CreateParams createParams = base.CreateParams;
createParams.ClassName = "RichEdit50W";
if (this.Multiline) {
if (((this.ScrollBars & RichTextBoxScrollBars.Horizontal) != RichTextBoxScrollBars.None) && !base.WordWrap) {
createParams.Style |= 0x100000;
if ((this.ScrollBars & ((RichTextBoxScrollBars)0x10)) != RichTextBoxScrollBars.None) {
createParams.Style |= 0x2000;
}
}
if ((this.ScrollBars & RichTextBoxScrollBars.Vertical) != RichTextBoxScrollBars.None) {
createParams.Style |= 0x200000;
if ((this.ScrollBars & ((RichTextBoxScrollBars)0x10)) != RichTextBoxScrollBars.None) {
createParams.Style |= 0x2000;
}
}
}
if ((BorderStyle.FixedSingle == base.BorderStyle) && ((createParams.Style & 0x800000) != 0)) {
createParams.Style &= -8388609;
createParams.ExStyle |= 0x200;
}
return createParams;
}
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (Environment.OSVersion.Version.Major > 6 ||
Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor >= 2) {
int opts = (int)SendMessage(this.Handle, EM_GETLANGOPTIONS, IntPtr.Zero, IntPtr.Zero);
opts |= IMF_SPELLCHECKING;
SendMessage(this.Handle, EM_SETLANGOPTIONS, IntPtr.Zero, new IntPtr(opts));
}
}
private static IntPtr moduleHandle;
private const int IMF_SPELLCHECKING = 0x0800;
private const int EM_SETLANGOPTIONS = 0x0400 + 120;
private const int EM_GETLANGOPTIONS = 0x0400 + 121;
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
UPDATE, you need a lot less of this code today when you target .NET 4.7 or higher. It already takes care of the CreateParams override to also use msftedit.dll: https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs,d2aebb12b70acde0
So here's the scoop:
I wrote a tiny C# app a while back that displays the hostname, ip address, imaged date, thaw status (we use DeepFreeze), current domain, and the current date/time, to display on the welcome screen of our Windows 7 lab machines. This was to replace our previous information block, which was set statically at startup and actually embedded text into the background, with something a little more dynamic and functional. The app uses a Timer to update the ip address, deepfreeze status, and clock every second, and it checks to see if a user has logged in and kills itself when it detects such a condition.
If we just run it, via our startup script (set via group policy), it holds the script open and the machine never makes it to the login prompt. If we use something like the start or cmd commands to start it off under a separate shell/process, it runs until the startup script finishes, at which point Windows seems to clean up any and all child processes of the script. We're currently able to bypass that using psexec -s -d -i -x to fire it off, which lets it persist after the startup script is completed, but can be incredibly slow, adding anywhere between 5 seconds and over a minute to our startup time.
We have experimented with using another C# app to start the process, via the Process class, using WMI Calls (Win32_Process and Win32_ProcessStartup) with various startup flags, etc, but all end with the same result of the script finishing and the info block process getting killed. I tinkered with rewriting the app as a service, but services were never designed to interact with the desktop, let alone the login window, and getting things operating in the right context never really seemed to work out.
So for the question: Does anybody have a good way to accomplish this? Launch a task so that it would be independent of the startup script and run on top of the welcome screen?
This can be done through a lot of Win32 API calls. I have managed to get a program with a GUI onto the Winlogon desktop (before anyone asks, it's not an interactive GUI). Basically you need to run a loader process as SYSTEM, which will then spawn the new process. Since you most likely want this process to run on start up, you can either use the task scheduler to run the loader as SYSTEM or you can use a service to do the same thing. I'm currently using a service, but I tried using the task scheduler and it did work just fine.
Short summary:
Grab the Winlogon.exe process (as a Process)
Grab the token of winlogon using OpenProcessToken using the .handle of the Process
Create a new token and duplicate the winlogon token to it
Elevate the privileges of the token
Create the process using CreateProcessAsUser, making sure to set lpDesktop to "Winsta0\Winlogon" and using the token you created.
Code example:
// grab the winlogon process
Process winLogon = null;
foreach (Process p in Process.GetProcesses()) {
if (p.ProcessName.Contains("winlogon")) {
winLogon = p;
break;
}
}
// grab the winlogon's token
IntPtr userToken = IntPtr.Zero;
if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, out userToken)) {
log("ERROR: OpenProcessToken returned false - " + Marshal.GetLastWin32Error());
}
// create a new token
IntPtr newToken = IntPtr.Zero;
SECURITY_ATTRIBUTES tokenAttributes = new SECURITY_ATTRIBUTES();
tokenAttributes.nLength = Marshal.SizeOf(tokenAttributes);
SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
threadAttributes.nLength = Marshal.SizeOf(threadAttributes);
// duplicate the winlogon token to the new token
if (!DuplicateTokenEx(userToken, 0x10000000, ref tokenAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenImpersonation, out newToken)) {
log("ERROR: DuplicateTokenEx returned false - " + Marshal.GetLastWin32Error());
}
TOKEN_PRIVILEGES tokPrivs = new TOKEN_PRIVILEGES();
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = new LUID();
if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out seDebugNameValue)) {
log("ERROR: LookupPrivilegeValue returned false - " + Marshal.GetLastWin32Error());
}
tokPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// escalate the new token's privileges
if (!AdjustTokenPrivileges(newToken, false, ref tokPrivs, 0, IntPtr.Zero, IntPtr.Zero)) {
log("ERROR: AdjustTokenPrivileges returned false - " + Marshal.GetLastWin32Error());
}
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "Winsta0\\Winlogon";
// start the process using the new token
if (!CreateProcessAsUser(newToken, process, process, ref tokenAttributes, ref threadAttributes,
true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
logInfoDir, ref si, out pi)) {
log("ERROR: CreateProcessAsUser returned false - " + Marshal.GetLastWin32Error());
}
Process _p = Process.GetProcessById(pi.dwProcessId);
if (_p != null) {
log("Process " + _p.Id + " Name " + _p.ProcessName);
} else {
log("Process not found");
}
This is one of those "You really need a good reason to do this" questions. Microsoft tries very hard to block applications running at the startup screen - every bit of code in Windows which interacts with the logon screen is very carefully code reviewed because the security consequences of a bug in code running at the logon screen are dire - if you screw up even slightly, you'll allow malware to get onto the computer.
Why do you want to run your program at the logon screen? Maybe there's a documented way of doing it that's not as risky.
I translated the code above in C++, if someone else needs it... Notice there are references to parts of my code, but it may help anyway:
static bool StartProcess(LPCTSTR lpApplicationPath)
{
CAutoGeneralHandle hWinlogonProcess = FindWinlogonProcess();
if (hWinlogonProcess == INVALID_HANDLE_VALUE)
{
DU_OutputDebugStringff(L"ERROR: Can't find the 'winlogon' process");
return false;
}
CAutoGeneralHandle hUserToken;
if (!OpenProcessToken(hWinlogonProcess, TOKEN_QUERY|TOKEN_IMPERSONATE|TOKEN_DUPLICATE, &hUserToken))
{
DU_OutputDebugStringff(L"ERROR: OpenProcessToken returned false (error %u)", GetLastError());
return false;
}
// Create a new token
SECURITY_ATTRIBUTES tokenAttributes = {0};
tokenAttributes.nLength = sizeof tokenAttributes;
SECURITY_ATTRIBUTES threadAttributes = {0};
threadAttributes.nLength = sizeof threadAttributes;
// Duplicate the winlogon token to the new token
CAutoGeneralHandle hNewToken;
if (!DuplicateTokenEx(hUserToken, 0x10000000, &tokenAttributes,
SECURITY_IMPERSONATION_LEVEL::SecurityImpersonation,
TOKEN_TYPE::TokenImpersonation, &hNewToken))
{
DU_OutputDebugStringff(L"ERROR: DuplicateTokenEx returned false (error %u)", GetLastError());
return false;
}
TOKEN_PRIVILEGES tokPrivs = {0};
tokPrivs.PrivilegeCount = 1;
LUID seDebugNameValue = {0};
if (!LookupPrivilegeValue(nullptr, SE_DEBUG_NAME, &seDebugNameValue))
{
DU_OutputDebugStringff(L"ERROR: LookupPrivilegeValue returned false (error %u)", GetLastError());
return false;
}
tokPrivs.Privileges[0].Luid = seDebugNameValue;
tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Escalate the new token's privileges
if (!AdjustTokenPrivileges(hNewToken, false, &tokPrivs, 0, nullptr, nullptr))
{
DU_OutputDebugStringff(L"ERROR: AdjustTokenPrivileges returned false (error %u)", GetLastError());
return false;
}
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0};
si.cb = sizeof si;
si.lpDesktop = L"Winsta0\\Winlogon";
// Start the process using the new token
if (!CreateProcessAsUser(hNewToken, lpApplicationPath, nullptr, &tokenAttributes, &threadAttributes,
true, CREATE_NEW_CONSOLE|INHERIT_CALLER_PRIORITY, nullptr, nullptr, &si, &pi))
{
DU_OutputDebugStringff(L"ERROR: CreateProcessAsUser returned false (error %u)", GetLastError());
return false;
}
return true;
}
I think you can do it, but it's pretty involved. Interactive apps aren't normally allowed to run on the welcome screen. At a high level, you'll need to:
Create a windows service that starts automatically
Use the windows service to create another process on the current session and desktop (using the Win32 methods WTSGetActiveConsoleSessionId and OpenInputDesktop)
I wrote an app that can interact somewhat with the login screen, but it doesn't show any UI. It probably can be done, but it may be even more involved.
Note: I found that I was unable to get results from OpenInputDesktop from my Windows service. I had to instead make the call in the other process and notify the service to restart the process on the correct desktop.
I hope that can at least get you started. Good luck!
Ignoring pre-Vista OS's, assuming you have TCB privs on your token (are running as System, basically), you can use CreateProcessAsUser to do this.
Example to be run as System (e.g.: an NT Service or with psexec -s) which will start notepad in the console session winlogon desktop:
#define WIN32_LEAN_AND_MEAN
#pragma comment(lib, "Userenv.lib")
#include <Windows.h>
#include <UserEnv.h>
#include <iostream>
#include <string>
HANDLE GetTokenForStart();
LPVOID GetEnvBlockForUser(HANDLE hToken);
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment);
int main(int argc, wchar_t* argv[])
{
//while (!IsDebuggerPresent()) Sleep(500);
try
{
HANDLE hUserToken = GetTokenForStart();
LPVOID env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
catch (std::wstring err)
{
auto gle = GetLastError();
std::wcerr << L"Error: " << err << L" GLE: " << gle << L"\r\n";
return -1;
}
}
HANDLE GetTokenForStart()
{
HANDLE hToken = 0;
{
HANDLE processToken = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE, &processToken))
{
throw std::wstring(L"Could not open current process token");
}
if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hToken))
{
throw std::wstring(L"Could not duplicate process token");
}
}
DWORD consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, TokenSessionId, &consoleSessionId, sizeof(consoleSessionId)))
{
throw std::wstring(L"Could not set session ID");
}
return hToken;
}
LPVOID GetEnvBlockForUser(HANDLE hToken)
{
LPVOID pEnvironment = NULL;
if (!CreateEnvironmentBlock(&pEnvironment, hToken, FALSE))
{
throw std::wstring(L"Could not create env block");
}
return pEnvironment;
}
void StartTheProcess(HANDLE hToken, LPVOID pEnvironment)
{
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
si.lpDesktop = (LPWSTR)L"winsta0\\winlogon";
wchar_t path[MAX_PATH] = L"notepad.exe";
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessAsUser(hToken, NULL, path, NULL, NULL, FALSE,
CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi))
{
throw std::wstring(L"Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw std::wstring(L"Could not close thread handle");
}
}
Or, if you prefer C#:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;
namespace StartWinlogonManaged
{
class Program
{
static void Main(string[] args)
{
var hUserToken = GetTokenForStart();
var env = GetEnvBlockForUser(hUserToken);
StartTheProcess(hUserToken, env);
}
const string
Advapi32 = "advapi32.dll",
Userenv = "userenv.dll",
Kernel32 = "kernel32.dll";
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr ProcessToken, int DesiredAccess, out IntPtr TokenHandle);
[DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr ExistingToken, int DesiredAccess,
IntPtr TokenAttributes, int ImpersonationLevel, int TokenType, out IntPtr NewToken);
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern int WTSGetActiveConsoleSessionId();
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetTokenInformation(IntPtr hToken,
int tokenInfoClass, ref int pTokenInfo, int tokenInfoLength);
static IntPtr GetTokenForStart()
{
IntPtr hToken = IntPtr.Zero;
{
IntPtr processToken = IntPtr.Zero;
if (!OpenProcessToken(GetCurrentProcess(), 0x2001f /* TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_EXECUTE */, out processToken))
{
throw new Win32Exception("Could not open current process token");
}
if (!DuplicateTokenEx(processToken, 0x02000000 /* MAXIMUM_ALLOWED */, IntPtr.Zero, 2 /* SecurityImpersonation */, 1 /* TokenPrimary */, out hToken))
{
throw new Win32Exception("Could not duplicate process token");
}
}
int consoleSessionId = WTSGetActiveConsoleSessionId();
if (!SetTokenInformation(hToken, 12 /* TokenSessionId */, ref consoleSessionId, 4 /* sizeof(int) */))
{
throw new Win32Exception("Could not set session ID");
}
return hToken;
}
[DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
static IntPtr GetEnvBlockForUser(IntPtr hToken)
{
IntPtr pEnvironment = IntPtr.Zero;
if (!CreateEnvironmentBlock(out pEnvironment, hToken, true))
{
throw new Win32Exception("Could not create env block");
}
return pEnvironment;
}
[DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessAsUser(IntPtr hToken,
StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION startupInformation);
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
static void StartTheProcess(IntPtr hToken, IntPtr pEnvironment)
{
var si = new STARTUPINFO();
si.cb = Marshal.SizeOf<STARTUPINFO>();
si.dwFlags = 1 /* STARTF_USESHOWWINDOW */;
si.wShowWindow = 5 /* SW_SHOW */;
si.lpDesktop = "winsta0\\winlogon";
var path = new StringBuilder("notepad.exe", 260);
PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hToken, null, path, IntPtr.Zero, IntPtr.Zero, false,
0x410 /* CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT */, pEnvironment, null, ref si, out pi))
{
throw new Win32Exception("Could not start process");
}
if (!CloseHandle(pi.hThread))
{
throw new Win32Exception("Could not close thread handle");
}
}
}
}
Note that this does require several privileges (TCB, AssignPrimaryToken, IncreaseQuota) enabled in your token. This code also leaks handles, does not formulate a full command line, use name constants, etc..., and is only intended as an expository reference - not as a ready solution.