{"id":3723,"date":"2008-10-31T17:22:00","date_gmt":"2008-10-31T17:22:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2008\/10\/31\/shell-games-matt-gertz\/"},"modified":"2024-07-05T14:05:32","modified_gmt":"2024-07-05T21:05:32","slug":"shell-games-matt-gertz","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/shell-games-matt-gertz\/","title":{"rendered":"Shell Games (Matt Gertz)"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">I was once temporarily taken off the VB team to get an unrelated project back on track, just a mere handful of weeks before it was due to ship.<span>&nbsp; <\/span>I won&rsquo;t go into the gory details; suffice it to say that we had reason to believe that the product would <b><i>have<\/i><\/b> to ship without delay, and that any major failure in the deliverable could create some seriously undesirable problems for both us and our customers.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">In the interest of expediting the development of the product, a external team knowledgeable in the underlying issue had been hired to do the actual work.<span>&nbsp; <\/span>When our testers were analyzing the results of their labors, they found wildly unacceptable performance in what had been produced.<span>&nbsp; <\/span>So, besides shepherding the work on the forty or so functional issues remaining in the product, I had to address the performance issues as well.<span>&nbsp; <\/span>This was bread and butter to me at the time; I&rsquo;d recently come off a stint as the VB Performance Lead, and had a good grasp of the tools of the trade.<span>&nbsp; <\/span>So, I set about to investigate the cause of the problems.<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">Without getting too deeply into the details of the project, the upshot was that each file on the operating system having a certain extension would be opened and analyzed so see if it contained any of a particular set of keywords.<span>&nbsp; <\/span>The extension was not an uncommon one; thus, a *lot* of files would have to be opened from the file system and examined.<span>&nbsp; <\/span>This, of course, was where I suspected the problems would be, but what I discovered in the code left me absolutely astonished.<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">The natural thing to do for this sort of program was to recursively iterate the directory, find the relevant files, open them , search for the data, and then add the file to a list if the data was found.<span>&nbsp; <\/span>What they did instead was this:<span>&nbsp; <\/span>once the relevant file was found, they had created a thread whose sole purpose was to launch an application that they had on hand which generated a log report on the file.<span>&nbsp; <\/span>The thread would listen for that application to terminate, and then open the log report and scan *it* for yes\/no results.<span>&nbsp; <\/span>Yes, that&rsquo;s right &ndash; imagine dozens of threads being opened at one, each of them launching a copy of this app, with four disk hits (find file, find &amp; launch app, open file, create log file) for each.<span>&nbsp; <\/span>Yeah, that might just cause a little slowness on a Pentium II Win98 machine!<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">After I calmed down (about an hour later), I asked them why they were doing it this way.<span>&nbsp; <\/span>&ldquo;Well, we don&rsquo;t actually know the criteria that would cause a &lsquo;yes&rsquo; answer for a given file, since we don&rsquo;t actually have the source code for the app that <b>does<\/b> know it,&rdquo; they replied.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Well, good grief!<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">Five minutes&rsquo; search on MSDN gave me the format of the file being scanned, and in another thirty minutes I&rsquo;d ripped out all of the threading, shell code, and log-parsing code, replacing it with ten lines of code designed to open the file and scan for the appropriate data.<span>&nbsp; <\/span>The ultimate savings?<span>&nbsp; <\/span>A total scan of a typical machine shrunk from <b>two hours<\/b> to <b>ten minutes<\/b>.<span>&nbsp; <\/span>All in all, a most satisfactory day&rsquo;s work.<\/font><\/p>\n<h2><font face=\"Cambria\" color=\"#4f81bd\" size=\"4\">Yeah, yeah, yeah&hellip; what&rsquo;s your point, Matt?<\/font><\/h2>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">My point in bringing up that story is to note the incredible damage that can be done by misusing shell calls.<span>&nbsp; <\/span>Shell calls are expensive and, worse, they create results that may not easily understandable by the calling program (beyond a simple succeed\/fail).<span>&nbsp; <\/span>If you think that you need to &ldquo;shell out&rdquo; to another program, remember this simple word :<span>&nbsp; <\/span><b>don&rsquo;t<\/b>.<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">Of course, it&rsquo;s very tempting to shell out when an existing application already exists.<span>&nbsp; <\/span>For example, if you want to get a listing of all subdirectories in a parent directory, and you want if formatted in a manner similar to that to which you are accustomed, then it&rsquo;s very tempting to make a call to <\/font><b><span>dir \/a:d &gt; out.txt<\/span><\/b><font face=\"Calibri\" size=\"3\"> and to parse the results.<span>&nbsp; <\/span>The alternative, after all, is to overcome inertia and learn how to navigate the file system from code, right?<span>&nbsp;&nbsp; <\/span>Sounds great, but the best advice here is <b>don&rsquo;t do that<\/b>.<span>&nbsp; <\/span>That&rsquo;s what snippets are for, after all.<\/font><\/p>\n<h2><font face=\"Cambria\" color=\"#4f81bd\" size=\"4\">&ldquo;There is still some goodness in him &ndash; I can feel it.&rdquo;<\/font><\/h2>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">Still here?<span>&nbsp; <\/span>OK, shell commands aren&rsquo;t actually evil &ndash; they&rsquo;re just misunderstood.<span>&nbsp; <\/span>They can be powerful tools if used right.<span>&nbsp; <\/span>And the best usage of a shell command is a &ldquo;fire and forget&rdquo; situation, where you really want the user to be interacting with a <i>bona fide<\/i> version of the application.<span>&nbsp; <\/span>In most cases, if the executable is producing output AND you care about it, it&rsquo;s best to in-line the functionality if at all possible.<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">In Visual Basic, you can &ldquo;shell out&rdquo; to an application in two easy ways, both of which either leverage or wrap the System.Diagnostics.Process class.<span>&nbsp; <\/span>These are detailed below.<\/font><\/p>\n<h3><font size=\"3\"><font color=\"#4f81bd\"><font face=\"Cambria\">When you don&rsquo;t care when the shelled application ends:<span>&nbsp; <\/span><\/font><\/font><\/font><\/h3>\n<p class=\"MsoListParagraph\"><font face=\"Calibri\" size=\"3\">This is rather easy to do, and there&rsquo;s actually a snippet that helps you when you forget the actual code.<span>&nbsp; <\/span>To find it, put your cursor in a method, launch the snippet picker by typing &ldquo;?&rdquo; and pressing &ldquo;Tab,&rdquo; then choose &ldquo;Windows System &ndash; Logging, Process, Registry, Services,&rdquo; &ldquo;Windows &ndash; Processes,&rdquo; and &ldquo;Start an Application&rdquo; in that order.<span>&nbsp; <\/span>(That&rsquo;s for VS2008 &ndash; it&rsquo;s slightly different in VS2005.)<span>&nbsp; <\/span>You&rsquo;ll get the following line:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Process.Start(<span>&#8220;notepad.exe&#8221;<\/span>)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">And you can replace &ldquo;notepad.exe&rdquo; with the name of whatever executable that you want to run.<span>&nbsp; <\/span>There are several overloads of this function, but you need to be careful with them, since a few of them allow you to specify a username and password to do a &ldquo;Run As,&rdquo; and that&rsquo;s information that you definitely don&rsquo;t want to hardcode or persist in human-readable form.<span>&nbsp; <\/span>There&rsquo;s a simple overload that allows you to pass command-line arguments in a string, which can be very useful for certain apps, and you&rsquo;d use it like this (for example):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Dim<\/span> proc <span>As<\/span> Process = <span>&nbsp;<\/span>_<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><span>Process.Start(<span>&#8220;notepad.exe&#8221;,&rdquo;c:UsersMattDocumentshello.txt&rdquo;<\/span>)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">The first argument to Process.Start can also be a document which will be launched with the application that&rsquo;s registered to it, and <\/font><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/h6ak8zt5.aspx\"><font face=\"Calibri\" color=\"#0000ff\" size=\"3\">this link<\/font><\/a><font size=\"3\"><font face=\"Calibri\"> shows an example of that usage.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">As I imply in the second example above, Process.Start returns an instance of the Process class which points to the process of the application you just created, and you can call all sorts of neat methods using that return value, including one that kills the process.<span>&nbsp; <\/span>I mention this because you might otherwise be tempted to kill the process you just created using &ldquo;Stop an Application&rdquo; snippet:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> processList() <span>As<\/span> Process<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>processList = Process.GetProcessesByName(<span>&#8220;notepad&#8221;<\/span>)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>For<\/span> <span>Each<\/span> proc <span>As<\/span> Process <span>In<\/span> processList<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> MsgBox(<span>&#8220;Terminate &#8220;<\/span> &amp; proc.ProcessName &amp; <span>&#8220;?&#8221;<\/span>, _<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span>MsgBoxStyle.YesNo, <span>&#8220;Terminate?&#8221;<\/span>) = MsgBoxResult.Yes <span>Then<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>proc.Kill()<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Next<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">This snippet iterates over all processes having the name &ldquo;notepad&rdquo; and asks you for each one if you want to terminate it.<span>&nbsp; <\/span>But since you already have a valid Process object, you don&rsquo;t need to use this snippet <span>&nbsp;<\/span>&ndash; just call Kill() on the return value of Start() directly.<\/font><\/p>\n<h3><font size=\"3\"><font color=\"#4f81bd\"><font face=\"Cambria\">When you want to wait for the new application to end before proceeding:<span>&nbsp; <\/span><\/font><\/font><\/font><\/h3>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">This involves using the Microsoft.VisualBasic.Interaction.Shell command, which gives you a few &ldquo;frequently used&rdquo; options for controlling applications that you launch.<span>&nbsp; <\/span>Here&rsquo;s a contrived example :<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> retval <span>As<\/span> <span>Integer<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>retval = Shell(<span>&#8220;C:windowssystem32notepad.exe&#8221;<\/span>, AppWinStyle.NormalFocus, <span>True<\/span>, 20000)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">I&rsquo;ve specified the path to the application in the first second.<span>&nbsp; <\/span>The second argument allows me to control whether the new application is minimized, maximized, etc &ndash; in the above example, I&rsquo;ve chosen for notepad to come up &ldquo;normal.&rdquo;<span>&nbsp; <\/span>The third argument specifies if I want to wait for the new application to complete and, given that I&rsquo;ve chosen &ldquo;True&rdquo; for that above, the fourth and final argument specifies how many milliseconds I want to wait for that to happen, with -1 indicating an infinite wait.<span>&nbsp; <\/span>(I&rsquo;ve coded for 20 seconds above.)<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">If the application shuts down before the time limit is reached, a zero will be returned from the Shell command.<span>&nbsp; <\/span>If, on the other hand, the time limit is reached and the application is still running, the program will stop blocking and return the process ID of the application &ndash; note that this will not affect the instance of the application that I launched, though.<span>&nbsp; <\/span>You can then use the process ID to take whatever action you need to do &ndash; for example:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> retval &lt;&gt; 0 <span>Then<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;<\/span><span>Dim<\/span> proc <span>As<\/span> Process = Process.GetProcessById(retval)<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&#8216; <\/span><\/span><span>(Call interesting methods on proc here.)<\/span><span><\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/p>\n<p><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><\/p>\n<p>&nbsp;<\/p>\n<p><\/span><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">creates a Process object based on the process ID returned from the above code, and you can act on it in the same way that you used the return value from Start() in the first example.<\/font><\/p>\n<h2><font face=\"Cambria\" color=\"#4f81bd\" size=\"4\">The bottom line<\/font><\/h2>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">If you can, I&rsquo;d try to avoid shelling out to other applications.<span>&nbsp; <\/span>It&rsquo;s much more performant to implement the functionality in-line or, better yet, leverage an assembly that&rsquo;s common to all of the applications in question.<span>&nbsp; <\/span>Still, you will occasionally come across situations where you want to launch the &ldquo;one true&rdquo; application programmatically.<span>&nbsp; <\/span>The methods I&rsquo;ve listed above are easiest and will get you going quickly.<\/font><\/p>\n<p class=\"MsoNormal\"><font face=\"Calibri\" size=\"3\">&lsquo;Til next time&hellip;<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><span>&nbsp; <\/span>&#8211;Matt&#8211;*<\/font><\/font><\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was once temporarily taken off the VB team to get an unrelated project back on track, just a mere handful of weeks before it was due to ship.&nbsp; I won&rsquo;t go into the gory details; suffice it to say that we had reason to believe that the product would have to ship without delay, [&hellip;]<\/p>\n","protected":false},"author":258,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[22,195],"tags":[101,165,166],"class_list":["post-3723","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-matt-gertz","category-visual-basic","tag-matt-gertz","tag-vb2005","tag-vb2008"],"acf":[],"blog_post_summary":"<p>I was once temporarily taken off the VB team to get an unrelated project back on track, just a mere handful of weeks before it was due to ship.&nbsp; I won&rsquo;t go into the gory details; suffice it to say that we had reason to believe that the product would have to ship without delay, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3723","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/258"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=3723"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3723\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=3723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=3723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=3723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}