Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (10/1/10)

Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (10/1/10)

  • Comments 2
  • Likes

Summary: The Scripting Guys show you how to use functions in Windows PowerShell background jobs, troubleshoot VBScript, and use WMI computer shutdown events.

 

 

In this post:

 

 WMI Computer Shutdown Events

Hey, Scripting Guy! Question

Hey, Scripting Guy! I see that you have been writing some good scripts. I have been using your examples to code some scripts for my customers. I see that is not much help provided for the Win32_ComputerSystemEvent WMI class. What is this for and when can we use this?

-- NS

 

Hey, Scripting Guy! Answer Hello NS,

 

Microsoft Scripting Guy Ed Wilson here. The Win32_ComputerSystemEvent is not used directly. It is the abstract parent class from which Win32_ComputerShutdownEvent is derived. You can see this in the MSDN article for Win32_ComputerShutdownEvent at the bottom of the article. This relationship is also shown in the following image.

Image of relationship

Win32_ComputerShutdownEvent will generate an event when a particular remote computer is shut down. I wrote an article called Hey, Scripting Guy! Can I Use WMI to Determine When Someone Logs Off a User or Shuts Down a Server? that illustrates using this class. 

 

 Use Functions in Windows PowerShell Background Jobs 

Hey, Scripting Guy! Question

 

Hey, Scripting Guy! I was investigating doing multithreading in Windows PowerShell and I found the Start-Job cmdlet. It works great, except when I try to run functions I created myself. The following command works fine:

Start-Job –Command { Get-Service }


However, if I try to run the Get-ParserPerformance function shown here, an error occurs:

Function Get-ParserPerformance { return “Whatever!” }

Start-Job –Command { Get-ParserPerformance }

The error I receive from attempting to run my custom function is shown here:

The term 'Get-ParserPerformance' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    + CategoryInfo          : ObjectNotFound: (Get-ParserPerformance:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

In addition, I cannot pass variables to a job. So this code…

$i = 23                                                     
$job = Start-Job { return “The number is $i.” }
Receive-Job $job

…returns, “The number is .” Is there a way to solve these two problems?

-- JE

 

 

Hey, Scripting Guy! Answer Hello JE,

 

 

The problems you are experiencing with your function and with passing variables are both symptoms of the same issue. Your problem is that jobs run in a new process, so functions and variables have a new scope. Therefore, you cannot access “normal” variables. However, one approach to your problem of variable access is to go “old school” in a “new school” sort of way and use an environmental variable. This is shown here:

PS C:\> New-Item -Name j -Value 23 -Path env:\

Name                           Value
----                           -----
j                              23


PS C:\> $env:j
23
PS C:\> $job = Start-Job { return “The number is $env:i” }

PS C:\> Receive-Job $job
The number is 23
PS C:\>

This environmental variable is only available within the current Windows PowerShell session, so you are not making a permanent environment change. After Windows PowerShell is closed and reopened, the newly created environmental variable is gone. Alternatively, you could explicitly delete it when you are concluded with your operation. This is shown here:

PS C:\> New-Item -Name j -Value 23 -Path env:\

Name                           Value
----                           -----
j                              23


PS C:\> $env:j
23
PS C:\> $job = Start-Job { return "The number is $env:j" }
PS C:\> Receive-Job $job
The number is 23
PS C:\> Remove-Item -Path env:j
PS C:\> $env:j
PS C:\>

A more comprehensive approach to your problem is to explicitly add the things you will need in your background job. For example, if you need a user defined function, you can make it available by using the initializationscript parameter of the Start-Job cmdlet. First I assign a script block containing the function to a variable, and then I use the initializationscript parameter to bring the function into the job. This is shown here:

PS C:\> $a = { function myfunction {return "whatever!"} }
PS C:\> $job = Start-Job {myfunction} -InitializationScript $a
PS C:\> Get-Job

Id              Name            State      HasMoreData     Location             Comm
                                                                                and
--              ----            -----      -----------     --------             ----
1               Job1            Completed  True            localhost            m...


PS C:\> Receive-Job $job
whatever!
PS C:\>

You can use the same technique to assign a value to a variable, and then bring that variable to your background job:

PS C:\> $a = { $i=23}
PS C:\> $job = Start-Job {return "the number is $i."} -InitializationScript $a
PS C:\> Receive-Job $job
the number is 23.
PS C:\>

If you have your functions in a function library, you can use the initializationscript scriptblock to dot source your function library to your background job. This is shown here:

PS C:\> $job = Start-Job {myfunction} -InitializationScript {. C:\fso\myFunction.ps1}

PS C:\> Receive-Job $job
Whatever!
PS C:\>

As you can see in the following image, the myfunction.ps1 script contains the myfunction function.

 

 

 Troubleshooting VBScript 

Hey, Scripting Guy! Question

 

Hey, Scripting Guy! I am trying to customize a VBScript that I have adapted from the Windows 2000 Scripting Guide. Here is the script for your reference.

GetServiceNames.vbs

strComputer = "."
Set objSWbemServices = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colSWbemObjectSet = objSWbemServices.ExecQuery _
   ("SELECT * FROM Win32_Service")
For Each objSWbemObject In colSWbemObjectSet
    Wscript.Echo "Name: " & objSWbemObject.Name
Next

My customized version of this script is shown here. When I run it, it displays the correct values of the IdentifyingNumber, Name, and Version properties of the instance of Win32_ComputerSystemProduct for the computer.

GetComputerSystemProduct.vbs

strComputer = "."
Set objSWbemServices = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colSWbemObjectSet = objSWbemServices.ExecQuery _
   ("SELECT * FROM Win32_ComputerSystemProduct")
For Each objSWbemObject In colSWbemObjectSet
   strIdentifyingNumber = objSWbemObject.IdentifyingNumber
   strName = objSWbemObject.Name
   strVersion = objSWbemObject.Version
Next
Wscript.Echo "IdentifyingNumber: " & strIdentifyingNumber
Wscript.Echo "Name: " & strName
Wscript.Echo "Version: " & strVersion

So far so good. When I try to merge this script into my previous one, I come up with the script shown here.

ScriptDoesNotWork.vbs

Option Explicit
On Error Resume Next
Dim strComputer
Dim strIdentifyingNumber
Dim strName
Dim strVersion
Dim strWMINamespace
Dim strWMIQuery
Dim objWMIService
Dim objSWbemServices
Dim colItems
Dim colSWbemObjectSet
Dim objItem
strComputer = "."
strWMINamespace = "\root\CIMV2"

Set objSWbemServices = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colSWbemObjectSet = objSWbemServices.ExecQuery _
   ("SELECT * FROM Win32_ComputerSystemProduct")
For Each objSWbemObject In colSWbemObjectSet
   strIdentifyingNumber = objSWbemObject.IdentifyingNumber
   strName = objSWbemObject.Name
   strVersion = objSWbemObject.Version
Next

strWMIQuery = ":Win32_ComputerSystemProduct.IdentifyingNumber=" & strIdentifyingNumber & ",Name=" & strName & ",Version=" strVersion

Set objWMIService = GetObject("winmgmts:\\" & strComputer & strWMINamespace & strWMIQuery)
WScript.Echo "Number of properties of " & strWMIQuery & " class is " & objWMIService.Properties_.count
For Each objItem in objWMIService.Properties_
    Wscript.Echo "Property: " & objItem.name & vbTab & "Value: " & objItem.value
Next

When I attempt to run the ScriptDoesNotWork.vbs script, I get “Microsoft VBScript compilation error: Expected end of statement” with the error pointing to the line shown here:

strWMIQuery = ":Win32_ComputerSystemProduct.IdentifyingNumber=" & strIdentifyingNumber & ",Name=" & strName & ",Version=" strVersion

This highlighted line is supposed to be equivalent to the following, which I obtained from WbemTest:

strWMIQuery = ":Win32_ComputerSystemProduct.IdentifyingNumber='MXG5380254 NA540',Name='PY196AV-ABA a1130e',Version='0n31211CT101AMBEM00'"

I think I have screwed up the quotation marks in the highlighted line. Can you help me fix them?

-- MT

 

Hey, Scripting Guy! AnswerHello MT,

 

You need to escape the quotes. Unfortunately, it will be a lot of trial and error. You might need to do something like the following, but keep in mind this is a single line:

strWMIQuery = "””:Win32_ComputerSystemProduct.IdentifyingNumber=””MXG5380254 NA540””,Name=””PY196AV-ABA a1130e””,Version=””0n31211CT101AMBEM00”””

On the other hand, you may need to do something that looks more like the following line of code. Again, this is all on one line of code:

strWMIQuery = "”””:Win32_ComputerSystemProduct.IdentifyingNumber=”””MXG5380254 NA540”””,Name=”””PY196AV-ABA a1130e”””,Version=”””0n31211CT101AMBEM00””””

This is one of the things I REALLY do not like about VBScript! Personally, I would use Windows PowerShell … it is much cleaner and easier to do. In Windows PowerShell you have two quotation marks-- the literal single quotation mark ‘ and the expanding double quotation mark “ .

VBScript does not have this. You need one quote outside everything. You need one literal quote inside to be passed in the string for WMI to use, and you need a quotation mark to escape the previous quotation mark … then you make a sandwich … I never get it right the first time. I have literally spent more than two hours trying to get the quotation marks correct for some complicated WMI queries. One trick I use, is to use Wscript.Echo to display the actual contents of the WMI query variable strWmiQuery in your script. This is one reason I do not like hard coding my WMI query string in to the WMI moniker – because it precludes such an easy troubleshooting technique.

Wscript.echo strWmiQuery

By viewing the contents of the strWmiQuery variable, you can see what is actually being passed to WMI. A good script editor will tell you, in general, if you are getting it close to balancing the pairs of quotation marks. Unfortunately, if the query gets too complicated, many of the script editors seem to get confused. In addition, when troubleshooting a complex WMI query, I write the entire WMI query on a single line to avoid the complication of the line continuation operator. When I have the query working properly, I will then attempt to use line continuation to break the line of code, without actually breaking the script. Oh, the flashback and bad memories.

 

This concludes another edition of Quick-Hits Friday. Join us tomorrow for Weekend Scripter.

We would love for you to follow us on Twitter and Facebook. If you have any questions, send email to us at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys


 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Terrific advices! Thanks

  • Ive got a question that I'm trying to figure out. I need help creating a script that will change the local Admin password to "test001" on the SAM file (NOT the Active Directory Domain Admin password).