Getting a list of users in an AD Group. using Vbscript.

I much prefer using Quest to do this (see this post), but I needed to use Vbscript as I had to output the results into an Excel spreadsheet.

If you look around the internet, you’ll find plenty of examples of how to do that.

But when I ran the following code on my system, it failed:
For Each objUser in objGroup.Members
    Wscript.Echo "Name: " & objUser.DisplayName
    Wscript.Echo "Department: " & objUser.department
    Wscript.Echo "Street address: " & objUser.streetAddress
    Wscript.Echo "Title: " & objUser.title
    Wscript.Echo "Description: " & objUser.description
    Wscript.Echo “Account Disabled?: “ & objUser.AccountDisabled
    Wscript.Echo
Next

After much head scratching, I realised that another AD Group was a member of the current group, AND AD Groups do not have a AccountDisabled attribute.

The solution was to check the class attribute to see if the group member was a “user” or something else.
For Each objUser in objGroup.Members
    Wscript.Echo "Name: " & objUser.DisplayName
    Wscript.Echo "Department: " & objUser.department
    Wscript.Echo "Street address: " & objUser.streetAddress
    Wscript.Echo "Title: " & objUser.title
    Wscript.Echo "Description: " & objUser.description
    If LCase(objUser.class) = “user” Then
        Wscript.Echo “Account Disabled?: “ & objUser.AccountDisabled
    Else
        Wscript.Echo “I am a AD Group. “
    End If
    Wscript.Echo
Next

References:
How Can I Return Information For Each Member in a Group? (Hey Scripting Guy! Blog)
User Attributes – Inside Active Directory by Sakari Kouti
Active Directory Explorer by Sysinternals

Using VBScript to read though a set of registry keys

I’m currently doing another deployment of Acrobat Reader, this time with VBScript.  I wrote about using Visual Basic to do that here.

Function CheckForPro
    ' returns TRUE if Acrobat Pro found OR we're not able to read registry key

    Dim strHost,sAddRemTmp,iRegRC,objFCReg,strAddRemValue,arrSubKeys,strSubKey
    Const strBaseKey = "Software\Microsoft\Windows\CurrentVersion\Uninstall\"   
    Const HKEY_LOCAL_MACHINE = &H80000002   

    strHost = "."
    sAddRemTmp = ""
    iRegRC = 0
    CheckForPro = False
    Set objFCReg = GetObject("winmgmts://" & strHost & "/root/default:StdRegProv")

    iRegRC = objFCReg.EnumKey (HKEY_LOCAL_MACHINE, strBaseKey, arrSubKeys)
    If iRegRC <> 0 Then
        CheckForPro = True
        Exit Function
    End If
    For Each strSubKey In arrSubKeys
           intAddRemRet = objFCReg.GetStringValue(HKEY_LOCAL_MACHINE, strBaseKey & strSubKey, "DisplayName", strAddRemValue)
           If intAddRemRet = 0 Then
            If InStr(lcase(strAddRemValue),"acrobat") <> 0 Then
                If InStr(lcase(strAddRemValue),"pro") <> 0 Then
                    CheckForPro = True
                    Exit Function
                End If
            End If
           End If
       Next

    CheckForPro = False

End Function

Update 11 DEC 2013:
A clever co-worker also points out that if you have a “acrobat.exe” somewhere in Acrobat Program Files directory, then this means you have a Acrobat Pro installation.

“You could use WMIC Product”

… suggested Adrian, of “Text Mode drivers and Sysprep” fame, in response to the “A script to list out the Add/Remove Program entries” post.

Yes, that would work as well.

wmic_product

You’d need to pipe the output of the WMIC Product command into a file, or write a WMI query on the product class. 

You’d need to be aware that there are some “gotta’s” with that though.  The Product class is not query optimized.  And, Microsoft has expanded the Product class over time.  Which is to say, if you’re expect to see a field like InstallSource, it didn’t exist until Windows Vista.

Further reading:
June 2010: Some example queries for WMI and WMIC

Resetting the "IE Browser Choice” preference.

We upgraded Internet Explorer to IE8.  Foolishly enough, we assumed that on a Corporate Desktop, that users would only use Internet Explorer.

Then the complaints from the vocal Firefox majority rolled in.

“How dare you force me to use Internet Explorer!”

So we had to provide those Firefox users with a “choice”.

The solution was to reset the Internet Explorer “browser choice” setting so next time IE was launched:
Browser choice

We did this by using a VBscript, that we depoloyed to all the computers:
On Error Resume Next
Set objWSH = CreateObject("WScript.Shell")
objWSH.RegWrite "HKCU\Software\Microsoft\Internet Explorer\Main\Check_Associations","Yes","REG_SZ"
Set objWSH = Nothing
WScript.Quit(0)

Roll forward 6 months, a query from the customer:

Can you reset the browser choice, as we’re moving away from Firefox …

“Why not use Group Policy?”
As it’s a corporate environment, we restrict what the users can change via the Internet Explorer menus.  In this environment, it was less hassle to use a VBscript to reset one option compared to allowing users to modify other menu options grouped in the same menu tab.

Writing log files with VBscript.

With some of our recent software deployments, we’ve used VBscript to install the software package(s).  We have a dedicated team which develops these installation scripts.  And their VBscripts mostly works well, except when they don’t.

Like when a user gets in the way of the install.

And when the user gets in the way of the install, we often don’t know what’s happened as we don’t include logging in these VBscripts. 

Until now that is.  I got tired of treating to guess where a script has failed, so I developed a 10 line function to drop into existing VBscripts, which will provide some logging.

Here it is:

Function WriteLogFileLine(sLogFileName,sLogFileLine)
    dateStamp = Now()

    Set objFsoLog = CreateObject("Scripting.FileSystemObject")
    Set logOutput = objFsoLog.OpenTextFile(sLogFileName, 8, True)

    logOutput.WriteLine(cstr(dateStamp) + " -" + vbTab + sLogFileLine)
    logOutput.Close

    Set logOutput = Nothing
    Set objFsoLog = Nothing

End Function

You call it with the following syntax:

Call WriteLogFileLine(“LogFileName”,"Log Message")

It writes a log file which looks like this:

16/07/12 1:42:20 PM -    Starting Install_KBv6
16/07/12 1:42:21 PM -    Hotfix not found - calling InstallKB
16/07/12 1:42:21 PM -    Applying newest hotfix.
16/07/12 1:42:21 PM -    Function InstallKB
16/07/12 1:42:28 PM -    Calling objWsh.Run with following command: "\\NODDY\IE8-WindowsXP-KB2699988-x86-ENU.exe" /Quiet /NoRestart
16/07/12 1:43:58 PM -    intResult 3010 Returned.

It has made investigation of software install failures much easier.

Using Visual Basic to read though a set of registry keys.

The problem we have with a corporate deployment of Acrobat Reader, was that we need to uninstall various older versions of Acrobat Reader.  Normally to do this, you need to know the uninstall command string, which changes with each Acrobat Reader release.  One of our senior application packagers said

Why don’t just loop though SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, detect Acrobat Reader in the DisplayName key, then grab the UninstallString.

By jove, what a clever idea.

Doing that in Visual Basic .Net proved me with an afternoon’s programming exercise.  Here is the simplified result:

Dim SoftwareRegistry = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ".")
Dim SoftwareUninstall = SoftwareRegistry.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", False)
Dim sDisplayName As String = vbEmpty
Dim sReaderUninstallString As String = vbEmpty
Dim sMSIUninstallGUID As String = vbEmpty


For Each item In SoftwareUninstall.GetSubKeyNames
'MessageBox.Show(item)

    Dim SoftwareUninstallItemKey = SoftwareRegistry.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" + "\" + item, False)

    sDisplayName = SoftwareUninstallItemKey.GetValue("DisplayName")

    If InStr(LCase(sDisplayName), "adobe reader", CompareMethod.Text) Then
sReaderUninstallString = SoftwareUninstallItemKey.GetValue("UninstallString")
If sReaderUninstallString <> "" Then
MessageBox.Show(sMSIUninstallGUID)
End If
End If

    SoftwareUninstallItemKey.Close()
Next

SoftwareUninstall.Close()
SoftwareRegistry.Close()

Code to launch an Internet Explorer window from VBscript.

set IE = WScript.CreateObject("InternetExplorer.Application")

dim ObjTitle

IE.Navigate(“about:blank”)
IE.Document.Title = “Software Update”
IE.FullScreen = 1
IE.Left       = 0
IE.Top        = 0
IE.MenuBar    = 0
IE.Toolbar    = 0
IE.StatusBar  = 0
IE.Width      = 540
IE.Height     = 151
IE.Visible    = 1

Set ObjBody  = IE.Document.Body
strHTML = “<b><h2>Software Update In Progress</H2><br>” &_
“Please do not use your computer during this time.  Thank you</B>”
objBody.InnerHTML = strHTML

“Push customized printers down to XP client machines in domain environment?”

This is a copy of a post which was on Ask MetaFilter, August 2, 2006.


In a Windows Server 2003 domain environment, is there a way to push printers to client machines if they are not shared printers? What about changing printer settings over the network?

I often need to setup networks of about 40 machines with 4 IP printers. They are all identical printers but with different IP addresses. I split them up in groups of 10, with each group printing to one of the printers. I would like to automatically push the printers to client machines.

My first thought was to create a share on the network, ie \\server1\Printers, and then create a login script that installs the printer. The problem with that is, it will attempt to install the printer every time the users log on unless I get rid of the script after the first login, which seems like it’d be counter-intuitive.

The other problem with this is that I need specific printer settings… paper size is Envelope Comm #10, landscape, paper source is Tray 1 (Manual Feed). Even if I could push an installation of the printer with a script, how would I push down these settings?

For my script to install the printer, I had something like this:
start /wait rundll32 printui.dll,PrintUIEntry /if /b “printer_name” /f \\server1\Printers\4350\hpc4x50b.inf /r “IP_192.168.0.252” /m “HP LaserJet 4250 PCL 5e” /z

I’ve read that with Windows Server 2003 R2 there is a new print management tool but I’ve also heard it’s only for network/shared printers.
(Client machines are XP Pro)

posted by Ekim Neems to computers & internet (2 answers total)


The printer share with mount on login (through login script) is the only real way to do this. You can map directly to the IP, but it doesn’t download the drivers to the workstation, configure the default settings of the printer, or allow you to manage who has access through permissions (which is what you’re doing when you mount the share on the workstation).

That is how print admins manage all those complex driver settings for individual users, manage permissions on the printers, and update/downgrade drivers when there are issues.

If you’re still worried about installing the printers every time, through VBScript (or more specifically, WSH) you can enumerate the printers on the workstation and then determine if you want to install the printers. Here’s some sample script:
‘ Declare variables.
Dim intInstallPrinterFlag, strPrinterName, WshNetwork, clPrinters

‘ This flag will determine if you want to remove the installed printer first (updated drivers, new settings etc.)
intInstallPrinterFlag = 0

‘ First argument to the script. Should be in the format \\SERVERNAME\PRINTERSHARENAME strPrinterName = WScript.Arguments.Item(0)

‘ Declare the WSH object to handle network printer connections.
Set WshNetwork = Script.CreateObject(“WScript.Network”)

‘ Enumerate all the printers on the local workstation.
Set clPrinters = WshNetwork.EnumPrinterConnections

‘ Install the printer, there are none on the workstation.
If clPrinters.Count <> 0 Then
WshNetwork.AddWindowsPrinterConnection strPrinterName

WshNetwork.SetDefaultPrinter strPrinterName

‘ Now, if there is a printer installed and the install printer flag is set, then remove the existing printer and re-install it.
ElseIf clPrinters.Count > 0 And intInstallPrinterFlag = 1 Then
WshNetwork.RemovePrinterConnection strPrinterName
WshNetwork.AddWindowsPrinterConnection strPrinterName
WshNetwork.SetDefaultPrinter strPrinterName
End If

Save the above in a .vbs file and add to the login script for a user.

This can obviously be built upon (for instance, the duplicate AddWindowsPrinterConnection calls could be wrapped up into a function or sub to prevent duplication.
Extra logic could be added to handle multiple printers on the workstations and how to assign the default printer etc.

If there are any bugs in the script I apologize. I wrote it quickly and from memory. You can find a lot of information about WSH at devguru.
posted by purephase at 12:03 PM on August 2, 2006


As for doing the settings: Open the printer’s Properties sheet, click the Advanced tab, then click the Printing Defaults button. This brings up a window that looks exactly like the usual Printing Preferences window except that the window title is “blah blah Printing Defaults” instead of “blah blah Printing Preferences”. Any settings you make here will become the defaults for any new installation of or connection to that printer.
posted by flabdablet at 9:09 PM on August 2, 2006

VBscript to get the system uptime from a group of computers

One of our corporate customers doubted that we had computers being left powered on overnight.  So I quickly wrote the script below to query each computer’s uptime.

The record uptime?  93 days!

One co-worker remarked that Windows 95 & 98 had a bug which caused it to crash after 49.7 days.  I’m amazed any Windows 95/98 system would make it to 49 days, in the first place.

Set objFSO = CreateObject("Scripting.FileSystemObject")
If not objFSO.FileExists("C:\computer_lists\uptime_check.txt") Then
    WScript.Quit(99)
End If

Set ObjStream = objFSO.OpenTextFile("C:\computer_lists\uptime_check.txt",1)

Do While Not ObjStream.AtEndOfStream
   strComputer = ObjStream.ReadLine
   ' strComputer shouldn't be blank, if it is, there is something wrong with the input file.

   If strComputer = ""    Then
      WScript.Quit
   End If

   If Not Reachable(strComputer) Then
      WScript.Sleep 100
   End If
   
   If Reachable(strComputer) Then
      wscript.Echo upTime(strComputer) 
   End If

Loop
WScript.Echo "Finished"
WScript.Quit

Function Reachable(strComputer)
   On Error Resume Next
  
   Dim wmiQuery, objWMIService, objPing, objStatus
   wmiQuery = "Select * From Win32_PingStatus Where Address = '" & strComputer & "'"
   Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
   Set objPing = objWMIService.ExecQuery(wmiQuery)
   For Each objStatus In objPing

     If IsNull(objStatus.StatusCode) Or objStatus.Statuscode<>0 Then
        Reachable = False 'if computer is unreachable, return false
      Else
         Reachable = True 'if computer is reachable, return true
      End If
   
   Next 
End Function

'Code pinched from here: http://www.visualbasicscript.com/Get-current-user-and-uptime-m32298.aspx
Function upTime(strComputer)
Dim objOS
Dim dtmBootup
Dim dtmLastBootupTime
Dim dtmSystemUptime
Dim colOperatingSystems
Dim objOperatingSystem
On error Resume Next
upTime = 0
objWMIServices = "winmgmts:{impersonationLevel=impersonate}!//"& strComputer &""
Set objUserSet = GetObject( objWMIServices ).InstancesOf ("Win32_ComputerSystem")
Set colOperatingSystems = GetObject( objWMIServices ).InstancesOf ("Win32_OperatingSystem")

For Each objOS in colOperatingSystems
   dtmBootup = objOS.LastBootUpTime
   dtmLastBootupTime = WMIDateStringToDate(dtmBootup)
   dtmSystemUptime = "Last system reboot occurred for "  & strComputer & " is:" & DateDiff("h", dtmLastBootUpTime, Now) & " hours, " & Int(DateDiff("n", dtmLastBootUpTime, Now)/60) & " minutes, " & DateDiff("n", dtmLastBootUpTime, Now) Mod 60 & " seconds ago." 
   'dtmSystemUptime = strComputer & "," & DateDiff("h", dtmLastBootUpTime, Now) & "," & Int(DateDiff("n", dtmLastBootUpTime, Now)/60) & "," & DateDiff("n", dtmLastBootUpTime, Now) Mod 60 & ",end" 
If Err.Number =0 Then
upTime = dtmSystemUptime
Else
upTime = "Last reboot time cannot be retrieved from " & strComputer
End If
Err.Clear
Next
End Function

Function WMIDateStringToDate(dtmBootup)
   WMIDateStringToDate = CDate(Mid(dtmBootup, 5, 2) & "/" & _
        Mid(dtmBootup, 7, 2) & "/" & Left(dtmBootup, 4) _
        & " " & Mid (dtmBootup, 9, 2) & ":" & _
        Mid(dtmBootup, 11, 2) & ":" & Mid(dtmBootup, _
        13, 2))
End Function