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.

Netware NDS Volumes Usage program


Netware NDS Volumes Usage program

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

Would appreciate any feedback that folks would care to offer.
;=============================================================================================
; 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