Hey, Scripting Guy! Question

Hey, Scripting Guy! I have a problem. I am trying to script the backup of a data folder used by our database application. The problem is that as long as the database service is running, the files that are used are locked, and I am unable to copy them. I have found the service that runs the database, and when I use WMI to stop the service, the script still fails to copy the database files. This is because the database service is too slow when stopping.

I decided to include a Start-Sleep command, and this works sometimes. The problem here is that sometimes the database service will stop quickly, and other times it takes quite a few minutes. I guess it is performing lazy writes, and it all depends on how heavily the application was being used. I could do several trial runs, and then add 10 percent to the maximum length of time it takes for the service to shut down, but then that is a waste of time for those occasions when the service would shut down really fast. Is there something you can do, some piece of scripting magic you can give me?

- CC

SpacerHey, Scripting Guy! Answer

Hi CC,

It seems as if you have a well thought-out plan. As you have seen, trying to time the shutdown of service can be problematic in even the best of conditions. It all depends on what the server is doing at the time: disk contention issues, processor utilization, and memory flushing. There are tons of things that go on when something is being shut down. It can seem hardly better than a controlled crash. In the past I used to just pause the script, and hope for the best. One day, I came up with the idea of using WMI eventing to help out with the problem.

This week we will are talking about WMI eventing. For some VBScript examples of these kinds of scripts, you can refer to the eventing section of the Hey, Scripting Guy! archive. Additional information can be obtained from this Web page. The Microsoft book, Microsoft Windows Scripting with WMI: Self-Paced Learning Edition, has an entire chapter on WMI eventing.

The StopServiceMonitor.ps1 script illustrates the use of WMI eventing to determine how long to wait before executing a command. The script stops a service, and then it creates a WMI event monitor to detect when the service is completely stopped. After the service is stopped, you can then add in your code to copy your database, or whatever you want to use the script for. Here is the script:

Clear-Host
$computer = "localhost"
$dte = get-date
$ServiceName = "'AdobeActiveFileMonitor6.0'"
$Query = "select * from __instanceModificationEvent within 5 where 
 targetInstance isa 'win32_Service' AND targetInstance.name = $ServiceName"

Write-Host "Waiting for $ServiceName to be terminated ... 
You will be notified within 5 seconds of  service  termination
 start time was $dte"

$WMI=Get-WmiObject -Class Win32_Service -computername $computer `
      -filter "name=$Servicename"
if($WMI.acceptstop)
  {
  $Eventwatcher = New-Object management.managementEventWatcher $Query
  $rtn=$WMI.stopservice() 
  "Stopping $ServiceName returned $($rtn.ReturnValue)"
  $Event = $Eventwatcher.waitForNextEvent()
  $Event.targetInstance | 
  Format-List -property [a-z]*
 }
ELSE { 
      "$ServiceName unable to accept stop command"
      exit
     }

The first thing we do is use the Clear-Host function to clear off the Windows PowerShell console. To do this, we simply call the Clear-Host function as seen here:

Clear-Host

Now we need to assign some values to a couple of variables. We use the $computer variable to hold the name of the computer we wish to monitor. We use the variable $dte to hold the current date-time stamp, and the $ServiceName variable to hold the name of the service we wish to monitor. This is shown here:

$computer = "localhost"
$dte = get-date
$ServiceName = "'AdobeActiveFileMonitor6.0'"

Next we create our WMI event query. Today we are looking for an __instanceModificationEvent. This is an event that will be triggered when something is modified. Yesterday we used an __instanceDeletionEvent to monitor when a process went away, and on Monday we used an __instanceCreationEvent to monitor when a process was created. The difference here is that a service that is defined always exists. The thing that is changing is its state (in other words, is it running or is it stopped?). This means the service is being modified, not created and deleted as in the case of a process. It is a subtle distinction, but one that is vital if the script is going to work.

The other thing that is new about our WMI event query is that we are using a compound query. We are, of course, looking for changes in a service state, but we are also interested in only one specific instance of the service. The one whose name we have identified in the $ServiceName variable. TargetInstance gets created when the event is triggered. It holds a copy of the object in question. This means we can use any property from the class we are monitoring.

The WMI event query is seen here:

$Query = "select * from __instanceModificationEvent within 5 where 
 targetInstance isa 'win32_Service' AND targetInstance.name = $ServiceName"

When we have defined our WMI event query, we now want to print a message to the screen to let the user know that the script is doing something. To do this, we use the Write-Host cmdlet as illustrated here:

Write-Host "Waiting for $ServiceName to be terminated ... 
You will be notified within 5 seconds of  service  termination
 start time was $dte"

Then we do a plain old everyday WMI query. The reason to do this is we want to see if the service we are getting ready to stop will accept a stop command. This is one way of reducing errors. We use the Get-WmiObject cmdlet and specify the class we are querying is the Win32_Service WMI class. We also select the computer whose name is stored in the $computer variable. We use the filter parameter to limit the results to only services specified in the $ServiceName variable. This query is shown here:

$WMI=Get-WmiObject -Class Win32_Service -computername $computer `
      -filter "name=$Servicename"

We now want to stop the service if it will accept a stop command. For this example script, we are using a service that does not have any dependencies. If we were to choose a service with dependencies, and if the service accepted a stop command, we would receive error 3 in return, which means that dependent services are running. In our case we are not worrying about that because we know that our service has no dependencies. If it will accept a stop command, we enter the rest of the script. This statement is seen here:

if($WMI.acceptstop)

When we make it past the if statement, we first create an instance of the ManagementEventWatcher .NET Framework class. This class resides in the System.Management .NET Framework namespace. To create the ManagementEventWatcher class, we give it the WMI event query that we stored in the $query variable. We store the resulting ManagementEventWatcher in the $EventWatcher variable as shown here:

{
  $Eventwatcher = New-Object management.managementEventWatcher $Query

After we have created the ManagementEventWatcher, we stop the service. To do this, we use the stopService method from the managementObject we stored as a result of our previous WMI query. When the stopService method is called, it returns a value, and we capture the return code in the $rtn variable. This is seen here:

$rtn=$WMI.stopservice()

Next we display the return code from the method call. To do this, we need to use a subexpression, and we use the dollar sign and a set of parentheses to constrain the unraveling effect of the variable inside expanding quotation marks. This is shown here:

"Stopping $ServiceName returned $($rtn.ReturnValue)"

Now we use the ManagementEventWatcher we stored in the $EventWatcher variable, and we wait for the next event. To do this, we use the waitForNextEvent method. When the ManagementEventWatcher detects that the service has changed, the __instanceModificationEvent is fired, and we store the returned managementObject in the $event variable.

One thing to keep in mind: As written, this script works for services that take a bit of time to start and to stop. If you use this script on a fast service or on a fast computer, you will miss the event. But it is after all only a demo. In a real script with a real service, you would only be using this technique on a slow service anyway, so it is a moot point (which has nothing to do with cows by the way):

$Event = $Eventwatcher.waitForNextEvent()

We can at this point in your script do whatever we need to do. We could start the service back up, or we could copy files that were locked by the service. In this demo script, we decide to simply print out all the properties of the modified instance of the service. To do this, we retrieve the targetInstance property from the managementObject and pipeline it to the Format-List cmdlet. We select only the properties whose name begins with the letters a through z and are followed by any other characters. This allows us to filter out the system properties (as they all begin with a double underscore)

$Event.targetInstance | 
  Format-List -property [a-z]*
 }

When the script is run, you will see an output similar to this:

Image of the output from running the script

 

If the service was unable to accept a stop command, we print out a message regarding that situation and exit the script as shown here:

ELSE { 
      "$ServiceName unable to accept stop command"
      exit
     }

Well, CC, thank you for an interesting question. We hope you enjoyed our little excursion into compound WMI event queries and working with services. We will see you tomorrow, when we continue our discussions about WMI eventing. Until then, take care.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys