Anda di halaman 1dari 16

$14.

95
A Penton Publication

May 2003

Use Csvde, Scriptomatic, and Excel to create easy-to-read reports


AD and WMI Reporting
by Alistair G. Lowe-Norris
When you need to extract Active Directory (AD) or Windows Management Instrumen-

tation (WMI) data, several tools are at your disposal. The Csvde command-line utility

(csvde.exe) and the Scriptomatic tool (scriptomatic.exe) are both extremely useful in this

respect. To get the most out of this data, you can use Microsoft Excel to format the out-

put into clean and easy-to-read reports. Here are some scripts to show you how.

AD Reporting information, the best method for making that


Csvde, which resides in %systemroot%\ data into legible reports (with formatted
system32 after you install Windows Server columns, headings, and so forth) is to use
2003 or Windows 2000 Server, lets you import Excel. You can import the .csv file into Excel,
and export AD data to a comma-separated then save the report as an Excel (.xls) file. Ive
value (CSV) file according to attribute and created a VBScript script, which Listing 1,
In This Issue object filters. For example, to extract all user- page 2, shows, that automates this process
object data to a .csv file called C:\report.csv, without visibly opening Excel. The script cre-
1 AD and WMI open a command window and type the fol- ates an Excel Application object, then uses the
Reporting lowing command: Workbooks::Open method to open the .csv
file that I used Csvde to create (i.e., C:\
csvde -f c:\report.csv -v -d
6 Rem dc=mycorp,dc=com
report.csv). The script then passes two
parameters to the ActiveWorkbook::SaveAs
-r (objectClass=User) -p SubTree
9 Progressive Perl for method. The first parameter specifies the new
This command directs Csvde to extract the file to be created (i.e., C:\report.xls), and the
Windows
information to the specified file (-f), use ver- second parameter defines the file format (i.e.,
bose mode onscreen while running (-v), start xlNormal). Note that running this script on a
13 Real-World Shell
from mycorp.coms root (-d), look for the Win2K Server that doesnt run Excel will result
Scripting specified objects (i.e., User objects) only (-r), in an error.
and scan the whole tree (-p). If you want to
15 Reader to Reader extract the user objects ADsPath property System Reporting
only, add the following parameter: If youre considering extracting system data for
reporting purposes, investigate Scriptomatic,
-l ADsPath
which automates the creation of complex
For more information about Csvdes syntax, WMI reporting scripts. (For information about
use the tools -? parameter, type the csvde com- WMI scripts and other reporting topics, see
mand with no parameters, or go to http:// Related Reading, page 2.) You can download
www.microsoft.com/technet/prodtechnol/ this free tool from http://www.microsoft
windowsnetserver/proddocs/entserver/csvde .com/technet/treeview/default.asp?url=/tech
.asp. net/scriptcenter/wmimatic.asp (at the time of
After youve used Csvde to extract AD this writing, the tool is also scheduled to be
AD and WMI Reporting
and Version is the OS build version
LISTING 1: Code to Convert a .csv File to an .xls File numberfor example, 5.1.2600.) Ive
Option Explicit also added code that creates an Excel
Const xlNormal = &HFFFFEFD1 Application object, adds a workbook
Dim appExcel to that object, and names the first
Set appExcel = CreateObject(Excel.Application) worksheet OS Data. The code then
places column headings (i.e., PC Name,
appExcel.Workbooks.Open C:\report.csv
appExcel.ActiveWorkbook.SaveAs C:\report.xls, xlNormal Caption, and Version) in the first three
appExcel.Workbooks.Close
cells in Row 1 (i.e., A1, B1, and C1). To
appExcel.Quit make the headings stand out, the code
sets those cells background color to
included in the Windows 2003 resource your organization.) For example, when dark blue and sets the text to a bold
kit). Scriptomatic works on Windows you select Win32_OperatingSystem white font. (To discover how I deter-
2003, Windows XP, and Win2K systems from the list, the tool instantly creates mined the code to use to automate
and on Windows NT 4.0 Service Pack 6 a script that reports every item within these Excel tasks, see the sidebar For-
(SP6) and Windows 98 systems on the Win32_OperatingSystem class. If matting the Reports, page 4.)
which youve installed Microsoft Inter- you then click Run, Scriptomatic uses The next three lines of code are
net Explorer (IE) 5.0, WMI, and Win- cscript.exe to execute the script, open- straight from the Scriptomatic script
dows Script Host (WSH) 5.6. You can ing a command window and placing and extract data from the Win32_Oper-
use the tool to generate basic WMI the results into that command win- atingSystem class into a collection
scripts, which you can then modify to dow. You can modify the created script called colItems. Then, to populate the
be more versatile. to export the results into Excel. spreadsheet, the code sets a row index
2 After you download Scriptomatic, Listing 2 shows the Scriptomatic- marker (which starts at Row 2, after my
run scriptomatic.hta to open the tool. generated Win32_OperatingSystem heading row) and enters a loop through
The primary screen contains a drop- class script, which Ive modified in sev- the collection. This loop adds data into
down list of WMI classes for you to eral ways. First, for the sake of brevity, the three columns, using the row index
select from. When you select a class, Ive shortened the script so that it to target the correct cell each time. At
the tool automatically returns a script extracts only the CSName, Caption, and the end of the loop, the code incre-
that will report every item of that class Version properties. (On an XP system, ments the row index marker. (Using a
on the local system. (The scripts always CSName is the clients short name, For EachNext loop through colItems
point to the local PC, but you can mod- Caption is some text along the lines of when only one item exists in the col-
ify the scripts to run on other PCs in Microsoft Windows XP Professional, lection might seem silly, but this script
is simply a generic example of how to
import your data into Excel. The advan-
RELATED READING tage of the Scriptomatic scripts is that
they print every item for every returned
You can obtain the following article from Windows & .NET Magazines Web site at http://www result. Depending on the WMI class you
.winnetmag.com. select, those items can become quite
numerous. You can modify the sample
ALISTAIR G. LOWE-NORRIS
script in Listing 2 as necessary to start
Generating Deployment Reports, March 2000, InstantDoc ID 8054 each new item on a new row.)
After all the data is in the spread-
You can obtain the following articles from the Windows Scripting Solutions Web site at
http://www.winscriptingsolutions.com. sheet, the code selects all the cells in
the worksheet and autofits the width
ALISTAIR G. LOWE-NORRIS of the columns and the rows to make
Scripting Solutions with WSH and COM: Creating Simple and Useful Scripts with WMI, August the report easier to read. Last, the code
2000, InstantDoc ID 9173
Scripting Solutions with WSH and COM: Simple Uses of WMI, July 2000, InstantDoc ID 8985
OBTAINING THE CODE
Graphing Windows 2000 User Logons with Excel 2000, May 2000, InstantDoc ID 8601
Listings are available for down-
Using Excel Objects to Manipulate a Spreadsheet, April 2000, InstantDoc ID 8387 loading from the Windows Script-
Using WSH with ADSI to Create Excel Spreadsheets for Debugging, April 2000, InstantDoc ID 8385 ing Solutions Web site at http://
www.winscriptingsolutions.com.
Automating Excel to Create Trend Charts, March 2000, InstantDoc ID 8186

Windows Scripting Solutions MAY 2003


LISTING 2: Code to Report the Current PC OS to an
.xls File
makes the Excel spreadsheet visible. I
could have written the script to make Option Explicit
the spreadsheet visible earlier so that I Const xlSolid = 1
could watch the script populate the Dim appExcel, strComputer, objWMIService, colItems, objItem, _
spreadsheet, but doing so introduces intRowIndex
the risk of the user accidentally click- Reference the Excel Application object, open the workbook,
ing the spreadsheet while the script is and rename the worksheet.
Set appExcel = CreateObject(Excel.Application)
workingan action that would inter-
appExcel.Workbooks.Add
rupt the script and cause it to abort appExcel.Sheets(Sheet1).Name = OS Data
with an error. If you never intend to appExcel.Sheets(OS Data).Select
show a systems user the data youre Set up the OS data sheet and print the column headers.
retrieving from that system, you can appExcel.Cells(1,1).Value=PC Name
simply leave out the code that makes appExcel.Cells(1,2).Value=Caption
appExcel.Cells(1,3).Value=Version
Excel visible and let the entire process
run in the background. Format the column headers.
If you want to save the Excel spread- appExcel.Range(A1:C1).Select
appExcel.Selection.Interior.ColorIndex = 5
sheet, then close Excel, simply add sev- appExcel.Selection.Interior.Pattern = xlSolid
eral lines to the end of the script. For appExcel.Selection.Font.ColorIndex = 2
example, to save the spreadsheet to the appExcel.Selection.Font.Bold = True
local system under the filename report Gather the data for the current PC.
.xls, use the following code: A strComputer = .
Set objWMIService = GetObject(winmgmts:\\ & strComputer & _
\root\cimv2) 3
appExcel.ActiveWorkbook.SaveAs _
Set colItems = objWMIService.ExecQuery(Select * from & _
C:\report.xls Win32_OperatingSystem,,48)
appExcel.Workbooks.Close
Starting at Row 2, loop through the results and print on
appExcel.Quit separate rows.
intRowIndex = 2
To open a saved file in another For Each objItem in colItems
script and have this second script per- appExcel.Cells(intRowIndex,1).Value=objItem.CSName
form additional changes, save the file, appExcel.Cells(intRowIndex,2).Value=objItem.Caption
and close it, use the following code: appExcel.Cells(intRowIndex,3).Value=objItem.Version
intRowIndex = intRowIndex + 1
appExcel.Workbooks.Open _ Next
C:\report.xls Autofit the columns and rows.
Perform your changes appExcel.Cells.Select
appExcel.Cells.EntireColumn.AutoFit
appExcel.ActiveWorkbook.Save
appExcel.Cells.EntireRow.AutoFit
appExcel.Workbooks.Close
appExcel.Quit Make Excel visible.
appExcel.Visible=True

Remote PCs
How can you modify the provided script as an array and cycle through Csvde and a modification to the Scrip-
script to return information about one each one in turn. However, if you want tomatic-generated WMI Win32_Oper-
or more remote PCs? The answer lies to check more PCssay, all the PCs in atingSystem script. The script that
in the scripts strComputer line, which a specific OUyou need a way to Listing 3, page 5, shows uses Csvde to
specifies the target computer. By retrieve all those long DNS names extract the list of client PCs to a .csv
default, Scriptomatic sets this line of from AD. That requirement takes us file, opens that file in Excel (placing
code (at callout A in Listing 2) to ".", back to Csvde, which you can use to the information about each PC in a
indicating the current PC. To check a export the names of all the PCs in a new row), and reads the file line by
remote PC instead, change "." to the certain group, a certain OU, or the line. The script then uses WMI to try to
full DNS name of the remote PC entire network. connect to each PC and read the OS
"anotherpc.emea.mycorp.com", for Suppose that you want to find all data. The script writes the results to a
example. What about checking multi- the PCs listed in AD, then pull out the second Excel spreadsheet, indicating
ple PCs? If you want to check only a OS Caption and Version information for each PC whether the script could
dozen PCs, you can add them to the from each one. You can do so by using connect to the PC and providing the

www.winscriptingsolutions.com
AD and WMI Reporting

Formatting the Reports


How did I determine what lines to add to the VBScript to automate your Excel Application object). The result looks like the following:
Microsoft Excel? The trick is simple. Open Excel and select Tools,
appExcel.ActiveWorkbook.SaveAs _
Macro, Record New Macro from the menu bar, then perform the
C:\Book2.xls,xlNormal, _
operations that you want your script to automate (e.g., saving,
, , False, False
opening, autofitting columns and rows, coloring cells). Stop the
macro, then open and edit it to see the Visual Basic for Applica- As long as I define xlNormal as a constant in my VBScript, this
tions (VBA) code that it generates. line of code will work. But do I truly need all those parameters? To
You cant copy this code exactly. First, constants such as xlNor- determine how many parameters are mandatory and how many are
mal and xlSolid are already defined in VBA, so youll need to find optional, I return to the Object Browser, select the Application class
out their values and define them in your script. To do so, press F2 (for my Excel.Application object), and select the ActiveWorkbook
from within the Microsoft Visual Basic (VB) macro editor (or select member (the method that Im calling). The results pane indicates
View, Object Browser from the editors menu bar) to open the that the Application classs ActiveWorkbook property returns a
Object Browser. Select the <globals> class from the Classes pane, Workbook object, which contains the SaveAs method. Conse-
then scroll through the Members of <globals> pane until you find quently, I pick the Workbook class from the left-hand pane and
the constant you want to identify. When you select the constant, its select SaveAs (the method Im using on the active workbook) from
definition appears in the box at the bottom of the Object Browser. the right-hand pane. The results pane shows the SaveAs parame-
Second, VBA is more verbose than VBScript when a subroutine, ters. Now, I can click the question mark (?) in the Object Browser to
function, or method has parameters, so youll want to edit the code. bring up Help material about SaveAs. This material reveals that all
Consider the following example, which is the record of my use of the parameters are optionalwhich was already obvious in this
the SaveAs method: case by looking at the results pane because all the SaveAs param-
4 eters are enclosed in square brackets ([]). However, I recommend
ChDir C:\ that you still consult the Help material because it contains detailed
ActiveWorkbook.SaveAs Filename:= _ information. In this case, that material tells me that all I need is the
C:\Book2.xls, FileFormat:= _ name and path of the sheet (i.e., the first parameter). So, my final
xlNormal, Password:="", _ VBScript code ends up as follows:
WriteResPassword:="", _ appExcel.ActiveWorkbook.SaveAs C:\Book2.xls
ReadOnlyRecommended:=False, _
You might wonder why this line of code doesnt include the sec-
CreateBackup:=False
ond parameter, which defines the file format (i.e., xlNormal) as I
The macro shows that Excel popped up a Save dialog box, from specified in the script in Listing 1 in the main article. The reason is
which I navigated to the C: root drive, then saved the spreadsheet that the second parameter sets the default file type of the file that
as Book2. Because the SaveAs command includes the path, I can youre saving. In this example, Im already dealing with an Excel file,
delete the first line of this code. The second line defines six param- so I dont need to specify a different file type. In Listing 1, the script
eters, each beginning with the parameter name, followed by :=, read in a .csv file, so if I didnt specify xlNormal, the script would
then the parameter assignment. To convert this code into VBScript, save the file as a .csv file, which isnt what I want. For more detailed
I remove the parameter names and := and preface the line of instructions, see Generating Deployment Reports, March 2000,
code with appExcel. (or whatever variable name you're using for http://www.winnetmag.com, InstantDoc ID 8054.

OS data for those PCs to which it to search for computers and the prop- extra header row identifying each
could connect. erty to be returned (-l) to the PCs long returned item. We dont need this infor-
The script begins by declaring the DNS names. mation in our report, so the script
necessary constants and variables. After the script populates the .csv deletes the first column and first row.
Youll need to change the specified file, it creates an Excel Application Now the .xls file contains the data
PATH, CSV, XLS, and AD_ROOT vari- object, opens the .csv file, and saves the we need in one worksheet named
able values to ones that are appropri- file as an .xls file, closing the original Machines (or whatever you called your
ate for your environment. .csv file in the process. The script then .csv fileExcel opens the .csv file into
The script then uses the WSH Shell:: uses the standard FileSystemObject:: one worksheet and gives that work-
Run method to run Csvde, ensuring DeleteFile call to delete the .csv file sheet whatever name you gave the .csv
that the Run window remains hidden and the csv.log file. file). Because only one worksheet
and that the scripts execution pauses The .xls file has one sheet containing exists, the script can rename the sheet
until Csvde has finished running. The the PC data. This data includes an extra (with a more descriptive name such as
script uses the Csvde string that I first column containing each returned OS Details for All PCs, for example)
described earlier but sets the filter (-r) items distinguished name (DN) and an simply by selecting all worksheets and

Windows Scripting Solutions MAY 2003


LISTING 3: Code to Retrieve OS Data from All PCs
Option Explicit
renaming the active sheet. I also need On Error Resume Next
to create a heading row, so the script
Declare constants and variables.
inserts a new first row, shifting all the
Const FSO_FILE_FORCE_DELETE = TRUE
other cells down. The script then Const xlNormal = &HFFFFEFD1
inserts column headings into the Const xlSolid = 1
empty row and formats those cells, as Const xlShiftDown = &HFFFFEFE7
Const xlShiftUp = &HFFFFEFBE
I described for Listing 2.
Const xlToLeft = &HFFFFEFC1
Now were ready to begin adding Const SHELL_RUN_HIDE_WINDOW = 0
data, starting at Row 2. The script uses Const SHELL_RUN_WAIT_UNTIL_FINISHED = TRUE
a While loop to determine whether the Const PATH = C:\
Const CSV = Machines.csv
first cell in that row (i.e., cell A2) is Const CSV_LOG = CSV.LOG
empty. If notin other words, if the Const XLS = Results.xls
cell contains a PC namethe script Const AD_ROOT = dc=mycorp,dc=com
continues, setting the strComputer Dim wshShell, appExcel, fso, intRowIndex, strComputer
object to the name in the cell and Dim objWMIService, colItems, objItem , strCommand
attempting to use the Scriptomatic Use Csvde to export all client PC DNS names from AD
WMI code to retrieve data from that to a .csv file.
system. Before entering the loop, the
strCommand = csvde.exe -f & PATH & CSV & -d & _
script confirms that the objWMI- A AD_ROOT & -r (&(objectClass=Computer))_
Service object isnt equal to Nothing. If -p SubTree -l dNSHostName
the object is equal to Nothing, the
Set wshShell=WScript.CreateObject(WScript.Shell)
script increments the rowcount and wshShell.run strCommand, SHELL_RUN_HIDE_WINDOW, _
skips to the next row. (Inside the While SHELL_RUN_WAIT_UNTIL_FINISHED
5
loop, the script sets the objWMIService
Open the .csv file in Excel and save the .xls file.
object to Nothing after each pass. Oth- Set appExcel = CreateObject(Excel.Application)
erwise, if WMI failed to connect to a appExcel.Workbooks.Open PATH & CSV
remote PC, the object would fail to appExcel.ActiveWorkbook.SaveAs PATH & XLS, xlNormal
loop to the next machine in line.) Delete old .csv file and log file.
When looping through colItems, Set fso = CreateObject(Scripting.FileSystemObject)
fso.DeleteFile PATH & CSV, FSO_FILE_FORCE_DELETE
the script sets the first cell of the cur-
fso.DeleteFile PATH & CSV_LOG, FSO_FILE_FORCE_DELETE
rent row to be strComputer and the
second and third cells to be the Cap- Delete unnecessary first column and first row.
appExcel.Columns(A:A).Select
tion and the Version, respectively. If appExcel.Selection.Delete xlToLeft
colItems returns more than one result, appExcel.Rows(1:1).Select
the script moves all the following appExcel.Selection.Delete xlShiftUp
rowswhich contain the names of the Set up the OS data sheet and insert a heading row.
PCs weve yet to connect todown to appExcel.Worksheets.Select
make room for this data. At the same appExcel.ActiveSheet.Name = OS Details for All PCs
appExcel.Rows(1:1).Select
time, the script adds the first cell, con-
appExcel.Selection.Insert xlShiftDown
taining the strComputer value, to these
Print and format the first row column headers.
new blank rows. So for example, if col-
appExcel.Cells(1,1).Value=PC Name
Items contains three results, the script appExcel.Cells(1,2).Value=Caption
will fill in Row 2 with the first set of data appExcel.Cells(1,3).Value=Version
from colItems, create a blank Row 3, appExcel.Range(A1:C1).Select
appExcel.Selection.Interior.ColorIndex = 5
insert strComputer and the next set of
appExcel.Selection.Interior.Pattern = xlSolid
data from colItems into Row 3, create a appExcel.Selection.Font.ColorIndex = 2
blank Row 4, insert strComputer and appExcel.Selection.Font.Bold = True
the last set of data from colItems into Loop through the .xls file and read PC DNS names.
Row 4, and create a blank Row 5. When intRowIndex = 2
the loop determines that colItems is While appExcel.Cells(intRowIndex,1).Value <>
empty and that we therefore dont Attempt to use WMI to connect to each PC and write out the
need that final blank row, the script results. Continued on page 6
deletes that row. The next row, which

www.winscriptingsolutions.com
AD and WMI Reporting

LISTING 3 continued INSTANTDOC ID


The number that appears at the
strComputer = appExcel.Cells(intRowIndex,1).Value end of each article is the articles
Set objWMIService = GetObject(winmgmts:\\ & strComputer _ InstantDoc ID. To access the arti-
& \root\cimv2)
cle, go to the newsletters home page
If Not (objWMIService Is Nothing) Then
(http://www.winscriptingsolutions.com)
Set colItems = objWMIService.ExecQuery(Select * from & _
Win32_OperatingSystem,,48) and enter the ID in the InstantDoc ID box.
For Each objItem in colItems
appExcel.Cells(intRowIndex,1).Value = strComputer
appExcel.Cells(intRowIndex,2).Value = objItem.Caption
shows so that it uses the Win32_Ser-
appExcel.Cells(intRowIndex,3).Value = objItem.Version vice class and the Description prop-
appExcel.Rows(intRowIndex + 1 & : & intRowIndex +1)._ erty, then watch what happens. (I
Insert xlShiftDown
strongly suggest that you limit the
intRowIndex = intRowIndex + 1
Next number of clients you connect to for
appExcel.Rows(intRowIndex & : & intRowIndex).Delete _ this test, because these changes add
xlShiftUp one line per service per PC, or as many
Set objWMIService = Nothing
Else as 100 more lines per client.) Rather
intRowIndex = intRowIndex + 1 than using Csvde, comment out the
End If lines at callout A in Listing 3 and ref-
Autofit the columns and rows, save the .xls file, and display erence an existing .csv file that con-
Excel. tains a dozen or so PC names. This test
appExcel.Cells.Select
will give you more than 1000 lines in
appExcel.Cells.EntireColumn.AutoFit
appExcel.Cells.EntireRow.AutoFit your spreadsheet. Pretty powerful
6 appExcel.ActiveWorkbook.Save stuff, isnt it? Play around with Csvde,
appExcel.Visible = True Scriptomatic, and Excelreporting
about AD and your WMI clients might
contains the next PC, moves up, and user that the file is available. You can actually become fun. 38401
the script continues. even have the script email the file,
Last, the script autofits the columns then remove it from the PC that the Alistair G. Lowe-Norris (alistair@winnet
mag.com) is a consultant specializing in
and rows, saves the .xls file, and opens script ran on.
Microsoft technologies. He is an MCSE, an
it for viewing. You can just as easily MCP+Internet, and the author of Win-
have the script quit Excel without dis- Reports in a Flash dows 2000 Active Directory (OReilly and
playing the file to the user and instead To see a large amount of data for each Associates), which is published in four
send an email message notifying the PC, modify the script that Listing 3 languages.

Rem Do you have a scripting-related question or problem? You can send


your question or problem to winscriptsol@winnetmag.com.
by Bob Wells

Q: I recently downloaded Microsofts Management Instrumentation (WMI) script to another scripting language is
Scriptomatic utility from http://www scripts, so its ideal for WMI scripting simple. After you know how to port
.microsoft.com/technet/treeview/default newbies like me. However, the utility gen- one Scriptomatic script, you can eas-
.asp?url=/technet/scriptcenter/wmimatic erates scripts in VBScript only. How can ily port any Scriptomatic script be-
.asp. Scriptomatic generates Windows I port a Scriptomatic-generated script to cause the scripts all follow the same
Perl? basic template.
AT A GLANCE For example, Listing 1 shows the
A: Scriptomatic has no built-in capa- VBScript code that Scriptomatic gen-
Porting Scriptomatic scripts to Perl . . . . . . . . . . . . . . 6 bility for porting its VBScript code to erates when you select the Win32_
Accessing a Win98SE computer remotely . . . . . . . . . 7 Perl. However, with a bit of Perl and Environment class, which lets you
Searching for users in an NT 4.0 domain . . . . . . . . . . 8 VBScript knowledge, you can port the manage environment variables on any
code yourself. Porting a Scriptomatic WMI-enabled computer. I selected

Windows Scripting Solutions MAY 2003


LISTING 1: Scriptomatic-Generated VBScript Code
On Error Resume Next
Win32_Environment as an example strComputer = .
for two reasons. First, the class is rela- Set objWMIService = _
tively small: It defines only eight prop- GetObject(winmgmts:\\ & strComputer & \root\cimv2)
erties. Second, all WMI-enabled Set colItems = objWMIService.ExecQuery _
(Select * from Win32_Environment,,48)
computers support this class. For Each objItem in colItems
Listing 2 shows the same script WScript.Echo Caption: & objItem.Caption
ported to Perl. Although spotting the WScript.Echo Description: & objItem.Description
WScript.Echo InstallDate: & objItem.InstallDate
changes is relatively easy, lets look at
WScript.Echo Name: & objItem.Name
the more interesting changes. The most WScript.Echo Status: & objItem.Status
important change is in the first line, WScript.Echo SystemVariable: & objItem.SystemVariable
which imports the Win32::OLE module. WScript.Echo UserName: & objItem.UserName
WScript.Echo VariableValue: & objItem.VariableValue
Win32::OLE is the Perl module that pro-
Next
vides Perl with the pixie dust it needs to
work with COM automation libraries,
such as the WMI Scripting Library.
The code at callout A in Listing 2 uses LISTING 2: VBScript Code Ported to Perl
the Win32::OLE modules GetObject
use Win32::OLE;
method to establish the WMI connec- my $strComputer = .;
tion. Notice that Perls dereference A my $objWMIService =
operator is an arrow (->) rather than a Win32::OLE->GetObject(winmgmts://$strComputer/root/cimv2)
dot (.). Also notice that the string or die WMI connection failed.\n;
passed to GetObject isnt pieced my $colItems = $objWMIService->ExecQuery(Select * from
together like the VBScript string. Perl Win32_Environment,WQL,48); 7
foreach my $objItem (in $colItems) {
supports interpolated strings, so you print Caption: $objItem->{Caption}\n;
dont need to concatenate the pieces print Description: $objItem->{Description}\n;
as you do in VBScript. print InstallDate: $objItem->{InstallDate}\n;
The last change worth noting is how print Name: $objItem->{Name}\n;
print Status: $objItem->{Status}\n;
you use Perl and Win32::OLE to refer- print SystemVariable: $objItem->{SystemVariable}\n;
ence and dereference COM collections. print UserName: $objItem->{UserName}\n;
You use a scalar to reference the col- print VariableValue: $objItem->{VariableValue}\n\n;
}
lection returned from the WMI Script-
ing Librarys ExecQuery method. You
use Perls hash syntax to dereference an
item in the collection (e.g., $Reference- remote procedure call (RPC) error. Can I Y is the default setting.
Name->{PropertyName}). To learn use a WMI script to access a Win98SE 3. Set the EnableRemoteConnect
more about using the Win32::OLE mod- computer remotely? value (of type REG_SZ) to Y. N is the
ule to access COM automation libraries default setting.
and components, see ActiveStates A: You can use WMI to access your 4. Navigate to the HKEY_LOCAL_
ActivePerl documentation at http:// Win98SE computers remotely, but you MACHINE\SOFTWARE\Microsoft\
aspn.activestate.com/ASPN/Reference/ first need to configure your Win98SE WBEM\CIMOM subkey.
Products/ActivePerl-5.6/site/lib/ computers to accept remote Distributed 5. Set the AutostartWin9x value (of
Win32/OLE.html. 38405 COM (DCOM) connections. Although type REG_SZ) to 1 or 2. For informa-
the WMI software development kit tion about each settings meaning, see
Q: Im working on a script that uses (SDK) describes the steps you need to the topic Automatically Invoking WMI
Windows Management Instrumentation take, those steps contain some errors. on Windows 95/98 (http://msdn
(WMI) to collect hardware and software The correct steps, which I adapted .microsoft.com/library/en-us/wmisdk/
information in a mixed OS environment. from the WMI SDK, are as follows. On wmi/automatically_invoking_wmi_on_
Most of our desktops run Windows 98 a Win98SE computer: windows_95_98.asp?frame=true) in
Second Edition (Win98SE); a few desk- 1. Use the registry editor to navigate the WMI SDK.
tops run Windows 2000 Professional. My to the HKEY_LOCAL_MACHINE\ 6. Add the winmgmt.exe file to the
script runs great against the Win2K Pro SOFTWARE\Microsoft\Ole subkey. Win98SE computers Startup directory.
computers, but when the script tries to 2. Set the EnableDCOM value (of You can find winmgmt.exe in the
connect to a Win98SE computer, I get a type REG_SZ) to Y if it isnt already set. %windir%\system\wbem directory.

www.winscriptingsolutions.com
Rem
time to run because our domain contains
LISTING 3: Code That Inefficiently Searches an thousands of user accounts. Can you
NT 4.0 Domain offer any suggestions to improve the
scripts performance?
Option Explicit
WScript.Echo Time
Dim blnFound, objDomain, objUser, strDomainName, strUserName A: To improve the scripts perfor-
blnFound = False mance, you can bind directly to the user
strDomainName = acme object in question instead of enumerat-
strUserName = johndoe
ing all users. When the user exists, the
Set objDomain = GetObject(WinNT:// & strDomainName) GetObject function succeeds. When the
objDomain.Filter = Array(User) user doesnt exist, GetObject fails. This
For Each objUser In objDomain approach will not only significantly
If LCase(objUser.Name) = LCase(strUserName) Then
improve your scripts performance but
blnFound = True
Exit For also reduce the scripts impact on your
End If network and domain controller (DC).
Next The script in Listing 4 demonstrates
how to use the bind operation to
WScript.Echo strUserName & : & blnFound
WScript.Echo Time determine whether a user exists. The
code at callout A in Listing 4 is key. This
code begins by enabling the On Error
7. Restart the Win98SE computer. (ADSI) WinNT provider doesnt have a statement, VBScripts error-handling
38406 search mechanism like the ADSI Light- mechanism. Next, the script tries to
8 weight Directory Access Protocol (LDAP) bind directly to the user object in the
Q: I need to search for users in our provider does, Im using a script similar NT 4.0 domain. This code works
Windows NT 4.0 domain. Because the to the one in Listing 3 to perform the equally well with Active Directory (AD)
Active Directory Service Interfaces search operation. This script takes a long in Windows 2000 and local SAM data-
bases in NT 4.0 member servers and
workstations. Following the GetObject
LISTING 4: Code to Efficiently Search an call, the script checks the VBScript Err
NT 4.0 Domain object to determine whether GetOb-
ject succeeded.
Option Explicit
When the Err objects Number
WScript.Echo Time
Dim blnFound, objUser, strDomainName, strUserName property is 0 (i.e., no error), GetObject
succeeded, so the script sets blnFound
Const USERNAME_NOT_FOUND = &h800708AD to True. When the Err objects Number
blnFound = False
property is equal to the Win32 error
To run this script, change the strDomainName variables code assigned to the USERNAME_
value to your domains name and change the strUserName NOT_FOUND constant, GetObject
variables value to the name of the user account to search for. failed, so the script sets blnFound to
strDomainName = acme
strUserName = johndoe False. When the Err objects Number
property is something other than 0
A On Error Resume Next or the USERNAME_NOT_FOUND
Set objUser = GetObject(WinNT:// & strDomainName _
constants value, some other error
& / & strUserName & ,User)
If Err.Number = 0 Then occurred. The script echoes that value
blnFound = True to the console and immediately exits.
ElseIf Err.Number = USERNAME_NOT_FOUND Then 38407
blnFound = False
Else Bob Wells (bobwells@winnetmag.com) is a
WScript.Echo CStr(Hex(Err.Number)) & : & Err.Description
contributing editor for Windows & .NET
WScript.Quit
Magazine. He is a programming writer at
End If
Microsoft, where he is contributing to a
WScript.Echo strUserName & : & blnFound new System Administration Scripting
WScript.Echo Time Guide that Microsoft will include in its
next Windows Server resource kit.

Windows Scripting Solutions MAY 2003


Progressive Perl for Windows
Converting Perl Scripts to Win32 Perl Services
by Dave Roth

Most programs require that a user log every minute to determine whether the the state of a service. Such requests can
on to the system to run them. How- server is online. This script is a perfect come from not only local or remote
ever, long ago before Windows ever candidate for a Win32 service because it users but also local or remote applica-
appeared, UNIX developers realized needs to run as soon as the OS is loaded tions and system components. Thus, the
the importance of having some pro- and must continue running regardless Perl script continuously monitors the
grams run from the time that a com- of which users log on and log off. SCM for state changes and reacts
puter booted until the computer was Win32 services are truly great tools. accordingly. For example, when the
shut down because the programs pro- However, when you use C or Visual state changes to SERVICE_PAUSE_
vided important services that had to Basic (VB) to develop them, the task is PENDING, the script reacts by perform-
run, even when nobody was logged fairly daunting because you have to ing whatever actions are required to
on. Hence, the daemon was born. A manage multiple threads interacting at make the script pause. The script must
daemon is software thats designed to different times. This task leaves you then inform the SCM that it has entered
run at boot time and keep running, with little time to benefit from the ser- the SERVICE_PAUSED state. The script
without user intervention. The OS vices. However, you can use a Perl reacts similarly to the SERVICE_START_
launches the program because a user script to create Win32 services, which PENDING, SERVICE_CONTINUE_
might not be available to do so. is far easier than using C or VB to cre- PENDING (essentially unpausing), and
Most of the common services asso- ate them. And modifying a service is as SERVICE_STOP_PENDING states and 9
ciated with the Internet are daemons, easy as modifying a Perl script. performs the necessary actions to enter
such as Web, FTP, email, firewall, the pending state.
streaming-media, and print services. How Win32 Perl Services One of the most important states is
These services start running when the Work SERVICE_RUNNING. When the SCM
computer starts and stop running Windows uses a specialized software reports this state, the script performs
when the computer shuts down. manager called the Service Control the actions for which it was designed.
Windows OSs based on the Win- Manager (SCM) to handle services. The For example, if the script was designed
dows NT kernel (e.g., Windows Server SCM manages the state of a service. to ping a server every minute, during
2003, Windows XP, Windows 2000, NT) When Windows boots up, the SCM the SERVICE_RUNNING state, the
refer to daemons as services. You can starts any service configured to start at script issues a ping every minute.
find your machines Win32 services and boot time. When a user wants to start,
their states (e.g., running, stopped) in pause, or stop a service, the SCM per- The Win32::Daemon Perl
the Control Panel Services applet, forms the action on the users behalf. Extension
which is in the Control Panels Admin- The SCM then interacts with the service A few tools are available to create a
istrative Tools folder. When you double- to inform it that its state must change. Win32 Perl service, such as the srvany
click the Services applet, the Microsoft When the SCM starts a service, the .exe freeware utility and ActiveStates
Management Console (MMC) Services SCM executes the services .exe file. For PerlSvc utility. However, srvany.exe has
snap-in appears. The Services snap-in Perl scripts, the SCM launches perl.exe limitations, and PerlSvc requires the
lists each services name and provides and passes to perl.exe the name of the purchase of ActiveStates Perl Dev Kit.
information about that service, such as Perl script and any parameters that the An alternative is the Win32::Daemon
its description and status. script needs. The Perl script then tells extension (http://www.roth.net/perl/
Win32 services are useful for many the SCM to treat it as a service. There- daemon) and the Win32::Daemon::
reasons. For example, Win32 services after, the script is officially considered Simple extension (http://jenda.krynicky
provide the means to run a program to be running as a service. In addition .cz), which depends on Win32::Dae-
when the OS starts. In addition, you to whatever tasks the script is pro- mon. These extensions provide full
can control a service from a remote grammed to do, the script has only one Win32 service functionality from a
machine. service-related task that it must per- simple Perl script.
You can make almost any applica- form: monitor the SCM for state You can install Win32::Daemon by
tion or tool a Win32 service. For exam- changes. running the Perl Package Manager
ple, consider a script that pings a server The SCM receives requests to change (PPM) from a command line:

www.winscriptingsolutions.com
Win32 Perl Services
from running the registry editors block of code. The code first sets the
LISTING 1: Code That Lists (regedit.exe and regedt32.exe) and the default configuration settings, then calls
the Applications FTP client. You can also use the script to the Configure( ) subroutine to check for
terminate a process after the process user-specified parameters, some of
to Kill has run for a specific amount of time. which might override the default set-
%PROC_LIST = ( For example, I configured the script to tings. Depending on the configuration
telnet.exe => 120, terminate any Telnet session that runs options you specify when you launch
ftp.exe => 0,
longer than 2 minutes. This termination ProcMon.pl, the code either installs the
regedit.exe => 0,
regedt32.exe => 0 prevents users from staying logged on to script as a service, stops the script from
); a Telnet client for long periods of time. running as a service, or displays the
The scripts %PROC_LIST hash lists scripts syntax and exits. Figure 1 shows
the applications to kill and when to kill the configuration options that you can
PPM install http://www.roth.net/ them, as the code in Listing 1 shows. use when launching ProcMon.pl.
perl/packages/win32-daemon.ppd Each hash key lists the applications After setting the service configura-
filename (e.g., telnet.exe), which must tions, the script creates the log file. The
be in all lowercase letters. The value default logs filename is the name of the
Creating a Process- associated with each hash key speci- script with the .log extension. So, if you
Monitoring Service fies how many seconds the application leave the scripts name as ProcMon.pl,
In the Code Library on the Windows is allowed to run before being termi- the resulting default log file will be
Scripting Solutions Web site (http:// nated. Notice that all but one of the ProcMon.log. The log file will reside in
www.winscriptingsolutions.com), youll applications have a value of 0 seconds. the same directory as the script. The
find ProcMon.pl, a script that creates a This value causes the script to termi- code at callout A in Listing 2 configures
10 process-monitoring service. You can nate those applications just after the this default location. However, you can
use this script to prevent specific pro- script notices theyre running. override the default by using the -l
cesses from running. For example, I Next, the script sets the service parameter when you launch the script.
configured the script to prevent users configurations. Listing 2 shows this
Starting the Service and
Processing Service States
LISTING 2: Code That Configures the Service Now that the script has made the nec-
%Config = ( essary preparations to run as a service,
machine => Win32::NodeName(), the script starts the service by calling the
A logfile => ( Win32::GetFullPathName( $0 ) =~
Win32::Daemon::StartService() function,
/^(.*)\.[^.]*$/ )[0] . .log,
service_alias => ProcMon, as Listing 3 shows. If the call is success-
service_name => Perl Process Monitor, ful, the SCM starts interacting with the
# Specify how long (in milliseconds) to sleep Perl script as if the script were a built-in
# after polling the service state.
Win32 service. If the call fails, the script
service_sleep_time => 100,
# Specify how often (in seconds) to query the list of logs an error message and exits.
# processes to determine whether theres a process to kill. After the service starts, the script
update_proc_list_time => 5 continuously checks the SCM for mes-
);
sages. The script uses a long loop that
Configure( \%Config, @ARGV );
if( $Config{install} ) continuously calls the Win32::Dae-
{ mon::State() method to check for any
InstallService(); state change. In this loop, which List-
exit;
}
ing 4, page 12, shows, the script has a
elsif( $Config{remove} ) block of code for each possible state
{ that can occur. The code executes an
RemoveService(); appropriate action for that state. For
exit;
} example, the block of code for the
elsif( $Config{help} ) SERVICE_PAUSE_PENDING state
{ doesnt do anything other than log an
Syntax();
indicator that specifies that the script is
exit;
} entering the paused state. Each block
of code calls the Win32::Daemon::

Windows Scripting Solutions MAY 2003


ProcMon.pl [-install [-account Account] [-password Password]
[-l LogFile] [-m Machine] [-n Name] [-proctime Time]] [-remove] [-help]
State() method, which passes in a new
state. The loop continues until the -install Installs the service.
state changes to SERVICE_STOPPED, -account Account Specifies the account under which the service
at which time the script cleans up any runs. Replace Account with your accounts
loose ends (e.g., closes open files, name. The account name must have the Logon
deletes temporary files), then ends. as a Service privilege on the machine the service
Of the different states that the main is installed on. The default is the local system.
loop monitors, the most important -password Password Specifies the password the service uses.
states are SERVICE_START_PENDING, Replace Password with your accounts password.
SERVICE_STOP_PENDING, SERVICE_ -l LogFile Specifies the location of the log file. Replace
RUNNING, and SERVICE_STOPPED. LogFile with your logs path.
The script enters the SERVICE_START_ -m Machine Specifies the machine to monitor. You must specify
PENDING state only onceat the an account that has administrative permissions on
beginning of the script. As the code at the remote machine.
callout A in Listing 4 shows, the code -n Name Specifies the services name. Replace Name
initiates a Windows Management with a name for your service.
Instrumentation (WMI) connection -proctime Time Specifies how often (in seconds) to check the list
that the script uses throughout the life of processes for any process that must be terminated.
of the service. The code then instructs -remove Removes the service. If you installed the service
the SCM that the service has officially with a name (i.e., used the -n parameter), specify
entered the SERVICE_RUNNING state. -n when removing the service.
The SCM returns a SERVICE_STOP_ -help Displays the scripts syntax.
PENDING state when a user, applica- 11
tion, or system component requests FIGURE 1:
the service to stop. In response, the Syntax to launch ProcMon.pl
script cleans up any loose ends and
prepares for the services termination. the permitted time in the %PROC_LIST that it successfully changed states
Afterward, the script calls the Win32:: hash. If the process has been running (e.g., moved from SERVICE_PAUSE_
Daemon::State(SERVICE_STOPPED) longer than the permitted time, the PENDING to SERVICE_PAUSED), the
function to indicate that the script has code terminates the process. script remembers the new state (e.g.,
stopped processing service requests. The final line of the code at callout SERVICE_PAUSED) by storing that
When the SCM returns the SERVICE_ B is important. It tells the script to fall information in the $PrevState variable.
STOPPED state, the main loop ends and asleep for 25 milliseconds (ms). This Then, when an unknown state occurs,
the script shuts down the service by call- sleep cycle occurs for each process to the script ignores the unknown state
ing the Win32::Daemon::StopService( ) prevent WMI from spiking the CPU and tells the SCM that the service is still
functions, then exits. load. in the state that the $PrevState variable
The script uses the SERVICE_ The last state that the main loop specifies, as the code at callout C in
RUNNING state to do the bulk of its checks for is an unknown state. Some Listing 4 shows.
work. When the loop detects this state, services have special states that specific Finally, the main loop falls asleep
it runs the code at callout B in Listing applications trigger. To the SCM, these for a short amount of time, as the code
4. This block of code first checks the states are unknown. Because the SCM at callout D in Listing 4 shows. This
clock to see whether enough time has can indicate an unknown state, the sleep cycle prevents any CPU spiking
passed since the last check of running script must be able to handle this state. that might occur if the loop runs
processes. This amount of time is con- Every time the script tells the SCM amok. Typically, the main loop doesnt
figurable through the -proctime com-
mand-line parameter. If the specified
amount of time has passed, the code LISTING 3: Code That Starts the Service
uses WMI to walk through each running if( ! Win32::Daemon::StartService() )
process on the machine. The code com- {
pares each process name with the Log( Could not start the $Config{service_name} service );
names in the %PROC_LIST hash. When Log( Error: . GetError() );
exit();
a match occurs, the code compares how }
long the process has been running with

www.winscriptingsolutions.com
Win32 Perl Services
need to run constantly anyway. By
LISTING 4: Code That Checks the SCM default, the script falls asleep for
100ms before starting over.
while( SERVICE_STOPPED !=
( my $State = Win32::Daemon::State() ) )
{ Customizing ProcMon.pl
if( SERVICE_START_PENDING == $State ) Now that you understand how a Win32
{
Perl Service works, you can customize
Log( Linking to WMI... );
if( $WMIServices = Win32::OLE-> ProcMon.pl to monitor the processes
A
GetObject( winmgmts://$Config{machine}/root/cimv2 ) ) that you want to limit. Simply modify
{ the %PROC_LIST hash in Listing 1 to
Log( ...successful. );
include those processes. Then, to install
Win32::Daemon::State( $PrevState = SERVICE_RUNNING );
Log( $Config{service_name} service has started. ); the script as a service, use the following
} command to launch ProcMon.pl:
else
{
Perl ProcMon.pl -install
Log( ...failure. );
Win32::Daemon::State($PrevState = SERVICE_STOPPED );
Log( $Config{service_name} service could not get a process To start the service, run the command
list object: Aborting. );
Log( Error: . Win32::OLE->LastError() );
} net start ProcMon
}
elsif( SERVICE_PAUSE_PENDING == $State ) To test the service, run a program thats
{
in your %PROC_LIST hash. You can
# Pausing
12 then check the log file for details about
Win32::Daemon::State($PrevState = SERVICE_PAUSED );
Log( $Config{service_name} service has paused. ); what the service has done.
next;
}
elsif( SERVICE_CONTINUE_PENDING == $State )
Debugging the Service
{ If all goes well, your Win32 Perl service
# Resuming will run smoothly. However, problems
Win32::Daemon::State($PrevState = SERVICE_RUNNING ); can arise, and you might need to de-
Log( $Config{service_name} service has resumed. );
bug the service.
next;
} Win32 services have limitations that
elsif( SERVICE_STOP_PENDING == $State ) can be difficult to work around. The
{ biggest limitation is that a service runs
# Stopping
Win32::Daemon::State($PrevState = SERVICE_STOPPED );
headless, which means it doesnt have
Log( $Config{service_name} service is stopping. ); a UI. Thus, Win32 services are difficult
next; to debug because they dont have any
} windows that display information, so
elsif( SERVICE_RUNNING == $State )
{ you cant directly interact with the ser-
B my $Now = time(); vices. This limitation can be especially
if( $Now - $LastRunTime >= $Config{update_proc_list_time} ) frustrating for Perl scriptwriters because
{
they typically use an interactive debug-
$LastRunTime = $Now;
my $ProcList = ger to debug their scripts.
$WMIServices->InstancesOf( Win32_Process ); The most practical way to debug a
foreach my $Process ( in( $ProcList ) ) Win32 Perl service is to have it write
{
debug messages to a log file. You can
my $ProcName = $Process->{Name};
if( defined $PROC_LIST{lc $ProcName} ) then examine the log file to see the data
{ that your script printed out. However,
my( @List ) = ( $Process->{CreationDate} =~ this type of debugging is slow and
/(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ );
# Index 0 = full year, 1 = month, 2 = day,
cumbersome because you have to walk
# 3 = hour, 4 = minute, 5 = second. Month through a potentially long log file just
# must be changed to be 0 indexed. to see the results of debug messages. A
$List[1] ; better alternative is to run SysLogD.pl,
Continued on page 13
which you can find in the Code

Windows Scripting Solutions MAY 2003


Library. SysLogD.pl is quite beneficial
LISTING 4 continued
when debugging a Win32 service
because the service writes debugging my $ProcStartTime = timelocal( reverse @List );
messages to a named pipe rather than my $ProcRunTime = $Now - $ProcStartTime;
if( $PROC_LIST{lc $ProcName} <= $ProcRunTime )
a log file. A named pipe acts as a {
medium between two processes: the if( 0 == $Process->Terminate( 0 ) )
server process that creates the named {
Log( Killed $ProcName started on .
pipe and the client process that con- localtime( $ProcStartTime ) );
nects to the named pipe. From the }
else
viewpoint of both applications, the
{
named pipe looks like a file that they Log( Failed to Kill $ProcName started on
can write to or read from. . localtime( $ProcStartTime ) );
}
SysLogD.pl uses the Win32::Pipe }
extension (which comes with Active- }
States ActivePerl) to create the named Win32::Sleep( 25 );
}
pipe. After creating the named pipe, }
the script waits for an application to }
connect to it. A perfect example of a else
{
client application connecting to the Win32::Daemon::State( $PrevState );
C
named pipe is a Win32 service that }
uses the named pipe as a log file. D Win32::Sleep( $Config{service_sleep_time} );
}
After an application connects to the
named pipe, the script reads from the 13
named pipe while the client is still con- Dir command, which redirects the Win32 Services for Everyone
nected. The call to the named pipes output to the named pipe: Win32 services are incredibly powerful
Read() function is a blocking call. There- tools that you shouldnt be without.
fore, the Read() function wont return Dir C:\*.* > \\YourMachineName\ When you use Perl scripts, creating
anything until the client application has pipe\syslog Win32 services is easy. So, have some
either written data to the named pipe fun and create some Win32 services.
or disconnected from the named pipe. Be sure to replace YourMachineName Drop me an email message sharing the
If the application wrote data to the with your computers name. The result kinds of Perl-based Win32 services
named pipe, the Read() function returns is a directory listing in the Perl scripts that youve created. 38404
the data. SysLogD.pl displays anything cmd.exe window.
that a client application might send to Because named pipes work over a Dave Roth (rothd@roth.net) is the author
it. If the application disconnected from network, you can have your scripts log of several Win32 Perl extensions, including
Win32::AdminMisc, Win32::ODBC, Win32::
the named pipe, the Read() function to a remote machine. If you have all
Daemon, and Win32::Perms. His most
returns nothing at all. your services on all your servers log to
recent book is Win32 Perl Programming:
To test SysLogD.pl, run the script in one remote machine, you would need The Standard Extensions, 2nd edition
a cmd.exe window. Then, in another to monitor only one computer to (New Riders Publishing/Macmillan Tech-
cmd.exe window, run the following monitor an entire network of services. nical Publishing).

Real-World Shell Scripting


Group Membership Tracking
by Dick Lewis
If you manage a large environment, though, youre responsible if a user is Groups, July 2002, http://www.win
youve probably delegated responsi- granted too much access or access scriptingsolutions.com, InstantDoc ID
bility for group memberships to thats otherwise inappropriate. 25275, I described a script that lets you
administrators in your domain or In Real-World Shell Scripting: quickly perform a periodic audit of key
organizational unit (OU). Ultimately, Auditing the Membership of Privileged local and global group memberships

www.winscriptingsolutions.com
Group Membership Tracking
shipTracker.bat running in your envi-
LISTING 1: Main Code to Configure ronment, perform the following set of
steps:
:: Replace London with your domains name.
Set Domain=London 1. Copy the script into a dedicated
folder on the server or workstation on
:: Replace FredSmith@yourcompany.com with the email addresses which you plan to schedule the script
:: of the recipients you want to receive the email message.
to run.
Set Recipients=FredSmith@yourcompany.com
2. Determine the local and global
:: Replace CindyWong@yourcompany.com with the email address groups that you need to monitor. Cre-
:: of the person sending the email message. ate localgroups.txt and globalgroups
Set From=CindyWong@yourcompany.com
.txt input lists, and place these files in
:: Replace mailserver.yourcompany.com with your SMTP servers name. the folder that holds GroupMember-
Set Mailserver=mailserver.yourcompany.com shipTracker.bat. If you have all local and
no global groups, or vice versa, you
on your servers. That script gives you This script assumes youre moni- dont need to create an empty input file.
a good snapshot of group member- toring key administration groups as If the script cant find an input file, it
ships for your quarterly audits, but its opposed to large groups with hundreds simply moves on to the other input file.
not much help when you want to track of members. The script can perform the 3. Configure GroupMembership-
day-to-day additions to and deletions latter task but can take a while to com- Tracker.bat for your environment. List-
from group memberships. Discovering plete. Most domains or OUs contain 6 ing 1 shows the code that you must
a user account with authority thats too to 10 key groups, such as Domain change. First, configure the domain in
generous is important at audit time, Admins, OU Admins, GPO Admins, which your local and global groups
14 but discovering potential problems Local PC Admins, and other adminis- exist. If you want to track groups in
sooner is better. trative groups that have sweeping more than one domain, you need to
domain powers and whose member- either run additional instances of the
Comparing Lists ship deserves constant attention. script or modify the code to handle
The GroupMembershipTracker.bat additional domains. Next, at the top of
script, which you can find in the Code Using Blat the script, configure the To and From
Library on the Windows Scripting Solu- Blat is a freeware utility that lets you email addresses that Blat will use for the
tionsWeb site (http://www.winscripting send email messages from a com- email notifications. For the email noti-
solutions.com), uses the Local and mand-shell script. In the case of fications To address, you can specify
Global utilities from the Microsoft GroupMembershipTracker.bat, Blat multiple recipients by separating the
Windows 2000 Server Resource Kit or lets you send notification messages addresses from one another with a
the Microsoft Windows NT Server 4.0 when any new group is added to the comma but no space. Finally, configure
Resource Kit to periodically list a local or global group tracking lists. You the SMTP server name that Blat will use
groups members. The script then can download Blat from http://www to send the email notification.
compares that list with the previous .interlog.com/~tcharron/blat.html. 4. Verify that the scripts utilities (i.e.,
list. For more information about Blat, see now.exe, local.exe, global.exe, and
The code that performs the line-by- Real-World Scripting: Using Blat to blat.exe) reside in the folder from
line comparison uses the Findstr com- Send Email Notification Messages, which the script will run. As Listing 2
mand to determine whether the new November 2000, http://www.win shows, the script sets the folder loca-
file contains every line entry from the scriptingsolutions.com, InstantDoc ID tion to the %~dp1 variable. The per-
old file and whether the old file con- 15848. The GroupMembershipTracker cent sign (%) specifies a replaceable
tains every line entry from the new file. .bat code shows Blat syntax for SMTP parameter. The ~dp1 portion tells the
If something doesnt match, someone servers that dont require authentica- scripting engine to capture the drive
has added or deleted a member and a tion. Review the Blat documentation letter (d) and the path (p). If your script
notification event occurs. In addition for information about the -u and -pw path is C:\AdminScripts\GroupMem-
to sending notification messages, authentication switches. bershipTracker.bat, the %~dp1 vari-
GroupMembershipTracker .bat creates able contains the C:\AdminScripts
a running timestamped log, in case Putting It Together folder location information. Thus, if
you need to review the entire history of GroupMembershipTracker.bat is com- your utilities arent in the same loca-
changes. If no changes exist, no notifi- patible with Windows XP, Win2K, and tion as the script or you want to locate
cation event is necessary. NT 4.0 systems. To get GroupMember- input and output files elsewhere, you

Windows Scripting Solutions MAY 2003


must modify the code in Listing 2 to
point to the utilities location. (For LISTING 2: Code to Configure the Utilities Location
more information about the %~dp1
:: This code appears in the scripts :routine module. If your
variable, see the Web-exclusive sidebar :: utilities arent in the folder you specified in the launch
Two Tricks for Your Scripting Toolbelt, :: command, replace =%~dp1 with the full path that points to the
http://www.winscriptingsolutions.com, :: utilities location.
Set FolderLocation=%~dp1
InstantDoc ID 38418.)
5. Manually test the script to verify
that its capturing and logging group This script is primarily designed to in combination with auditing and
changes and properly sending email identify unintentional group member- Security event-log monitoring.
messages. The script creates a tempo- ship changes. If youre looking for GroupMembershipTracker.bat can
rary file for each group that youve tar- security violations or patterns indicat- help you get a handle on your key
geted for tracking so that it can ing intentional changes that might group memberships. The potential for
compare past script run results with necessitate employee discipline or dis- surprises at audit time will diminish,
the next script run results. You might missal, you have some additional con- and youll have an extra set of eyes for
want to make your changes to one or siderations. You would want to tracking group members to be sure that
two test groups so that you dont affect frequently run the script on a PC or no security breaches occur because of
your production group memberships server, and you would want to limit incorrect group memberships in your
when you add and delete users to trig- knowledge of the scripts existence domain or OU. 38400
ger messages. and location. If a user has Change
6. Determine how often you want access to the location of the group sta-
Dick Lewis (dlewis@winnetmag.com) is a
the monitoring script to run. You could tus files, he or she could potentially
senior systems engineer with CKT Consult- 15
schedule it to run every few minutes, edit these files to mask user account
ing in Riverside, California. He is an MCSE
but once or twice a day is probably suf- additions and removals. To accurately and an MCT, specializing in enterprise
ficient. Use Task Scheduler to schedule track such intentional activities, you management of Windows 2000 and Win-
the script. would definitely want to use the script dows NT servers and workstations.

Reader to Reader
Win2K and NT Which Command
[Editors Note: Share your scripting dis- Ive found the Which command so script uses only batch tools and relies
coveries, comments, problems, solu- useful in UNIX that I decided to dupli- on the native Win2K and NT tool find-
tions, and experiences with products. cate its behavior in Win2K and NT. I str.exe. The VBScript file requires Win-
Email your contributions (500 words or wrote a command-shell script, which dows Script Host (WSH). You can use
less) to r2rwinscriptsol@winnetmag Web Listing 1 (http://www.winscripting Win2Ks default version of WSH; for
.com. We edit submissions for style, solutions.com, InstantDoc ID 38399) NT, you must manually install a version
grammar, and length. If we print your shows, and a VBScript file, which Web of WSH. Both of my scripts validate
submission, youll get $100.] Listing 2 shows. The command-shell command-line syntax and search for

The UNIX Which command lets you Windows Scripting Solutions (ISSN 1537-4483; USPS 017-852) is published monthly by Penton Technology Media, Inc.,
find in the current users path a speci- 221 E. 29th St., Loveland, CO 80538

fied executable. By default, Which lists Vol. 5, No. 5 2003 by Penton Media, Inc. ALL RIGHTS RESERVED.
Subscriptions in US $129.00 for one year; add $7.00 for all countries outside the US.
only the first instance of the specified
Windows Scripting Solutions is an independent publication not affiliated with Microsoft Corporation. Microsoft Cor-
file in the path. If you use the -a option, poration is not responsible in any way for the editorial policy or other contents of the publication.
Which lists all locations of the file in Windows Scripting Solutions, 221 E. 29th St., Loveland, CO 80538, 800-621-1544 or 970-663-4700.
any directory in the path. In UNIX, the Sales and Marketing Offices: 221 E. 29th St., Loveland, CO 80538
Periodicals Postage Paid at Loveland, CO and additional mailing offices.
current directory isnt in the path by
POSTMASTER: Send address changes to Windows Scripting Solutions, P.O. Box 447, Loveland, CO 80539-0447
default. In Windows 2000 and Windows
SUBSCRIBERS: Send all inquiries, payments, and address changes to Windows Scripting Solutions, Circulation Depart-
NT, the current directory is in the path ment, P.O. Box 447, Loveland, CO 80539-0447, 970-203-2782 or 800-793-5697, scripting@winnetmag.com.
but only implicitly.

www.winscriptingsolutions.com
Publisher
EDITORIAL
Kim Paulsen
Reader to Reader
kpaulsen@winnetmag.com
Editor-in-Chief Janet Robbins
janet@winnetmag.com
Technical Editors Alistair G. Lowe-Norris any file that the OS deems executable. Both overran the command-line character limit.
alistair@winnetmag.com
Dino Esposito scripts also explicitly include the current The VBScript file is simpler than the com-
dino@winnetmag.com
Bob Wells
directory. mand-shell script. I use the VBScript file
bobwells@winnetmag.com The command-shell script contains some solely from the command line, so Ive written
Dick Lewis
dlewis@winnetmag.com unusual logic to delimit certain strings cor- a small batch wrapper for the file, which Web
Editor Michelle Crockett rectly. This script also breaks some For loops, Listing 3 shows. You must write the full path
crockett@winnetmag.com jumping in and out of them based on certain to which.vbs into the wrapper batch file.
Group Managing Editor Dianne Russell conditions. I repeated one line, which tests for 38399
drussell@winnetmag.com
Editorial Content Manager Amy Eisenberg
the existence of the command issued as the Glen Campbell
amy@winnetmag.com argument, because the elements I needed to gcampbel@dante.com
Editors Karen Bemowski
execute based on the commands success
Jason Bovberg
Rob Carson
Juliann Feuerbacher
Barb Gibbens
Renee Munshi
Lisa Pere
Warren Pickett
Gayle Rodcay

Copy Editors Angela Brew


Joan Roberts
Shauna Rumbaugh
Administrative Coordinator Kathy Milner
kmilner@winnetmag.com

Senior Art Director Larry Purvis

Associate Production Director Linda Kirchgesler

Circulation Marketing Gregg Kinnes


Manager greggk@winnetmag.com

Thomas L. Kemp Chairman and CEO


Daniel J. Ramella President and COO

PENTON TECHNOLOGY AND


LIFESTYLE MEDIA
David B. Nussbaum President
Darrell Denny President, IT Media Group

LETTERS AND SUBSCRIPTIONS


Send your letters and comments to
winscriptsol@winnetmag.com.
Send all inquiries, payments, and address
changes to scripting@winnetmag.com.

www.winscriptingsolutions.com

Printed in the USA

Anda mungkin juga menyukai