Learn about Windows PowerShell
(Note: These solutions were written for Beginner Event 10.)
Beginner Event 10 (Windows PowerShell)
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.”
From the second line of the error, we can see that the issue is with line 4:
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:
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.
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:
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:
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
The corrected script now looks like this:
$wmi = Get-WmiObject -Class Win32_computerSystem -Property UserName
$wshShell = New-Object -ComObject wscript.shell
When we run this corrected script, the following message box is 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:
Then use the show method of the system.windows.forms.messagebox class to display the message box:
Beginner Event 10 (VBScript)
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.
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:
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:
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:
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.
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.
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.
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:
'* 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 firstname.lastname@example.org or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys
Did I read somewhere recently that this syntax:
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.