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
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
   End If

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

WScript.Echo "Finished"

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
         Reachable = True 'if computer is reachable, return true
      End If
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
upTime = "Last reboot time cannot be retrieved from " & strComputer
End If
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

How to uninstall all versions of a particular piece of software.

We’re in the mist of doing an Adobe Acrobat Reader upgrade, and one of my co-workers found this place of code on the net.  The vbscript loops though WMI’s Win32_Product, calling the Uninstall method for each matching product name.
(in the script below, obviously we’re uninstalling Acrobat Reader).

Note: the software must have been installed using Microsoft MSI, for it to be uninstallable via this script.

Function Uninstall_Any()
    On Error Resume Next
    Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    Set colSoftware = objWMIService.ExecQuery("Select * from Win32_Product Where Name LIKE 'Adobe Reader%'")
    For Each objSoftware In colSoftware
End Function

‘Program starts here
Call Uninstall_Any()

Microsoft MSDN: WMI Tasks – Computer Software

How to set file/folder permissions on a remote computer.

Say, for example, you want to grant permissions on a remote folder.  One way to do this is use the Microsoft XCACLS.VBS utility.

Go read the Microsoft article I linked to.

One important tip:
/E allows you to retain the existing ACLs for other users.  If you forget to use the /E switch, you end up deleting the existing ACLs.

A sample execution:

C:\Scripts>cscript xcacls.vbs \\POWERGIRL\c$\localdata\temp /e /g POWERGIRL\Users:M

Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.

Starting XCACLS.VBS (Version: 5.2) Script at 4/11/10 3:11:59 PM

Startup directory:

Arguments Used:
        Filename = "\\POWERGIRL\c$\localdata\temp"
        /E (Edit ACL leaving other users intact)
        /G (Grant rights)

- Changing /G user/group: "POWEGIRL\Users" to "BUILTIN\Users"

Directory: \\POWERGIRL\c$localdata\Temp
Granting NTFS rights (M access for This Folder, Subfolders and Files) for "BUILT
Completed successfully.

Operation Complete
Elapsed Time: 6.625 seconds.

Ending Script at 4/11/10 3:12:06 PM


SMS Client Health Startup Script, and logging

sms2003_150x70The pilot of the SMS Client Health Startup Script has gone reasonably well.  So far the script has found about 30 broken PCs.

But the logging has been a bit hit and miss.  We write to a central log server, and we’re been getting quite a few “Path not found” errors.

Now, I think it’s either one of two things.

  1. The DNS server lying to me, and/or
  2. The section of the code which does the central log file creation.

For the lying DNS server, I’ve added a Ping of the central log server, so I’m hoping that’ll help.

Now the section of the SMS Client Health Script which checks that the log directory existed, was doing this:
If objFSO.FolderExists(LogPath) Then
Set objFolder = objFSO.GetFolder(LogPath)
Set objFolder = objFSO.CreateFolder(LogPath)
End If

"Set objFolder = objFSO.GetFolder(LogPath)" – WTF???

The script is trying to get a folder listing of ALL the log files that the central log server has.  I don’t understand why.  The code doesn’t do anything with the information.  Unless it’s some perverted way of checking that the log directory is online.

My guess is that the GetFolder call is timing out, and causing the later log file write call to fail.

So, I replaced the offending code with:
If Not objFSO.FolderExists(LogPath) Then
COLLECTMSG "Set_LOGFOLDER","ERROR","GoodLogPath not found " & err & " " & err.description
End If

We’ll see how it goes over the next week.

My Corporate DNS Server was lying to me, often.

I was trying to copy some files to a group tonormaldnsoperation a computers.  What I’d do is check if the computer is alive first, by doing a network ping.  If the computer answered the Ping, then the computer was alive, so I would then copy files to it.

It mostly worked, except when it didn’t.  Here is the clue which lead me to the answer:

C:\>ping alfred

Pinging ALFRED [] with 32 bytes of data:
Reply from bytes=32 time<1ms TTL=128

C:\>ping powergirl

Pinging POWERGIRL [] with 32 bytes of data:
Reply from bytes=32 time<1ms TTL=128


The DNS server was returning the same IP address for two different computers.  As POWERGIRL was powered on, it answered the Ping request for ALFRED.  When I tried to copy to ALFRED, it failed as the file copy was trying to verify that ALFRED was at, and it wasn’t.

The cause?  The DNS server was retaining IP addresses for 24 hours.  Sometimes a computer’s IP will change more often than that.  So the DNS server ended up with 2 or more DNS computer records with the same IP.
The fix?  Tell the DNS server to only retain DNS records for 2 hours.  (for the more technically minded, we adjusted the Time To Live value).

How I originally detected the DNS server was lying to me?
I pinged the computer (ALFRED) to get it’s IP address (
I then asked IP address what it’s computer name was.  In the example above, it was actually POWERGIRL.

The code to do that is in the Reachable function, below:

Continue reading

SMS Client Health Startup Script and Cross Domain logons.

PowerShell logoI’m in the process of piloting the DudeWorks/1E/Shaun Cassells SMS SCCM Client Health script.  I’ve set it up so it runs in the user’s logon script.  But there’s a bug with that.

  • User FRED is the CONTOSO domain
  • User FRED logons onto a computer which is part of the TOLERDO domain.
  • SMS Client Health script tries to repair the TOLERDO domain computer
    (not a good thing).

The fix?
Update the script to check to see if the user is logging onto a computer in their own domain.  The scripts changes were:

  1. CONFIG Settings section
    Added sThisDomain="CONTOSO"
  2. In the START section, just after the Create Log file call
    ' Check Domain to ensure a CONTOSO user isn't logging onto a TOLERDO PC.
  3. And finally, added this subroutine:
    Sub Sub_Check_Domain
         If InStr(objSysInfo.ComputerName, LCase(sThisDomain)) = 0 Then
    COLLECTMSG "Sub_Check_Domain","","A " + sThisDomain + " user has logged onto a non " + sThisDomain + " PC"

            COLLECTMSG "Sub_Check_Domain","","objSysInfo.ComputerName is set to: " + objSysInfo.ComputerName
             CLIENTSTATE = 99
             StrERRType = StrERRType & "WRONG_Domain_"
             COLLECTMSG "Sub_Check_Domain","","PC is in domain:" + sThisDomain
        End If
    End Sub

Bookmark and Share

WMI has stopped working on some remote computers.

And I don’t know why.  My strong guess is that it is related to one of the 10+ recent Microsoft security patches I’ve deployed to the fleet.  (I’m looking at you .Net Framework 2.0)

The fix so far? Rebuild WMI and repair the SMS client.

I do this by:

  • copying wmirebuild.bat and runrepair.vbs to the target computer.
    the purpose of runrepair.vbs is to HIDE the running of wmirebuild.bat, so the end user doesn’t see it.
  • remotely run runrepair.vbs via a PSEXEC command.

Yes, it’s a bit kludgey.  It’s a fast fix.

Dim objFSO,objNet,objShell,drive_status
Dim objWMIService, objProcess, colProcess, strComputer, strProcessKill
Dim sWbemPath,sSystemRoot

On Error Resume Next

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")

sSystemRoot = objShell.ExpandEnvironmentStrings("%systemroot%")

'check that system32 directory exists.

If Not objFSO.FolderExists(sSystemRoot) Then
End If

If Not objFSO.FileExists("c:\temp\wmirebuild.bat") Then
objShell.Run "c:\temp\wmirebuild.bat", 0, True
End If

Set objShell = Nothing
Set objFSO = Nothing


net stop ccmexec
net stop VMAuthdService
net stop winmgmt
cd %systemroot%\system32\wbem
rem rd /S /Q repository

regsvr32 /s %systemroot%\system32\scecli.dll
regsvr32 /s %systemroot%\system32\userenv.dll

mofcomp cimwin32.mof
mofcomp cimwin32.mfl
mofcomp rsop.mof
mofcomp rsop.mfl

for /f %%s in ('dir /b /s *.dll') do regsvr32 /s %%s
for /f %%s in ('dir /b *.mof') do mofcomp %%s
for /f %%s in ('dir /b *.mfl') do mofcomp %%s

net start winmgmt
net start VMAuthdService
net start ccmexec

echo done >>c:\temp\done.txt

Then kick it all off with
psexec \\COMPNAME -s c:\winnt\system32\cscript.exe //B c:\temp\runrepair.vbs

Update: the wmirebuild.bat script I found here: How Do I Rebuild A Corrupt WMI Repository?

The next post in this series is, “Grumble grumble – revisiting the WMI fix”.

Bookmark and Share

The WMI Overflow Error with GetObject

VBscript_bookThat’s what I thought at least.  Consider this snippet of code from the Delete computers from SMS VBscript:

Set objResource = GetObject( "WinMgmts:\\" & strSMSServer & _ 
                  "\root\SMS\site_" & strSMSSiteCode & _
                  ":SMS_R_System.ResourceID=" & cint(intResourceID))

This statement would fail about 50% of the time.  The error code was
Err.Number: 6
Err.Description: Overflow

Google was not my friend.  It was down to debugging the code line by line.  Now I’d like to say I spotted it immediately, but it took me two hours of looking over the script to finally work out what was going on.

The number of computers in our SMS database exceeds 32768, which is the limit for an integer variable.  The cint function was reporting an Overflow error as the value of intResourceID was over 32768.  Foiled by a bug in 4 year old code from Microsoft.

The answer was to change cint(intResourceID)) to  cstr(intResourceID)) .

Bookmark and Share

VBscript: "Pinging" a computer.

VBscript_book If you looked at yesterday’s post, carefully I’ll admit as WordPress loves to mangle the display of <CODE>; you might have noticed that I check to see if a computer is on the network, before I try and do something to it.
The reason is simple.
Checking that a computer is on the network, is faster, than just trying to copy a file to it, and checking for a failure.  I don’t know why, but I suspect a “Ping” is faster than a file operation.
Here is some (crude) code I wrote the other day, which shows a ping in use:

Continue reading

VBscript to uninstall a Microsoft security patch

This is a small script I wrote many years ago to uninstall security patches.  I customise it when I need it.

In this example, I’ve customised it for MS09-032, but the principles apply to most patches.

Note that I don’t check for admin rights, I’m assuming that I’ll be using a desktop software deployment tool to run it on the user’s desktop.

' VBscript to roll back a security patch, in this case MS09-032.
‘ Dale Robinson – 2006 –> 2009

Option Explicit
On Error Resume Next

Dim bUninstallFileExists, OSSystemRoot, objWSHShell, objEnv, sMS09032Installed, sPatchCmd,sUninstallExe

Set objWSHShell = WScript.CreateObject("WScript.Shell")
Set objEnv = objWSHShell.Environment("Process")

' MS09-032 sets this registry key if it's installed.

Continue reading