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! 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
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.
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! 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
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! 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
Hello 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
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).