{"id":110979,"date":"2025-03-19T07:00:00","date_gmt":"2025-03-19T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110979"},"modified":"2025-03-19T08:20:37","modified_gmt":"2025-03-19T15:20:37","slug":"20250319-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250319-00\/?p=110979","title":{"rendered":"You can&#8217;t simulate keyboard input with PostMessage, revisited"},"content":{"rendered":"<p>A customer was trying to manipulate another program, so they tried posting messages to it. They found that input didn&#8217;t work reliably because the program had its own <code>WH_<wbr \/>KEYBOARD<\/code> hook, and the posted messages weren&#8217;t triggering the hook, so the program saw input come in the message queue with no corresponding <code>WH_<wbr \/>KEYBOARD<\/code> hook, and things got out of sync and didn&#8217;t work right. What&#8217;s going on?<\/p>\n<p>What&#8217;s going on is that <a title=\"You can't simulate keyboard input with PostMessage\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050530-11\/?p=35513\"> you can&#8217;t simulate keyboard input with PostMessage<\/a>. Posting input messages is like prank calling the program. &#8220;Hello, this is (giggle) the input system? Yeah, and um (<i>covers handset, whispers<\/i> what should I say? <i>back into handset<\/i>), yeah the user hit the um, the Enter key? When? Um, like (other person whispers in their ear) 5 seconds ago? Yeah, okay, so like Enter key, got it? Cool, thanks.&#8221; How successful this is depends on how much the program allows itself to be fooled.<\/p>\n<p>In this case, the program was correlating the input messages with information from another channel (the <code>WH_<wbr \/>KEYBOARD<\/code> hook), and only keyboard input goes through the <code>WH_<wbr \/>KEYBOARD<\/code> hook, because it is when a message is fetched from the input queue that the <code>WH_<wbr \/>KEYBOARD<\/code> hook is called. Posted messages masquerading as keyboard input don&#8217;t come from the input queue; they come from the posted message queue. And the code that pulls messages from the posted message queue doesn&#8217;t call the <code>WH_<wbr \/>KEYBOARD<\/code> hook.<\/p>\n<p>As usual, the typical\u00b9 answers are either to use UI Automation to drive the target program, or if the target program&#8217;s support for UI Automation is insufficient, you can use <code>Send\u00adInput<\/code> to generate synthetic input. Synthetic input is treated like real input, and it goes through the input system like hardware input.\u00b9<\/p>\n<p>\u00b9 An atypical answer is to use the target program&#8217;s object model if it offers one. For example, Microsoft Word lets you manipulate the application and its active document through <a title=\"Application object (Word)\" href=\"https:\/\/learn.microsoft.com\/en-us\/office\/vba\/api\/word.application\"> its Application object<\/a>.<\/p>\n<p>\u00b2 Synthetic input can still be detected, for example, by looking for the <code>LLKHF_INJECTED<\/code> flag in the <code>KBDLLHOOKSTRUCT<\/code>&#8216;s flags.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If it didn&#8217;t go through the input system, it only looks like input as much as the app allows itself to be fooled.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110979","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>If it didn&#8217;t go through the input system, it only looks like input as much as the app allows itself to be fooled.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110979","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110979"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110979\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110979"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110979"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110979"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}