Can't find the information you are looking for here? Then leave a message over on our WinBatch Tech Support Forum.
;============================================================================================= ; 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