Is it Power Management or Power Mis-Management?

So I hear a lot of complaints about network cards getting shut off when users leave their systems for a while.  This apparently drives our local branch techs a little crazy (who can blame them!) so I needed to find a command to add to our imaging process that would fix this issue post deployment.  Unfortunately it can’t be fixed on the image if your are using Sysprep and Mini-Setup to detect and install software drivers.  We use a single image with MDT 2010 and WDS to deploy to over 12 different models. So, while this script looks pretty simple it was a royal pain in my butt to find the solution, then only 15 minutes to write and test.  Oh well.  : /

Set NetworkAdapters = GetObject("WinMgmts://./root/Cimv2")._
ExecQuery("SELECT * FROM Win32_NetworkAdapter WHERE AdapterTypeId=0")
For Each NetworkAdapter in NetworkAdapters
  NetworkAdapterID = UCase(NetworkAdapter.PNPDeviceID)
  Set DevPwrMgtSettings = GetObject("winmgmts://./root/WMI")._
  InstancesOf("MSPower_DeviceEnable",48)
  For Each DevPwrMgtSetting in DevPwrMgtSettings
    DeviceID = UCase(Left_
    (DevPwrMgtSetting.InstanceName, Len(NetworkAdapterID)))
    If NetworkAdapterID = DeviceID Then
      DevPwrMgtSetting.Enable=False
      DevPwrMgtSetting.Put_
    End If
  Next
Next

So, breaking it down: First we get a list of network adapters [Set NetworkAdapters = GetObject(“WinMgmts://./root/Cimv2”).ExecQuery(“SELECT * FROM Win32_NetworkAdapter WHERE AdapterTypeId=0”)]. The AdapterTypeID property is used to select only ethernet adapters. Then we pull the PnPID of the adapter [NetworkAdapterID = UCase(NetworkAdapter.PNPDeviceID)] as this is how the MSPower WMI provider identifies devices. Next build our collection of devices MSPower can access [Set DevPwrMgtSettings = GetObject(“winmgmts://./root/WMI”).InstancesOf(“MSPower_DeviceEnable”,48)]. Then we make the InstanceName property of the MSPower object comparable to the NIC PnPID [DeviceID = UCase(Left(DevPwrMgtSetting.InstanceName, Len(NetworkAdapterID)))]. To make sure we are changing the power management setting on a network adapter and not some other device (USB hub, whatever) we compare the PnPID and InstanceName [If NetworkAdapterID = DeviceID Then] and then do the real work, turning off power management [DevPwrMgtSetting.Enable=False]. The final command [DevPwrMgtSetting.Put_] is extremely important, because without it your setting will not be saved!

Now save this script to a .VBS file and run it using cscript.exe: cscript \.vbs

And that’s it, how to easily turn the Power Management settings on your network adapter off (and on if you change the false to true). It is also possible to manage other settings like WOL using some other components of the MSPower WMI provider.

Concurrent Processing (or faux multithreading)

Have you ever needed to run a command against 2000 workstations and thought, "Man that batch file is going to run forever!"  You might even worry the data will be too stale to matter when it finally gets to you.  I’ve run into this situation multiple times and the fix was to write a quick and dirty app in .NET that uses some simple multithreading.  I just change around some strings, recompile, and I’m off to the races.  Not a programmer you say?  Well, you need to learn a little.  And if .NET isn’t your thing, learn some VBScript and PowerShell.  Next year I plan to start abandoning VBScript <sniff> and moving to PowerShell, but because PowerSehll is not standard on all Windows systems yet it is easier to write i VBScript.  So what I provide for you here is a simple VBScript that will run multiple processes concurrently.  This is not multti-threading, so it uses more resources than my .NET code does, but it’s pretty darn cool.
 
So let’s start with the main piece the Class we create to handle our new process.
 
Class RunCmd
  Private p_Shell
  Private p_Exec
  Private p_Name
  Private p_Available
  Private p_ExitCode
  Public PID
  Public EC_Retrieved
 
  Private Sub Class_Initialize()
    Set p_Shell = CreateObject("WScript.Shell")
    p_Exec = Empty
    p_Available = "True"
    EC_Retrieved = ""
  End Sub
 
  Public Sub Open(cmd)
    Set p_Exec = p_Shell.Exec(cmd)
    PID = p_Exec.ProcessID
    p_Available = "False"
    p_ExitCode = ""
    EC_Retrieved = "False"
  End Sub
 
  Public Property Let Name(tempname)
    p_Name = tempname
  End Property
 
  Public Property Get Name
    Name = p_name
  End Property
 
  Public Property Get Available
    If Not IsEmpty(p_Exec) Then
      If p_Exec.Status <> 0 Then
        p_Available = "True"
        p_ExitCode = p_Exec.ExitCode
      End If
    End If
    Available = p_Available
  End Property
 
  Public Property Get ExitCode
    ExitCode = p_ExitCode
    EC_Retrieved = "True"
  End Property
End Class
 
The class has the constructor, one method, and 5 properties.  Next we look at how I used them.
 
First we set up a sample array of computers to test against.  This can be easily read in from Active Directory, a text file, or database table.  Then we create an array to store new instances of our RunCmd class.  In VBScript we cannot use the For Each…Next statement to populate each array element with a new instance of our class, me must use the standard For…Next which requires us to specify the number of items in our array.
 
Computers = Array("comp1","comp2","comp3","comp4","comp5","comp6","comp7")
Dim RunningProcesses(50)
for i = 0 to UBound(RunningProcesses)
  Set RunningProcesses(i) = New RunCmd
Next
 
After the array of RunCmd objetcs is set up it’s time to move to the main loop where the work is started.  StartProcess is called, which verifies the RunCmd object stored in that array element is not currently running an active process and if it is not it starts a new process to connect to the current computer fom the Computers array.  Once the process is started we move to the CheckProcess function which checks the number of objects in the RunningProcesses array have active processes running.  For the example I have set the number to 3.  So once 3 active processes have been reached it will loop until less than 3 are running.  As the CheckProcesses it calls it will retrieve data from the objects that have data.  So far the only thing I am capturing os the ExitCode.  I have not attempted to capture the StdOut as normally VBScript will wait for the process to finish, and we are try to avoid that!
 
For Each computer in computers
  StartProcess(computer)
  Do While CheckProcesses() >= 3
    WScript.Sleep 500
  Loop
Next
 
Once we have looped througn all of the computer we want to connect to we could still have processes in play, so we continue to run CheckProcesses while until we have 0 processes running.
 
Do While CheckProcesses() > 0
Loop
 
Here is the code for the StartProcess subroutine.  We pass the name of the computer we are attempting to connect to then we pass a command to to the Open method of object p.  When the method Open runs it assigns the PID of the process we started to the PID property of the object.
 
Sub StartProcess(cname)
  For Each p in RunningProcesses
    If p.Available = "True" Then
      p.open("cmd /c sc \\" & cname & " query snare|find /i ""_name""")
      p.Name = cname
      WScript.StdOut.WriteLine "Process Started: " & p.pid
      Exit For
    End iF
  Next
End Sub
 
The CheckProcess function is where we determine how many processes are running and grab the exit codes of completed processes.  When we get the Available property it check to see if the p_Exec object inside of our RunCmd object has a status other than 0, which means it has completed.  If it has not completed it increments the ProcessCount variable.  If it has completed it checks the EC_Retrieved property to see if we have already retrieved the ExitCode of the process.  If we have not retrieve it yet access the ExitCode property.  When we request this property it turns the EC_Retrieved property flag to True.  You can then code for whatever the expected error code might be.
 
Function CheckProcesses
  ProcessCount = 0
  For Each p in RunningProcesses
    If p.Available = "False" Then
      ProcessCount = ProcessCount + 1
    Else
      If p.EC_Retrieved = "False" Then
        If p.ExitCode = "1" Then
          WScript.StdOut.WriteLine p.Name & " – Snare is not installed."
        ElseIf p.ExitCode = "0" Then
          WScript.StdOut.WriteLine p.Name & " – Snare is installed."
        End If
      End If
    End If
  Next
  CheckProcesses = ProcessCount
End Function

 

There’s a lot of application for this code, and it’s a fun tutorial (I think) for creating your own classes in VBScript. Making your own class is a powerful tool and it can be used in tons of different applications.  Feel free to post a comment if you have questions or you’d like more examples!

EDIT: I have posted the complete code in the VBScripts download section!

Fun VBScript tip

When writing a VBScript designed to run at a command prompt (or "console") you some times want to not have to write data to a new line.
 
For instance, you might want to write a "." each time you loop through a array or collection so that you know something is happening.  Using WScript.Echo each use generates a CR\LF, much like using the vbCrLf constant or the textstream.writeline method.
 
This is where the WScript.StdOut.Write method comes in handy.  While not well documented this method works exactly like we need it to.  Take the following example where rsNames is a result set from an ADO query:
 
 
 
Do While Not rsNames.EOF
  If rsNames.Fields.Item(0).Value = "Bob" Then
    ‘ Write a period to show work then start a new line
    WScript.StdOut.WriteLine "."
    Wscript.StdOut.WriteLine "I found Bob!"
    Exit Do
  Else
    ‘ Write a period to show work
    Wscript.StdOut.Write "."
  End If
Loop
 
And if you really want to have some fun you could go for the "rotating line":
 
a = 0
b = 10000000
c = "\"
do while a < b
  if c = "\" then
    c = "|"
  elseif c = "|" then
    c = "/"
  elseif c = "/" then
    c = "-"
  elseif c = "-" then
    c = "\"
  end if
  wscript.stdout.write chr(08) & c
  wscript.sleep(100)
loop
 
Just for clarification chr(08) is a backspace and the sleep for 100 milliseconds makes the rotation direction of the line more obvious. : )
 
Have fun!

Hmmm… HTA…

I am a big fan a writing utilities that make everyone’s (especially mine) life a little easier.  On of the small tasks that are performed on a regular basis where I work is transaction lookups.  Not everyone likes writing SQL and can remember the syntax of a query.  An HTA is an easy way to put together a little app to allow a user to execute a query against a database or allow you to graphically manage your administrative scripts.  An HTA is unfortunately inherently insecure but as long as you use current security contexts or, in the case of data access, ODBC connections.  Below is a little sample of how you can combine HTML and VBScript to create a flexible and useful tool for everyday tasks.
 
An HTA starts like a basic HTML page, but in the head you define an HTA section with important information like application name, window state, border type, etc.
 
After the HTA section you can define your styles and then you write the juicy part of the HTA, the scripts!  Outside of writing back to the HTA window, your VBScripts work in almost the same exact way.  You define subroutines and functions and call them as you normally would.  Using the HTA allows you to add dropdown lists or radio buttons to define option pieces of your scripts.  In this example we allow for access to multiple databases and different search types.
 
In the body of our HTML we define the graphical side of our HTA.  We use a dropdown list to select our database (could easily be available Active Directory domains) and radio buttons to choose our search type, Business Date or Transaction Number (maybe we would make this a search based on object type, to stay in the same theme). 
 
I hope to post another example of how you can use an HTA in more of an administrative capacity.
 
<html>
<head>
<title>Transaction Lookup</title>
<HTA:APPLICATION
ID = "TLApp"
APPLICATIONNAME = "Transaction Lookup"
BORDER = "thick"
SHOWINTASKBAR = "yes"
RESIZABLE = "no"
SINGLEINSTANCE = "yes"
SYSMENU = "yes"
WINDOWSTATE = "Maximize"
SCROLL = "yes"
SCROLLFLAT = "yes"
VERSION = "1.0"
INNERBORDER = "no"
SELECTION = "no"
MAXIMIZEBUTTON = "no"
MINIMIZEBUTTON = "no"
NAVIGABLE = "yes"
CONTEXTMENU = "yes"
BORDERSTYLE = "normal">
</head>
<style>
BODY {
  background-color: buttonface;
 font-family: arial, helvetica;
}
 
</style>
<script language="VBScript">
Sub Window_Onload
‘   execute_button.Disabled  = True
‘   save_button.Disabled = True
  
   code.InnerHTML = ""
  
   GetData.FreeFrm.Disabled = True
   GetData.QFreeFrm.Disabled = True
End Sub
Function TransLookUp
‘START DATA VALIDATION
If GetData.dbselect.value = "NONE" Then
    Msgbox "Please select a database",16,"Database Select Error"
    Exit Function
End If
If getdata.qbusdate.checked = true then
    If isdate(GetData.busdate.value) Then
        busdate2 = cdate(GetData.busdate.value)
    Else
        Msgbox "Please enter a valid date" & vbcrlf & vbcrlf & "    MM/DD/YYYY",16,"Date Format Error"
        GetData.BusDate.Value=""
        Exit Function
    End If
End If
‘END DATA VALIDATION
‘SET QUERY
If getdata.qbusdate.checked = true then
    SQLString = "select * from trn_trans with(nolock) where rtl_loc_id = ‘" & getdata.storenum.value & "’ and business_date = ‘" & busdate2 & "’"
ElseIf getdata.qtransno.checked = true then
    SQLString = "select * from trn_trans with(nolock) where rtl_loc_id = ‘" & getdata.storenum.value & "’ and trans_seq = ‘" & getdata.transno.value & "’"
End If   
‘CREATE MYDB OBJECT
set MyDB = CreateObject("ADODB.Connection")
‘SET CONNECTION STRING
Select Case GetData.dbselect.value
  Case "DATABASEP"
    sqlsrvr = "SERVERP"
    sqldb = "DATABASEP"
  Case "DATABASEQ"
    sqlsrvr = "SERVERQ"
    sqldb = "DATABASEQ"
  Case "DATABASED"
    sqlsrvr = "SERVERD"
    sqldb = "DATABASED"
End Select
MyDB.ConnectionString = "driver={SQL Server};server=" & sqlsrvr & ";uid=" & UnObs("7C68616A6F6E6C79") & ";pwd=" & UnObs("6D6F6594627A6173e3") & ";database=" & sqldb
‘OPEN DB AND PERFORM QUERY
MyDB.Open
set qresults = MyDB.Execute(SQLString)
‘FORMAT DATA AS HTML TABLE
recno = 1
strhtml = strhtml & "<h2>Results:</h2>"
strhtml = strhtml & "<table rules=all><tr><td>Record&nbsp;#</td>"
For Each c in qresults.fields
 strhtml = strhtml & "<td>" & c.name & "</td>"
Next
strhtml = strhtml & "</tr>"
Do Until qresults.EOF
 strhtml = strhtml & "<tr>"
 strhtml = strhtml & "<td>" & RecNo & "</td>"
 For Each r in qresults.fields
  If r.value <> "" Then
   val = r.value
  Else
   val = "&nbsp;"
  End If
  strhtml = strhtml & "<td>" & val & "</td>"
 Next
 strhtml = strhtml & "</tr>"
 RecNo=RecNo + 1
 qresults.MoveNext
Loop
strhtml = strhtml & "</table>"
‘CLOSE MYDB OBJECT
MyDB.Close
‘ASSIGN DATA TO C
code.innerHTML = strhtml
end Function
Sub ClearFields
getdata.reset()
code.innerHTML=""
End Sub
 
‘ Decode PW’s for connection string – NOT SECURE, just makes it more difficult to read.  : )
Function UnObs(obs)
  For i = 1 to Len(obs) Step 2
    UnObs = UnObs & CStr(ChrW(CLng("&H" & (mid(obs,i, 2)))))
  Next
End Function   
</script>
<body>
<h2>Transaction Lookup</h2>
<form name="getdata">
<table>
<tr><td width=450px valign=top>
<table>
<tr><td colspan=2>
<fieldset><legend>Database Selection</legend>
<select id="dbselect" style="width: 350px">
    <option value="NONE">Please select a database…</option>
    <option value="DATABASEP">Production Database</option>
    <option value="DATABASEQ">QA Database</option>
    <option value="DATABASED">DEV Database</option>
</select>
</fieldset>
</td></tr>
<tr><td colspan=2>
<fieldset><legend>Store Number</legend>
<input type="text" name="storenum"></input></td></tr>
</fieldset>
</td></tr>
<tr><td>
<fieldset><legend>Query Selection</legend>
<table>
<tr><td><input name="qtype" id="qbusdate" type="radio">Business Date:</input></td><td><input type="text" name="busdate"></input></td></tr>
<tr><td><input name="qtype" id="qtransno" type="radio">Transaction #:</input></td><td><input type="text" name="transno"></input></td></tr>
<tr><td><input name="qtype" id="qfreefrm" type="radio">Free Form:</input></td><td><textarea name="freefrm" rows=10 cols=30></textarea></td></tr>
</table>
</fieldset>
</tr>
</table>
</td>
<td>
<div id="code" name="code" style="width: 650px;height: 600px; overflow:scroll; background-color:White;"></div>
</td>
</tr>
</table>
</form>
<button onclick="TransLookUp"
  accesskey="P" style="background-color: #b0b0f0">
  <b><u>P</u>rocess</b></button>
<button onclick="ClearFields"
  accesskey="C" style="background-color: #b0b0f0"><b><u>C</u>lear</b></button>
</body>
</html>

Email Address List

Have you ever wondered what all the email addresses in your organization are?  No, me neither, that’s what the Outlook Address Book is for, right?  But what if you have some Unix guys who want a text file with all the email addresses for some app?  So here’s my solution:
 
‘ ***************************************************************************************
‘ **
‘ **  Email_Addr.vbs – Version 1.0.0
‘ **    Written By   : Scott Heath
‘ **    Created On   : July 22, 2008
‘ **    Last Updated : July 22, 2008
‘ ** 
‘ **  Description
‘ **    This script retrieves all the user objects from AD
‘ **    with email addresses assigned and then writes them
‘ **    to a file called ’email_addr.dat"
‘ **
‘ **
‘ ***************************************************************************************
On Error Resume Next
‘ Set Constants
‘ ***************************************************************************************
Const ForWriting = 2
‘ Query AD for all user objects with email address and write them to a file
‘ ***************************************************************************************
Set ADConnection = CreateObject("ADODB.Connection")
Set LDAPQuery    = CreateObject("ADODB.Command")
Set UserList     = CreateObject("System.Collections.ArrayList")
Set FSO          = CreateObject("Scripting.FileSystemObject")
ADConnection.Provider = "ADsDSOObject"   
ADConnection.Open "Active Directory Provider"
err.Clear
Set  LDAPQuery.ActiveConnection = ADConnection
LDAPQuery.Properties("Page Size") = 10000
LDAPQuery.CommandText = "<
LDAP://DC=yourdomain,DC=com>;(&(objectCategory=User)(mail=*@*)" & _
        "(!userAccountControl:1.2.840.113556.1.4.803:=2));Name, Mail;Subtree" 
Set QueryResults = LDAPQuery.Execute
If err.Number = 0 Then
  ‘Success
Else
  ‘Failure
  WScript.Quit(2)
End If
Do Until QueryResults.EOF
  UserInfo = QueryResults("Name") & "|" & QueryResults("Mail") & "|"
  UserList.Add UserInfo
  QueryResults.movenext
Loop
UserList.Sort()
Set OutputFile = FSO.OpenTextFile("email_addr.dat", ForWriting, True)
For Each User in UserList
  OutputFile.WriteLine User
Next
OutputFile.Close
 
So this is a pretty simple script that uses the ActiveX Data Object (ADODB) to query Active Directory.  I use this in a lot of my scripts because Active Directory is typically your source of record for email addresses and user names (samAccountName) unless your use Groupwise or some such.  The request was to get output like this:
 
 
and to sort it alphabetically.  After I looked at the first run I noticed there were many peope who no longer worked for the company and even more objects without email addresses.  If we look at the query:
 
LDAPQuery.CommandText = "<LDAP://DC=yourdomain,DC=com>;(&(objectCategory=User)(mail=*@*)" & _
        "(!userAccountControl:1.2.840.113556.1.4.803:=2));Name, Mail;Subtree" 
 We are including only objects whose class is User and whose mail property matches the pattern *@*.  That gets rid of computer objects which reduces our data set.  Then it gets rid of user objects without email address or with invalid email addresses.  The second line starts with:
 
(!userAccountControl:1.2.840.113556.1.4.803:=2)
 
What the heck is that!  Well, props to the MS Scripting Guys for their excellent article on how to find disabled accounts (click here).  In a nutshell that crazy userAccountControl:1.2.840.113556.1.4.803:=2 is a bitwise operation just like:
 
If object.userAccountControl AND 2
 
This operation checks for the value 2 which indicates that the account has been disabled.  But wait!  Scott said he needed account that WEREN’T disabled.  That were the ! at the beginning of the line comes in.  Using the ! is the same as using NOT.  Then finally we tell the query to return the Name and Mail properties and to search the entire subtree.
 
The other cool feature is the use of the Systems.Collections.ArrayList.  This great object allows you to do fun stuff like Sort() and Reverese() your list with ease.
 
Then finally we write the data to the text file then we’re done!

Catching output from OSQL

Recently one of our DBAs came to me and asked if I could make a script that would allow him to type the name of a script and then have it run against all of the SQL servers we have, catch the output, and then display a message should an error occur.  Od course I said, "Sure!".  I whipped up a script, which I’ll post here later, that did exactly what he wanted.  I used his supplied OSQL command that included the -o option which saves the output to a text file.  I then used the Scripting.FileSystemObject to open the log files and parse them for specific words.  I then added it to a ErrorMessage string and at the end of the script displayed all servers that had received errors.
 
This is where I ran into issues.  You see OSQL sometimes writes output as ANSI and sometimes as Unicode!  I thought this was odd and tried several options, such as always write Unicode and forces vbscript to always read the file as Unicode.  This is when I discovered vbscript and OSQL’s Unicode was based on a different standard.  I was using the WScript.Shell object’s Run method to launch the OSQL command.  So I changed tactics and used the Exec method instead.  This allowed my to create an object out of the command that was running and return values like Status and ExitCode.  Most importantly, I was able to use the StdOut.ReadAll() method to grab the output and write it to a logfile, thus solving my file format issues.
 
Now our script runs totally awesome and everyone is happy.  I’ll try to post it Monday along with an update of the Server Inventory script.  I changed the ChassisType table to show the string based value instead of trying to interpret the integer value and determine server\non-server hardware.  What I am going to add to my website is the ability to uncheck certain types of system model to remove them from the drop downs and reporting.  I also added a new table for Memory Location and added some field for MemoryCapacity and MemoryDetails to store the Tag property.  This allows me to correctly assign memory to memory boards and report the correct number of free memory slots per board.
 
Sorry it was long between posts.  I had a week and a bit of vacation in June, plus the fiscal year of the company I work for is drawing to a close and we are super busy!