Bookmark and Share

 

(Note: These solutions were written for Advanced Event 5 of the 2010 Scripting Games.)

 

Advanced Event 5 (Windows PowerShell)

Photo of Dr. Tobias Weltner


Dr. Tobias Weltner is a Microsoft Windows PowerShell MVP, has created the Windows PowerShell development environment PowerShellPlus (http://www.powershellplus.com), and runs one of the largest Windows PowerShell communities worldwide (http://www.powershell.com). He has written more than 100 books on computers, and his latest book about Windows PowerShell 2.0 was published by Microsoft Press Germany. Most of all, Tobias enjoys working as a trainer for quality Inhouse Windows PowerShell workshops throughout Europe. You can reach him at tobias.weltner@scriptinternals.de.

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

At first, retrieving video memory seems to be a trivial task: WMI has a class called Win32_VideoController with an AdapterRAM property, and to format it to MB/GB requires a simple condition in which you compare the byte value and format it to GB when it is at least 1 GB; otherwise, format it to MB.


The task has two tricky aspects, though.


First, you want to retrieve information locally and remotely from multiple computers. To be able to do this, your function needs to implement computername and credential parameters. You cannot simply hand those over to Get-WMIObject though because if the user did not specify credentials, a null value would still invoke the credentials dialog, and also credentials are not allowed on local systems. Furthermore, when you pass on lists of computers directly to Get-WMIObject, if one system is down or access denied, all other computers in that list would fail, too.


This is why the function checks whether a credential was supplied and only then calls Get-WMIObject with -Credential, and why the computername parameter is passed into -Computername via Foreach-Object, making sure Get-WMIObject processes only one computer at a time.


The second tricky aspect is that you are supposed to return the video memory as nicely formatted MB/GB, but at the same time you want to be able to compare the video size to filter out systems based on some limits. If you return a string, you no longer can compare against byte values, and if you return a number, you cannot format it.


The function solves the problem by using a composed object. It is an integer that you can easily compare against other values, but has an overridden toString() to show the nicely formatted string number. Because you might want to run the function against multiple computers, it also has custom properties and methods that tell you which system the number was retrieved from.


As a result, the function is very versatile and can be used in a number of scenarios including submitting data over the pipeline. The following image (running the function in my favorite Windows PowerShell development environment, PowerShellPlus) demonstrates the different ways you can employ this function. Note how a warning is written when systems are unavailable or access is denied, and how you can retrieve the video size or all details about the video controller. Because the result is still an integer, you can also use the function to filter out systems ready for upgrade, and because the function accepts computernames via pipeline, they can come from spreadsheets, databases, or directly from your AD.

Image of different ways to deploy function

 

Here is my script:

function Get-VideoMemory {

      param(

      [ValidateNotNullOrEmpty()]

      [Parameter(ValueFromPipeline=$true)]

      [System.String[]]

      $ComputerName = 'localhost',

      $Credential = $null

      )

 

      begin {

 

            # retrieve WMI class

            # supply credentials parameter only if credential was specified

            # make sure to call Get-WMIObject with ONE computer at a time by feeding them in via foreach-object

            # Get-WMIObject DOES support string arrays and can process more than one computer

            # but then when one call fails, all subsequent computers fail, too:

            function Get-VideoController {

                  $private:ofs = ','

                  if ($credential) {

                        $computerName | ForEach-Object { $cn = $_; try { Get-WmiObject Win32_VideoController -ComputerName $cn -Credential $credential } catch { Write-Warning "Problem with $cn : $_"} }

                  } else {

                        $computerName | ForEach-Object { $cn = $_; try { Get-WmiObject Win32_VideoController -ComputerName $cn } catch { Write-Warning "Problem with $cn : $_"} }

                  }

            }

 

            # return video memory as integer, attach videocontroller object for reference

            # select AdapterRAM property and attach original WMI videocontroller instance as new VideoAdapter property for future reference

            # add a new script method ShowDetails() to show computername and video adapter key properties

            # Note that the orginal variable type is an integer and holds the video RAM as bytes

            filter Get-VideoRAM {

                  $_.AdapterRAM |

                  Add-Member NoteProperty VideoAdapter -Value $_ -PassThru |

                  Add-Member ScriptMethod ShowDetails -Value { $this.VideoAdapter | Select-Object SystemName, VideoProcessor, VideoModeDescription, DriverVersion } -PassThru

            }

 

            # identify local system

            # Because WMI does not allow usage of credentials on local computers, and because local computers are always

            # online and do not need to be checked nor would the check work for all cases,

            # make sure you identify local systems:

            function is-Local($computername) {

                  $local = '.','localhost','127.0.0.1', $env:computername

                  $local -contains $computername

            }

 

            # filter out unavailable systems

  # this uses Test-Connection with a Count of 1 to speed it up. It uses If/Else instead of Where-Object to be able

            # to write a warning for unavailable systems. It omits local systems because Test-Connection wrongly identifies them

            # as unavailable when you are not connected to a network or use shortcuts such as ".":

            filter Test-Online {

                  if (is-Local $_) {

                        # skip online test

                        $_

                  } else {

                        $_ | ForEach-Object { if (Test-Connection -Count 1 -Quiet $_) { $_ } else { Write-Warning "Computer '$_' not reachable." } }

                  }

            }

      }

 

      # produce video memory objects, change display to formatted text

      process {

 

            # clean up submitted computers and remove those not available

            $online = $computerName | Test-Online

            if ($online -eq $null) {

                  # if none are available, skip remaining logic using "continue":

                  continue

            } else {

                  # if some or all are online, reassign cleaned list to parameter:

                  $computerName = $online

            }

 

  # use Switch as loop and condition in one. It checks all instances returned by the command in parentheses

            # it overrides the toString() method and displays video size as "cooked" value in MB/GB

            # when you output the result later, it shows the nicely formatted value

            # when you COMPARE the result later, it still is an integer in bytes

            switch (Get-VideoController | Get-VideoRAM )

            {

                  # if memory is greater or equal 1GB, show in GB...

                  {                       $_ -ge 1GB }

                  {                       $_ | Add-Member ScriptMethod toString -Value {'{0:0} GB' -f ($this / 1GB)} -force -passthru }

 

                  # ...else, show in MB...

                  default

                  {                       $_ | Add-Member ScriptMethod toString -value {'{0:0} MB' -f ($this / 1MB)} -force -passthru }

            }

      }

}

 

 

Advanced Event 5 (VBScript)

Photo of Gary Siepser

 

Gary Siepser is a senior premier field engineer (PFE) for Microsoft. He is a Microsoft Certified Master for Exchange Server 2007. As a PFE, he spends most of his time in front of Microsoft’s Premier customers delivering Windows PowerShell and Exchange workshops, Exchange Risk Assessments (ExRAP), various custom Exchange and Windows PowerShell knowledge transfers, and the occasional critical situation onsite assistance. He maintains a blog for Exchange and Windows PowerShell topics at http://blogs.technet.com/gary.

 

To tackle this event, as in any data gathering related script, the first hurdle for me was where to get this piece of information. WMI is normally a great place to look, but it can be tricky to find what you are looking for. I actually decided to use my normal Windows PowerShell tricks to attempt to find this information. I know this is a VBScript solution, but I use Windows PowerShell quite a bit and doing the research on Windows PowerShell wasn’t cheating in my book. If I am in a confession mood, I also used Windows PowerShell to repeatedly test launch the script as I was composing and debugging it. I tried out Adersoft’s VBSEdit to compose the script, though I didn’t use its built-in debugging features. I am not a regular VBScripter any more, so this script posed an interesting challenge for me. I will further confess that I completely wrote the solution in Windows PowerShell before even touching VBScript. Why did I do this? Because I can compose code very quickly in Windows PowerShell. I also suspected that WMI would be the key to this solution. Knowing that, I realized that a WMI Query is a WMI query regardless of if it’s used from Windows PowerShell or VBScript.


Suspecting that WMI is the most logical source for this information, I explored WMI through Windows PowerShell where WMI is very discoverable. I was able to find a WMI class called Win32_VideoController in the default root/CIMv2 namespace, which contained a property called AdapterRAM. This property contained a value in bytes of the amount of RAM on the system’s video controllers. It is possible that a system can have more than one video adapter, and because this challenge doesn’t specify specific details about multiple cards, I chose to simply report on a card that had greater than “0” adapterRAM. The WMI query that I built specifically—and remember that WMI query construction is irrelevant of the calling technology (in this case VBScript)—was this:

SELECT SystemName,AdapterRAM,Name  FROM Win32_VideoController WHERE AdapterRAM>0



The above query returns only three properties that are of interest to me, and filters out instances where the adapterRAM is not greater than “0”. This query, and the use of WMI, is the key to this solution. Everything else about the solution is simply a wrapper for presenting the information returned by this query.


The other key parts of this script was code to automatically size the amount of RAM, creating logic to operate this script against multiple machines that might be remote, and ensuring that this script was only run with Windows Script Host’s CScript.exe.


I created the function, AutoByteUnitSizer, to make for nicely reusable code to perform the automatic unit sizing. I agonized for a bit on the best logic to use for this, but in the end, I simply check if the value is larger than 1 GB, then 1 MB, and then 1 KB to determine the best unit to use. The function came out quite simple in the end and through all the numbers I could throw at it to test it out, it worked very well.


I also created the function, CheckScriptHost, to check if this script was running in CScript.exe. Rather than just hard-coding the script to only check for CScript, I decided to make the function take a single argument, being either WScript.exe or CScript.exe, and it would output the correct error message and halt the script.


To allow this script to work against multiple machines, I didn’t bother to create a function; I just created a loop on an array of machine names. The script attempts a basic WMI connection to the name, and then only when the connection was successful does the script go on and actually perform the WMI query.


The rest of the script was relatively routine script flow control and output using several wscript.echo and If statements and such. The output of the script is simple text as shown in the following image. For this test run, I gave the script three machine names: a “.”, “localhost”, and a bogus name to ensure it ran against multiple machines and handled a server that is “not responding.”

Image of output of script


I tried to comment the code well enough so that the flow of is should be easy enough to follow. Thanks for reading my version of a solution to this fun event.


Here is my script.

' Inventory of Video Adapter RAM for Windows 7 Aero Suitability

 

Option Explicit

On Error Resume Next

 

Dim arrComputerNames, strWQLQuery, strComputer, colItems

Dim objItem, objWMIService, strScriptHost

 

Const KB = 1024  '1KB is 1024Bytes

Const MB = 1048576  '1MB is 1024 * 1024 Bytes

Const GB = 1073741824  '1GB is 1024 * 1024 * 1024 Bytes

 

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''The following line is editable, simply''''''''''''''''''''''''''''

''''''add resolvable machine names separated''''''''''''''''''''''''''''

''''''by commas and in quotes.''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

arrComputerNames = Array(".","localhost","bogus")

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 

'This is the query to be used to query WMI for AdapterRAM in the Win32_

'VideoController class.

strWQLQuery = "SELECT SystemName,AdapterRAM,Name " & _

      "FROM Win32_VideoController WHERE AdapterRAM>0"

 

'Because we have the potential for many lines of output, let’s ensure

'we are running with CScript so we don't have to hit "OK" a ton of

'times. This is a function.

CheckScriptHost("cscript.exe")

 

'Now we simply loop through the computer names we have in the array to allow

'this script to work against multiple machines in one run.

For Each strComputer In arrComputerNames

      objWMIService = vbNull

      colItems = vbNull

      objItem = 0

      Err.Clear

      'Connect to the WMI service on the target machine

      Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

      If Err.Number > 0 Then

            WScript.Echo "ERROR: " & Err.Description & " - " & strComputer & vbCrLf

      Else

            'As long as we don’t have an error making the connection, we go

            'and query the machine for the video memory

            Set colItems = objWMIService.ExecQuery(strWQLQuery,,48)

     

            For Each objItem in colItems

                  WScript.Echo "SystemName: " & objItem.SystemName

                  WScript.Echo "Name: " & objItem.Name

                  WScript.Echo "AdapterRAM: " & AutoByteUnitSizer(objItem.AdapterRAM)

                  'Let’s verify if we have enough RAM for Win7 Aero

                  If objItem.AdapterRAM > (128 * MB) Then

                        WScript.Echo "Video Adapter is ready for upgrade"

                  Else

                        WScript.Echo "Video Adapter Requires Upgrade"

                  End If

                  WScript.Echo ""

            Next

      End If

Next

 

'This function automatically returns a string presentation of the

'number passed to it. It will determine the best unit size, round

'the number to two decimal places, and append the unit abbreviation

'to the end of the string.

Function AutoByteUnitSizer (intRawValue)

      Dim strUnit,strValue

     

      If ((intRawValue / GB) >= 1) Then

            strUnit = "GB"

            strValue = Round((intRawValue / GB),2)

      ElseIf ((intRawValue / MB) >= 1) Then

            strUnit = "MB"

            strValue = Round((intRawValue / MB),2)

      ElseIf ((intRawValue / KB) >= 1) Then

            strUnit = "KB"

            strValue = Round((intRawValue / KB),2)

      Else

            strUnit = "Bytes"

            strValue = intRawValue

      End If

           

      AutoByteUnitSizer = (strValue & strUnit)

End Function

 

'This subroutine checks to see if the script is running under the correct

'host, simple pass the sub the either "cscript.exe" or "wscript.exe".

Sub CheckScriptHost(strDesiredHost)

      Dim strScriptHost, strCurrentHost

 

      strScriptHost = LCase(Wscript.FullName)

      strCurrentHost = Right(strScriptHost, 11)

     

      If strCurrentHost <> strDesiredHost Then

            WScript.Echo "This script is currently running under " & strCurrentHost & _

                  " and " & _

                  "will now exit. Please run this script only under " & strDesiredHost & "."

            WScript.Quit

      End If

     

End Sub

 

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