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 --> http://live.sysinternals.com/

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! ***
C
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"
Else
D
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
Wscript.Quit
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
s
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
Loop
objTextFile.Close
Set objFSO = Nothing
DebugPrint "info", runcount & " lines were processed"
DebugPrint "info", "script process completed"


Here's the LIST text file contents...

; COMPUTERLIST.TXT
; Replace/Add/Delete to use actual computer names
Computer1
Computer2
Computer3

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

@ECHO OFF
REM -----------------------------------------------------
REM 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
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 -----------------------------------------------------
REM EXAMPLE: Import Registry Data File into local registry
REM
REM reg import \\server\path\my_reg_data_file.reg
REM
REM -----------------------------------------------------
REM EXAMPLE: Delete registry key and values
REM
REM reg delete HKLM\Software\TestKey /va /f
REM
REM -----------------------------------------------------
REM EXAMPLE: Stop and Start a Local Service
REM
REM sc stop "My Service Name"
REM sc start "My Service Name"
REM
REM -----------------------------------------------------
REM EXAMPLE: Force Automatic Updates detection cycle
REM
REM wuauclt /detectnow
REM
REM -----------------------------------------------------
REM EXAMPLE: Change / Set local Administrator password
REM
REM net user Administrator P@ssw0rD$123
REM
REM -----------------------------------------------------
REM EXAMPLE: Add Domain group into Local Group
REM
REM net localgroup Administrators /ADD "Desktop Admins" /DOMAIN
REM
REM -----------------------------------------------------
REM EXAMPLE: Download and register a DLL file
REM
REM copy \\servername\path\myfile.dll c:\windows\system32\
REM cd /d c:\windows\system32
REM regsvr32 /s myfile.dll
REM
REM -----------------------------------------------------
REM EXAMPLE: Uninstall a software application
REM
REM msiexec /x {SOME_REALLY_LONG_GUID} /norestart /qb! /Lvaio! c:\windows\temp\mylog.log
REM
REM -----------------------------------------------------
REM EXAMPLE: Force Group Policy Refresh
REM
REM gpupdate /force /wait:1000
REM
REM -----------------------------------------------------
REM EXAMPLE: Restart computer
REM
REM shutdown -r -f -t 30 -c "remote restart requested"
REM
REM -----------------------------------------------------
REM EXAMPLE: Shutdown computer
REM
REM shutdown -s -f -t 0 -c "remote shutdown requested"
REM
REM -----------------------------------------------------
REM EXAMPLE: Flush local DNS resolver cache
REM
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.

6 comments:

Jose Guia said...

I tried something similar sometime back and never got it to work .. something about the permissions it was executed under, even though I was specifying the admin account .. it was actually for deploying AutoCAD Updates.

Anyhow - tried yours and it still refuses to run for me - no error - just doesn't run.

Thanks for sharing.

skatterbrainz said...

Some application executables require interactive (the "-i" option for psexec) while others do not. Some require system account execution (-s), and some just will not execute at all unless there is a user context envelop (an active session). I'm dealing with one of those at work right now. A crappy little app that will only install remotely if a user is logged on (they can also lock the computer, that still works). If nobody is logged on, it does just what you're experiencing: nothing.

One other thing you can try is replacing the BAT file as the delivery package in the Psexec string with your actual EXE or MSI, etc. Have it copy that over (using -c -f) and run it locally.

Jose Guia said...

Thanks for replying, since I had already addressed it sometime back using a login script I'm not really in a hurry to get this going.

But thanks for the post and the reply, I'll give it another shot soon enough.

A little more info on my app - I wrote a VB.net app that actually scanned for all machines on your network and loaded them into a grid, it then ran the psexec command from the app.

It was really slick from an interface perspective, and I could get somethings to run, just not all apps I tried.

skatterbrainz said...

Sounds cool. If you're using VB.NET though, you can invoke remote processes without having to shell out to invoke PsExec. As for login scripts, they work great for queries and doing user-context tasks, but if the users don't have elevated rights they can't do things like install apps, or modify system-wide settings, etc. Start-up scripts work better for that and are pretty easy to setup and deploy as well. Thanks for the feedback and interest!

Jose Guia said...

Users have local admin rights, so we were covered there.

the next step in the app would have been remote processing, just never got around to it.

btw - i noticed in your visual lisp book you mentioned Randall Rath - I take it you spent some time on vbdesign.net?

What was your screen name? I spent a lot of time there as well.

skatterbrainz said...

I spent a little time on Randall's site way back when. I can't recall my screen name, but it was probably "dstein". I left the AutoCAD world for about 8 years, but am now back in a different capacity: IT management. I work in a large corp IT dept that packages, deploys and supports Autodesk apps on about 5,000 clients over a WAN. It's not as fun but it has its rewards.