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







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


 

Comments (2)

  1. Alex Oleynikov says:

    Terrific advices! Thanks

  2. James Nichols says:

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

Skip to main content