Long story short... I am using a pc to open a .txt file located on a server... but sometimes the .txt file is not finished (there is still data stored in a buffer of the source computer).
FilePath = "D:\test.txt"
Workbooks.Open(Filename:=FilePath, ReadOnly:=True, IgnoreReadOnlyRecommended:=True)
Someone smarter than I am has identified that the .txt file is "locked" by the operating system until it is finished but I am still able to open it. I would like to wait for the .txt file to be "not locked" before opening it. How do I do this?
Note: The "smarter" person than me explained... the .txt file can be opened by a "dumb" program like "notepad" but if you try to use "Microsoft Word" to open it... you get a message telling you that it is locked...
import time
is_open = False
while not(is_open):
try:
f = open('test.txt','w')
is_open=True
except:
time.sleep(1)
I don't know how well the code below will work for your scenario.
You could change the constants into parameters (if you think their values need to change/be determined dynamically).
You could also change the GetFileOrWait's implementation such that it allows an infinite loop (I chose to avoid this, but maybe you want this).
All in all, the below is basically a function which tries to return the workbook in 120 seconds (or times out with an error) -- which you can hopefully use in a parent subroutine/procedure.
You may be able to specify finer frequencies (seconds may be too coarse) by using Timer instead or other lower level APIs.
Option Explicit
Private Function GetFileOrNothing() As Workbook
Const FILE_PATH As String = "D:\test.txt" ' Could pass this in as argument (if needed).
On Error Resume Next
Set GetFileOrNothing = Workbooks.Open(Filename:=FILE_PATH, ReadOnly:=True, IgnoreReadOnlyRecommended:=True)
On Error GoTo 0
End Function
Private Function GetFileOrWait() As Workbook
' Attempts to open a file. If access fails, waits n seconds before trying again.
' This function raises an error (times out to prevent infinite loop) after N seconds.
Const MAXIMUM_WAIT_IN_SECONDS As Long = 10
Const INTERVAL_WAIT_IN_SECONDS As Long = 1
Dim timeToStopAt As Date
timeToStopAt = DateAdd("s", MAXIMUM_WAIT_IN_SECONDS, Now)
Do While Now < timeToStopAt
Dim outputWorkbook As Workbook
Set outputWorkbook = GetFileOrNothing()
If Not (outputWorkbook Is Nothing) Then Exit Do
Application.Wait DateAdd("s", INTERVAL_WAIT_IN_SECONDS, Now)
DoEvents
Loop
If outputWorkbook Is Nothing Then
Err.Raise vbObjectError + 5, , "Failed to access file within the specified time frame."
End If
Set GetFileOrWait = outputWorkbook
End Function
Related
I have this code from a program that was created to take data and export it into excel it is in vb6 and I dont know much about vb6 I started coding in vb.net could someone tell me why this isnt working with excel 2013 it opens but then closes right away and i am unsure as to why.
Sub GetExcel()
Dim MyExcel As Object ' Variable to hold reference
' to Microsoft Word.
Dim ExcelWasNotRunning As Boolean ' Flag for final release.
' Test to see if there is a copy of Microsoft Excel already running.
10 On Error Resume Next ' Defer error trapping.
' Getobject function called without the first argument returns a
' reference to an instance of the application. If the application isn't
' running, an error occurs.
20 Set MyExcel = GetObject(, "XLMAIN")
30 If Err.Number <> 0 Then ExcelWasNotRunning = True
40 Err.Clear ' Clear Err object in case error occurred.
' Check for Microsoft Excel. If Microsoft Excel is running,
' enter it into the Running Object table.
50 DetectExcel
' Set the object variable to reference the file you want to see.
60 Set MyExcel = GetObject(App.Path & "\test.xls")
' Show Microsoft Word through its Application property. Then
' show the actual window containing the file using the Windows
' collection of the MyWord object reference.
' MyExcel.Application.Visible = True
' MyExcel.document(1).Visible = True
70 MyExcel.Show , f1
'//////////////////////////////////////////////
' Do manipulations of your file here.
'//////////////////////////////////////////////
' ...
' If this copy of Microsoft Excel was not running when you
' started, close it using the Application property's Quit method.
' Note that when you try to quit Microsoft Excel, the
' title bar blinks and a message is displayed asking if you
' want to save any loaded files.
80 If ExcelWasNotRunning = True Then
90 MyExcel.Application.Quit
100 End If
110 Set MyExcel = Nothing ' Release reference to the
' application and spreadsheet.
End Sub
Sub DetectExcel()
' Procedure dectects a running Word and registers it.
Const WM_USER = 1024
Dim hwnd As Long
' If Excel is running this API call returns its handle.
10 hwnd = FindWindow("XLMAIN", 0)
20 If hwnd = 0 Then ' 0 means Word not running.
30 Exit Sub
40 Else
' Word is running so use the SendMessage API
' function to enter it in the Running Object Table.
50 SendMessage hwnd, WM_USER + 18, 0, 0
60 End If
End Sub
even if i can get some direction on how to rewrite this it would be appreciated.
It's been a long time since I've used it, so you may need to address some of the finer points... But:
First and foremost - get rid of the On Error Resume Next it masks whatever happens next.
This is not a situation where you want to have errors ignored. Capture the error, show something useful (not debug information and not details that can be used for hacking) to the user, and resume or return (after cleanup of assigned object variables to avoid memory leaks).
Then step through the code to see what errors you get.
Some of the things that may help:
Change the specification in your GetObject to be "Excel.Application" i.e.
Set MyExcel = GetObject(, "Excel.Application")
Instead of MyExcel.Show which may or may not be supported, use actual Excel objects and their methods and properties, for instance the commented MyExcel.Application.Visible = True works, while I question the MyExcel.document(1).Visible = True.
Look up the Excel object model help for details. And never hardcode the index - obtain the actual reference that you want and use it.
You can still find articles about Excel and VB6 on line. Use your search engine of choice and good luck.
Beware of those who say something is impossible - first see if it works. Some say you can's install VB6 on a 64-bit system - that is not true, but there are some issues getting it to work. Some say you can't interact with 64-bit Office from VB6, but that is not true. Maybe you can't do some things - I haven't done much with it, just enough to know it is possible to do some things.
Consider developing and testing with typed objects in VB6. It can be very helpful, but to make the application version independent, you will need to remove the object typing before final test and deployment.
I am using secureCRT to connect to a Linux server. SecureCRT reads VBScript, and I am new to this language so my problem might sound easy for you.
When I connect to the server from secureCRT, I a script containing a command let's say "date" ,the output of the command must come out on a text file on my local host ( windows ) and not on the server.
This is the script that I am using:
# $language = "VBScript"
# $interface = "1.0"
' This script demonstrates how to capture line by line output
' from a command sent to a server. It then saves each line of output
' to a file. This script shows how the 'WaitForStrings' command can be
' used to wait for multiple possible outputs.
' Constants used by OpenTextFile()
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
Sub Main
crt.Screen.Synchronous = True
' Create an instance of the scripting filesystem runtime so we can
' manipulate files.
'
Dim fso, file
Set fso = CreateObject("Scripting.FileSystemObject")
' Open a file for writing. The last True parameter causes the file
' to be created if it doesn't exist.
'
Set file = fso.OpenTextFile("C:\Users\elieme\Desktop\TTX1.txt", ForWriting, True)
' Send the initial command then throw out the first linefeed that we
' see by waiting for it.
'
crt.Screen.Send "gsh list_imsins" & Chr(10)
crt.Screen.WaitForString Chr(10)
' Create an array of strings to wait for.
'
Dim waitStrs
waitStrs = Array( Chr(10), "linux$" )
Dim row, screenrow, readline, items
row = 1
Do
While True
' Wait for the linefeed at the end of each line, or the shell prompt
' that indicates we're done.
'
result = crt.Screen.WaitForStrings( waitStrs )
' If we saw the prompt, we're done.
If result = 2 Then
Exit Do
End If
' The result was 1 (we got a linefeed, indicating that we received
' another line of of output). Fetch current row number of the
' cursor and read the first 20 characters from the screen on that row.
'
' This shows how the 'Get' function can be used to read line-oriented
' output from a command, Subtract 1 from the currentRow to since the
' linefeed moved currentRow down by one.
'
screenrow = crt.screen.CurrentRow - 1
readline = crt.Screen.Get(screenrow, 1, screenrow, 45 )
' NOTE: We read 20 characters from the screen 'readline' may contain
' trailing whitespace if the data was less than 20 characters wide.
' Write the line out with an appended '\r\n'
file.Write readline & vbCrLf
Wend
Loop
crt.screen.synchronous = false
End Sub
I read the script several time to understand it, and tried to manipulate it for hours, and asking for help was my last resort.
In the script I have crt.Screen.Send "date" & Chr(10) which will send my command and execute it. Then when I go in the loop, I don't understand what does this mean.
'If we saw the prompt, we're done.
If result = 2 Then
Exit Do
End If
What do they mean by if we saw the prompt? Is it something that i have to input for it to exit ? Because i tried several keys and I'm always stuck in this while loop.
I tried to manipulate the script but every time I was either getting an empty file as output, or wrong data in the file.
Is there anyway to make this script execute only the command that I sent ("date"), and output what this command will do on the text file ?
If not, is there any shortcut to stop the script without having to go in the menu and selecting cancel script?
Thank you
EDIT:
I fixed it guys, very easy.
The rt.Screen.WaitForStrings takes as a second parameter a timeout number, so that fixed everything.
Thanks
Fixed
The rt.Screen.WaitForStrings takes as a second parameter a timeout number, so that fixed everything.
I've got a problem with a vbscript which creates Excel objects and reads from an Excel file.
The vbscript is executed from an Excel macro, and then creates and opens the Excel file and reads from it. The problem is that the Excel object isn't allways closed, even though I'm trying to to it.
Here's the code from the vbscript:
Set ExcelObject = createobject("Excel.Application")
ExcelObject.workbooks.open testWorkBookPath
Set testActionArray = CreateObject( "System.Collections.ArrayList" )
Function getTestsCaseActions (testsPath, esheet, row, col)
Set my_sheet = ExcelObject.sheets.item(esheet)
tempArray = array(my_sheet.cells(row, col-2), "")
testActionArray.Add tempArray
Do While my_sheet.cells(row, col).Value <> ""
tempArray = array(my_sheet.cells(row, col), my_sheet.cells(row+1, col))
testActionArray.Add tempArray
col = col+1
Loop
End Function
getTestsCaseActions testWorkBookPath, testCaseSheet, 3, 4
ExcelObject.Quit
Now, if I run the above code and watch the process explorer, a new Excel process is spawned when the script is started, and then closes, as expected.
However, if I insert this code after running the function, before the ExcelObject.Quit line:
For Each ArrayItem in testActionArray
IF ArrayItem(1) = "" Then
Wscript.Echo ArrayItem(0)
Else
Wscript.Echo ArrayItem(0) & " -> " & ArrayItem(1)
End If
Next
ExcelObject.Quit (STILL HERE)
then the spawned process does NOT quit, and the process list grows until Excel goes completely bananas.
I don't understand this; All the last bit of code does is loop through the ArrayList and print the contents. Why's not the process quitting?
EDIT: At seems that at least some of the Excel objects eventually disappear from the Process Explorer, but this takes about 20-30 minutes. And it's just a few of them - most are still there. At least my list at the moment has shrinked some, but there are still about 15 Excel processes running.
Also, suddenly this message appears:
File Now Available
'filename.xlsm ' is now available for editing. Choose Read-Write to open it for editing.
This line seems to help. It doesn't completely remove all extra processes, but the number grows to five, then goes back to two, and so on. So it works pretty well.
dim book: for each book in ExcelObject.Workbooks: book.saved = true: next
Sorry about the non descriptive Title, I just didn't know how to describe my goal.
I'm new at VBA and didn't yet understand how things really work.
I've written a function which gets a directory from the user, and displays data from the first file in the directory. Now, I want to add a "next" button.
When the "next" button is pressed, my code should display data from the next file in the directory.
I tried to use global variables but they seem to get initialized each time the button is pressed.
What is the best way to achieve my goal? Do I have to use the spreadsheet as memory and write and read everything from there? Or does Excel VBA have some other "live memory" mechanism?
Thanks,
Li
Globals will not normally be reinitialized when you click a button. They will be reinitialized if you recompile your VBA project. Therefore, while debugging, you may see a global being reinitialized.
You can use the spreadsheet as memory. One way to do this is to have a worksheet whose Visibility property you set to xlSheetVeryHidden (you can do this from the VBA project). This worksheet won't be visible to users, so your VBA application can use it to store data.
This could be approached many ways, as with any problem I guess!
You could break the problem up into two subroutines:
1) Retrieve all the file names in the selected directory and display the first file's data
2) If it's not the last file, get the next file's data and display it
You could use a global variable to store the filenames and an index to remember where you are up to in the collection of filenames.
Global filenames As Collection
Global fileIndex As Integer
Public Sub GetFilenames()
Dim selectedDirectory As String
Dim currentFile As String
selectedDirectory = "selected\directory\"
currentFile = Dir$(selectedDirectory)
Set filenames = New Collection
While currentFile <> ""
filenames.Add selectedDirectory & currentFile
currentFile = Dir$()
Wend
' Make sure there were files
If filenames.Count >= 1 Then
fileIndex = 1
' Call a method to display data
DisplayData(filenames(fileIndex))
Else
' No files
End If
End Sub
Public Sub GetNextFile()
' Make sure we have a filenames object
If Not filenames Is Nothing Then
If fileIndex < filenames.Count Then
fileIndex = fileIndex + 1
' Call the display method again
DisplayData(filenames(fileIndex))
Else
' Decide what to do after reaching the final file
End If
Else
' No filenames
End If
End Sub
I didn't include the DisplayData procedure as I'm not sure what type of files you're grabbing or what you are doing with them but if it were say excel files it could be something like:
Public Function DisplayData(filename As String)
Dim displayWb As Workbook
Set displayWb = Workbooks.Open(filename)
' Do things with displayWb
End Function
You could then set the macro of the button to "GetNextFile" and it will cycle through the files after each click. As for the lifetime of global variables, they only reinitialize when the VBA project is reset or when they are specifically initialized through a procedure or the immediate window.
Perhaps these two functions can also help you:
SaveSetting
GetSetting
as showed here: http://www.j-walk.com/ss/excel/tips/tip60.htm
OK, I've just written a very simple VBA script that goes off and grabs the file size of a file found at a specified location (edit update code from dscarr):
Public Function FileSize(path As String) As Variant
On Error GoTo Err_FileSize:
Dim retVal As Variant
Dim filesys As Object
Dim file As Object
retVal = ""
Set filesys = CreateObject("Scripting.FileSystemObject")
Set file = filesys.GetFile(path)
retVal = file.Size
Exit_FileSize: On Error Resume Next
FileSize = retVal
Exit Function
Err_FileSize: retVal = "Error: " & Err.Description
Resume Exit_FileSize
End Function
If I import this into a new workbook, save the document as a "Excel Macro-Enabled Workbook" alongside an empty file called readme.txt and then put "./readme.txt" in A1 and "=FileSize(A1)" in A2, A2 evaluates properly to the file size of readme.txt, 0 bytes. Great. If I then save the document, close and reopen it, I'm warned that macros are disabled. If I enable them, the content of A2 has changed to #VALUE! and nothing I do will make the function work again. Has anyone seen this before, can anyone point out the mistake that I'm making here?
edit: the issue seems to be caused by me using relative paths. I can't explain why it works for a new workbook, but not once saved and reopened, but using absolute paths solves the problem, which as dscarr identified, was a "File Not Found" issue.
I duplicated your code in an Excel 2007 Macro Enabled workbook and found that it worked just fine with the following caveats
It does not automatically update the value (e.g. run the FileSize function) when you open the workbook. This can be compensated for by adding some code to the Worrkbook_Open event handler that calculates the workbook.
I get the #VALUE error when it cannot find the file that is specified. Indeed, when the file is missing or incorrectly named the line "Set file = filesys.GetFile(path)" throws the error number 53 "File Not Found". In this case the return value is never set because the line of code that sets it is never called. You could try setting a default value of "File Not Found" before actually attempting to retrieve the file size.
In Excel 2010, Go File->Options->Trust Center->Macro Settings and make sure the Enable option button has been selected.