Can't find the information you are looking for here? Then leave a message over on our WinBatch Tech Support Forum.
Provided as is. Use at your own risk. Problems, damage, loss of business are your responsibility. Review, adjust and test accordingly, before using.
Until NetwareX extenders support for Netware volumes is enhanced (apparently in progress), partially employs NDS and partially employs drive mapping and generic/non-NDS disk size and disk free calls.
In the output, the NDS path is reversed, for sortability and human readability
Further investigation/reporting of why some volumes don't map might be useful..... to assure program accuracy and to supply report readers with more info.
Might want to double check my calculations and rounding.... may be off a bit from what ConsoleOne or BindView report.
Run speed tends to vary, depending on tree size, context level, wide area network, etc. involvement and also on whether is run multiple times (subsequent runs seem faster, due to cacheing?). Implemented a crude incremental progress bar to provide some feedback to the user during latter part of the processing (when is mapping and getting info for each volume), but not for the initial NDS search for the list of volume objects (due to the processing/delay all occurring within the single statement search, could probably fake it in some fashion but haven't pursued doing so)
Notes regarding Excel automation and display
;============================================================================================= ; 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: W16059
File Created: 2004:03:30:15:42:38
Last Updated: 2004:03:30:15:42:38