Date: | September 8, 2014 / year-entry #215 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20140908-00/?p=53 |
Comments: | 26 |
Summary: | In honor of NotepadConf's new KickStarter video, today's Little Program takes its stdin and puts it in a Notepad window. using System; using System.Diagnostics; using System.Windows.Automation; using System.Runtime.InteropServices; class Program { static void Main(string[] args) { // Slurp stdin into a string. var everything = Console.In.ReadToEnd(); // Fire up a brand new Notepad. var process... |
In honor of NotepadConf's new KickStarter video, today's Little Program takes its stdin and puts it in a Notepad window. using System; using System.Diagnostics; using System.Windows.Automation; using System.Runtime.InteropServices; class Program { static void Main(string[] args) { // Slurp stdin into a string. var everything = Console.In.ReadToEnd(); // Fire up a brand new Notepad. var process = new Process(); process.StartInfo.UseShellExecute = false; process.StartInfo.FileName = @"C:\Windows\System32\notepad.exe"; process.Start(); process.WaitForInputIdle(); // Find the Notepad edit control. var edit = AutomationElement.FromHandle(process.MainWindowHandle) .FindFirst(TreeScope.Subtree, new PropertyCondition( AutomationElement.ControlTypeProperty, ControlType.Document)); // Shove the text into that window. var nativeHandle = new IntPtr((int)edit.GetCurrentPropertyValue( AutomationElement.NativeWindowHandleProperty)); SendMessage(nativeHandle, WM_SETTEXT, IntPtr.Zero, everything); } [DllImport("user32.dll", EntryPoint="SendMessage", CharSet=CharSet.Unicode)] static extern IntPtr SendMessage( IntPtr windowHandle, int message, IntPtr wParam, string text); const int WM_SETTEXT = 0x000C; } The comments pretty much lay out the steps. The part that may not be obvious is the part that deals with UI Automation: We take the main Notepad window, then ask UI Automation to find Document element inside it.
From that element, we extract the window handle,
then drop to Win32 and
send a
If you save this program under the name dir | 2np and it will open a Notepad window with a directory listing inside it. Change one line of code, and this program will launch Wordpad instead. |
Comments (26)
Comments are closed. |
I do a lot of work in the Command Prompt, mangling snippets of data with Perl and regex, this piping stdout directly into Notepad is something I have been thinking of for a long time.
I'm just a BOFH with programming experience from C on MS-DOS in the 90s, how do I build this C# thingy in Visual Studio?
I have access to a machine with Visual Studio 2010 Ultimate, I created a C# Console App and pasted in the code into Program.cs.
The build stops on this error:
The type or namespace name 'Windows' does not exist in the namespace 'System' (are you missing an assembly reference?)
I found the References list and the .Net tab, but there is no System.Windows in the list, what am I supposed to do to make the build work?
@BOFH You need to add a reference to uiautomationclient.dll. See msdn.microsoft.com/…/system.windows.automation.automationelement(v=vs.85).aspx.
@BOFH: From the Add references dialog, select the .NET tab, then add a reference to the UIAutomationClient component.
I had tried that, but it produced other errors instead so I backed out from that route.
CarlD's link clarifies that it is the way to go so I looked more closely at those errors and realized that I need to add UIAutomationTypes as well to make it work.
Thanks! :)
This functionality is incredibly useful.
I wrote a GUI text editor years ago that accepts piped input and I still use it almost daily.
You can build Raymond's app without Visual Studio thus:
Save the source from above in a file called 2np.cs and, at a command-prompt in the same directory, run
set FW=C:WindowsMicrosoft.NETFramework64v4.0.30319
%FW%csc /lib:%FW%WPF /r:UIAutomationClient.dll /r:UIAutomationTypes.dll 2np.cs
The FW path may well be a bit different on your computer, but you want the folder containing "csc.exe".
If you don't mind an extra step, Windows comes with a "clip" utility that you copies its stdin to the clipboard. This is what I do when I want to get some data from a command line program into Excel or a text editor.
@BOFH: Always read error messages that you get. If you miss UIAutomationTypes, the message is pretty clear:
The type 'System.Windows.Automation.AutomationProperty' is defined in an assembly that is not referenced. You must add a reference to assembly 'UIAutomationTypes, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
I used notepad to write a batch file that does sorta kinda the same thing. Usage is "whatever.bat command".
@ECHO OFF
FOR /F "delims=:. tokens=1-4" %%t IN ("%TIME: =0%") DO (SET FILENAME="%TEMP%foo-%%t%%u%%v%%w")
%1 > %FILENAME%
notepad %FILENAME%
DEL /F %FILENAME% 2>NUL
I just noticed that 2np doesn't properly work with Unicode text (characters are replaced by "?")
This program can be replaced with just a simple 2-line batch file:
@findstr "^" > %temp%2np.txt
@start notepad %temp%2np.txt
It accepts input from the pipeline (or even the keyboard!), just as Raymond's program:
dir /b | 2np
It would be sweet if we could host these code samples on a git repo. That way improvements could be published to them, and everyone enjoys the benefits.
@Mike: blogs.msdn.com/…/10116870.aspx
I started with something similar a long time ago. I've since added the ability to keep text coming in from the pipe to Notepad in near-realtime with EM_SETSEL and EM_REPLACESEL, and deal with oddball tools that output different line endings.
It's incredibly useful for a host of things.
@Raymond: these things are right, but I don't think they affect the most frequent use of the command: capturing the (long) output of a command so you can read it slowly, search it for keywords and maybe copy portions of it. The race condition doesn't matter too much in practice because this command is interactive in nature, so it isn't probable to run it twice at the same time. If you need to capture a second command's output before closing the first Notepad, you can do that, as far as you understand that the temporary file is, well, temporary, and can be overwritten without warning. And if you want to save the full output to a file (it would have been simpler to use a >file.txt redirection in the first place!), after pressing Ctrl+S and noticing the Save As dialog didn't show, I'd realize I had to use the Save As command.
I understand the purpose of all this is to have a nice exercise in GUI programming and adding a tool to our toolset. I was just showing an alternative to your code, even if the batch file doesn't do everything that your C# program do.
Can we be sure that Notepad (or Wordpad, for that matter) properly updates all of its state variables when other programs manipulate its internal controls? Don't turn this Little Program into a Big Program unless you know. CreateWindow(L"EDIT", everything, …) is probably easier and safer.
Use a real operating system, and this is just somecommand | gvim –
</flamebait>
I've been using something like this that I wrote for years, only instead of notepad, it writes the input to a text file and opens it in the default web browser.
Hum got a weird message about a shim error on Windows 8.1 64bit compiling under .net 4.5. (might be something to do with copying the exe to System32 folder?) Changed project to .net 3.5 and all is well :)
thanks for this
@rs: Have you heard of that fancy "event queue" invention of recent years? Coupled with an innovative "event loop" technique, and given that "event queue" is maintained by the timesharing operating system you use, and the "event loop" is employed by an application run under supervision of the said TS/OS, one can maintain confidence… okay, I am tired of writing this.
Although, seriously, it's just sending a WM_SETTEXT message to a window. Even X-Windows can do something like this more or less reliably and without causing chaos.
@Chris: Yep, the whole Windows is defective because its core system component, namely Notepad, doesn't read data from STDIN.
So what's the difference between somecommand | gvim – on a real OS and somecommand | 2np on some other OS again?
Lot's of people posting saying how incredibly useful this is. Why? What's the advantage over just piping to a file or just displaying in the command prompt?
@Innocent Bystander: here are a few things you can do more easily in a text editor. Long output can be scrolled and searched more easily. You can cut and paste some or all of the output; this is nice if you are answering a support question by email. You can annotate the text to aid in debugging or explaining. You can save the text for future reference.
The reason I will use this though is that selecting and copying text in the Windows command prompt just works weird (for good reasons no doubt). Even after years of using it I still cut off content on the right side because it does a block select. This just works better.
@Tim: thanks for the clipboard trick.
Windows.Automation, eh? Never heard of it. But it looks very interesting. Wonder if I get this working with my other favourite editor, Notepad++.
I wonder why you did not use the value pattern to set the text? The value pattern would also update the internal state of the notepad so it would ask you to save when you exit.
Or were there a hidden agenda to show how to use SendMessage with a string parameter?
@Raymond: Ok, I were looking at notepad in Windows 7: there the control type is Edit, not Document, and it does support the value pattern.
To your example:
dir | 2np
You could try:
dir | clip | notepad
ctrl V will paste the results into the open notepad window.