WinBatch Tech Support Home

Database Search

If you can't find the information using the categories below, post a question over in our WinBatch Tech Support Forum.

TechHome

NetwareX Extender

Can't find the information you are looking for here? Then leave a message over on our WinBatch Tech Support Forum.

NDS Volumes Usage


;=============================================================================================
; program: 		NDS Volumes Usage 
;
; language: 	WinBatch
; extenders:	NetwareX 		WWNWX34I.DLL
;
; author:		Michael Harris
; created:		4/14/2003		last updated: 4/25/2003
;
;
; CAUTION:  Provided as is.  Use at your own risk.
;           Problems, damage, loss of business are your responsibility.
;           Review, adjust and test accordingly, before using.
;
;
; description:
;		Can be run interactively (no command line arguments, will prompt for values)
;		or batch (with command line arguments.... see Command Line/Default Arguments below.
;		(NOTE:  Batch use has not been tested.)
; 		Queries NDS for all volume objects 
;		and obtains matching volume size and free space info
;		(NOTE: until NetwareX volume support is enhanced, 
;		       uses nwMap drive mapping and DiskSize and DiskFree to get that info)
;		(NOTE: double check my size calculations and rounding..... may be off a bit) 
;		and outputs all that as tab delimited text file
;		and then (using Excel in an automated fashion) also saves it as a spreadsheet file
;		and leaves it up in Excel for the user to view, manipulate, print, etc.
;		for subsequent viewing, manipulation, printing in Excel
;		(Delimited files can be brought into Excel two different ways :
;			- Data | Get External Data | Import Text File - (column widths are set properly)
;			- File | Open - (column widths are not set properly and must be manually adjusted)
;		 In this program, the file is brought in via Data | Get External Data,
;		 as that approach results in column widths being adjusted properly (per data width).)
;
; output file location/name:
;		Currently, location is specified in FilePathDefaultValue
;		and file name is 
;			"NDS Volumes Usage %CurrentTimeYmdHmsWithBlanks%.txt" 
;=============================================================================================


;================================ SET DEFAULT VALUES HERE ====================================
; Default Values 
NDSTreeDefaultValue = '\\ACME_TREE\'
NDSContextDefaultValue = '.ACME'							; command line argument #1 (param1)
;FilePathDefaultValue = 'F:\Winbatch\'					; for testing
FilePathDefaultValue = '\\R006_server\V006\groups\Isd_techservices\WinBatch\Reports\'
DialogTitle = 'NDS Volumes Usage'
SpreadsheetDelimiter = @TAB
MappedDrive = 'L:'
GigaByte = 1024.0 * 1024.0 * 1024.0
;=============================================================================================


;================================ SET FORMATTING HERE ========================================
; When displaying floating point numbers, show two decimal places
; (Note: does not affect format in output file.... and keep in mind that Excel displays
;        differently than the data it imports (i.e. even if supplied "0.00", is shows "0",
;        unless formatted otherwise (no formatting is included in the output file)
;Decimals(2) 
;=============================================================================================


;================================ SET TOLERANT MODE HERE, IF NEEDED ==========================
; Set tolerant error mode.... 
;  @OFF		will suppress minor errors and keep running 
;           (@OFF required to test for nonexistent NDS contexts/objects without aborts)
;           (just leave @OFF from start.... or turn on/off only at spots needed?)
;  @CANCEL	will end program on any error
ErrorMode(@OFF)
;ErrorMode(@CANCEL)
; Note that is turned off when start extracting volume object attribute values,
; as ................. (needed?).......
; which results in non-minor error and program termination if set to @CANCEL
;=============================================================================================


;---------------------------------------------------------------------------------------------
;Load the NetwareX extender (Netware/NDS API) for use in this WinBatch program
AddExtender('WWNWX34I.DLL')
;---------------------------------------------------------------------------------------------


;------------- DEFINE FUNCTIONS HERE (FOR SUBSEQUENT USE IN MAIN LOGIC BELOW) ----------------
; note: function variables are local, subroutine variables are global
;
;
#DefineFunction PadAndReverseNDSNames(ListOfNDSNames)
; Inserts a blank (space) name segment (' ') between object name segment and remaining path segments
; (so that objects will sort out ahead of subcontainers) (empty segment will be removed later)
; and reverses all segments of each (now padded) NDS name in list
; from ".object..container...org" to ".org...container..object"
; The result is useful for sorting into organizational chart order....
; but must then be unpadded and reversed again before can be used to query NDS objects. 
NumberOfNames = ItemCount(ListOfNDSNames, @TAB)
; Process names, a name at a time
ListofReversedNDSNames = ListofNDSNames
For ListIndex = 1 to NumberOfNames by 1
	NDSName = ItemExtract(ListIndex, ListOfReversedNDSNames, @TAB)
	NDSName = ItemInsert(' ', 2, NDSName, '.')
	ReversedNDSName = NDSName
	NumberOfSegments = ItemCount(NDSName, '.')
	For SegmentIndex = 1 to NumberOfSegments by 1
		Segment = ItemExtract(SegmentIndex, NDSName, '.')
		ReversedNDSName = ItemReplace(Segment, (NumberOfSegments - SegmentIndex + 1), ReversedNDSName, '.')
	Next
	ListOfReversedNDSNames = ItemReplace(ReversedNDSName, ListIndex, ListOfReversedNDSNames, @TAB)
Next
Return (ListOfReversedNDSNames)
#EndFunction
	
	
;
;
#DefineFunction UnpadAndReverseNDSNames(ListOfReversedNDSNames)
; Reverses all segments of each (now padded) reversed NDS name in list
; from ".org...container..object" back to ".object..container...org"  
; and removes the blank (space) name segment (' ') from between object name segment and remaining 
; path segments (was there so that objects would sort out ahead of subcontainers).
; The result is a list of object names, in organizational chart order, but with names 
; in regular ".object..container...org" NDS notation, so can be used to call for object info. 
NumberOfNames = ItemCount(ListOfReversedNDSNames, @TAB)
; Process names, a name at a time
ListofNDSNames = ListOfReversedNDSNames
For ListIndex = 1 to NumberOfNames by 1
	ReversedNDSName = ItemExtract(ListIndex, ListOfNDSNames, @TAB)
	NDSName = ReversedNDSName
	NumberOfSegments = ItemCount(ReversedNDSName, '.')
	For SegmentIndex = 1 to NumberOfSegments by 1
		Segment = ItemExtract(SegmentIndex, ReversedNDSName, '.')
		NDSName = ItemReplace(Segment, (NumberOfSegments - SegmentIndex + 1), NDSName, '.')
		Next
	NDSName = ItemRemove(3, NDSName, '.')
	ListOfNDSNames = ItemReplace(NDSName, ListIndex, ListOfNDSNames, @TAB)
Next
Return (ListOfNDSNames)
#EndFunction
	
	
;
;
;---------------------------------------------------------------------------------------------


;-------------------------------- MAIN LOGIC STARTS HERE --------------------------------------

; Note:  Default values (NDS context, etc.) were set up up in SET DEFAULT VALUES HERE

; Note:  Command line arguments are available as param1, param2, etc.
; 			param0 contains the total number of command line arguments

; param0 will equal 0, if running interactively
; param0 will equal ???, if running batch with required ????? command line arguments
; if running batch, assure correct number of command line arguments
;If ((param0 == 1) || (param0 >=4))
;	MessageLine = StrCat('ERROR: Incorrect number of command line arguments.  param0 = ', param0)
;	Message(DialogTitle, MessageLine)
;Endif

; Get/check NDS context
; (may need to add check that begins with '\\ACME_TREE\.')
; Command line arguments supplied?
If (param0 == 0)
	; No command line arguments were supplied (i.e. is being run interactively)
	; Ask user for NDS context (assure isn't empty or a number)
	BaseObjectSpec = ''
	ResultCode = 1
	While ((ResultCode != 0) || (IsNumber(BaseObjectSpec)) || (StrSub(BaseObjectSpec, 1, 1) != '.')) 
		BaseObjectSpec = AskLine(DialogTitle, 'NDS context within %NDSTreeDefaultValue%?', NDSContextDefaultValue)
		nwGetObjInfo(BaseObjectSpec, 2)
		ResultCode = LastError()
		; diagnostic, if needed
		;MessageLine = StrCat("BaseObjectSpec=", BaseObjectSpec, " nwGetObjInfo ResultCode=", ResultCode)
		;Message(DialogTitle, MessageLine)
		If ((ResultCode != 0) || (IsNumber(BaseObjectSpec)) || (StrSub(BaseObjectSpec, 1, 1) != '.'))
			MessageLine = StrCat('ERROR: NDS context not found, must begin with leading period  You entered = ', BaseObjectSpec)
			Message(DialogTitle, MessageLine)
		Endif
	EndWhile
Else
	; Command line arguments were supplied (i.e. is being run batch)
	BaseObjectSpec = param1
	; Assure is searching ACME_TREE (otherwise, can get a very long delay before erroring)
   If ((BaseObjectSpec != '') && (!(IsNumber(BaseObjectSpec))) && (StrSub(BaseObjectSpec, 1, 1) == '.'))
		nwGetObjInfo(BaseObjectSpec, 2)
		ResultCode = LastError()
	Endif
	If ((BaseObjectSpec == '') || (IsNumber(BaseObjectSpec)) || (ResultCode != 0) || (StrSub(BaseObjectSpec, 1, 1) != '.'))
		MessageLine = StrCat('ERROR: Invalid NDS context, must begin with leading period  Argument supplied = ', BaseObjectSpec)
		Message(DialogTitle, MessageLine)
		; End program
		Exit
	Endif
Endif

if (param0 == 0)
	; No command line arguments were supplied (i.e. being run interactively)
	; Tell user to wait while list of volumes is built
	; (NOTE: Depending on the NDS tree size, context level, wide area net involved, etc.
	;        this can take a while.)
	MessageLine = "List of objects being built.  Please wait...."
	BoxOpen(DialogTitle, MessageLine)
Endif

; get current date/time
CurrentTimeYmdHms = TimeYmdHms()

; Generate output file name and open file (be default, will go where this program is run from)
; replace colons with time with blanks
CurrentTimeYmdHmsWithBlanks = StrReplace(CurrentTimeYmdHms, ':', ' ')
OutputFileName = FilePathDefaultValue
OutputFileName = StrCat(OutputFileName, 'NDS Volumes Usage ') 
OutputFileName = StrCat(OutputFileName, CurrentTimeYmdHmsWithBlanks, '.txt') 
; to be safe, delete file by that name if already exists (not likely, but best to be safe
; in case change file naming strategy (though "WRITE" should zero out and overwrite existing file)
If (FileExist(OutputFileName))
	FileDelete(OutputFileName)
Endif
FileHandle = FileOpen(OutputFileName, 'WRITE')
If (FileHandle == 0)
	MessageLine = StrCat('File open/create of ', OutputFileName, ' failed')
	Message(DialogTitle, MessageLine)
	;terminate the program
	Exit
Endif

; generate the full NDS tree/context
BaseObjectSpec = StrCat(NDSTreeDefaultValue, BaseObjectSpec)

; Set up a search filter
; (is cumulative... use *FREE_BUFFER* to reset/deactivate)
; (source of attribute names was the NDS Snoop program....
;  see www.novell.com/coolsolutions/tools/1005.html)
; (another source of attribute names is the output displayed by the example
;  WinBatch program supplied in the nwGetObjValue writeup in the NetwareX help)
nwSearchFilter(BaseObjectSpec, 'BASECLS','',0,0,0)
nwSearchFilter(BaseObjectSpec, 'ANAME','Volume',0,0,0)
nwSearchFilter(BaseObjectSpec, 'END','',0,0,0)

; Set up search flags (combine/add various bit flag values)
; value of 2 says search scope is base-object and all its subordinates
Flags = 2

; Tell WinBatch's NetwareX extensions to return typeless names from NDS
nwSetOptions(1,@TRUE)

; Do the search and get the result code
ObjectListDistinguishedTabbed = nwSearchObjects(BaseObjectSpec, '', '', Flags, '')
ResultCode = LastError()

; Verify that search was ok
If (ResultCode != 0)
	; Diagnostic (if needed) report result code
	MessageLine = StrCat("Objects search failed with result code = ", ResultCode)
	Message(DialogTitle, MessageLine)
	; End the program
	Exit
Endif

; Reset/deactivate the search filter (don't need when done with nwSearchObjects)
nwSearchFilter('', '*FREE_BUFFER*', '', 0, 0, 0)

; diagnostic (if needed)
;Message(DialogTitle, ObjectListDistinguishedTabbed)
; diagnostic program termination (if needed)
;exit

; Sort the list (comes from NDS in unsorted sequence) by
; a) flipping the NDS names from ".object.container..org" to ".org.container..object",
; b) sorting the list of flipped names (into organizational chart sequence)
; C) reflipping the names back to ".object.container..org"
ObjectListDistinguishedTabbed = PadAndReverseNDSNames(ObjectListDistinguishedTabbed)
; diagnostic (if needed)
; Message(DialogTitle, UserListDistinguishedTabbed)
ObjectListDistinguishedTabbed = ItemSort(ObjectListDistinguishedTabbed, @TAB)
ObjectListDistinguishedTabbed = UnpadAndReverseNDSNames(ObjectListDistinguishedTabbed)

; diagnostic (if needed)
;Message(DialogTitle, ObjectListDistinguishedTabbed)
; diagnostic program termination (if needed)
;exit

; Determine number of user objects returned
NumberOfObjects = ItemCount(ObjectListDistinguishedTabbed, @TAB)

; If running interactively, report number of volumes found in the search scope
If (param0 == 0)
	MessageLine = StrCat("Number of objects found = ", NumberOfObjects, @CR, "Please wait while file is produced....")
	BoxText(MessageLine)
Endif


; Initialize needed variables for use in following loop
; 

; Write a report header record (with title/info spread across several columns,
; so as to not mess up the autoadjusted width of columns when this
; file is imported into Excel via Data | Get External Data | Import Text File.
; (note that width of title (including trailing spaces) causes Excel to
;  adjust column widths accordingly during data import of this file)
;xxxxOutputFileName = StrCat(OutputFileName, 'NDS Users Home Directory Space Limits And Usage On ') 
;xxxxOutputFileName = StrCat(OutputFileName, CurrentTimeYmdHmsWithBlanks, '.txt') 

OutputLine = ""
OutputLine = StrCat(OutputLine, "NDS Volumes Usage", SpreadsheetDelimiter)
OutputLine = StrCat(OutputLine, "(produced: ", CurrentTimeYmdHms, ")")
;a CR-LF is appended at the end of the line by the write
FileWrite(FileHandle, OutputLine)

; Write a header record with field descriptions delimited by tabs
; (note that width of title (including trailing spaces) causes Excel to
;  adjust column widths accordingly during data import of this file)
OutputLine = ""
OutputLine = StrCat(OutputLine, "NDS Path (reversed)", SpreadsheetDelimiter)
OutputLine = StrCat(OutputLine, "Volume", SpreadsheetDelimiter)
OutputLine = StrCat(OutputLine, "Size (GB)", SpreadsheetDelimiter)
OutputLine = StrCat(OutputLine, "Free (GB)", SpreadsheetDelimiter)
; use two percentsign characters, to get one to actually be output
OutputLine = StrCat(OutputLine, "Used (%%)")
;a CR-LF is appended at the end of the line by the write
FileWrite(FileHandle, OutputLine)

;Set tolerant error mode.... as intolerant mode aborts program if attribute missing
;(example: "Given Name" returns value for most, but errors on some) 
; @OFF		will suppress minor errors and keep running
; @CANCEL	will end program on any error
ErrorMode(@OFF)

; Loop through and process all volumes
For ObjectListIndex = 1 to NumberOfObjects by 1
	; Process current object
	; If running interactively
	If (param0 == 0)
		; Update box message/progress bar on every tenth object
		ObjectListIndexDividedBy10 = ObjectListIndex / 10.0
		ObjectListIndexDividedBy10Int = Int(ObjectListIndexDividedBy10)
		If (ObjectListIndexDividedBy10 == ObjectListIndexDividedBy10Int)
			ProgressLine = StrCat(MessageLine, @CR, StrFill("=>", ObjectListIndexDividedBy10Int))
		BoxText(ProgressLine)
		Endif
	Endif
	; Extract object full typeless name from tab delimited list
	ObjectFullTypelessName = ItemExtract(ObjectListIndex, ObjectListDistinguishedTabbed, @TAB)
	; To be safe, verify that user still exists... otherwise go to next user
	; NOTE: Does not appear to work reliably (returns non-zero value of 275 for some that exist)
	;       Worthwile to test for 'NDS' (vs. 'BINDERY') return value instead?
	;NDSorBinderyObject = ''
	;NDSorBinderyObject = nwGetObjInfo(ObjectFullTypelessName, 2)
	;ResultCode = LastError()
	;MessageLine = StrCat('ObjectFullTypelessName=', ObjectFullTypelessName, 'NDSorBinderyObject=', NDSorBinderyObject, ' ResultCode=', ResultCode)
	;Message(DialogTitle, MessageLine)
	; If (ResultCode) then Continue ???????
	;  Object still exists
	; (note: As in "Netware user report by snivling" example program on ehelp author forum,
	;        attribute values could be gotten/handled via a list of attribute names and a loop)
	; (NOTE: Attributes of some objects are not guaranteed to exist.  May exist and contain/not contain something
	;        or may not exist (depends on whether has ever had something entered?).
	;        Empty or not exist returns value of 0.)
	; Extract Object simple name from full typeless name (period delimited, with leading period)
	Object = ItemExtract(2, ObjectFullTypelessName, ".")
	; Generate a reversed (root-to-container) version of user object's NDS path
	; Remove leading period (empty item and its period delimiter, in terms of period delimited list)
	NDSPath = ItemRemove(1, ObjectFullTypelessName, ".")
	; Remove Object simple name item (and its period delimeter)
	NDSPath = ItemRemove(1, NDSPath, ".")
	; Determine number items (number of NDS path segments) remaining in the list
	ItmCnt = ItemCount(NDSPath, ".")
	; Swap/reverse the items (path segments)
	; Initialize reversed name list with proper number of items and delimiters by brute force cheat
	; of copying the "to be reversed" list into it
	ReversedNDSPath = NDSPath
	For Index = 1 to ItmCnt by 1
		; This could be done as a single line, but is broken out for readability and troubleshooting
		Item = ItemExtract(Index, NDSPath, ".")
		ReversedNDSPath = ItemReplace(Item, (ItmCnt - Index + 1), ReversedNDSPath, ".")
	Next
	; Now get the rest of desired info for this particular object
	;
	; verify that the volume exists/can be mounted/etc.
	; Map drive letter to current volume
	nwMap(ObjectFullTypelessName, MappedDrive, 0)
   ; Note: Testing indicates that successful/unsuccessful mappings take about same amount of time
	;       I.E. No significant delay results from failed mapping
	; Assure that map was successful
	ReturnCode = LastError()
	If (ReturnCode != 0)
		; map was not successful
		; (a failed map (volume object exists, but volume does not (or is not mounted or ????))
		; put "(Invalid)" in all fields, so will stand out in report
		VolumeDiskSize = "(Invalid)"
		VolumeDiskFree = "(Invalid)"
		VolumePercentUsed = "(Invalid)"
	Else
		; map was successful
		; Get volume size, in floating point (as integers over 2GB get strange/unpredictable)
		; and adjust it to gigabytes (no rounding, to be consistent with ConsoleOne)
		VolumeDiskSize = (DiskSize(MappedDrive, 0) + 0.0) 
		; Get volume free space, in floating point (as integers over 2GB get strange/unpredictable)
		; and adjust it to gigabytes (no rounding, to be consistent with ConsoleOne)
		VolumeDiskFree = (DiskFree(MappedDrive, 0) + 0.0) 
		; Calculate volume space used, in floating point, then convert to integer
		; (note that Int itself rounds)
		VolumePercentUsed = Int((((VolumeDiskSize - VolumeDiskFree) / VolumeDiskSize) * 100.0) + 0.4)
		; diagnostic, if needed
		;MessageLine = StrCat("VolumeDiskSize=", VolumeDiskSize, "VolumeDiskFree=", VolumeDiskFree, "VolumePercentUsed", VolumePercentUsed)
		;Message(DialogTitle, MessageLine)
		; Shorten/truncate volume size and free space to two decimal places
		; (we postponed doing this, so that above percentage calculation would be more precise)
		; (NOTE: Even though we assure two decimal places here and in the output file,
		;        keep in mind that Excel will not show trailing zeros unless formatted to do so
		;        and we are not currently making Excel do any such formatting)
		VolumeDiskSize = (VolumeDiskSize / GigaByte) + 0.0001
		VolumeDiskSize = StrSubWild(VolumeDiskSize, "*.??", 1)
		VolumeDiskFree = (VolumeDiskFree / GigaByte) + 0.0001
		VolumeDiskFree = StrSubWild(VolumeDiskFree, "*.??", 1)
	Endif
	; Write a record with current user's information delimited by spreadsheet delimiter character
	OutputLine = ""
	OutputLine = StrCat(OutputLine, ReversedNDSPath, SpreadsheetDelimiter)
	OutputLine = StrCat(OutputLine, Object, SpreadsheetDelimiter)
	OutputLine = StrCat(OutputLine, VolumeDiskSize, SpreadsheetDelimiter)
	OutputLine = StrCat(OutputLine, VolumeDiskFree, SpreadsheetDelimiter)
	;OutputLine = StrCat(OutputLine, VolumePercentUsed, SpreadsheetDelimiter, TimeYmdHms ( ))
	OutputLine = StrCat(OutputLine, VolumePercentUsed)
	; a CR-LF is appended at the end of the line by the write
	FileWrite(FileHandle, OutputLine)
Next

; close the output file
FileClose(FileHandle)

; close "please wait" box and provide ending message to user
If (param0 == 0)
	BoxShut()
	MessageLine = StrCat("DONE.", @CR)
	MessageLine = StrCat(MessageLine, "Number of objects found = ", NumberOfObjects, @CR)
	MessageLine = StrCat(MessageLine, "See delimited text file:", @CR, OutputFileName)
	Message(DialogTitle, MessageLine)
Endif

; offer the user the option of also saving the file in spreadsheet file format
If (param0 == 0)
	; ask user if wish to also save the file as a spreadsheet file
	; change output file name file type from .txt to .xls
	SpreadsheetFileName = ItemReplace("xls", ItemCount(OutputFileName, "."), OutputFileName, ".")
	MessageLine = "Also save as spreadsheet file (same name, with .xls file type)?"
	MessageLine = StrCat(MessageLine, @CR, SpreadsheetFileName)
	YesNoAnswer = AskYesNo(DialogTitle, MessageLine)
	If (YesNoAnswer == @YES)
		; note: might be better to do the following via OLE extender
		;       for reliability, timing/speed sake?
		; start the spreadhsheet program and wait for it to be ready
		; NOTE: time delays amounts are set high to be conservative/safe
		Run("excel.exe", "")
		TimeDelay(5)
		WinWaitExist("~Microsoft Excel", 60)
		; start a new sheet (to be safe, in case Excel is already being used on a sheet)
		; File | New | OK and wait for it to be ready
		SendKeysTo("~Microsoft Excel", "!fn{ENTER}")
		TimeDelay(5)
		; import the delimited text file (doing this, instead of opening the file,
		; causes the column widths to be adjusted according to incoming data values maximum width)
		; Data | Get External Data | Import Text File
		; (copy/paste the output file name into the entry field, as paste is faster than typein)
		ClipPut(OutputFileName)
		SendKeysTo("~Microsoft Excel", "!ddt^v{ENTER}")
		TimeDelay(10)
		; supply the import the next, next, next, ... that it needs
		SendKeysTo("~Microsoft Excel", "{ENTER}{ENTER}{ENTER}{ENTER}{ENTER}")
		TimeDelay(10)
		; save the sheet out as spreadsheet format file
		; (copy/paste the spreadsheet file name into the entry field, as paste is faster than typein)
		ClipPut(SpreadsheetFileName)
		SendKeysTo("~Microsoft Excel", "!fa^v{ENTER}")
		MessageLine = "Spreadsheet being saved.  Please wait...."
		BoxOpen(DialogTitle, MessageLine)
		TimeDelay(10)
		BoxShut()
		; let the user know the sheet has been saved and can now be viewed, etc. as they wish
		MessageLine = "Spreadsheet has been saved.  View or close as you wish."
		Message(DialogTitle, MessageLine)
	Endif
Endif

; Set intolerant error mode.... will terminate execution on any error
ErrorMode(@CANCEL)

; end program
exit

Article ID:   W16058
File Created: 2004:03:30:15:42:38
Last Updated: 2004:03:30:15:42:38