Expert Solutions: Beginner Event 10 of the 2010 Scripting Games

Expert Solutions: Beginner Event 10 of the 2010 Scripting Games

  • Comments 3
  • Likes

 

Bookmark and Share

(Note: These solutions were written for Beginner Event 10.)

Beginner Event 10 (Windows PowerShell)

Photo of Jonathan Medd

Jonathan Medd is a Windows PowerShell MVP and co-host of the Get-Scripting podcast. He regularly posts about Windows PowerShell on his blog, http://jonathanmedd.net, and you can tune in to the monthly Windows PowerShell podcast at http://get-scripting.blogspot.com. You can follow his Windows PowerShell and other system administrator antics at http://twitter.com/jonathanmedd.

The first step in troubleshooting this script is to comment out the opening line:

#$errorActionPreference = "silentlycontinue"


The default value of $errorActionPreference in Windows PowerShell is continue. This means that upon hitting an error, Windows PowerShell will flag the error, but attempt to carry on processing the script. By commenting out the first line, which has changed the value of $errorActionPreference to silentlycontinue, we will be able to troubleshoot what is wrong with the script.

If we now run the script again we get the following error: “You cannot call a method on a null-valued expression as seen below.”

Image of error message

From the second line of the error, we can see that the issue is with line 4:

$Shell.popup($w.UserName)


The method is the part of the line after the dot so we know that for some reason the variable $Shell is empty. This is because in line 3, the variable used to store the wscript.shell ComObject has been named $wshShell. So to correct that, line 4 should look like this:


$wshShell.popup($w.UserName)

If we run the updated script again, we receive the below message box. No error this time, but it is empty. So we know that we can create a message box, but there must be something wrong with populating the text.

Image of empty message box

This is again partly to do with the naming of variables. In line 1, we used $wmi to store the results of the WMI query to retrieve the currently logged-on user. This is the variable we need to use in the popup method to display the correct text. So line 3 should be further corrected:


$wshShell.popup($wmi.UserName)

However, running the script again still returns an empty message box. Going back to the original WMI query in line 1, if we run it interactively we get the below result:

Image of results of running WMI query interactively

The value of the Name property is the name of the computer, not the logged on user. Also this property is not called UserName, which we are trying to use in line 4. By changing the WMI query to instead use the UserName property, we get the correct logged-on user:


Get-WmiObject
-Class Win32_computerSystem -Property UserName

Image of running WMI query with UserName property

The corrected script now looks like this:

Beginner10_Fixed.ps1

#$errorActionPreference = "silentlycontinue"

$wmi = Get-WmiObject -Class Win32_computerSystem -Property UserName

$wshShell = New-Object -ComObject wscript.shell

$wshShell.popup($wmi.UserName)


When we run this corrected script, the following message box is displayed.

Image of message box displayed

A slightly more advanced way to create a message box would be to use the message box class within the System.Windows.Forms .NET Framework namespace. While not providing much extra in this particular example, more complex forms could be created using these tools.

You first need to load the system.windows.forms assembly (only a subset of the .NET Framework is available by default in Windows PowerShell):

[reflection.assembly]::loadwithpartialname('system.windows.forms') | Out-Null


Run the same WMI query as before:

$wmi = Get-WmiObject -Class Win32_computerSystem -Property UserName


Then use the show method of the system.windows.forms.messagebox class to display the message box:

[system.Windows.Forms.MessageBox]::show($wmi.username)


Image of message box

 

Beginner Event 10 (VBScript)

Photo of Georges Maheu

Georges Maheu is the Premier Field Engineer (PFE) Security Technology Lead for Microsoft Canada. As a Senior PFE, he focuses on delegation of authority (how many domain administrators do you really have?), server hardening, and doing security assessments using his own scripts. Georges has a passion for VBScript, Windows PowerShell, and WMI and uses them on a regular basis to gather information from Active Directory and computers. Georges also delivers popular scripting workshops to Microsoft Premier Customers worldwide.

------------

VBScript solution

On Error Resume Next
Set wmi = GetObject("winmgmts:")
colitems = wmi.Execquery("Select user from Win32_computersystem")
For Each item In colitem
WScript.Echo item.username
Next


This is a classic: a script written by someone else without comments. In this situation, the first thing I do after examining the source code is to double-click the script in a test environment. Personally, I use Virtual PC for all my script development and testing. Never run a script in production until you fully and totally understand what it does.

Well, running this script did not help. Nothing seems to happen. My next reflex is to comment out the first line and to add the Option explicit statement to force variable declarations:


' On Error Resume Next 'GLM: commented out
Option explicit ’GLM: added

The apostrophe character is used to add comments or descriptive text in a VBScript. In this case, it will prevent the error-handling statement On error resume next from executing. On error resume next makes your scripts ignore errors (no error messages either) and move on to the next line. I could have deleted the line but I like to keep the original code around while I debug. I also add comments preceded by my initials, GLM.

Now, when I run the script, I get this dialog box:

Image of dialog box shown when script is run

As a best practice, all variables should be declared, and adding the Option explicit statement makes this mandatory. Variables are declared with the Dim statement. I add the following:

Dim WMI


I then repeat the process until all the variables are declared:

Dim colItems
Dim item


During this process, I notice there is a typo in colItem. An s is missing at the end!

colItems = wmi.Execquery("Select user from Win32_computersystem")
' For Each item In colItem 'original code
For Each item In colItems 'GLM: modified


At this point, the code looks like this:

'On Error Resume Next 'GLM: commented out
Option explicit 'GLM: added
Dim WMI 'GLM: added
Dim colItems 'GLM: added
Dim item 'GLM: added
Set WMI = GetObject("winmgmts:")
colItems = WMI.Execquery("Select user from Win32_computersystem")
' For Each item In colItem 'original code
For Each item In colItems 'GLM: modified
WScript.Echo item.username
Next


And I get the following error message:

Image of error message

A search (using Bing) on “vbscript error 800A01C2” may give you an indication of what the problem could be. Fortunately, I found a webpage with a similar problem and remembered that the WMI ExecQuery requires a Set to work:


' colItems = wmi.Execquery("Select user from Win32_computersystem") 'original code
Set colItems = wmi.Execquery("Select user from Win32_computersystem") 'GLM: modified

Let’s run the code and see what happens.

Image of another error

OK, another error. And I thought this was going to be easy.

However, line #10 (For Each item In colItems) seems to be fine. One thing you learn quickly when you write or debug scripts is that errors sometimes occur in cascades. If you can’t find anything wrong with a particular line, it could be the error occurred in the code before the line in which the error was reported. The question is, where?

WMI, which stands for Windows Management Instrumentation, is in some respect Microsoft’s implementation of WBEM, a standard created by DMTF. WMI uses WQL, a query language similar to SQL. Knowing this, I decided to replace the WQL query with the following:


' colItems = WMI.Execquery("Select user from Win32_computersystem") 'original code
Set colItems = WMI.Execquery("Select * from Win32_computersystem") 'GLM: modified

I did this because the “*” is like a wild card character and will return all the properties associated with the Win32_computerSytem class. Unlike PowerShell, there is no simple way to list properties in VBScript. You could write some code (review Scriptomatic source code for an example), but this would probably be more work than debugging this code! Another option is to use CIM Studio from the free WMI tools.

Image of CIM Studio

CIM Studio will give you a complete list of all the properties and methods for a given class. With this information, I could also have rewritten the line to:


Set colItems = WMI.Execquery("Select userName from Win32_computersystem")

Let’s run the script one more time.

Image of running script one more time

Yes! It works.

Here is the working script after our debugging session:


'On Error Resume Next 'GLM: commented out
Option explicit 'GLM: added
Dim WMI 'GLM: added
Dim colItems 'GLM: added
Dim item 'GLM: added
Set WMI = GetObject("winmgmts:")
' colitems = WMI.Execquery("Select user from Win32_computersystem") 'original code
Set colItems = WMI.Execquery("Select * from Win32_computersystem") 'GLM: modified
' For Each item In colItem 'original code
For Each item In colItems 'GLM: modified
WScript.Echo item.UserName
Next

I save this version as a reference and then clean the code. You will notice I removed the On Error Resume Next statement. Because I’m not doing any error handling, there is no point using this statement. Here is the final script with added comments:

ShowLoggedUserAccount.vbs

'* File: ShowLoggedUserAccount.vbs
'* Version: 1.06
'* Date: 2010/04/06
'* Author: Georges Maheu, Microsoft PFE
'*
'* Based on the original file
'* File: VBScriptDoesNotWOrk.vbs
'* Version: 1.0
'* Date: 2010/03/21
'* Author: John Doe
'* Modifications: documented in file Debug-VBScriptDoesNotWOrk.vbs
'
' This script will display the name of the current logged-on user
' on a local or remote computer
' ================================================================
Option Explicit 'make variable declaration mandatory
Dim WMI 'WMI service object
Dim colItems 'collection of items returned by WMI query
Dim item 'individual item from the WMI collection variable colItems
Dim computerName 'name of computer to query
'LocalHost can be replaced by a remote computer name or IP address
'must be run from an account with sufficient permissions to see the data
computerName = "localhost"
'create WMI service object and connect to CIMv2 namespace on computerName
Set WMI = GetObject("winmgmts:\\"+computerName+"\root\CIMv2")
'retrieve userName property from Win32_computerSystem WMI class
set colItems = WMI.Execquery("Select userName from Win32_computersystem")
'WMI ExecQueries returns a SWbemObjectSet collection data structure
'even when there is only one item in the collection. For this reason
'we use the For Each construct to list the userName property
For Each item In colItems 'iterate through collection
WScript.Echo item.userName 'display user name on screen
Next


Just for fun, I also created a very short version of this script:

WScript.Echo GetObject("winmgmts:").Execquery("Select * from Win32_computersystem").itemIndex(0).userName


If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail 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
  • Did I read somewhere recently that this syntax:

    [reflection.assembly]::loadwithpartialname('system.windows.forms') | Out-Null

    was deprecated in favour of Add-Type? (referenced at the end of the PS section above).

    Would love to have that link handy for a reference. Perhaps the scripting personages can answer.