
|


|
|
|
Add Printers Based on Name of Computer
Here's a logon script you can
use to solve a complicated problem in printer management: performing
a logon task based on the name of the computer being logged
into
The Code
[Discuss (2) | Link to this hack] |
The CodeTo get the code for the script, I suggest downloading the
Logon.vbs file from the
O'Reilly web site (http://www.oreilly.com/catalog/winsvrhks/),
because it's too long to type from scratch. This
version of the script was tested on Windows 2000 Service Pack 2
running Internet Explorer 6 Service Pack 1 with Microsoft Windows
Script v5.6. It was also tested on Windows XP Professional
participating in a small Active Directory domain. WARNINGI have had a variation of this script in production for a long time
now with great success. But, as always, either the differences in
your environment or something I missed in editing the script for this
hack might cause things behave unexpectedly.
Depending on your environment, the code requires: For more information on using Internet Explorer for status messages,
see "Using an IE Window to Display
Progress" (http://www.myitforum.com/articles/11/view.asp?id=3489),
"VBScript Forms (Part 1): Using Internet Explorer
for Data Input/Output Forms" (http://www.myitforum.com/articles/11/view.asp?id=4390),
and "Using IE to Browse for Files"
(http://www.myitforum.com/articles/11/view.asp?id=4229).
Perform initial tasksThis section sets up the basics of the script. In this script, I also
call a few subroutines/functions (listed further in later sections)
that either gather information or perform my required tasks. I like
using subroutines and functions, because it makes my code reusable or
easily stored in a code library for future coding endeavors. This
section accomplishes the following major tasks: Call a subroutine that gathers basic system information Exit the script if user is logged on locally to the server Call subroutines to gather group memberships
The script also performs other tasks, as discussed in comments
throughout the code. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' File: Logon.vbs
' Updated: April 2003
' Version: 2.1
' Author: Dan Thomson, myITforum.com columnist
' I can be contacted at dethomson@hotmail.com
'
' Usage:
' This script can be directly assigned as a logon script for
' Windows 2000 or greater clients. For older systems, this
' script will need to be called from a logon batch file.
'
' Input:
'
' Requirements:
' Win 9x, ME or NT 4:
' - Active Directory Client Extensions
' http://www.microsoft.com/windows2000/techinfo/howitworks/
' activedirectory/adsilinks.asp
' - Windows Script
' http://msdn.microsoft.com/library/default.asp?url=/downloads
' /list/webdev.asp
' - A recent version of Internet Explorer
'
' Notes:
' Tested on Windows 2000 Professional running Windows Script v5.6
' and participating in an AD domain
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Define Variables and Constants
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim objFileSys
Dim objIntExplorer
Dim objWshNetwork
Dim objWshShell
Dim strDomain 'Domain of the user
Dim strHomePage 'Homepage to be set for user
Dim strLogonPath 'Path to location from where the script is running
Dim strOSProdType 'OS Product type (WinNT, LanmanNT, ServerNT)
Dim strWorkstation 'Local Computer Name
Dim strUserGroups 'List of groups the user is a meber of
Dim intCounter 'General counter
Const UseNTServer = 0 'Sets whether this script runs when logging on locally
'to Windows Servers.
'Values are: 1 (Yes) OR 0 (No)
'Initialize common scripting objects
Set objFileSys = CreateObject( "Scripting.FileSystemObject" )
Set objWshNetwork = CreateObject( "WScript.Network" )
Set objWshShell = CreateObject( "WScript.Shell" )
'Pause script until user is fully logged on (applies only to Win 9x or ME)
'This will timeout after 10 seconds
strUser = ""
intCounter = 0
Do
strUserID = objWshNetwork.Username
intCounter = intCounter + 1
Wscript.Sleep 500
Loop Until strUserID <> "" OR intCounter > 20
'Check for error getting username
If strUserID = "" Then
objWshShell.Popup "Logon script failed - Contact the Helpdesk @ x 345", , _
"Logon script", 48
Call Cleanup
End If
'Setup IE for use as a status message window
Call SetupIE
'Display welcome message
Call UserPrompt ("Welcome " & strUserID)
'Add horizontal line as a 'break'
objIntExplorer.Document.WriteLn("<hr style=""width:100%""></hr>")
'Gather some basic system info
Call GetSystemInfo
If IsTerminalServerSession <> True Then
'Exit if we are logging on locally to a server and the
'script is set to NOT run on servers
IF UseNTServer = 0 AND (strOSProdType = "LanmanNT" OR strOSProdType = "ServerNT") Then
objWshShell.Popup "Windows Server - Exiting Logon Script!", 10, _
"Logon to " & strDomain, 16
Call CleanUp
End if
End If
'Get group memberships
strUserGroups = ""
Call GetLocalGroupMembership
Call GetGlobalGroupMembership
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Map drives, add shared printers and set default homepage
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Determining workstation settingsThis section determines which settings should be applied to the
workstation, based on the name of the
workstation. Our environment has a nice naming convention: the
building, room number, and station number are identified in the name.
For example, the name Blg4Rm105-03 identifies a
computer as being station 3, located in building 4, room 105. To
determine which mappings get assigned to a computer, all I have to do
is base my criteria on everything on the left of the dash
(-). ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Part A
' This section performs actions based on computer name
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'The left side of the computer name contains building and room information
If Instr( 1, strWorkstation, "-", 1) > 0 Then
strWorkstation = _
Left( strWorkstation, ( Instr( 1, strWorkstation, "-", 1)))
End If
Select Case UCase( strWorkstation )
Case "BLD1RM101-"
Call MapDrive ("U:", "MyShareSvr1", "MyShare1")
Call AddPrinter ("Mydomain2", "MyPrtSvr2", "Bld1Rm101-HP4050")
objWshNetwork.SetDefaultPrinter "\\MyPrtSvr2\Bld1Rm101-HP4050"
strHomePage = "http://www.chesapeake.edu/academic_info/ " & _
"acad_computing.asp"
Case "BLD1RM202-"
Call MapDrive ("U:", "MyShareSvr2", "MyShare2")
Call AddPrinter ("Mydomain1", "MyPrtSvr1", "Bld1Rm202-HP4000")
objWshNetwork.SetDefaultPrinter "\\MyPrtSvr1\Bld1Rm202-HP4000"
strHomePage = "http://www.chesapeake.edu/library/default.asp"
Case "BLD3RM104-"
'This room uses TCP/IP printing instead of a print server.
'Only set homepage
strHomePage = "http://www.chesapeake.edu/writing/wchome.htm"
Case Else
End Select
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Adding mappings based on group membershipAdding mappings based upon the computer name is
cool. However, there will
always be a need to perform tasks based on specific group membership.
This section takes care of such tasks. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Part B
' This section performs actions based on group membership
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
If InGroup( "ShareForStaff" ) Then
Call MapDrive ("X:", "StaffSvr1", "StaffShare1")
strHomePage = "http://www.chesapeake.edu/generalinfo/cambridge.asp"
End If
If InGroup( "ShareForStudents" ) Then
Call MapDrive ("Y:", "StudentSvr1", "StudentShare1")
strHomePage = "http://www.chesapeake.edu"
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' End section
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Setting the IE home page and final messageThis section sets the IE home page (if specified), starts
SMSls.bat, and posts a final message to the
user. The script tests to determine if the user is a member of the
Domain Admins or
DoNotInstallSMS groups. If the user is a member of
either of these groups, SMSls.bat is skipped.
This is helpful if you want to get in quick to do a small task or to
keep SMS off some of your more persnickety users'
computers. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Set default homepage
If strHomePage <> "" Then
Err.Clear
objWshShell.RegWrite _
"HKCU\Software\Microsoft\Internet Explorer\Main\Start Page", strHomePage
If Err = 0 Then Call UserPrompt ("Set Internet home page to " & strHomePage)
End If
'Start SMSls.bat
'Do not run if a member of the Domain Administrators
'or in the global group DoNotInstallSMS
If InGroup("Domain Admins") OR InGroup("DoNotInstallSMS") Then
Call UserPrompt ("Skipping SMSLS.BAT")
Else
objWshShell.Run "%COMSPEC% /c " & strLogonPath & "\smsls.bat", 0, False
End If
'Add horizontal line as a 'break'
objIntExplorer.Document.WriteLn("<hr style=""width:100%""></hr>")
'Inform user that logon process is done
Call UserPrompt ("Finished network logon processes")
'Wait 10 seconds
Wscript.Sleep (10000)
'Close Internet Explorer
objIntExplorer.Quit( )
Call Cleanup
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
That's the end of the first major section of the
script. The following subsections list and explain the various
subroutines and functions.
Connecting to a shared network printerThe following
routine is where the
printer mapping occurs. It first verifies that the share is
accessible and creates the mapping. If the share is not accessible or
is not a valid print share, the user will be prompted with an error
message that lets her know she should call the help desk. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: AddPrinter
'
' Purpose: Connect to shared network printer
'
' Input:
' strPrtServerDomain Domain in which print server is a member
' strPrtServer Name of print server
' strPrtShare Share name of printer
'
' Output:
'
' Usage:
' Call AddPrinter ("Mydomain2", "MyPrtSvr2", "Bld1Rm101-HP4050")
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub AddPrinter(strPrtServerDomain, strPrtServer, strPrtShare)
On Error Resume Next
Dim strPrtPath 'Full path to printer share
Dim objPrinter 'Object reference to printer
Dim strMsg 'Message output to user
Dim blnError 'True / False error condition
blnError = False
'Build path to printer share
strPrtPath = "\\" & strPrtServer & "\" & strPrtShare
'Test to see if shared printer exists.
'Proceed if yes, set error condition msg if no.
Set objPrinter = GetObject _
("WinNT://" & strPrtServerDomain & "/" & strPrtServer & "/" & _
strPrtShare)
If IsObject( objPrinter ) AND _
(objPrinter.Name <> "" AND objPrinter.Class = "PrintQueue") Then
'Different mapping techniques depending on OS version
If objWshShell.ExpandEnvironmentStrings( "%OS%" ) = "Windows_NT" Then
Err.Clear
'Map printer
objWshNetwork.AddWindowsPrinterConnection strPrtPath
Else
'Mapping printers for Win9x & ME is a pain and unreliable.
End If
Else
blnError = True
End IF
'Check error condition and output appropriate user message
If Err <> 0 OR blnError = True Then
strMsg = "Unable to connect to network printer. " & vbCrLf & _
"Please contact the Helpdesk @ ext 345" & vbCrLf & _
"and ask them to check the " & strPrtServer & " server." & _
vbCrLf & vbCrLf & _
"Let them know that you are unable to connect to the '" _
& strPrtShare & "' printer"
objWshShell.Popup strMsg,, "Logon Error !", 48
Else
Call UserPrompt ("Successfully added printer connection to " & _
strPrtPath)
End If
Set objPrinter = Nothing
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Mapping a drive to a shared folderThis routine is where the drive mapping occurs. It first removes any
preexisting drive mapping that
might be using the designated drive letter. Then, it verifies that
the share is accessible and creates the mapping. If the share is not
accessible, the user will be prompted with an error message that lets
him know he should call the help desk. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: MapDrive
'
' Purpose: Map a drive to a shared folder
'
' Input:
' strDrive Drive letter to which share is mapped
' strServer Name of server that hosts the share
' strShare Share name
'
' Output:
'
' Usage:
' Call MapDrive ("X:", "StaffSvr1", "StaffShare1")
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub MapDrive( strDrive, strServer, strShare )
On Error Resume Next
Dim strPath 'Full path to printer share
Dim blnError 'True / False error condition
blnError = False
'Disconnect Drive if drive letter is already mapped.
'This assures everyone has the same drive mappings
If objFileSys.DriveExists(strDrive) = True Then
objWshNetwork.RemoveNetworkDrive strDrive, , True
End If
'Build path to share
strPath = "\\" & strServer & "\" & strShare
'Test to see if share exists. Proceed if yes, set error condition if no.
If objFileSys.DriveExists(strPath) = True Then
Err.Clear
objWshNetwork.MapNetworkDrive strDrive, strPath
Else
blnError = True
End If
'Check error condition and output appropriate user message
If Err.Number <> 0 OR blnError = True Then
'Display message box informing user that the connection failed
strMsg = "Unable to connect to network share. " & vbCrLf & _
"Please contact the Helpdesk @ ext 345 and ask them " & _
"to check the " & strServer & " server." & vbCrLf & _
"Let them know that you are unable to connect to the " & _
"'" & strPath & "' share"
objWshShell.Popup strMsg,, "Logon Error !", 48
Else
Call UserPrompt ("Successfully added mapped drive connection to " & strPath)
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Gathering local group membershipsThis routine collects information about any local groups to which the
user might belong. The names of these groups get placed into the
strUserGroups variable for future reference. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: GetLocalGroupMembership
'
' Purpose: Gather all local groups to which the current user belongs
'
' Input:
'
' Output: Local group names are added to strUserGroups
'
' Usage: Call GetLocalGroupMembership
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub GetLocalGroupMembership
On Error Resume Next
Dim colGroups 'Collection of groups on the local system
Dim objGroup 'Object reference to individual groups
Dim objUser 'Object reference to individual group member
'Verify system is not Windows 9x or ME
If objWshShell.ExpandEnvironmentStrings( "%OS%" ) = "Windows_NT" Then
'Connect to local system
Set colGroups = GetObject( "WinNT://" & strWorkstation )
colGroups.Filter = Array( "group" )
'Process each group
For Each objGroup In colGroups
'Process each user in group
For Each objUser in objGroup.Members
'Check if current user belongs to group being processed
If LCase( objUser.Name ) = LCase( strUserID ) Then
'Add group name to list
strUserGroups = strUserGroups & objGroup.Name & ","
End If
Next
Next
Set colGroups = Nothing
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Gathering global group membershipsThis routine is similar to the previous one, except it collects
information about any global (rather than local) groups to which the
user might belong. The names of the groups also get placed into the
strUserGroups variable for future reference. Since
some users might still be running Windows NT domains, I use the WinNT
syntax instead of LDAP to perform the query, for cross-platform
interoperability. This way is a little easier anyway. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: GetGlobalGroupMembership
'
' Purpose: Gather all global groups the current user belongs to
'
' Input:
'
' Output: Global group names are added to strUserGroups
'
' Usage: Call GetGlobalGroupMembership
'
' Notes: Use WinNT connection method to be backwards
' compatible with NT 4 domains
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub GetGlobalGroupMembership
On Error Resume Next
Dim objNameSpace
Dim objUser
Const ADS_READONLY_SERVER = 4
Set objNameSpace = GetObject( "WinNT:" )
'Use the OpenDSObject method with the ADS_READONLY_SERVER
'value to grab the "closest" domain controller
'Connect to user object in the domain
Set objUser = objNameSpace.OpenDSObject( _
"WinNT://" & strDomain & "/" & strUserID, "", "", ADS_READONLY_SERVER)
'Process each group
For Each objGroup In objUser.Groups
'Add group name to list
strUserGroups = strUserGroups & objGroup.Name & ","
Next
Set objNameSpace = Nothing
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Determining if user belongs to a specified groupThis simple routine searches the list of group names that is
contained in the strUserGroups variable for the
specified group and returns True if the group is
found. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Function: InGroup
'
' Purpose: Determine if user belongs to specified group
'
' Input: Name of group to test for membership
'
' Output: True or False
'
' Usage: If InGroup("Domain Admins") Then <do something>
'
' Requirements:
' strUserGroups must have been previously populated via
' GetLocalGroupMembership and/or GetGlobalGroupMembership
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function InGroup(strGroup)
On Error Resume Next
InGroup = False
'Search strUserGroups for strGroup
If Instr( 1, LCase( strUserGroups ), LCase( strGroup ), 1) Then InGroup = True
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Gathering basic information about the local systemHere is another routine that gathers specific information about the
local computer, such as the user domain, workstation name, product
type, and the path to the location from which the script is running. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: GetSystemInfo
'
' Purpose: Gather basic information about the local system
'
' Input:
'
' Output: strDomain, strOSProdType, strWorkstation, strLogonPath
'
' Usage: Call GetSystemInfo
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub GetSystemInfo
On Error Resume Next
'Get domain name
If objWshShell.ExpandEnvironmentStrings( "%OS%" ) = "Windows_NT" Then
strDomain = objWshNetwork.UserDomain
Else
strDomain = objWshShell.RegRead( "HKLM\System\CurrentControlSet\" & _
"Services\MSNP32\NetWorkProvider\AuthenticatingAgent" )
End If
'Get Product Type from Registry (WinNT, LanmanNT, ServerNT)
strOSProdType = objWshShell.RegRead( _
"HKLM\System\CurrentControlSet\Control\ProductOptions\ProductType")
'Get computer name
If IsTerminalServerSession = True Then
'Set strWorkstation to the real name and not the name of the server
strWorkstation = objWshShell.ExpandEnvironmentStrings( "%CLIENTNAME%" )
Else
strWorkstation = objWshNetwork.ComputerName
End If
'Get the path to the location from where the script is running
strLogonPath = Left( Wscript.ScriptFullName, _
( InstrRev( Wscript.ScriptFullName, "\") -1))
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Determining if the script is running in a terminal server sessionThe following routine identifies whether the user is logging on via a
Windows terminal session and returns True if this
is the case. The determinant test criteria is whether the workstation
has a valid %ClientName% environment variable set. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Function: IsTerminalServer
'
' Purpose: Determine if the script is running in a terminal server session
'
' Input:
'
' Output:
' True if running in a terminal server session
' False if not running in a terminal server session
' Usage:
' If IsTerminalServerSession = True Then <Do Something>
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function IsTerminalServerSession
On Error Resume Next
Dim strName
'Detect if this is a terminal server session
'If it is, set some names to the terminal server client name
strName = objWshShell.ExpandEnvironmentStrings( "%CLIENTNAME%" )
If strName <> "%CLIENTNAME%" AND strName <> "" Then _
IsTerminalServerSession = True
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Setting up IE for use as a status message windowI like to use Internet Explorer as a general status message screen
for users. This routine gets Internet Explorer set up and ready for
this purpose. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: SetupIE
'
' Purpose: Set up Internet Explorer for use as a status message window
'
' Input:
'
' Output:
'
' Usage: Call SetupIE
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub SetupIE
On Error Resume Next
Dim strTitle 'Title of IE window
Dim intCount 'Counter used during AppActivate
strTitle = "Logon script status"
'Create reference to objIntExplorer
'This will be used for the user messages. Also set IE display attributes
Set objIntExplorer = Wscript.CreateObject("InternetExplorer.Application")
With objIntExplorer
.Navigate "about:blank"
.ToolBar = 0
.Menubar = 0
.StatusBar = 0
.Width = 600
.Height = 350
.Left = 100
.Top = 100
End With
'Set some formating
With objIntExplorer.Document
.WriteLn ("<!doctype html public>")
.WriteLn ("<head>")
.WriteLn ("<title>" & strTitle & "</title>")
.WriteLn ("<style type=""text/css"">")
.WriteLn ("body {text-align: left; font-family: arial; _
font-size: 10pt}")
.WriteLn ("</style>")
.WriteLn ("</head>")
End With
'Wait for IE to finish
Do While (objIntExplorer.Busy)
Wscript.Sleep 200
Loop
'Show IE
objIntExplorer.Visible = 1
'Make IE the active window
For intCount = 1 To 100
If objWshShell.AppActivate(strTitle) Then Exit For
WScript.Sleep 50
Next
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Using IE as a status message windowFinally, the last routine is just a little helper for the status
message window. There's nothing fancy going on here. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: UserPrompt
'
' Purpose: Use Internet Explorer as a status message window
'
' Input: strPrompt
'
' Output: Output is sent to the open Internet Explorer window
'
' Usage:
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Sub UserPrompt( strPrompt )
On Error Resume Next
objIntExplorer.Document.WriteLn (strPrompt & "<br />")
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Sub: Cleanup
'
' Purpose: Release common objects and exit script
'
' Input:
'
' Output:
'
' Usage: Call Cleanup
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub Cleanup
On Error Resume Next
Set objFileSys = Nothing
Set objWshNetwork = Nothing
Set objWshShell = Nothing
Set objIntExplorer = Nothing
'Exit script
Wscript.Quit( )
End Sub
Showing messages 1 through 2 of 2.
-
SMSLS.bat
2005-10-18 04:00:06
Rabbitmo
[View]
-
Logon Scritp
2005-01-28 10:50:20
riskier4ra
[View]
|
Showing messages 1 through 2 of 2.
|
|
O'Reilly Home | Privacy Policy

© 2007 O'Reilly Media, Inc.
Website:
| Customer Service:
| Book issues:
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
|
|
|
Is there an example of this, Ican't see itin the book .
Thanks