{"id":64993,"date":"2007-04-26T21:28:00","date_gmt":"2007-04-26T21:28:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2007\/04\/26\/how-can-i-start-a-process-and-then-log-the-user-off-when-that-process-ends\/"},"modified":"2007-04-26T21:28:00","modified_gmt":"2007-04-26T21:28:00","slug":"how-can-i-start-a-process-and-then-log-the-user-off-when-that-process-ends","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/how-can-i-start-a-process-and-then-log-the-user-off-when-that-process-ends\/","title":{"rendered":"How Can I Start a Process and Then Log the User Off When That Process Ends?"},"content":{"rendered":"<p><IMG class=\"nearGraphic\" title=\"Hey, Scripting Guy! Question\" border=\"0\" alt=\"Hey, Scripting Guy! Question\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/q-for-powertip.jpg\" width=\"34\" height=\"34\"> \n<P>Hey, Scripting Guy! How can I write a script that starts a process, waits until that process ends, and then logs the user off the computer?<BR><BR>&#8212; AG<\/P><IMG border=\"0\" alt=\"Spacer\" src=\"https:\/\/devblogs.microsoft.com\/scripting\/wp-content\/uploads\/sites\/29\/2019\/05\/spacer.gif\" width=\"5\" height=\"5\"><IMG class=\"nearGraphic\" title=\"Hey, Scripting Guy! Answer\" border=\"0\" alt=\"Hey, Scripting Guy! Answer\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/a-for-powertip.jpg\" width=\"34\" height=\"34\"><A href=\"http:\/\/go.microsoft.com\/fwlink\/?linkid=68779&amp;clcid=0x409\"><IMG class=\"farGraphic\" title=\"Script Center\" border=\"0\" alt=\"Script Center\" align=\"right\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/ad.jpg\" width=\"120\" height=\"288\"><\/A> \n<P>Hey, AG. You know, the Scripting Guy who writes this column is never one to brag, mainly because he never has much of anything to brag about. In addition tot that, however, he is a humble and modest sort of person; that\u2019s why he hasn\u2019t said a word about something he did a couple weeks ago. But seeing as how he doesn\u2019t have anything else to talk about today (You say he could talk scripting? What, in a daily scripting column?) maybe it\u2019s time to mention what was undoubtedly the greatest athletic feat in recorded history.<\/P>\n<P>What\u2019s that? Lance Armstrong? Well, sure, Lance Armstrong has had a few successes. But, let\u2019s face it, all Lance Armstrong did was ride a bike. Did Lance Armstrong ever throw a basketball the length of the court and make a basket? <\/P>\n<P>Oh, he did? Well, that figures. But too bad. If Lance Armstrong wants to brag about his athletic feats, well, let him get his <I>own<\/I> daily scripting column!<\/P>\n<P>The occasion was yet another one-on-one basketball game between the Scripting Dad and the Scripting Son. This time out the Scripting Dad managed to win, sinking a last-second shot from behind the three-point line. The Scripting Son \u2013 always a good sport when he plays against his father \u2013 picked up the ball and heaved it towards the basket at the end of the gym. As is so often the case, that act of frustration suddenly turned into a competition, with the two vying to see who could be the first to make a full-court basket. <\/P>\n<P>To be honest, the Scripting Dad\u2019s first attempt was a bit pathetic; it bounced right around the foul line (to the vast amusement of the Scripting Son, we might add). But that embarrassment only spurred the Scripting Dad on, and around his 15<SUP>th<\/SUP> try or so: swish! Game over, Scripting Son!<\/P>\n<P>Not that the Scripting Dad takes any pleasure in defeating his own son, mind you. <\/P>\n<P>At any rate, we\u2019re sure everyone will agree that making a full-court basket is the greatest athletic feat since the beginning of time; heck, it might very well be the great feat of <I>any<\/I> kind, athletic or not. As for the <I>second<\/I>-greatest feat of all time, that\u2019s hard to say. However, and with all due modesty, we\u2019d like to nominate a script that starts a process, waits until that process ends, and then logs the user off the computer:<\/P><PRE class=\"codeSample\">strComputer = &#8220;.&#8221;<\/p>\n<p>Set objWMIService = GetObject _\n    (&#8220;winmgmts:\\\\&#8221; &amp; strComputer &amp; &#8220;\\root\\cimv2:Win32_Process&#8221;)<\/p>\n<p>objWMIService.Create &#8220;Notepad.exe&#8221;, null, null, intProcessID<\/p>\n<p>Set objWMIService = GetObject(&#8220;winmgmts:\\\\&#8221; &amp; strComputer &amp; &#8220;\\root\\cimv2&#8221;)<\/p>\n<p>Set colItems = objWMIService. _\n    ExecNotificationQuery(&#8220;Select * From __InstanceDeletionEvent &#8221; _ \n            &amp; &#8220;Within 1 Where TargetInstance ISA &#8216;Win32_Process'&#8221;)\nDo \n    Set objProcess = colItems.NextEvent\n    If objProcess.TargetInstance.ProcessID = intProcessID Then\n        Exit Do\n    End If\nLoop<\/p>\n<p>Set objWMIService = GetObject(&#8220;winmgmts:{(Shutdown)}\\\\&#8221; &amp; _\n        strComputer &amp; &#8220;\\root\\cimv2&#8221;)<\/p>\n<p>Set colItems = objWMIService.ExecQuery _\n    (&#8220;Select * from Win32_OperatingSystem&#8221;)<\/p>\n<p>For Each objItem in colItems\n    objItem.Win32Shutdown(0)\nNext\n<\/PRE>\n<P>You know, you\u2019re right: this <I>is<\/I> kind of a long script, isn\u2019t it? But what the hey; let\u2019s walk through it anyway and see if we can figure out how it all works.<\/P>\n<P>As you can see, we start out by binding to the WMI service on the local computer. There are two important things to note here. First, we bind directly to the Win32_Process class; that\u2019s what the <B>\\root\\cimv2\\:Win32_Process<\/B> portion of this command does:<\/P><PRE class=\"codeSample\">Set objWMIService = GetObject _\n    (&#8220;winmgmts:\\\\&#8221; &amp; strComputer &amp; &#8220;\\root\\cimv2:Win32_Process&#8221;)\n<\/PRE>\n<P>Second, any time we show you a WMI script we usually say, \u201cBlah, blah, blah. But, of course, you can also run this script against a remote computer.\u201d Is that the case here, <I>can<\/I> you run this script against a remote computer? Well, maybe. The script will actually run just fine against a remote machine; however, the process you create will run in a hidden window. In other words, that process will not be visible on the remote machine (or anywhere else, for that matter). If your application requires no user intervention to run (and then to terminate) then this script <I>will<\/I> work fine against a remote machine. However, if your application requires some sort of user intervention, well, in that case this script <I>won\u2019t<\/I> work against a remote machine. That\u2019s because no one will be able to intervene with the application.<\/P>\n<P>Incidentally, that\u2019s all done for security reasons, and there\u2019s no way to change it.<\/P>\n<P>After we connect to the WMI service (and the Win32_Process class) we then use this line of code to start the application (in this case, Notepad.exe):<\/P><PRE class=\"codeSample\">objWMIService.Create &#8220;Notepad.exe&#8221;, null, null, intProcessID\n<\/PRE>\n<P>As you can see, this isn\u2019t too terribly complicated: we simply call the <B>Create<\/B> method followed by the name of the process we want to create. That process name is, in turn, followed by a pair of Null parameters (these are optional parameters that we don\u2019t care about for this script), which are followed by an \u201cout parameter\u201d we named intProcessID.<\/P>\n<P>This out parameter turns out to be the key to getting the script to work. An out parameter is nothing more than a variable that you supply to a method; in turn, the method assigns a value to that variable. In the case of the Create method, that value is the process ID for our new instance of Notepad. Needless to say, that\u2019s how we\u2019ll be able to determine when our application is terminated. If that process ID disappears, that can only mean that our application is no longer running.<\/P>\n<P>After we\u2019ve started the application our next step is to sit around and wait for the application to end. To do that we re-bind to the WMI service (this time binding just to the root\\cimv2 namesapce) and then create a new WMI query, one that looks like this:<\/P><PRE class=\"codeSample\">Set colItems = objWMIService. _\n    ExecNotificationQuery(&#8220;Select * From __InstanceDeletionEvent &#8221; _ \n            &amp; &#8220;Within 1 Where TargetInstance ISA &#8216;Win32_Process'&#8221;)\n<\/PRE>\n<P>What we\u2019re doing here is creating an event subscription: we\u2019re asking WMI to notify us each time there are new instances of the <B>__InstanceDeletionEvent<\/B> class; as the name implies, a new instance of this class is created any time an object is deleted. <\/P>\n<P>You know, you\u2019re right: we <I>did<\/I> kind of mislead you, didn\u2019t we? After all, we aren\u2019t asking to be notified when <I>any<\/I> object is deleted; instead, and as our Where clause states, we only want to be notified if the object in question (the <B>TargetInstance<\/B>) happens to be a member of the Win32_Process class. In other words, we only want to be notified when processes are deleted (that is, each time a process is terminated).<\/P>\n<TABLE id=\"ECF\" class=\"dataTable\" cellSpacing=\"0\" cellPadding=\"0\">\n<THEAD><\/THEAD>\n<TBODY>\n<TR class=\"record\" vAlign=\"top\">\n<TD>\n<P class=\"lastInCell\"><B>Note<\/B>. __InstanceDeletionEvent class? TargetInstance? You say you have no idea what we\u2019re talking about here? In that case, you should take a peek at the <A href=\"http:\/\/msevents.microsoft.com\/cui\/eventdetail.aspx?EventID=1032268754&amp;culture=en-US\" target=\"_blank\"><B>Scripting Week 2 webcast<\/B><\/A> on WMI events. Granted, you probably <I>still<\/I> won\u2019t have any idea what we\u2019re talking about; this column has that effect on people. But at least you\u2019ll have a better understanding of things like __InstanceDeletionEvent and TargetInstance.<\/P><\/TD><\/TR><\/TBODY><\/TABLE>\n<DIV class=\"dataTableBottomMargin\"><\/DIV>\n<P>After setting up our event subscription we then create a Do loop designed to run forever (notice that there\u2019s no exit criteria anywhere):<\/P><PRE class=\"codeSample\">Do \n    Set objProcess = colItems.NextEvent\n    If objProcess.TargetInstance.ProcessID = intProcessID Then\n        Exit Do\n    End If\nLoop\n<\/PRE>\n<P>Of course you\u2019re right, as usual: <I>nothing<\/I> lasts forever, does it? And, as it turns out, this loop won\u2019t last forever, either. Instead, what it\u2019s going to do is wait until a process gets deleted; that will trigger both our event subscription and, in turn, move the script off this line of code (a line of code designed to \u201cblock\u201d the script until an event of interest has occurred):<\/P><PRE class=\"codeSample\">Set objProcess = colItems.NextEvent\n<\/PRE>\n<P>Once the event has fired we go ahead and check the <B>ProcessID<\/B> property of the TargetInstance in order to determine whether that value is equal to intProcessID, the ProcessID of the application we started:<\/P><PRE class=\"codeSample\">If objProcess.TargetInstance.ProcessID = intProcessID Then\n<\/PRE>\n<P>Suppose the two process IDs are different. No problem: that just means that the process that was terminated wasn\u2019t the process that we started. Therefore we loop back around and wait for the next process to be deleted.<\/P>\n<P>On the other hand, suppose the two process IDs <I>are<\/I> identical. Because process IDs must be unique at any given time, that can only mean one thing: our process has come to an end. And that means that it\u2019s time to log the user off the computer.<\/P>\n<P>To do that we first call the <B>Exit Do<\/B> statement to exit our heretofore un-exitable loop. As soon as we\u2019re safely out of the loop we then run this block of code:<\/P><PRE class=\"codeSample\">Set objWMIService = GetObject(&#8220;winmgmts:{(Shutdown)}\\\\&#8221; &amp; _\n        strComputer &amp; &#8220;\\root\\cimv2&#8221;)<\/p>\n<p>Set colItems = objWMIService.ExecQuery _\n    (&#8220;Select * from Win32_OperatingSystem&#8221;)<\/p>\n<p>For Each objItem in colItems\n    objItem.Win32Shutdown(0)\nNext\n<\/PRE>\n<P>Here we start out by binding to the WMI service one more time, this time including the <B>Shutdown<\/B> privilege in our binding string. (And, yes, we could have included the Shutdown privilege earlier and then simply reused that object reference. But we thought this approach makes it a little easier for everyone to follow what we were doing.) After making the connection we then use this query to retrieve all instances of the <B>Win32_OperatingSystem<\/B> class. (Because this class can only retrieve information about the current operating system, there will always be one \u2013 and only one \u2013 item in the resulting collection.)<\/P><PRE class=\"codeSample\">Set colItems = objWMIService.ExecQuery _\n    (&#8220;Select * from Win32_OperatingSystem&#8221;)\n<\/PRE>\n<P>We then set up a For Each loop to loop through each item in the collection; for each of those items we call the <B>Win32Shutdown<\/B> method, passing the value 0 as the sole method parameter:<\/P><PRE class=\"codeSample\">objItem.Win32Shutdown(0)\n<\/PRE>\n<P>In case you\u2019re wondering, the 0 tells Win32Shutdown to log the user off the computer. Are there other values we could supply to Win32Shutdown, values that would enable you to, say, restart or completely shut down a machine? You bet there are; take a look at the <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/guide\/sas_cpm_jleo.mspx\"><B>Microsoft Windows 2000 Scripting Guide<\/B><\/A> for more information.<\/P>\n<P>And that should do it, AG.<\/P>\n<P>For those of you who keep track of this sort of thing, the Scripting Son later got his revenge: the next time he and the Scripting Dad played one-on-one he absolutely annihilated his father. But, for the moment at least, the Scripting Dad retains the last laugh. After that particular game they had a three-point shooting contest. The Scripting Son went first, and made 7 out of 15 three-pointers. How many shots did the Scripting Dad need to win the contest? You got it: 8. <\/P>\n<P>Actually, we agree with you: the Scripting Dad <I>should<\/I> quit his job and become a full-time basketball player. Interestingly enough, that\u2019s something his manager has been suggesting he do for years now \u2026.<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey, Scripting Guy! How can I write a script that starts a process, waits until that process ends, and then logs the user off the computer?&#8212; AG Hey, AG. You know, the Scripting Guy who writes this column is never one to brag, mainly because he never has much of anything to brag about. In [&hellip;]<\/p>\n","protected":false},"author":595,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[16,42,31,87,3,4,386,5],"class_list":["post-64993","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-desktop-management","tag-events-and-monitoring","tag-operating-system","tag-processes","tag-scripting-guy","tag-scripting-techniques","tag-startup-and-shutdown","tag-vbscript"],"acf":[],"blog_post_summary":"<p>Hey, Scripting Guy! How can I write a script that starts a process, waits until that process ends, and then logs the user off the computer?&#8212; AG Hey, AG. You know, the Scripting Guy who writes this column is never one to brag, mainly because he never has much of anything to brag about. In [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/64993","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/595"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=64993"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/64993\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=64993"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=64993"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=64993"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}