Wednesday, April 8, 2009

Scripting with Glue and Tape

Well, I suppose that I’m back.
To me, writing script code is like assembling Lego blocks. For anyone that spent any time with Lego's, you will understand what I mean. I'm not talking about the brain-dead pre-arranged "kits" for building the helicopter, fire station or school house. I'm talking about the huge Lego construction kits, which were simplistic trays of various groupings of block types and colors. The intent was to let YOUR imagination run wild, rather than spoon-feed you with "here it is, build this thing right here."

In case you're wondering: No. That is not a picture of me. It's just a picture I found from a Google image search on the phrase "Lego blocks". Cute, huh? (Note: I would suggest that the unfinished arm signifies a metaphor for budget shortfalls with respect to software development projects. Clever? I didn't think so).

Scripting is scripting, in some respects. Be it PowerShell, VBScript, JavaScript, KiXtart, BAT, Korn, Bourne, Bash, or whatever. Even AutoLISP qualifies in most respects. Each one has a dictionary of "words" and phrases, which you can assemble to build whatever you need (or want). There are constraints obviously, but there are constraints with all things, even Lego blocks. The beauty, and the creativity, come with bending and stretching those constraints to their limit while pursuing some ideal or goal. Every script I've ever seen that was worth a crap was the result of trying to solve a real problem or task, rather than aimless "I wonder what I can do with this..." meandering. The same is true for software programming in general. I'm not saying that letting the mind wander is bad, it isn't. I'm only saying that a framework has to exist which is derived or driven from a need to solve a challenge.
In my case, I needed to QUICKLY solve a problem: Initiate an unknown collection of tasks on an unknown collection of remote Windows computers, from a single location.
There are many, MANY, examples of how this can be illustrated. Changing local Administrator passwords, modifying registry keys and values, stopping and starting services, installing or removing software applications, shutting down or restarting, modifying local file and folder permissions, and the list goes on and on.
Here is just one way, out of possibly thousands of ways.
Ingredients: Microsoft/Sysinternals' PSEXEC.exe, a BAT file, a VBS file, a cup of caffeinated liquid, some background music, some free time, happy thoughts, clean underwear.

PSEXEC.exe. If you haven't heard of PsExec.exe, oh boy. Stop right now and defer this reading until you've had time to ingest the general scope of "PSTools". You can learn all about it at the Microsoft Sysinternals web site (click here) and download any/all of the tools here -->

The BAT file. This is a generic wrapper or "package" which will essentially "pushed" to each remote computer on your list (or on your network, your choice, keep reading). You can use and reuse this as often or however you wish. Each time you run the root script (VBS in this case) it will re-deploy the BAT file and execute it on the remote computer. You can fork this (careful, let's keep this clean, ok?) into multiple root scripts that can each deploy a different BAT file and, well, you can multiply and diversify and magnify and exemplify... til your brain cells, um, fry. Ok, just kidding.

The LIST file. This is a ordinary ASCII text file with the NetBIOS name of each computer listed on its own row. To make it easier to manage, the VBS script will ignore lines that start with a semi-colon ";". This makes it easy for you to reuse the LIST file and simply prefix a semi-colon to "comment" it out and have it ignored by the Root script.

The VBS file. This is the "root script" I mentioned above. It is what you run on YOUR computer, which invokes PSEXEC.exe to deploy and execute the BAT file on each computer named in your ComputerList.txt file.
Place all of these into the same folder on your local hard drive. In this scenario, I chose "c:\scripts" because I'm really not that inventive and couldn't think of a more clever name.
The meat and potatoes: Here's the VBS (VBScript) file contents.
' Filename..: batch_remote.vbs
' Author....: David M. Stein
' Date......: 04/02/09
' Purpose...: execute batch script on list of remote computers
' using psexec.exe (sysinternals)
Option Explicit
Const psexec = "c:\scripts\psexec.exe"
Const package = "c:\scripts\runme.bat"
Const inputFile = "c:\scripts\computerlist.txt"
' the following line is expanded later in script
' replace /c with /k if you want to debug each computer step while running
Const cmd = "cmd.exe /c PSEXEC \\COMPUTER -i -c -f PKG"
Const TestMode = False
Const Verbose = True
' *** do NOT modify any code below this point in this file! ***
onst ForReading = 1
Const ForWriting = 2
Dim objFSO, objFile, ln, objTextFile, strLine
Dim objShell, cmdstr, runcmd, runcount
Sub DebugPrint(p, s)
If Verbose Then
WScript.Echo Now & vbTab & p & vbTab & s
End If
End Sub
runcount = 0
DebugPrint "info", "initializing script process"
If TestMode = True Then
DebugPrint "info", "runtime mode is TEST MODE"
ebugPrint "info", "runtime mode is PRODUCTION"
End If
DebugPrint "info", "opening input file"
Set objFSO = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
Set objTextFile = objFSO.OpenTextFile(inputFile, ForReading)
If Err.Number <> 0 Then
DebugPrint "error", "unable to open input file"
Set objFSO = Nothing
End If
DebugPrint "info", "processing data from input file"
Set objShell = CreateObject("Wscript.Shell")
cmdstr = Replace(Replace(cmd, "PSEXEC", psexec), "PKG", package)
DebugPrint "info", "runtime script = " & cmdstr
Do Until objTextFile.AtEndOfStream
trLine = Trim(objTextFile.Readline)
If strLine <> "" And Left(strLine, 1) <> ";" Then
DebugPrint "info", "connecting to " & strLine
runcmd = Replace(cmdstr, "COMPUTER", strLine)
If TestMode <> True Then
objShell.Run runcmd, 1, True
End If
runcount = runcount + 1
End If
Set objFSO = Nothing
DebugPrint "info", runcount & " lines were processed"
DebugPrint "info", "script process completed"

Here's the LIST text file contents...

; Replace/Add/Delete to use actual computer names

Here's the BAT file contents. Save as "RUNME.BAT"...

REM -----------------------------------------------------
REM This script is called by BATCH_REMOTE.VBS and is
REM copied to each remote computer named in a separate
REM list file and executed on the remote computer using
REM Sysinternals' PSEXEC utility
REM -----------------------------------------------------
REM Below are some example tasks to illustrate how this
REM might be used
REM -----------------------------------------------------
REM EXAMPLE: Create a registry key and some values
REM reg add HKLM\Software\TestKey /v TestValue1 /t REG_SZ /d "This is a String" /f
REM reg add HKLM\Software\TestKey /v TestValue2 /t REG_EXPAND_SZ /d ^%systemroot%^ /f
REM reg add HKLM\Software\TestKey /v TestValue3 /t REG_DWORD /d 1 /f
REM -----------------------------------------------------
REM EXAMPLE: Import Registry Data File into local registry
REM reg import \\server\path\my_reg_data_file.reg
REM -----------------------------------------------------
REM EXAMPLE: Delete registry key and values
REM reg delete HKLM\Software\TestKey /va /f
REM -----------------------------------------------------
REM EXAMPLE: Stop and Start a Local Service
REM sc stop "My Service Name"
REM sc start "My Service Name"
REM -----------------------------------------------------
REM EXAMPLE: Force Automatic Updates detection cycle
REM wuauclt /detectnow
REM -----------------------------------------------------
REM EXAMPLE: Change / Set local Administrator password
REM net user Administrator P@ssw0rD$123
REM -----------------------------------------------------
REM EXAMPLE: Add Domain group into Local Group
REM net localgroup Administrators /ADD "Desktop Admins" /DOMAIN
REM -----------------------------------------------------
REM EXAMPLE: Download and register a DLL file
REM copy \\servername\path\myfile.dll c:\windows\system32\
REM cd /d c:\windows\system32
REM regsvr32 /s myfile.dll
REM -----------------------------------------------------
REM EXAMPLE: Uninstall a software application
REM msiexec /x {SOME_REALLY_LONG_GUID} /norestart /qb! /Lvaio! c:\windows\temp\mylog.log
REM -----------------------------------------------------
REM EXAMPLE: Force Group Policy Refresh
REM gpupdate /force /wait:1000
REM -----------------------------------------------------
REM EXAMPLE: Restart computer
REM shutdown -r -f -t 30 -c "remote restart requested"
REM -----------------------------------------------------
REM EXAMPLE: Shutdown computer
REM shutdown -s -f -t 0 -c "remote shutdown requested"
REM -----------------------------------------------------
REM EXAMPLE: Flush local DNS resolver cache
REM ipconfig /flushdns

To help illustrate some tasks you might want to invoke on remote computers, I included a few in the above example. Remove the "REM" prefix to enable a given line to be executed, but be SURE to edit it to suit your needs before using it. Also, ALWAYS ALWAYS ALWAYS test this on computers in an isolated environment to avoid making mistakes in a production environment.
How to use it?
  1. Like I said, copy/save the files into your chosen folder (e.g. "c:\scripts").
  2. Open a CMD console (aka "DOS window" for you Windows 95 nut jobs).
  3. Change to the directory where your files are stored (e.g. "c:\scripts").
  4. Use CSCRIPT to run the root script as follows:
    C:\Scripts\> cscript batch_remote.vbs
  5. That's pretty much "it".
You should see it fire off and a separate CMD window will briefly open and close for each computer entered into your list file. If you think you have a bogus command in the BAT file, change the "cmd /c" entry to "cmd /k" in the root script (VBS) file to keep each CMD window open after it is finished running on each computer. This will act like a "pause" and let you see what's going on with each computer. As you close the CMD window, the next will pop-up and continue until the list is finished.
As always, feel free to use, adapt, modify, whatever. This is made available for informational purposes only and is not intended to be used “as-is” in a production or business-critical environment without user testing. Author assumes no responsibility, liability, eligibility, mobility, culpability, flexibility, gullibility or hill-billity for any use, misuse, consequential or inconsequential damages arising from any direct or indirect or misdirected use in any situation, scenario, environment, paradigm, universe, metaverse or omniverse. Not even during the third verse. In other words: USE THIS STUFF AT YOUR OWN RISK.
I hope this is useful/helpful to someone out there. If so, great. If not, well, never mind. Drop me a line if you have any questions or comments on this. I don't mind helping but I may not be able to respond immediately due to having a crazy schedule.
Post a Comment